|
| 1 | +# Managed Structure Chain Revisions for 3.0 |
| 2 | + |
| 3 | +## Summary |
| 4 | +An overhaul of the managed wrappers and helpers for the use of |
| 5 | +Vulkan structure chains. |
| 6 | + |
| 7 | +# Contributors |
| 8 | +- [Khitiara](https://github.com/Khitiara) |
| 9 | + |
| 10 | +# Current Status |
| 11 | +- [x] Proposed |
| 12 | +- [x] Discussed with API Review Board (ARB) |
| 13 | +- [x] Approved |
| 14 | +- [ ] Implemented |
| 15 | + |
| 16 | +## Background |
| 17 | + |
| 18 | +### The Underlying Vulkan API |
| 19 | +Ubiquitous in Vulkan are the use of "structure chains," a system |
| 20 | +whereby many Vulkan structs can form a singly linked list. This |
| 21 | +is used by the Vulkan API primarily to support extending core |
| 22 | +function calls with additional functionality, e.g. an |
| 23 | +`ImageCreateInfo` used to create a GPU-side image object may |
| 24 | +be extended with an `ImageFormatListCreateInfo` used to specify |
| 25 | +additional image formats, the latter type having been originally |
| 26 | +defined as `ImageFormatListCreateInfoKHR` in an extension before |
| 27 | +being promoted to core api surface. To implement this system, |
| 28 | +Vulkan structs which participate in the structure chaining |
| 29 | +system have some additional fields, presented here with C# |
| 30 | +pseudo-code. |
| 31 | + |
| 32 | +```csharp |
| 33 | +public enum StructureType : int { |
| 34 | + // ... |
| 35 | + ImageCreateInfo = 14, |
| 36 | + // ... |
| 37 | + ImageFormatListCreateInfo = 1000147000 |
| 38 | + // ... |
| 39 | +} |
| 40 | + |
| 41 | +public struct ImageCreateInfo { |
| 42 | + [FieldOffset(0)] |
| 43 | + public StructureType SType = StructureType.ImageCreateInfo; |
| 44 | + [FieldOffset(4)] |
| 45 | + public void* PNext; |
| 46 | + // ... |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +This system also includes two bare-bones additional structures, |
| 51 | +`BaseInStructure` and `BaseOutStructure`, both containing only |
| 52 | +the `SType` and `PNext` members at the same offsets. Any `PNext` |
| 53 | +in a chain may be reinterpreted to one of those two types, |
| 54 | +`SType`inspected to determine the actual value type, and then |
| 55 | +the pointer reinterpreted to the appropriate complete structure |
| 56 | +type. The existence of both `BaseInStructure` and `BaseOutStructure` |
| 57 | +seems to exist to distinguish structs meant to be passed to or |
| 58 | +from vulkan, though no consistent guideline on which struct to |
| 59 | +use could be easily found leading the author of this proposal to |
| 60 | +surmise that the distinction may exist primarily as an |
| 61 | +implementation detail nonetheless exposed publicly. |
| 62 | + |
| 63 | +### The Situation in Silk.NET 2.X |
| 64 | + |
| 65 | +In Silk.NET 2.X, a number of utility types and methods are |
| 66 | +available to facilitate easier use of these structure chain |
| 67 | +types from managed C# code, where normal use would require |
| 68 | +an absurd amount of `fixed` statements or the use of unsafe |
| 69 | +bcl APIs. The available utility functionality in Silk.NET 2.X |
| 70 | +is broadly divisible into two distinct systems: a set of |
| 71 | +convenient wrappers over the unsafe ref-manipulation bcl |
| 72 | +APIs for common chain operations, and a set of managed |
| 73 | +`Chain<...>` types which perform manual memory allocation |
| 74 | +in a single unmanaged memory block where the structs are |
| 75 | +stored using `unsafe` pointer reinterpreting, and exposing |
| 76 | +properties and methods which allow the structure instances |
| 77 | +in a chain to be manipulated while keeping the chain of |
| 78 | +`PNext` pointers intact. These two systems are both built |
| 79 | +on a set of marker interfaces which are implemented on |
| 80 | +vulkan structure types by SilkTouch. |
| 81 | + |
| 82 | +The wrapper functions over ref-manipulation, comprised mostly |
| 83 | +of extension methods on `ref T`, are unaffected by this |
| 84 | +current proposal, as the motivating issues are primarily |
| 85 | +design choices for the managed heap-safe `Chain<...>` types |
| 86 | +and for the marker interfaces which were made early in |
| 87 | +Silk.NET 2.X and are either not possible to change within |
| 88 | +the 2.X cycle due to the Silk.NET breaking change policy or |
| 89 | +which could be augmented by features in newer .NET versions |
| 90 | +which cannot be relied on by Silk.NET 2.X, such as static |
| 91 | +abstracts on interfaces. As Silk.NET 3.0 will target .NET 8 |
| 92 | +and permits breaking changes for the major version rollover |
| 93 | +only, a number of improvements, both breaking and nonbreaking, |
| 94 | +may be made for this upcoming major version release. |
| 95 | + |
| 96 | +## Prior Proposals |
| 97 | +- [Chain Polymorphism Proposal](https://github.com/dotnet/Silk.NET/blob/e8c0026fd2ebbc382b89b923b9bcaffc3ba1ede3/documentation/proposals/Proposal%20-%20Chain%20Polymorphism.md), |
| 98 | +proposed in [#1145](https://github.com/dotnet/Silk.NET/pull/1145). |
| 99 | +- [Addition of ref properties in #1420](https://github.com/dotnet/Silk.NET/pull/1420), |
| 100 | +amended in [#1423](https://github.com/dotnet/Silk.NET/pull/1423) due to errors in |
| 101 | +ref semantics. |
| 102 | + |
| 103 | +## Proposed API Changes from 2.X |
| 104 | +~~Apologies for the lack of C# syntax highlighting, as |
| 105 | +highlighting the differences has been prioritized~~ |
| 106 | + |
| 107 | +```diff |
| 108 | + public interface IStructuredType |
| 109 | + { |
| 110 | ++ public static abstract StructureType StructureType { get; } |
| 111 | ++ |
| 112 | +- public StructureType StructureType(); |
| 113 | ++ public StructureType FixStructureType(); |
| 114 | + } |
| 115 | +``` |
| 116 | +The existing `IStructuredType.StructureType()` method sets the |
| 117 | +struct's `SType` field to be the correct value for the particular |
| 118 | +struct type and then returns it. It is proposed that the .NET 7+ |
| 119 | +static abstracts in interfaces feature be used to force the |
| 120 | +presence of a static property to return the correct enum value |
| 121 | +for that type in a static context, and that the `StructureType()` |
| 122 | +method be renamed to suggest its updated purpose of correcting |
| 123 | +the `SType` field on the struct. The return behavior of the |
| 124 | +method is left as-is to permit ease of implementing the changes |
| 125 | +as much of the existing implementation relies on this behavior |
| 126 | +for concise implementation. |
| 127 | + |
| 128 | +```diff |
| 129 | + public class Chain<TChain, T1, ...> : Chain |
| 130 | + { |
| 131 | + // ... |
| 132 | +- public unsafe BaseInStructure* Item1Ptr { get; } |
| 133 | ++ public unsafe T1* Item1Ptr { get; } |
| 134 | + // ... |
| 135 | + } |
| 136 | +``` |
| 137 | +This change has been proposed previously but was rejected at the |
| 138 | +time as source-breaking changes during the 2.X cycle were not |
| 139 | +permitted. As Silk.NET 3.0 permits breaking changes from 2.X |
| 140 | +the change can now be made. Having readily accessible type-safe |
| 141 | +pointers helps a lot with much of how the `Chain<...>` types |
| 142 | +are commonly used. |
| 143 | + |
| 144 | +This design also renames the non-generic `Chain` instead and gives it |
| 145 | +the head structure type `TChain` as a generic argument. |
| 146 | +```diff |
| 147 | + public abstract class BaseChain<TChain> |
| 148 | + { |
| 149 | + // ... |
| 150 | +- public abstract unsafe BaseInStructure* HeadPtr { get; } |
| 151 | ++ public abstract unsafe TChain* HeadPtr { get; } |
| 152 | + // ... |
| 153 | + } |
| 154 | + |
| 155 | + public class Chain<TChain, ...> : BaseChain<TChain> |
| 156 | + { |
| 157 | + // ... |
| 158 | +- public override unsafe BaseInStructure* HeadPtr { get; } |
| 159 | ++ public override unsafe TChain* HeadPtr { get; } |
| 160 | + // ... |
| 161 | + } |
| 162 | +``` |
| 163 | + |
| 164 | +The reason for separating `BaseChain<TChain>` from `Chain<TChain>`, |
| 165 | +the chain of one element, in this design is that the latter |
| 166 | +performs management of unmanaged memory sized to hold the singular |
| 167 | +`TChain` object whereas the former is the base type of all chains |
| 168 | +starting with `TChain`, which is to say all chains before generic |
| 169 | +specialization, and has no inherent knowledge of the size of |
| 170 | +the allocated unmanaged memory. |
| 171 | + |
| 172 | +```diff |
| 173 | + public class Chain<TChain, ...> : Chain |
| 174 | + { |
| 175 | + // ... |
| 176 | ++ public ref TChain GetPinnableReference(); |
| 177 | + // ... |
| 178 | + } |
| 179 | +``` |
| 180 | +Adding `GetPinnableReference()` with that precise name permits the use |
| 181 | +of the following relatively convenient syntax for converting a full |
| 182 | +`Chain<...>` to a pointer; providing this method is a common |
| 183 | +convention for memory-holding types in modern C# that this method |
| 184 | +should be provided despite the `Chain<...>` object already holding |
| 185 | +a reference to unmanaged memory and thus not needing to be pinned |
| 186 | +by the garbage collector. |
| 187 | +```csharp |
| 188 | +Chain<ImageCreateInfo> chain = ...; |
| 189 | +fixed(ImageCreateInfo* ptr = chain) { |
| 190 | + _vk.CreateImage(device, ptr, null, &image); |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +```diff |
| 195 | + public interface IChain<TChain, T1, ...> |
| 196 | + { |
| 197 | + // ... |
| 198 | ++ public unsafe TChain* HeadPtr { get; } |
| 199 | + // ... |
| 200 | + } |
| 201 | +``` |
| 202 | +The `IChain` interfaces permit polymorphism of managed chain objects and were |
| 203 | +introduced by the [Chain Polymorphism Proposal](https://github.com/dotnet/Silk.NET/blob/e8c0026fd2ebbc382b89b923b9bcaffc3ba1ede3/documentation/proposals/Proposal%20-%20Chain%20Polymorphism.md), |
| 204 | +proposed in [#1145](https://github.com/dotnet/Silk.NET/pull/1145). |
| 205 | +Adding typed pointer members to the `IChain` interfaces may be done |
| 206 | +without regard to the design chosen for the type of `HeadPtr` in the |
| 207 | +concrete chain types thanks to explicit interface implementation in the |
| 208 | +case no change is made to that member, and is easily implemented in any |
| 209 | +case no matter if the concrete `ItemNPtr` members are changed to type-safe |
| 210 | +through the same mechanism of explicit interface implementation. |
| 211 | + |
| 212 | +# Meeting Notes |
| 213 | + |
| 214 | +## 19/11/2023 |
| 215 | + |
| 216 | +[Video](https://www.youtube.com/live/yXNDZDE3AHE?feature=shared&t=1221) |
| 217 | + |
| 218 | +- We believe that the chain abstractions break down significantly anyway when used as an output with layers that can possibly inject additional PNexts that aren't reflected in these abstractions. |
| 219 | +- As a result, this chain abstraction only really works with inputs in every case. |
| 220 | +- UntypedHeadPtr being changed from BaseInStructure is contrary to this assumption. |
| 221 | +- Why is it IStructuredType instead of ITypedStructure? Changing this doesn't affect too much, but leaving it as is doesn't affect too much either. |
| 222 | +- 3.0 is the opportunity to fix long-standing issues like this as it's already massively breaking, so we can do silly changes like this. |
| 223 | +- IStructuredType is already broken with this proposal, so let's just rename it anyway. |
| 224 | +- We believe that IBaseStructure is probably the most sensible name given how Vulkan names this. |
| 225 | +- Everything else looks fine, approved notwithstanding the above. |
0 commit comments