diff --git a/Silk.NET.sln b/Silk.NET.sln
index 78bccfff41..108159ba81 100644
--- a/Silk.NET.sln
+++ b/Silk.NET.sln
@@ -270,9 +270,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Silk.NET.NUKE", "build\nuke\Silk.NET.NUKE.csproj", "{B9A8D738-FE7D-4860-A446-4A03E3DDEB74}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft", "Microsoft", "{F2CF5D32-4B41-425E-B229-8FFC48F88063}"
-ProjectSection(SolutionItems) = preProject
- src\Microsoft\dxva.h = src\Microsoft\dxva.h
-EndProjectSection
+ ProjectSection(SolutionItems) = preProject
+ src\Microsoft\dxva.h = src\Microsoft\dxva.h
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Silk.NET.Direct3D11", "src\Microsoft\Silk.NET.Direct3D11\Silk.NET.Direct3D11.csproj", "{F3B7A9D6-5B15-45E8-925B-20B5BBD33428}"
EndProject
@@ -474,6 +474,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.OpenXR.Extensions.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.OpenXR.Extensions.HTCX", "src\OpenXR\Extensions\Silk.NET.OpenXR.Extensions.HTCX\Silk.NET.OpenXR.Extensions.HTCX.csproj", "{782B6A7E-9F04-429A-9DCD-D7273AA3882E}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PrototypeStructChaining", "PrototypeStructChaining", "{B15922CB-815C-4038-B635-EE2D8A8F700B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrototypeStructChaining", "src\Lab\Experiments\PrototypeStructChaining\PrototypeStructChaining\PrototypeStructChaining.csproj", "{EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrototypeStructChaining.Test", "src\Lab\Experiments\PrototypeStructChaining\PrototypeStructChaining.Test\PrototypeStructChaining.Test.csproj", "{BD19250B-E143-4F4E-9E1D-18829CCB3642}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Vulkan.Tests", "src\Vulkan\Silk.NET.Vulkan.Tests\Silk.NET.Vulkan.Tests.csproj", "{225BA79C-36FE-421A-85E4-D15F8B61869B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -2823,6 +2831,42 @@ Global
{782B6A7E-9F04-429A-9DCD-D7273AA3882E}.Release|x64.Build.0 = Release|Any CPU
{782B6A7E-9F04-429A-9DCD-D7273AA3882E}.Release|x86.ActiveCfg = Release|Any CPU
{782B6A7E-9F04-429A-9DCD-D7273AA3882E}.Release|x86.Build.0 = Release|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Debug|x64.Build.0 = Debug|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Debug|x86.Build.0 = Debug|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Release|x64.ActiveCfg = Release|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Release|x64.Build.0 = Release|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Release|x86.ActiveCfg = Release|Any CPU
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C}.Release|x86.Build.0 = Release|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Debug|x64.Build.0 = Debug|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Debug|x86.Build.0 = Debug|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Release|x64.ActiveCfg = Release|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Release|x64.Build.0 = Release|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Release|x86.ActiveCfg = Release|Any CPU
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642}.Release|x86.Build.0 = Release|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Debug|x64.Build.0 = Debug|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Debug|x86.Build.0 = Debug|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Release|x64.ActiveCfg = Release|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Release|x64.Build.0 = Release|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Release|x86.ActiveCfg = Release|Any CPU
+ {225BA79C-36FE-421A-85E4-D15F8B61869B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3052,6 +3096,10 @@ Global
{3E30D674-9282-4297-AD1F-9B233A166308} = {0651C5EF-50AA-4598-8D9C-8F210ADD8490}
{606214B8-07FC-436F-9523-02AF32E1AB1E} = {90471225-AC23-424E-B62E-F6EC4C6ECAC0}
{782B6A7E-9F04-429A-9DCD-D7273AA3882E} = {90471225-AC23-424E-B62E-F6EC4C6ECAC0}
+ {B15922CB-815C-4038-B635-EE2D8A8F700B} = {39B598E9-44BA-4A61-A1BB-7C543734DBA6}
+ {EEFF37DA-E4F2-406E-AF97-8615BB7BC34C} = {B15922CB-815C-4038-B635-EE2D8A8F700B}
+ {BD19250B-E143-4F4E-9E1D-18829CCB3642} = {B15922CB-815C-4038-B635-EE2D8A8F700B}
+ {225BA79C-36FE-421A-85E4-D15F8B61869B} = {E2ABDF45-C329-47B2-8E09-B7298E2557F7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F5273D7F-3334-48DF-94E3-41AE6816CD4D}
diff --git a/Silk.NET.sln.DotSettings b/Silk.NET.sln.DotSettings
index 7e543f9188..15296a6115 100644
--- a/Silk.NET.sln.DotSettings
+++ b/Silk.NET.sln.DotSettings
@@ -57,6 +57,7 @@
TrueTrueTrue
+ TrueTrueTrueTrue
diff --git a/build/cache/vulkan.json.gz b/build/cache/vulkan.json.gz
index 98d27c384c..5a4565b913 100644
Binary files a/build/cache/vulkan.json.gz and b/build/cache/vulkan.json.gz differ
diff --git a/documentation/proposals/Proposal - Vulkan Struct Chaining Metadata extensions.md b/documentation/proposals/Proposal - Vulkan Struct Chaining Metadata extensions.md
new file mode 100644
index 0000000000..ef1deb9825
--- /dev/null
+++ b/documentation/proposals/Proposal - Vulkan Struct Chaining Metadata extensions.md
@@ -0,0 +1,386 @@
+# Summary
+
+Having extended `BuildTools` to make use of the `structextends` attribute, as part of
+the [unmanaged chaining proposal](Proposal%20-%20Vulkan%20Struct%20Chaining%20-%20%232%20Unmanaged%20Chaining.md)
+it is possible to supply that metadata in a ready form for use by consumers of the library, without them needing to
+resort to reflection.
+
+There are a number of potential use cases, most specifically validating that a supply `IChainable` structure is valid
+for a given chain, at runtime. To facilitate the most common scenario 4 extension methods are also added.
+
+# Contributors
+
+- [Craig Dean, DevDecoder](https://github.com/thargy)
+
+# Current Status
+
+- [x] Proposed
+- [x] Discussed with API Review Board (ARB)
+- [ ] Approved
+- [ ] Implemented
+
+# Design Decisions
+
+- It is possible to get the same information presented statically by this proposal by creative use of reflection,
+ however, it is non-trivial and costly to do so.
+- The above functionality makes handling of non-generic chains at runtime significantly easier, which is great for
+ library writers.
+- Though 4 collections are proposed, they are grouped in pairs, providing two-way mapping. It is possible to only
+ provide one of each pair, and leave the reversal to the consumer, though that would make the extension methods
+ impractical.
+- As .Net Standard 2.0 does not support `IReadOnlySet` we make use of the `IReadOnlyCollection` interface.
+
+# Proposed API
+
+## Auto-generated Metadata Structures
+
+The `Extensions` dictionary is added to the [`ChainExtensions`](../../src/Vulkan/Silk.NET.Vulkan/ChainExtensions.cs)
+extensions class for discoverability. It is a direct mapping of the `structextends` attribute, and is therefore trivial
+to generate. Below is a cut down example to illustrate what will be generated:
+
+```csharp
+public static class ChainExtensions : IChainable
+{
+ ...
+ ///
+ /// Provides a set of all the s that can be extended by a .
+ ///
+ public static readonly IReadOnlyDictionary> Extensions =
+ new Dictionary>
+ {
+ [StructureType.PhysicalDeviceFeatures2] = new HashSet
+ {
+ StructureType.DeviceCreateInfo
+ },
+ [StructureType.PhysicalDeviceDescriptorIndexingFeatures] = new HashSet
+ {
+ StructureType.PhysicalDeviceFeatures2,
+ StructureType.DeviceCreateInfo
+ },
+ [StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr] = new HashSet
+ {
+ StructureType.PhysicalDeviceFeatures2,
+ StructureType.DeviceCreateInfo
+ },
+ ...
+ };
+}
+```
+
+## Extenders (Optional)
+
+By contrast the `Extenders` dictionary, it the reverse mapping of the `Extensions` dictionary, providing quick reverse
+lookup. As this is slightly more complex to generate, it could be left to the consumer (who can generate it easily
+from `Extensions` when needed).
+
+```csharp
+public struct Chain : IChainable
+{
+ ...
+ ///
+ /// Provides a set of all the s that can extend a .
+ ///
+ public static readonly IReadOnlyDictionary> Extenders =
+ new Dictionary>
+ {
+ [StructureType.DeviceCreateInfo] = new HashSet
+ {
+ StructureType.PhysicalDeviceFeatures2, StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr
+ },
+ [StructureType.PhysicalDeviceFeatures2] = new HashSet
+ {
+ StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr
+ },
+ ...
+ };
+}
+```
+
+### ClrTypes
+
+The `ClrTypes` dictionary provides a `ClrType` for each `StructureType`, it is trivial to generate with existing
+information:
+
+```csharp
+public struct Chain : IChainable
+{
+ ...
+ ///
+ /// Provides a mapping from the to the corresponding .
+ ///
+ public static readonly IReadOnlyDictionary ClrTypes =
+ new Dictionary
+ {
+ [StructureType.DeviceCreateInfo] = typeof(DeviceCreateInfo),
+ [StructureType.PhysicalDeviceFeatures2] = typeof(PhysicalDeviceFeatures2),
+ [StructureType.PhysicalDeviceDescriptorIndexingFeatures]= typeof(PhysicalDeviceDescriptorIndexingFeatures),
+ [StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr] = typeof(PhysicalDeviceAccelerationStructureFeaturesKhr),
+ ...
+ };
+}
+```
+
+### StructureTypes (Optional)
+
+The `StructureTypes` is the reverse mapping of `ClrTypes` and is likewise trivial to generate.
+
+```csharp
+public struct Chain : IChainable
+{
+ ...
+ ///
+ /// Provides a mapping from the to the corresponding .
+ ///
+ public static readonly IReadOnlyDictionary StructureTypes =
+ new Dictionary
+ {
+ [typeof(DeviceCreateInfo)] = StructureType.DeviceCreateInfo,
+ [typeof(PhysicalDeviceFeatures2)] = StructureType.PhysicalDeviceFeatures2,
+ [typeof(PhysicalDeviceDescriptorIndexingFeatures)]= StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ [typeof(PhysicalDeviceAccelerationStructureFeaturesKhr)] = StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr
+ ...
+ };
+}
+```
+
+## Extension Methods
+
+The following extension methods are not auto-generated and so can be added simply.
+
+### ClrType()
+
+Gets the corresponding `ClrType` for a `StructureTYpe`.
+
+```csharp
+public static class Chain
+{
+ ...
+ ///
+ /// Gets the corresponding for a , if any.
+ ///
+ /// The structure type.
+ /// The corresponding for , if any; otherwise,
+ /// .
+ public static Type ClrType(this StructureType structureType)
+ {
+ return Chain.ClrTypes[structureType];
+ }
+}
+```
+
+Usage:
+
+```csharp
+Assert.Equal(typeof(DeviceCreateInfo), StructureType.DeviceCreateInfo.ClrType());
+```
+
+### StructureType() (Optional)
+
+Gets the corresponding `ClrType` for a `StructureTYpe`.
+
+```csharp
+public static class Chain
+{
+ ...
+ ///
+ /// Gets the corresponding for a , if any.
+ ///
+ /// The CLR type.
+ /// The corresponding for , if any; otherwise,
+ /// .
+ public static StructureType? StructureType(this Type type)
+ {
+ return Chain.StructureTypes.TryGetValue(type, out var structureType) ? structureType : null;
+ }
+}
+```
+
+Usage:
+
+```csharp
+Assert.Equal(StructureType.DeviceCreateInfo, typeof(DeviceCreateInfo).StructureType());
+Assert.Null(typeof(PhysicalDeviceFeatures).StructureType());
+```
+
+### IsChainStart()
+
+Tests whether the `StructureType` or `Type` can be used at the start of a chain:
+
+```csharp
+public static class Chain
+{
+ ...
+ ///
+ /// Whether the can start a chain.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainStart(this StructureType type)
+ {
+ return Chain.Extenders.ContainsKey(type);
+ }
+
+ ///
+ /// Whether the can start a chain.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainStart(this Type type)
+ {
+ return Chain.StructureTypes.TryGetValue(type, out var structureType) &&
+ Chain.Extenders.ContainsKey(structureType);
+ }
+}
+```
+
+Usage:
+
+```csharp
+Assert.True(StructureType.DeviceCreateInfo.IsChainStart());
+Assert.True(typeof(DeviceCreateInfo).IsChainStart());
+Assert.False(StructureType.PhysicalDeviceDescriptorIndexingFeatures.IsChainStart());
+Assert.False(typeof(PhysicalDeviceDescriptorIndexingFeatures).IsChainStart());
+```
+
+### IsChainable()
+
+Tests whether the `StructureType` or `Type` can be used in a chain:
+
+```csharp
+public static class Chain
+{
+ ...
+ ///
+ /// Whether the is chainable.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainable(this StructureType type)
+ {
+ return Chain.Extenders.ContainsKey(type) ||
+ Chain.Extensions.ContainsKey(type);
+ }
+
+ ///
+ /// Whether the is chainable.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainable(this Type type)
+ {
+ return Chain.StructureTypes.TryGetValue(type, out var structureType) &&
+ (Chain.Extenders.ContainsKey(structureType) || Chain.Extensions.ContainsKey(structureType));
+ }
+}
+```
+
+Usage:
+
+```csharp
+Assert.True(StructureType.DeviceCreateInfo.IsChainable());
+Assert.True(typeof(DeviceCreateInfo).IsChainable());
+Assert.True(StructureType.PhysicalDeviceDescriptorIndexingFeatures.IsChainable());
+Assert.True(typeof(PhysicalDeviceDescriptorIndexingFeatures).IsChainable());
+```
+
+### CanExtend()
+
+Tests whether the `StructureType` or `Type` can extend the corresponding type:
+
+```csharp
+public static class Chain
+{
+ ...
+
+ ///
+ /// Whether the current can extend the .
+ ///
+ /// The to test.
+ /// The of the chain.
+ /// if the can extend the ; otherwise, false.
+ ///
+ public static bool CanExtend(this StructureType next, StructureType chain)
+ {
+ return Chain.Extensions.TryGetValue(next, out var extensions) && extensions.Contains(chain);
+ }
+
+ ///
+ /// Whether the current can extend the .
+ ///
+ /// The to test.
+ /// The of the chain.
+ /// if the can extend the ; otherwise, false.
+ ///
+ public static bool CanExtend(this Type next, Type chain)
+ {
+ return
+ Chain.StructureTypes.TryGetValue(next, out var nextType) &&
+ Chain.StructureTypes.TryGetValue(chain, out var chainType) &&
+ Chain.Extensions.TryGetValue(nextType, out var extensions) &&
+ extensions.Contains(chainType);
+ }
+}
+```
+
+Usage:
+
+```csharp
+Assert.True(StructureType.PhysicalDeviceFeatures2.CanExtend(StructureType.DeviceCreateInfo));
+Assert.False(StructureType.DeviceCreateInfo.CanExtend(StructureType.PhysicalDeviceFeatures2));
+Assert.True(typeof(PhysicalDeviceFeatures2).CanExtend(typeof(DeviceCreateInfo)));
+Assert.False(typeof(DeviceCreateInfo).CanExtend(typeof(PhysicalDeviceFeatures2)));
+```
+
+### CanBeExtendedBy()
+
+Tests whether the `StructureType` or `Type` can be extended by the corresponding type:
+
+```csharp
+public static class Chain
+{
+ ...
+
+ ///
+ /// Whether the current can be extended by the .
+ ///
+ /// The of the chain.
+ /// The to test.
+ /// if the can be extended the ; otherwise, false.
+ ///
+ public static bool CanBeExtendedBy(this StructureType chain, StructureType next)
+ {
+ return Chain.Extenders.TryGetValue(chain, out var extenders) && extenders.Contains(next);
+ }
+
+ ///
+ /// Whether the current can be extended by the .
+ ///
+ /// The of the chain.
+ /// The to test.
+ /// if the can extend the ; otherwise, false.
+ ///
+ public static bool CanBeExtendedBy(this Type chain, Type next)
+ {
+ return
+ Chain.StructureTypes.TryGetValue(next, out var nextType) &&
+ Chain.StructureTypes.TryGetValue(chain, out var chainType) &&
+ Chain.Extenders.TryGetValue(chainType, out var extenders) &&
+ extenders.Contains(nextType);
+ }
+}
+```
+
+Usage:
+
+```csharp
+Assert.False(StructureType.PhysicalDeviceFeatures2.CanBeExtendedBy(StructureType.DeviceCreateInfo));
+Assert.True(StructureType.DeviceCreateInfo.CanBeExtendedBy(StructureType.PhysicalDeviceFeatures2));
+Assert.False(typeof(PhysicalDeviceFeatures2).CanBeExtendedBy(typeof(DeviceCreateInfo)));
+Assert.True(typeof(DeviceCreateInfo).CanBeExtendedBy(typeof(PhysicalDeviceFeatures2)));
+```
\ No newline at end of file
diff --git a/documentation/structure-chaining/chaining.puml b/documentation/structure-chaining/chaining.puml
new file mode 100644
index 0000000000..4ac7ada912
--- /dev/null
+++ b/documentation/structure-chaining/chaining.puml
@@ -0,0 +1,61 @@
+@startuml
+!theme black-knight
+package "Interfaces" {
+ interface IStructuredType {
+ +StructureType StructureType()
+ }
+
+ interface IChainable {
+ +BaseInStructure* PNext { get; set; }
+ }
+
+ interface IExtendsChain {
+ }
+
+ interface IChainStart {
+ }
+
+ IStructuredType <|-- IChainable
+
+ IChainable <|-- IExtendsChain
+ IChainable <|-- IChainStart
+}
+
+package "Structures" {
+ class BaseInStructure implements IChainable {
+ +StructureType SType
+ +BaseInStructure* PNext
+
+ +BaseInStructure* IChainable.PNext { get; set; }
+
+ +StructureType IStructureType.StructureType()
+ }
+
+ class PhysicalDeviceFeatures2 implements IChainStart {
+ +StructureType SType
+ +BaseInStructure* PNext
+ +PhysicalDeviceFeatures Features
+
+ +BaseInStructure* IChainable.PNext { get; set; }
+
+ +StructureType IStructureType.StructureType()
+ }
+
+ IExtendsChain <|.. PhysicalDeviceFeatures2
+
+ class PhysicalDeviceVariablePointersFeatures implements IExtendsChain {
+ +StructureType SType
+ +BaseInStructure* PNext
+ +Bool32 VariablePointersStorageBuffer
+ +Bool32 VariablePointers
+
+ +BaseInStructure* IChainable.PNext { get; set; }
+
+ +StructureType IStructureType.StructureType()
+ }
+
+ IExtendsChain <|.. PhysicalDeviceVariablePointersFeatures
+ IExtendsChain <|.. PhysicalDeviceVariablePointersFeatures
+ IExtendsChain <|.. PhysicalDeviceVariablePointersFeatures
+}
+@enduml
\ No newline at end of file
diff --git a/documentation/structure-chaining/chaining.svg b/documentation/structure-chaining/chaining.svg
new file mode 100644
index 0000000000..5979e63457
--- /dev/null
+++ b/documentation/structure-chaining/chaining.svg
@@ -0,0 +1,449 @@
+
\ No newline at end of file
diff --git a/documentation/structure-chaining/managed-chaining.md b/documentation/structure-chaining/managed-chaining.md
new file mode 100644
index 0000000000..0c0edf686b
--- /dev/null
+++ b/documentation/structure-chaining/managed-chaining.md
@@ -0,0 +1,486 @@
+# Managed Chaining
+
+[Table of Contents](overview.md#table-of-contents)
+
+- [Introduction](managed-chaining.md#introduction)
+ - [Type Constraints](managed-chaining.md#type-constraints)
+ - [Disposal](managed-chaining.md#disposal)
+ - ['Any' Overloads](managed-chaining.md#any-overloads)
+ - [Equality](managed-chaining.md#equality)
+ - [Casting to pointers](managed-chaining.md#casting-to-pointers)
+- [Usage](managed-chaining.md#usage)
+ - [Creation (Create/CreateAny)](managed-chaining.md#creation-createcreateany)
+ - [Modifying values](managed-chaining.md#modifying-values)
+ - [Duplication (Duplicate)](managed-chaining.md#duplication-duplicate)
+ - [Loading from an unmanaged chain (Load/LoadAny)](managed-chaining.md#loading-from-an-unmanaged-chain-loadloadany)
+ - [Adding to a chain (Add/AddAny)](managed-chaining.md#adding-to-a-chain-addaddany)
+ - [Truncating (Truncate/TruncateAny)](managed-chaining.md#truncating-truncatetruncateany)
+ - [Deconstruction](managed-chaining.md#deconstruction)
+- [Chain Base Class](managed-chaining.md#chain-base-class)
+ - [IReadOnlyList](managed-chaining.md#ireadonlylist)
+ - [Clearing (Clear)](managed-chaining.md#clearing-clear)
+- [Performance](managed-chaining.md#performance)
+
+## Introduction
+
+The managed chaining methodology use
+the [`Silk.NET.Vulkan.Chain` abstract class](../../src/Vulkan/Silk.NET.Vulkan/Chain.cs) and its descendents. Similar to
+the [`System.Tuple` class](https://docs.microsoft.com/en-us/dotnet/api/system.tuple?view=net-6.0), these are
+[auto-generated](../../src/Vulkan/Silk.NET.Vulkan/Chain.gen.tt) and take the form `Chain`, `Chain`
+, `Chain`
+, ..., `Chain`; supporting chains of length 1
+to 16 (including the head). The generated code can be seen [here](../../src/Vulkan/Silk.NET.Vulkan/Chain.gen.cs).
+
+### Type Constraints
+
+Each item type (including the head) is constrained to be `IChainable`, e.g.:
+
+```csharp
+public unsafe sealed class Chain : Chain, IEquatable>
+ where TChain : unmanaged, IChainable
+ where T1 : unmanaged, IChainable
+{ ... }
+```
+
+**NOTE**: Chains are not themselves 'tightly' constrained (that is insisting on `IChainStart`
+and `IExtendsChain`
+types) as they need to support the `*Any` use case (see [below](#any-overloads)). However the various default static
+method _do_ constrain the types more strictly, e.g.:
+
+```csharp
+///
+/// Creates a new with 2 items.
+///
+/// The head of the chain.
+/// Item 1.
+/// The chain type
+/// Type of Item 1.
+/// A new with 2 items.
+///
+[MethodImpl(MethodImplOptions.AggressiveInlining)]
+public static Chain Create(TChain head = default, T1 item1 = default)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ => new(head, item1);
+```
+
+as compared to:
+
+```csharp
+///
+/// Creates a new with 2 items.
+///
+/// The head of the chain.
+/// Item 1.
+/// The chain type
+/// Type of Item 1.
+/// A new with 2 items.
+/// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+/// useful for situations where the specification does not indicate required chain constraints. You should generally
+/// try to use the none `Any` version in preference.
+///
+[MethodImpl(MethodImplOptions.AggressiveInlining)]
+public static Chain CreateAny(TChain head = default, T1 item1 = default)
+where TChain : unmanaged, IChainable
+where T1 : unmanaged, IChainable
+=> new(head, item1);
+```
+
+Note how `CreateAny` supports the looser constraints of `IChainable`, but the default `Create` method insists
+on `TChain` implementing `IChainStart`, whilst all subsequent items must implement `IExtendsChain`.
+
+You may also note
+the [`unmanaged` constraint](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.3/blittable)
+, this is a tighter constraint than `struct` and is met by all the Vulkan API structures. Usefully, it ensures that we
+are not being passed a structure which has pointers to managed memory (e.g. objects on the heap), this allows use to
+'blit' the chain's items directly to/from memory safely.
+
+### Disposal
+
+It is useful to note, that although Chains offer a wealth of functionality, almost all of it is provided via statics or
+properties, and in reality each instance only contains a single pointer field, which points to the start of a chain in
+memory:
+
+```csharp
+private nint _headPtr`;
+```
+
+As this pointer points to an 'unmanaged' block of process memory, then it is vital that it is freed when the chain is no
+longer used, to prevent memory leaks. As such, every `Chain` object implements `IDisposable` and you _must always
+dispose it_ when you have finished with it. The easiest way, of course, is to use the C# `using` statement; however, if
+you are storing a `Chain` within another object, you will usually implement `IDisposable` on the container and
+call `Dispose` on the `Chain` when appropriate.
+
+**WARNING**: Many of the chain manipulation methods listed will return a new instance of the chain. When using these
+methods you should be extra careful and remember to dispose the original chain after modification, if it no longer being
+used.
+
+### 'Any' Overloads
+
+[_(see also)_](vulkan.md#any-overloads)
+
+The `Chain` classes include `*Any` versions of many methods. In fact, the `Chain` type constraints are
+themselves 'loose', that is they only require types to be `IChainable` rather than requiring the stricter constraints
+that prevent unrelated chain elements being added, or used as the start of a chain.
+
+You cannot create or change the length of a chain save through static methods, and the preferred versions do include the
+tighter constraints. For conciseness we do only show the default methods below, but we indicate where an `*Any` overload
+is available in the titles.
+
+### Equality
+
+All the fully generic `Chain` types implement the corresponding `IEquatable>`
+interfaces, and equality operators, as well as `GetHashCode`. The base `Chain` also implements `IEquatable` and
+the equality operators.
+
+Two separate instances of a `Chain` will hold separate blocks of unmanaged memory, however, they can be directly
+compared, so long as the `PNext` locations are skipped as the pointers will point to different internal locations. As
+such, two chains are considered equal if all their contents are equal, except their `PNext` pointers.
+
+### Casting to pointers
+
+Ultimately, the main use of many `Chain`s is to pass it to the VulkanAPI. To facilitate this, all chains implicitly cast
+to `nint`, `void*`, `BaseInStructure*` and `THead*`. For example:
+
+```csharp
+using var chain = Chain.Create();
+
+PhysicalDeviceFeatures2* p = chain;
+Assert.Equal((nint)p, (nint)chain.HeadPtr);
+
+BaseInStructure* b = chain;
+Assert.Equal((nint)b, (nint)chain.HeadPtr);
+
+void* v = chain;
+Assert.Equal((nint)v, (nint)chain.HeadPtr);
+
+nint n = chain;
+Assert.Equal(n, (nint)chain.HeadPtr);
+
+// We can pass a chain directly to many API calls
+vk.GetPhysicalDeviceFeatures2(device, features2);
+```
+
+## Usage
+
+### Creation (Create/CreateAny)
+
+The following will create a chain starting with `PhysicalDeviceFeatures2`, pointing
+to `PhysicalDeviceDescriptorIndexingFeatures` and finishing with a `PhysicalDeviceAccelerationStructureFeaturesKHR`
+structure:
+
+```csharp
+using var chain = Chain.Create
+ (
+ default(PhysicalDeviceFeatures2),
+ default(PhysicalDeviceDescriptorIndexingFeatures),
+ default(PhysicalDeviceAccelerationStructureFeaturesKhr)
+ );
+
+// Ensure all STypes set correctly
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain.Item2.SType);
+
+// Ensure pointers set correctly
+Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+Assert.Equal((nint) 0, (nint) chain.Item2.PNext);
+```
+
+The structures are held in unmanaged memory, preventing movement by the GC, and ensuring that the pointers remain fixed.
+
+You can also specify the generic types directly when initialising a chain with entirely default values, e.g.:
+
+```csharp
+using var chain = Chain.Create<
+ DeviceCreateInfo,
+ PhysicalDeviceFeatures2,
+ PhysicalDeviceDescriptorIndexingFeatures>();
+```
+
+### Modifying values
+
+We can easily modify any value in the `Chain`, and it will maintain the pointers automatically. You do this using
+the `Head` property, or one of the `Item[#]` properties (e.g. `Item1`), for example:
+
+```csharp
+using var chain = Chain.Create<
+ DeviceCreateInfo,
+ PhysicalDeviceFeatures2,
+ PhysicalDeviceDescriptorIndexingFeatures>();
+
+// Ensure all STypes set correctly
+Assert.Equal(StructureType.DeviceCreateInfo, chain.Head.SType);
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Item1.SType);
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item2.SType);
+
+// Ensure pointers set correctly
+Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+Assert.Equal((nint) 0, (nint) chain.Item2.PNext);
+
+Assert.Equal(0U, chain.Head.Flags);
+
+var headPtr = chain.HeadPtr;
+
+// Get the current head (this is a copy)
+var head = chain.Head;
+// Update the flags
+head.Flags = 1U;
+// Update the chain
+chain.Head = head;
+
+Assert.Equal(1U, chain.Head.Flags);
+
+// The head ptr should not change, as we overwrite the same memory location with the new value
+Assert.Equal((nint) headPtr, (nint) chain.HeadPtr);
+// But the next pointer should not change
+Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+```
+
+**Note**: When we update any item in the chain it overwrites the existing memory location, so any `PNext` value pointing
+to it is maintained. The supplied value also has its `SType` and `PNext` correctly set before storing. As such,
+overwriting any item in the chain is an O(1) operation and very quick as no lookup is required (item positions are
+calculated only once per type).
+
+**WARNING**: As the item value returned from a chain is a `struct` (i.e. a value-type like an `int`) modifying it _does
+not modify the stored value in the chain_; we have to set the item's value back to the modified value to store it.
+
+### Duplication (Duplicate)
+
+You can efficiently duplicate a managed chain by calling Duplicate on it (this works even when the chain is held as
+a `Chain` base class):
+
+```csharp
+using var chain = new Chain.Create();
+using var copy = chain.Duplicate();
+
+// Test equality
+Assert.Equal(chain, copy);
+Assert.True(chain == copy);
+```
+
+**WARNING**: The `Duplicate[Any]` methods return a new instance of a `Chain`, so you must remember
+to [dispose](#disposal) the previous instance if no longer used. In the above sample, we correctly use the `using`
+statements to do this for us.
+
+**Note**: The `copy` is [equal](#equality) to the `chain` until you modify it's contents, as chains implement
+the `IEquatable`
+interface, and overload the equality operators (and `GetHasHCode` method). However, both instances point to different
+blocks of unmanaged memory, and the `PNext` pointers will therefore be different (and hence are not considered when
+comparing chains for equality).
+
+### Loading from an unmanaged chain (Load/LoadAny)
+
+If you have created, or received an unmanaged chain (either by using [structure chaining](structure-chaining.md)
+or [raw chaining](raw_chaining.md)) and would like to load that into a `Chain` you can use one of the
+`Chain.Load` methods:
+
+```csharp
+// Create an unmanaged chain
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+PhysicalDeviceFeatures2
+.Chain(out var unmanagedChain)
+.SetNext(ref indexingFeatures)
+.AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+// Loads a new managed chain from an unmanaged chain
+using var managedChain =
+ Chain.Load(unmanagedChain);
+
+// Check the flag still set
+Assert.True(managedChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+```
+
+There are also versions of the `Load[Any]` methods that return an output parameter `errors` as their first parameter.
+The `errors` parameter will be `string.Empty` if there are no errors, otherwise each line will contain a separate error
+for each issue found during loading. There is also an overload that accepts a single argument `chain` for when you don't
+care if there are any errors (e.g. in `Debug` builds). As these overloads allocate a `StringBuilder` under the hood,
+then they should generally be avoided in production or where performance is more critical.
+
+Either method always succeeds, even if the unmanaged chain doesn't match exactly - for example it is shorter or longer
+than the chain being loaded, or if the managed chain has different structure types in any of the positions. Any
+structure type in the expected position will always be loaded into the new `Chain`.
+
+```csharp
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+// Create an unmanaged chain
+DeviceCreateInfo
+ .Chain(out var unmanagedChain)
+ .AddNext(out PhysicalDeviceFeatures2 features2)
+ .SetNext(ref indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+// Loads a new managed chain from an unmanaged chain
+using var managedChain =
+ Chain.Load<
+ DeviceCreateInfo,
+ // Note we are supplied a PhysicalDeviceFeatures2 here from the unmanaged chain
+ PhysicalDeviceAccelerationStructureFeaturesKHR,
+ PhysicalDeviceDescriptorIndexingFeatures,
+ PhysicalDeviceAccelerationStructureFeaturesKHR,
+ // Note that the unmanaged chain did not supply a 5th entry
+ PhysicalDeviceFeatures2>(out var errors, unmanagedChain);
+
+// Check for errors
+Assert.Equal(
+@"The unmanaged chain has a structure type PhysicalDeviceFeatures2Khr at position 2; expected PhysicalDeviceAccelerationStructureFeaturesKhr
+The unmanaged chain was length 4, expected length 5",
+ errors);
+
+// Despite the errors indexing features was at the right location so was loaded
+Assert.True(managedChain.Item2.ShaderInputAttachmentArrayDynamicIndexing);
+```
+
+### Adding to a chain (Add/AddAny)
+
+You can call `Add` on a `Chain` (of length < 16) to efficiently create a new, larger, `Chain` with a new item appended
+to the end, e.g:
+
+```csharp
+using var chain = Chain.Create(
+ item1: new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true});
+
+// The new chain, will efficiently copy the old chain and append a new structure to the end
+using var newChain = chain.Add();
+// You will usualy wish to dispose the old chain here, the two chains are now independent of each other.
+
+// Check the flag from the first chain is still set in the new chain.
+Assert.True(newChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+```
+
+**Note** As a `Chain` holds a block of unmanaged memory, it must be [disposed](#disposal) when it is finished with. When
+using the `Add` method you will produce a new `Chain` and should not forget to [dispose](#disposal) the original if it
+is no longer needed. The above example uses the `using` statements to do this for us.
+
+Only the `AddAny` method is available from the `Chain` base class as it does not know the concrete `Chain`'s
+type. For a similar reason, it will throw an `InvalidOperationException` if the chain is already at the maximum length.
+
+### Truncating (Truncate/TruncateAny)
+
+Similarly, you can `Truncate` a chain (of length > 1) to get an instance of a smaller chain:
+
+```csharp
+using var chain = Chain.Create();
+using var chain2 = chain.Add();
+// Remove the indexing features we just added (note the out parameter is optional)
+using var chain3 = chain2.Truncate(out var indexingFeatures);
+
+Assert.Equal(1, chain.Count);
+Assert.Equal(2, chain2.Count);
+Assert.Equal(1, chain3.Count);
+```
+
+For convenience, there is also a `Truncate` method that does return the tail.
+
+**Note** As a `Chain` holds a block of unmanaged memory, it must be [disposed](#disposal) when it is finished with. When
+using the `Add` method you will produce a new `Chain` and should not forget to [dispose](#disposal) the original if it
+is no longer needed. The above example uses the `using` statements to do this for us.
+
+Only the `TruncateAny` method is available from the `Chain` base class as it does not know the
+concrete `Chain`'s type. For a similar reason, it will throw an `InvalidOperationException` if the chain is
+already at the minimum length.
+
+### Deconstruction
+
+Like `Tuple`s, each `Chain` has a
+corresponding [deconstructor](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct) for
+convenience, e.g.:
+
+```csharp
+using var chain = new Chain();
+
+// Deconstruct
+var (physicalDeviceFeatures2, indexingFeatures, accelerationStructureFeaturesKhr) = chain;
+
+// Ensure all STypes set correctly
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, physicalDeviceFeatures2.SType);
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, accelerationStructureFeaturesKhr.SType);
+```
+
+## Chain Base Class
+
+The `Chain` base class is `abstract` and cannot be created directly, however, as all Chains descend from it directly, it
+can be used to hold and manipulate a chain of indeterminate length, which can be prove very useful. To facilitate that,
+all chains implement the following functionality.
+
+### IReadOnlyList
+
+All the fully generic `Chain` types extend `Chain` which implements `IReadOnlyList`. The latter
+allowing for easy consumption of any `Chain`, e.g.:
+
+```csharp
+using var chain = new Chain();
+
+Assert.Equal(3, chain.Count);
+
+// Ensure all STypes set correctly using indexer
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain[0].StructureType());
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain[1].StructureType());
+Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain[2].StructureType());
+
+Assert.Throws(() => chain[3]);
+
+// Get array using IEnumerable implementation
+IChainable[] structures = chain.ToArray();
+
+// Check concrete types
+Assert.IsType(structures[0]);
+Assert.IsType(structures[1]);
+Assert.IsType(structures[2]);
+```
+
+### Clearing (Clear)
+
+The base class also provides a clear method, which will reset all items to their `default` values (except the `SType`
+and `PNext` will be maintained correctly as always). The `Clear` method also optionally accepts an `includeHead`
+parameter (which defaults to `true`), when `false` this will skip clearing the head. For example:
+
+```csharp
+using var chain = Chain.Create
+(
+ new PhysicalDeviceFeatures2 {Features = new PhysicalDeviceFeatures {AlphaToOne = true}},
+ new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true},
+ new PhysicalDeviceAccelerationStructureFeaturesKHR {AccelerationStructure = true}
+);
+
+Assert.True(chain.Head.Features.AlphaToOne);
+Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+Assert.True(chain.Item2.AccelerationStructure);
+
+// Don't clear the head
+chain.Clear(false);
+
+Assert.True(chain.Head.Features.AlphaToOne);
+Assert.False(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+Assert.False(chain.Item2.AccelerationStructure);
+
+// Clear the head as well this time
+chain.Clear();
+
+Assert.False(chain.Head.Features.AlphaToOne);
+Assert.False(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+Assert.False(chain.Item2.AccelerationStructure);
+```
+
+## Performance
+
+Although the `Chain` instances are objects, and therefore held on the heap and subject to garbage collection, they are
+very small. Many of the operations are optimised to manipulate memory blocks directly and blit values, as such they
+should be suitable for most applications and are well worth using by default until an issue is identified.
+
+In hot paths, you may want to consider to keeping the `Chain` objects cached, and `Clear`ing or overwriting them on each
+use. However, you should always consider benchmarking solutions to find the optimal approach for each use case.
\ No newline at end of file
diff --git a/documentation/structure-chaining/overview.md b/documentation/structure-chaining/overview.md
new file mode 100644
index 0000000000..bdb8826e74
--- /dev/null
+++ b/documentation/structure-chaining/overview.md
@@ -0,0 +1,147 @@
+# Overview
+
+## Table of Contents
+
+- [Overview](#overview)
+ - [Table of Contents](#table-of-contents)
+ - [Background](#background)
+ - [Managed Chains](#managed-chaining) _(main article [here](managed-chaining.md))_
+ - [Structure Chaining](#structure-chaining) _(main article [here](structure-chaining.md))_
+ - [Raw Chaining](#raw-chaining) _(main article [here](raw_chaining.md))_
+- [Vulkan Chaining](vulkan.md)
+ - [BaseInStructure](vulkan.md#baseinstructure)
+ - [IChainable](vulkan.md#ichainable)
+ - [IStructureType](vulkan.md#istructuredtype)
+ - [IExtendsChain<TChain>](vulkan.md#iextendschainlttchaingt)
+ - [IChainStart](vulkan.md#ichainstart)
+ - ['Any' Overloads](vulkan.md#any-overloads)
+ - [Class Diagram](vulkan.md#class-diagram)
+- [Managed Chains](managed-chaining.md) _(recommended)_
+ - [Introduction](managed-chaining.md#introduction)
+ - [Type Constraints](managed-chaining.md#type-constraints)
+ - [Disposal](managed-chaining.md#disposal)
+ - ['Any' Overloads](managed-chaining.md#any-overloads)
+ - [Equality](managed-chaining.md#equality)
+ - [Casting to pointers](managed-chaining.md#casting-to-pointers)
+ - [Usage](managed-chaining.md#usage)
+ - [Creation (Create/CreateAny)](managed-chaining.md#creation-createcreateany)
+ - [Modifying values](managed-chaining.md#modifying-values)
+ - [Duplication (Duplicate)](managed-chaining.md#duplication-duplicate)
+ - [Loading from an unmanaged chain (Load/LoadAny)](managed-chaining.md#loading-from-an-unmanaged-chain-loadloadany)
+ - [Adding to a chain (Add/AddAny)](managed-chaining.md#adding-to-a-chain-addaddany)
+ - [Truncating (Truncate/TruncateAny)](managed-chaining.md#truncating-truncatetruncateany)
+ - [Deconstruction](managed-chaining.md#deconstruction)
+ - [Chain Base Class](managed-chaining.md#chain-base-class)
+ - [IReadOnlyList](managed-chaining.md#ireadonlylist)
+ - [Clearing (Clear)](managed-chaining.md#clearing-clear)
+ - [Performance](managed-chaining.md#performance)
+- [Structure Chaining](structure-chaining.md)
+ - [Introduction](structure-chaining.md#introduction)
+ - [Usage](structure-chaining.md#usage)
+ - [Creation (Chain)](structure-chaining.md#creation-chain)
+ - [Understanding the Chain method's return value](structure-chaining.md#understanding-the-chain-methods-return-value)
+ - ['Any' Overloads](structure-chaining.md#any-overloads)
+ - [Extending the chain](structure-chaining.md#extending-the-chain)
+ - [Adding (AddNext/AddNextAny)](structure-chaining.md#adding-addnextaddnextany)
+ - [Try adding (TryAddNext/TryAddNextAny)](structure-chaining.md#try-adding-tryaddnexttryaddnextany)
+ - [Setting or Replacing (SetNext/SetNextAny)](structure-chaining.md#setting-or-replacing-setnextsetnextany)
+ - [Indexing (IndexOf/IndexOfAny)](structure-chaining.md#indexing-indexofindexofany)
+ - [Performance](structure-chaining.md#performance)
+- [Raw Chaining](raw_chaining.md)
+ - [Overview](raw_chaining.md#overview)
+ - [Best Practices](raw_chaining.md#best-practices)
+ - [Performance](raw_chaining.md#performance)
+
+## Background
+
+Several of the APIs exposed by Silk.NET pass data using
+a [Singly Linked List](https://en.wikipedia.org/wiki/Linked_list) of structures. We refer to these lists as 'chains'.
+This is achieved by have a standardised pointer (`void*`) that can be used to point to the next item, with
+the `nullptr` (i.e. `0`) used to indicate the end of the list.
+
+However, pointers are particularly problematic in managed programming as the runtime is often free to move objects on
+the heap around during Garbage Collection. As such, C# requires you to explicitly mark code that works with pointers
+as `unsafe`, to let the compiler know that you are aware of the dangers. The Runtime usually manages pointers for you
+under the hood, repointing them if it moves any objects around and, as such, many developers do not use them frequently
+enough to be aware of the pitfalls, and the potential bugs that can occur can be subtle and hard to trace.
+
+Silk.NET always exposes the lowest level API possible, allowing you to directly manipulate these chains via directly
+setting the pointers as appropriate. However, it also exposes 2 new methods of maintaining these chains more easily and
+safely.
+
+Currently, these new methods are only available in the `Silk.NET.Vulkan` package when using the Vulkan API, however, if
+popular, they will be considered for more of the exposed APIs in future.
+
+## Chaining methodologies
+
+There are 3, chaining methodologies supported by Silk.Net.
+
+### Managed Chaining
+
+[_(Covered in more detail here)_](managed-chaining.md)
+
+This is the _recommended_ approach. `Chain` is a lightweight C# object that manages the memory of chain, and ensure
+internal pointers remain valid throughout it's lifetime. For example:
+
+```csharp
+using (var features2 = Chain.Create
+(
+ default(PhysicalDeviceFeatures2),
+ default(PhysicalDeviceDescriptorIndexingFeatures),
+ default(PhysicalDeviceAccelerationStructureFeaturesKHR)
+)) {
+ vk.GetPhysicalDeviceFeatures2(device, features2);
+ var depthBounds = features2.Head.Features.DepthBounds;
+ var runtimeDescriptorArray = features2.Item1.RuntimeDescriptorArray;
+ var accelerationStructure = features2.Item2.AccelerationStructure;
+}
+```
+
+### Structure Chaining
+
+[_(Covered in more detail here)_](structure-chaining.md)
+
+If you are creating a chain once, and then throwing it away, it can be done so safely, so long as the structures you
+create never leave the stack. Silk.NET provides fluent extension methods that allow you to manipulate `IChainable`
+structures directly, performing the pointer logic for you, and providing compile-time type validation. Although,
+avoiding the heap entirely, there are some scenarios where this may still be slower than
+using [Managed Chains](managed-chains.md), and so this approach should only be considered when looking to optimise hot
+paths. For example:
+
+```csharp
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+vk.GetPhysicalDeviceFeatures2(device, &features2);
+var depthBounds = features2.Features.DepthBounds;
+var runtimeDescriptorArray = indexingFeatures.RuntimeDescriptorArray;
+var accelerationStructure = accelerationStructureFeaturesKhr.AccelerationStructure;
+```
+
+### Raw Chaining
+
+[_(Covered in more detail here)_](raw_chaining.md)
+
+Where you are looking to optimise a hot path, and every cycle counts, direct modification of chains is always available.
+Again though, you should benchmark against the above two methodologies to ensure you are gaining the benefit you hope
+for. For example:
+
+```csharp
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR
+ {SType = StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr};
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ SType = StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ PNext = &accelerationStructureFeaturesKhr
+};
+var features2 = new PhysicalDeviceFeatures2
+{
+ SType = StructureType.PhysicalDeviceFeatures2,
+ PNext = &indexingFeatures
+};
+vk.GetPhysicalDeviceFeatures2(device, &features2);
+var depthBounds = features2.Features.DepthBounds;
+var runtimeDescriptorArray = indexingFeatures.RuntimeDescriptorArray;
+var accelerationStructure = accelerationStructureFeaturesKhr.AccelerationStructure;
+```
\ No newline at end of file
diff --git a/documentation/structure-chaining/raw_chaining.md b/documentation/structure-chaining/raw_chaining.md
new file mode 100644
index 0000000000..2c88ee5faa
--- /dev/null
+++ b/documentation/structure-chaining/raw_chaining.md
@@ -0,0 +1,86 @@
+# Raw Chaining
+
+[Table of Contents](overview.md#table-of-contents)
+- [Overview](raw_chaining.md#overview)
+- [Best Practices](raw_chaining.md#best-practices)
+- [Performance](raw_chaining.md#performance)
+
+## Overview
+
+Raw chaining, refers to using the `IChainable` structures directly. The user is responsible for ensuring the `SType`
+and `PNext` fields are set, and that any data pointed to by `PNext` does not move whilst it is being consumed by the
+API.
+
+## Best Practices
+
+The easiest way to prevent pointers moving is to ensure that structures are created and used locally in the same
+function. This ensures that they remain in the current stack frame, preventing the runtime from moving any data.
+Sometimes, it is desirable to store structures for later use, in which case [Managed Chaining](managed-chains.md) should
+be considered.
+
+Each structure defines a constructor which accepts the fields as parameters and specifies defaults, including the
+correct
+`SType`. As such it is possible, to setup the structures like so:
+
+```csharp
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR(pNext: null);
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures(pNext: &accelerationStructureFeaturesKhr);
+var features2 = new PhysicalDeviceFeatures2(pNext: &indexingFeatures);
+vk.GetPhysicalDeviceFeatures2(device, &features2);
+var depthBounds = features2.Features.DepthBounds;
+var runtimeDescriptorArray = indexingFeatures.RuntimeDescriptorArray;
+var accelerationStructure = accelerationStructureFeaturesKhr.AccelerationStructure;
+```
+
+However, notice the 'gotcha' on the first line where we set `pNExt` to `null`, even though the default is
+already `null`:
+
+```csharp
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR(pNext: null);
+```
+
+We _have_ to do that, as calling `new PhysicalDeviceAccelerationStructureFeaturesKHR()`, with no parameters is
+equivalent to using `default`, in which case the `SType` is not set correctly.
+
+As such it is safest to always be explicit, e.g.:
+
+```csharp
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR
+ {SType = StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr};
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ SType = StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ PNext = &accelerationStructureFeaturesKhr
+};
+var features2 = new PhysicalDeviceFeatures2
+{
+ SType = StructureType.PhysicalDeviceFeatures2,
+ PNext = &indexingFeatures
+};
+vk.GetPhysicalDeviceFeatures2(device, &features2);
+var depthBounds = features2.Features.DepthBounds;
+var runtimeDescriptorArray = indexingFeatures.RuntimeDescriptorArray;
+var accelerationStructure = accelerationStructureFeaturesKhr.AccelerationStructure;
+```
+
+Alternatively, you can make use of `IChainable`, though that incurs a boxing overhead, at which point you should
+consider whether raw chaining is the right approach. For example:
+
+```csharp
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR();
+((IChainable) accelerationStructureFeaturesKhr).StructureType();
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {PNext = &accelerationStructureFeaturesKhr};
+((IChainable) indexingFeatures).StructureType();
+var features2 = new PhysicalDeviceFeatures2 {PNext = &indexingFeatures};
+((IChainable) features2).StructureType();
+```
+
+## Performance
+
+In general Raw Chaining will almost always allow the greatest performance, however it may not be significantly better
+than either of the other two methodologies, depending on the exact use case. In some cases it can be slower, for example
+if the chain ends up be passed around and copy operations are triggerred inadvertently on larger structures. For this
+reason, it is usually better to start with [Managed Chaining](managed-chaining.md), and optimise hot paths were
+necessary, using benchmarking to validate results. Starting with one of the other two approaches will usually make it
+easier to validate chain types, and improve compile time checking during development.
\ No newline at end of file
diff --git a/documentation/structure-chaining/structure-chaining.md b/documentation/structure-chaining/structure-chaining.md
new file mode 100644
index 0000000000..40368f6653
--- /dev/null
+++ b/documentation/structure-chaining/structure-chaining.md
@@ -0,0 +1,206 @@
+# Structure Chaining
+
+[Table of Contents](overview.md#table-of-contents)
+
+- [Introduction](structure-chaining.md#introduction)
+- [Usage](structure-chaining.md#usage)
+ - [Creation (Chain)](structure-chaining.md#creation-chain)
+ - [Understanding the Chain method's return value](structure-chaining.md#understanding-the-chain-methods-return-value)
+ - ['Any' Overloads](structure-chaining.md#any-overloads)
+ - [Extending the chain](structure-chaining.md#extending-the-chain)
+ - [Adding (AddNext/AddNextAny)](structure-chaining.md#adding-addnextaddnextany)
+ - [Try adding (TryAddNext/TryAddNextAny)](structure-chaining.md#try-adding-tryaddnexttryaddnextany)
+ - [Setting or Replacing (SetNext/SetNextAny)](structure-chaining.md#setting-or-replacing-setnextsetnextany)
+ - [Indexing (IndexOf/IndexOfAny)](structure-chaining.md#indexing-indexofindexofany)
+- [Performance](structure-chaining.md#performance)
+
+## Introduction
+
+The Structure Chaining methodology logically sits between the [Managed Chaining methodology](managed-chaining.md) and
+the [Raw Chaining methodology](raw_chaining.md). In a very real sense, it can either be seen as having the best elements
+of both, or of being a compromise that isn't as good as either alternative!
+
+Notwithstanding, the methodology makes use of a set of well documented extension methods, found
+in [`ChainExtensions`](../../src/Vulkan/Silk.NET.Vulkan/ChainExtensions.cs) to allow the type-safe manipulation
+of `IChainable` structures efficiently; with the exception of the `ref Chain(out)` instance convenience method which we
+cover [below](#creation-chain), that is implemented automatically on any structure that implements `IChainStart`.
+
+## Usage
+
+### Creation (Chain)
+
+You can happily create the start of a chain as you would with [Raw Chaining](raw_chaining.md), by declaring a variable
+of the chain's head first. Indeed it is necessary to do so if you wish to specify non-default values for the head
+structure (though you can also make use of `SetNext`s replace functionality). You also need to use this approach when
+starting a chain which is not explicitly defined as a [chain start](vulkan.md#ichainstart) by the specification. If you
+do start a chain with such a structure, you will have to use the [`*Any` overloads](#any-overloads) of the extension
+methods below to continue manipulating it.
+
+Regardless, the `SType` and `PNext` will be overwritten whenever you start manipulating the chain, so you should never
+set them manually. For example:
+
+```csharp
+var createInfo = new DeviceCreateInfo
+{
+ Flags = 1U
+};
+// When you call any chaining method it will set the chain's SType automatically.
+createinfo.AddNext...
+```
+
+In many cases, we only want to create a default structure for population by the API. To do so, we can make use of the
+static `ref Chain(out)` convenience method like so:
+
+```csharp
+PhysicalDeviceFeatures2.Chain(out var features2)
+```
+
+This has several advantages:
+
+- The method is only available for structures that are valid at the start of a chain; providing compile-time validation.
+- The structure's `SType` will be correctly set immediately.
+- The syntax is fluent, and creates more readable code when used with the other chaining methods (
+ see [below](#adding-addnextaddnextany)).
+
+#### Understanding the Chain method's return value
+
+All the chaining methods return the current start of the chain by reference (including `ref Chain(out)`). This allows
+each method to scan the entire chain. More importantly, it allows the Type constraints to be checked during compile time
+to ensure that a type actually can extend the chain (unless you are using the [`*Any` overloads](#any-overloads) which
+define looser constraints). One side effect of this approach is that `ref Chain(out)` outputs the newly created chain _
+and_ returns a reference to it. This can cause confusion to less experienced C# devs, for example:
+
+```csharp
+// TL;DR Never do this assignment
+var a = ChainStart.Chain(out var b).AddNext(out ChainExtension c);
+```
+
+Both `a` and `b` will appear to be identical structures; however 'a' is actually a copy of 'b', _being separate
+locations on the current stack frame_. In most cases, that is really no problem at all (though there is a performance
+impact due to the copy) as the copy occurs at the end of the statement (during the assignment operation) so both `a`
+and `b` are a copy of the _start_ of the chain that point to the same next item. However, if further modifications are
+made to the head of either `a` or `b`, they will not be reflected in the other. None of this is undefined behaviour, but
+as it is generally poorly understood none of the examples ever recommend assigning the output of a chain, and it should
+be avoided.
+
+The `ref Chain(out)` method is a static method that is only implemented automatically on `IChainStart` structures, the
+remaining methods are actually extension methods.
+
+### 'Any' Overloads
+
+[_(see also)_](vulkan.md#any-overloads)
+
+The methods ending with `Any` can be used with any `IChainable` structure, but they do not constrain the entries, or the
+head of the chain to being structures explicitly mentioned by the specification. The non-`Any` methods are more
+restrictive, and should usually be used in preference.
+
+### Extending the chain
+
+#### Adding (AddNext/AddNextAny)
+
+The most common use case is to add an empty structure to the end of a chain for it to be populated by the Vulkan API,
+this can be done using the `AddNext` method like so:
+
+```csharp
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ // CreateNext will create an empty struct, with the correct SType (as well as ensuring the
+ // chain's SType is set correctly).
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+```
+
+Each method `out` puts a struct into the local stack frame for querying once populated, and the pointers point to this
+local variable. Despite generics and interfaces being used, the chain methods avoid the heap entirely.
+
+#### Try adding (TryAddNext/TryAddNextAny)
+
+You may only want to add a structure if it doesn't already exist in the chain, this can be done with `TryAddNext`, e.g.:
+
+```csharp
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ // As there is already a PhysicalDeviceDescriptorIndexingFeatures structure the following
+ // will not add anything to the chain and `added` will be `false`.
+ .TryAddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures2, out bool added);
+```
+
+#### Setting or Replacing (SetNext/SetNextAny)
+
+Sometimes we may wish to set the initial state of a structure, or replace any existing item within the structure that
+has the same `StructureType` we can do this with `SetNext`:
+
+```csharp
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+
+// Unlike AddNext, SetNext will only add the structure if isn't already present, otherwise it will overwrite it.
+// So we can provide a default to SetNext like so:
+var accelerationStructureFeaturesKhr = default(PhysicalDeviceAccelerationStructureFeaturesKHR);
+
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ // SetNext accepts an existing struct, note, it will coerce the SType and blank the PNext
+ .SetNext(ref indexingFeatures)
+ .SetNext(ref default(PhysicalDeviceAccelerationStructureFeaturesKHR));
+```
+
+*NOTE* you can mix and match `AddNext` and `SetNext` (and any chaining method) in the same method chain.
+
+By default, `SetNext` will _replace_ any item in the chain with a matching `SType`, this behaviour can be changed by
+setting the optional `alwaysAdd` parameter to `true`;
+
+```csharp
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+var indexingFeatures2 = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = false
+};
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR
+{
+ AccelerationStructure = true
+};
+
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .SetNext(ref indexingFeatures)
+ // This will add the second 'indexingFeatures' struct, even though one is already present in the chain.
+ .SetNext(ref indexingFeatures2, true);
+```
+
+### Indexing (IndexOf/IndexOfAny)
+
+Sometimes it's useful to know if a structure you previously supplied is still in a chain, this can be done
+with `IndexOf`, which returns a non-negative index (zero-indexed) if the structure is found, eg.:
+
+```csharp
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+// Check indices
+Assert.Equal(1, features2.IndexOf(ref indexingFeatures));
+Assert.Equal(2, features2.IndexOf(ref accelerationStructureFeaturesKhr));
+```
+
+## Performance
+
+Structure chaining has the advantage that it avoids the heap entirely, and encourages you to pass the structures around
+as references (avoiding copies). Note though, it can't stop you from sticking something on the heap or doing an
+unnecessary copy, but it doesn't encourage the behaviour.
+
+However, every action begins at the chain's head, so extending the chain is always an O(n) operation. Technically, it
+should still be faster than [extending a Managed Chain](managed-chaining.md#adding-to-a-chain-addaddany) which needs to
+copy the entire chain so is also O(n), as it merely skips from pointer to pointer, whilst coercing the `SType` field.
+However [Raw Chaining](raw_chaining.md) can extend a chain quickly if it keeps track of the tail.
+
+The pay off is a compact syntax that works well with IDE tools and compile time checking, due to the type constraint
+system, whilst encouraging good practice. As such, it should be compared to [Raw Chaining](raw_chaining.md) when trying
+to optimise a hot path.
\ No newline at end of file
diff --git a/documentation/structure-chaining/vulkan.md b/documentation/structure-chaining/vulkan.md
new file mode 100644
index 0000000000..cf94235cf9
--- /dev/null
+++ b/documentation/structure-chaining/vulkan.md
@@ -0,0 +1,219 @@
+# Vulkan Chaining
+
+[Table of Contents](overview.md#table-of-contents)
+- [BaseInStructure](vulkan.md#baseinstructure)
+- [IChainable](vulkan.md#ichainable)
+- [IStructureType](vulkan.md#istructuredtype)
+- [IExtendsChain<TChain>](vulkan.md#iextendschainlttchaingt)
+- [IChainStart](vulkan.md#ichainstart)
+- ['Any' Overloads](vulkan.md#any-overloads)
+- [Class Diagram](vulkan.md#class-diagram)
+
+## BaseInStructure
+
+The Vulkan Specification provides
+a [machine-readable XML version](https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml) of its API
+which is used by Silk.NET's `BuildTools` to automatically generate the API in the `Silk.NET.Vulkan` namespace. Each
+structure type, used by the API, is clearly defined in the document. Two of those types are `VkBaseOutStructure`
+and `VkBaseInStructure`, the latter of which maps to
+the [BaseInStructure](../../src/Vulkan/Silk.NET.Vulkan/Structs/BaseInStructure.gen.cs) type in Silk.NET, which contains
+2 fields:
+
+```csharp
+public unsafe partial struct BaseInStructure : IChainable
+{
+ public StructureType SType;
+ public BaseInStructure* PNext;
+ ... // Abbreviated for clarity
+}
+```
+
+This structure represents a 'base type' of all 'chainable' structures in the Vulkan API; that is, structures that can be
+chained together in a
+[Singly Linked List](https://en.wikipedia.org/wiki/Linked_list) using the `PNext` field. Unfortunately, plain old C#
+structs cannot extend each other, so we indicate this relation using the `IChainable` interface, which guarantees that
+any unmanaged structure that implements it has the same two fields in the first two positions. Most importantly this
+means that we can use `BaseInStructure*` as the type for `PNext` instead of just `void*`, and so can access
+the `StructureType` of each item in the chain as well as the next item.
+
+## IChainable
+
+As mentioned above, the `IChainable` interface acts as a guarantee that the first two fields of an unmanaged struct
+contain the
+`StructureType` in a field called `SType` and a pointer to the next item in a chain (usually as `void*`, but
+alternatively as `BaseInStructure*` as above, or even `BaseOutStructure`) in a field called `PNext`. C# interfaces
+cannot specify fields though, so this guarantee is enforced during generation. It is important, therefore, that you do
+not add the interface to your own types, unless you are very clear on the implications and make the same guarantees.
+
+The interface is defined [here](../../src/Vulkan/Silk.NET.Vulkan/IChainable.cs):
+
+```csharp
+///
+/// Base interface for any struct that has can set the next value.
+///
+/// Note that any structure marked must start with a
+/// and a void* field, in that order. This is so that a pointer to it can be coerced
+/// to a pointer to a .
+public interface IChainable : IStructuredType
+{
+ ///
+ /// Points to the next in this chain, if any; otherwise .
+ ///
+ unsafe BaseInStructure* PNext { get; set; }
+}
+```
+
+As you can see, it _does_ specify a Property `PNext`, which we implement explicitly on any structure that implements the
+interface, to avoid confusion with the equivalent field. e.g.:
+
+```csharp
+public void* PNext;
+
+///
+unsafe BaseInStructure* IChainable.PNext
+{
+ get => (BaseInStructure*) PNext;
+ set => PNext = value;
+}
+```
+
+## IStructuredType
+
+You may also note that `IChainable` extends `IStructuredType` which is
+defined [here](../../src/Vulkan/Silk.NET.Vulkan/IStructuredType.cs):
+
+```csharp
+///
+/// Base interface for any struct that has a field called `SType`, that must be correctly
+/// set when passing into the Vulkan API.
+///
+public interface IStructuredType
+{
+ ///
+ /// Gets the structured type's enum value.
+ ///
+ ///
+ /// Retrieving the also ensures it is set to the correct value (if any).
+ ///
+ StructureType StructureType();
+}
+```
+
+Although this is currently never implemented directly (as all such structures in Vulkan are [Chainable](#ichainable)),
+it is split out from `IChainable` for clarity.
+
+Notice that `StructureType()` is a method rather than a property. This is because it not only returns
+the `StructureType` for the current structure, it also ensures it is correctly set. This is because C# structures do not
+have a way to force field initialisation. This method is used by Silk.NETs chaining system to ensure the `StructureType`
+is always set correctly, and is implemented automatically, e.g:
+
+```csharp
+[NativeName("Name", "VkDeviceCreateInfo")]
+public unsafe partial struct DeviceCreateInfo : IChainStart
+{
+ ...
+ ///
+ StructureType IStructuredType.StructureType()
+ {
+ return SType = StructureType.DeviceCreateInfo; // Assigns correct value AND returns it
+ }
+ ...
+}
+```
+
+## IExtendsChain<TChain>
+
+For _some_ chainable structures,
+the [Vulkan Specification](https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml) goes further and
+specifies which chains the structure can be used in. For example, take the `VkPhysicalDeviceVariablePointersFeatures`
+structure, which maps
+to [`PhysicalDeviceVariablePointersFeatures`](../../src/Vulkan/Silk.NET.Vulkan/Structs/PhysicalDeviceVariablePointersFeatures.gen.cs)
+. It is defined in the specification as:
+
+```xml
+
+
+ ...
+
+```
+
+Which, `BuildTools` converts to:
+
+```csharp
+[NativeName("Name", "VkPhysicalDeviceVariablePointersFeatures")]
+[NativeName("Aliases", "VkPhysicalDeviceVariablePointersFeaturesKHR, VkPhysicalDeviceVariablePointerFeaturesKHR, VkPhysicalDeviceVariablePointerFeatures")]
+public unsafe partial struct PhysicalDeviceVariablePointersFeatures :
+ IExtendsChain,
+ IExtendsChain,
+ IExtendsChain
+{ ... }
+```
+
+As you can see, it _doesn't_ seem to implement `IChainable`, instead it implements `IExtendsChain<>` 3 times. The first
+part is easy enough to understand when we see the definition
+of `IExtendsChain` [here](../../src/Vulkan/Silk.NET.Vulkan/IExtendsChain.cs):
+
+```csharp
+///
+/// Marks a chainable struct indicating which chain this type
+/// extends.
+///
+/// A chain start structure.
+public interface IExtendsChain : IChainable
+ where TChain : unmanaged, IChainable
+{
+}
+```
+
+Clearly, `IExtendsChain` implements `IChainable` and doesn't add anything, acting instead as additional metadata for the
+structure, by indicating which chains the structure is valid on. This is pivotal to `Silk.NET`'s chaining system,
+allowing it to enforce such constraints at compile time.
+
+But where does the `IExtendsChain` come from? As the API only lists 2 structures in
+the `structextends` attribute? Well a clue can be found in the `PhysicalDeviceFeatures2`
+structure [here](../../src/Vulkan/Silk.NET.Vulkan/Structs/PhysicalDeviceFeatures2.gen.cs):
+
+```csharp
+[NativeName("Name", "VkPhysicalDeviceFeatures2")]
+[NativeName("Aliases", "VkPhysicalDeviceFeatures2KHR")]
+public unsafe partial struct PhysicalDeviceFeatures2 : IChainStart, IExtendsChain
+{ ... }
+```
+
+As you can see this indicates that the specification defines an alias for `VkPhysicalDeviceFeatures2`
+called `VkPhysicalDeviceFeatures2KHR`, which maps to the `PhysicalDeviceFeatures2KHR` structure
+found [here](../../src/Vulkan/Silk.NET.Vulkan/Structs/PhysicalDeviceFeatures2KHR.gen.cs).
+
+## IChainStart
+
+From `PhysicalDeviceFeatures2` we can see another new interface called `IChainStart`, similar to
+`IExtendsChain`, it also implements `IChainable` and is
+defined [here](../../src/Vulkan/Silk.NET.Vulkan/IChainStart.cs):
+
+```csharp
+///
+/// Marks a chainable struct as being allowed at the start of a chain.
+///
+/// Any will have a corresponding static `BaseInStructure(out var chain)`
+/// convenience method.
+public interface IChainStart : IChainable
+{
+}
+```
+
+This allows us to specify which structures can be used as the start of a chain.
+
+## 'Any' Overloads
+
+However, not all possible chains are defined explicitly, so throughout Silk.NET's chaining system we support `*Any`
+overloads, which relax the generic constraints. That is, instead of requiring `IChainStart` types at the head/start of a
+chain, and the corresponding `IExtendsChain` types throughout the chain), `*Any` methods only
+require `IChainable` structures throughout.
+
+## Class Diagram
+
+The above structures can be visualized below:
+
+
\ No newline at end of file
diff --git a/src/Core/Silk.NET.BuildTools/Bind/ProjectWriter.cs b/src/Core/Silk.NET.BuildTools/Bind/ProjectWriter.cs
index 24932a3493..bb2f589938 100644
--- a/src/Core/Silk.NET.BuildTools/Bind/ProjectWriter.cs
+++ b/src/Core/Silk.NET.BuildTools/Bind/ProjectWriter.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using Silk.NET.BuildTools.Common;
@@ -9,6 +11,30 @@ namespace Silk.NET.BuildTools.Bind
{
public static class ProjectWriter
{
+ ///
+ /// Creates a unique file name (that doesn't already exist), preventing clashes of names with different cases on
+ /// case insensitive filesystems.
+ ///
+ /// The name (goes before any disambiguator)
+ /// The extension (goes after any disambiguator)
+ /// Any number of directory segements
+ /// A unique filename.
+ private static string GetFileName(string name, string ext, params string[] directories)
+ {
+ var count = 1;
+ string filename;
+ Array.Resize(ref directories, directories.Length + 1);
+ var nameIndex = directories.Length - 1;
+ do
+ {
+ directories[nameIndex] = $"{name}{(count > 1 ? count.ToString() : "")}{ext}";
+ filename = Path.Combine(directories);
+ count++;
+ } while (File.Exists(filename));
+
+ return filename;
+ }
+
///
/// Writes this project in the given folder, with the given settings and parent subsystem.
///
@@ -38,14 +64,16 @@ public static void Write(this Project project, string folder, Profile profile, B
(
x => x.WriteStruct
(
- Path.Combine(folder, ProfileWriter.StructsSubfolder, $"{x.Name}.gen.cs"), profile, project, task
+ GetFileName(x.Name, ".gen.cs", folder, ProfileWriter.StructsSubfolder), profile, project, task
)
);
project.Enums.ForEach
(
x => x.WriteEnum
- (Path.Combine(folder, ProfileWriter.EnumsSubfolder, $"{x.Name}.gen.cs"), profile, project, task)
+ (
+ GetFileName(x.Name, ".gen.cs", folder, ProfileWriter.EnumsSubfolder), profile, project, task
+ )
);
project.WriteMixedModeClasses(profile, folder, task);
diff --git a/src/Core/Silk.NET.BuildTools/Bind/StructWriter.cs b/src/Core/Silk.NET.BuildTools/Bind/StructWriter.cs
index 4d5c5b8f1b..d6b4ee5ea4 100644
--- a/src/Core/Silk.NET.BuildTools/Bind/StructWriter.cs
+++ b/src/Core/Silk.NET.BuildTools/Bind/StructWriter.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
@@ -27,10 +27,11 @@ public static class StructWriter
/// The file to write to.
/// The subsystem containing this enum.
/// The project containing this enum.
+ /// The bind state.
public static void WriteStruct
(this Struct @struct, string file, Profile profile, Project project, BindState task)
{
- if (@struct.Attributes.IsBuildToolsIntrinsic(out var args))
+ if (@struct.Attributes.IsBuildToolsIntrinsic(out var args) && args.FirstOrDefault() == "$PFN")
{
WriteBuildToolsIntrinsic(@struct, file, profile, project, task, args);
return;
@@ -47,14 +48,67 @@ public static void WriteStruct
sw.WriteLine($"namespace {ns}{project.Namespace}");
sw.WriteLine("{");
string guid = null;
-
+
static bool IsChar(Type type) => type.Name == "char" || type.GenericTypes.Any(IsChar);
var needsCharSetFixup = @struct.Fields.Any(x => IsChar(x.Type));
-
+ string structuredType = null;
+ var isChainable = false;
+ // Which chain this struct extends
+ IReadOnlyList chainExtensions = null;
+ // Which chain extend the current struct
+ IReadOnlyList chainExtenderss = null;
+ IReadOnlyList aliases = null;
+ string aliasOf = null;
+
foreach (var attr in @struct.Attributes)
{
if (attr.Name == "BuildToolsIntrinsic")
{
+ if (attr.Arguments.Count > 0)
+ {
+ switch (attr.Arguments[0])
+ {
+ case "$VKSTRUCTUREDTYPE":
+ {
+ structuredType = attr.Arguments.Count > 1 ? attr.Arguments[1] : null;
+ break;
+ }
+ case "$VKCHAINABLE":
+ {
+ isChainable = true;
+ break;
+ }
+ case "$VKEXTENDSCHAIN":
+ {
+ chainExtensions = attr.Arguments.Count > 1 ? attr.Arguments.Skip(1).ToArray() : null;
+ break;
+ }
+ case "$VKCHAINSTART":
+ {
+ chainExtenderss = attr.Arguments.Count > 1 ? attr.Arguments.Skip(1).ToArray() : null;
+ break;
+ }
+ case "$VKALIASOF":
+ {
+ aliasOf = attr.Arguments.Count > 1 ? attr.Arguments[1] : null;
+ break;
+ }
+ case "$VKALIASES":
+ {
+ aliases = attr.Arguments.Count > 1 ? attr.Arguments.Skip(1).ToArray() : null;
+ break;
+ }
+ default:
+ {
+ Console.WriteLine
+ (
+ $"Unexpected build intrinsic attribute '{attr.Arguments[0]}' on '{@struct.Name}' struct!"
+ );
+ break;
+ }
+ }
+ }
+
continue;
}
@@ -72,13 +126,51 @@ public static void WriteStruct
sw.WriteLine($" {attr}");
}
+ // Build interface list
+ var interfaces = new List();
+ if (chainExtenderss?.Any() == true)
+ {
+ interfaces.Add("IChainStart");
+ }
+
+ interfaces.AddRange
+ (
+ chainExtensions?.Select(e => $"IExtendsChain<{e}>") ??
+ Enumerable.Empty()
+ );
+ if (!interfaces.Any())
+ {
+ // We only need to add these interfaces if a descendant not already added above.
+ if (isChainable)
+ {
+ interfaces.Add("IChainable");
+ }
+ else if (structuredType is not null)
+ {
+ interfaces.Add("IStructuredType");
+ }
+ }
+
if (needsCharSetFixup)
{
sw.WriteLine(" [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]");
}
sw.WriteLine($" [NativeName(\"Name\", \"{@struct.NativeName}\")]");
- sw.WriteLine($" public unsafe partial struct {@struct.Name}");
+ if (!string.IsNullOrWhiteSpace(aliasOf))
+ {
+ sw.WriteLine($" [NativeName(\"AliasOf\", \"{aliasOf}\")]");
+ }
+
+ if (aliases is not null)
+ {
+ sw.WriteLine($" [NativeName(\"Aliases\", \"{string.Join(", ", aliases)}\")]");
+ }
+
+ sw.WriteLine
+ (
+ $" public unsafe partial struct {@struct.Name}{(interfaces.Any() ? " : " + string.Join(", ", interfaces) : string.Empty)}"
+ );
sw.WriteLine(" {");
if (guid is not null)
{
@@ -214,7 +306,10 @@ public static void WriteStruct
}
else if (structField.NumBits is not null)
{
- WriteBitfield(structField, ref bitfieldIdx, ref bitfieldPsz, ref bitfieldRbs, ref bitfieldLbt, sw, profile);
+ WriteBitfield
+ (
+ structField, ref bitfieldIdx, ref bitfieldPsz, ref bitfieldRbs, ref bitfieldLbt, sw, profile
+ );
}
else if (!(structField.Count is null))
{
@@ -347,7 +442,7 @@ public static void WriteStruct
}
foreach (var function in @struct.Functions.Concat
- (ComVtblProcessor.GetHelperFunctions(@struct, profile.Projects["Core"])))
+ (ComVtblProcessor.GetHelperFunctions(@struct, profile.Projects["Core"])))
{
using (var sr = new StringReader(function.Signature.Doc))
{
@@ -364,7 +459,7 @@ public static void WriteStruct
}
using (var sr = new StringReader
- (function.Signature.ToString(null, accessibility: true, semicolon: false)))
+ (function.Signature.ToString(null, accessibility: true, semicolon: false)))
{
string line;
while ((line = sr.ReadLine()) != null)
@@ -383,6 +478,89 @@ public static void WriteStruct
sw.WriteLine();
}
+ if (structuredType is not null)
+ {
+ sw.WriteLine
+ (
+ @"
+ /// "
+ );
+ if (structuredType.Length < 1)
+ {
+ sw.WriteLine(" /// Note, there is no fixed value for this type.");
+ }
+
+ sw.Write
+ (
+ @" StructureType IStructuredType.StructureType()
+ {
+ return SType"
+ );
+ if (structuredType.Length > 0)
+ {
+ sw.Write(" = ");
+ sw.Write(structuredType);
+ }
+
+ sw.WriteLine
+ (
+ @";
+ }"
+ );
+
+
+ if (isChainable)
+ {
+ // Correct for none void* or BaseInStructure* PNext
+ var pNextType = @struct.Fields.FirstOrDefault(f => f.Name == "PNext")?.Type.Name ?? "void";
+ string getCast, setCast;
+ switch (pNextType)
+ {
+ case "void":
+ getCast = "(BaseInStructure*) ";
+ setCast = "";
+ break;
+ case "BaseInStructure":
+ getCast = setCast = "";
+ break;
+ default:
+ getCast = "(BaseInStructure*) ";
+ setCast = $"({pNextType}*) ";
+ break;
+
+ }
+ sw.WriteLine
+ (
+ @$"
+ ///
+ unsafe BaseInStructure* IChainable.PNext
+ {{
+ get => {getCast}PNext;
+ set => PNext = {setCast}value;
+ }}"
+ );
+ }
+
+ if (chainExtenderss?.Any() == true && structuredType.Length > 0)
+ {
+ sw.WriteLine
+ (
+ @$"
+ ///
+ /// Convenience method to start a chain.
+ ///
+ /// The newly created chain root
+ /// A reference to the newly created chain.
+ public static unsafe ref {@struct.Name} Chain(
+ out {@struct.Name} capture)
+ {{
+ capture = new {@struct.Name}({structuredType});
+ return ref capture;
+ }}"
+ );
+ }
+ }
+
sw.WriteLine(" }");
sw.WriteLine("}");
sw.Flush();
@@ -395,7 +573,9 @@ public static void WriteBuildToolsIntrinsic
if (args[0] == "$PFN")
{
WriteFunctionPointerWrapper
- (@struct, file, profile, project, task, args[1], args[2], Enum.Parse(args[3]));
+ (
+ @struct, file, profile, project, task, args[1], args[2], Enum.Parse(args[3])
+ );
}
else
{
@@ -468,7 +648,8 @@ CallingConvention conv
sw.WriteLine($" => SilkMarshal.PtrToDelegate<{delegateName}>(pfn);");
sw.WriteLine();
sw.WriteLine($" public static implicit operator {fnPtrSig}({pfnName} pfn) => pfn.Handle;");
- sw.WriteLine($" public static implicit operator {pfnName}({fnPtrSig} ptr) => new {pfnName}(ptr);");
+ sw.WriteLine
+ ($" public static implicit operator {pfnName}({fnPtrSig} ptr) => new {pfnName}(ptr);");
sw.WriteLine(" }");
sw.WriteLine();
// type.FunctionPointerSignature.Name = delegateName;
@@ -476,7 +657,10 @@ CallingConvention conv
// type.Name = type.FunctionPointerSignature.NativeName;
// type.IndirectionLevels--;
using (var sr = new StringReader
- (type.FunctionPointerSignature.ToString(null, @delegate: true, semicolon: true, accessibility: true)))
+ (
+ type.FunctionPointerSignature.ToString
+ (null, @delegate: true, semicolon: true, accessibility: true)
+ ))
{
string line;
while ((line = sr.ReadLine()) != null)
@@ -490,7 +674,8 @@ CallingConvention conv
sw.Flush();
}
- public static void WriteFusedField(Struct @struct, Project p, Field field, List args, StreamWriter sw)
+ public static void WriteFusedField
+ (Struct @struct, Project p, Field field, List args, StreamWriter sw)
{
var temporaryValue = IsTemporaryValue(p, @struct, args);
if (!temporaryValue)
@@ -519,7 +704,8 @@ public static void WriteFusedField(Struct @struct, Project p, Field field, List<
static bool IsTemporaryValue(Project p, Struct @struct, List args)
{
// ReSharper disable AccessToModifiedClosure
- var fusingStruct = p.Structs.First(x => x.Name == @struct.Fields.First(y => y.Name == args[1]).Type.Name);
+ var fusingStruct = p.Structs.First
+ (x => x.Name == @struct.Fields.First(y => y.Name == args[1]).Type.Name);
var fusingFieldInst = fusingStruct.Fields.First(x => x.Name == args[2]);
if (fusingFieldInst.NumBits is not null)
{
@@ -609,7 +795,8 @@ Profile profile
}
var bitfieldOffset = currentSize * 8 - remainingBits;
- var bitwidthHexStringBacking = ((1 << fieldDecl.NumBits.Value) - 1).ToString("X") + typeNameBacking switch
+ var bitwidthHexStringBacking = ((1 << fieldDecl.NumBits.Value) - 1).ToString
+ ("X") + typeNameBacking switch
{
"byte" => string.Empty,
"sbyte" => string.Empty,
diff --git a/src/Core/Silk.NET.BuildTools/Common/Attribute.cs b/src/Core/Silk.NET.BuildTools/Common/Attribute.cs
index 377d89b047..aa78182f00 100644
--- a/src/Core/Silk.NET.BuildTools/Common/Attribute.cs
+++ b/src/Core/Silk.NET.BuildTools/Common/Attribute.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
+using System.Linq;
using System.Text;
namespace Silk.NET.BuildTools.Common
@@ -39,5 +40,18 @@ public override string ToString()
sb.Append(")]");
return sb.ToString();
}
+
+ ///
+ /// Clones this attribute.
+ ///
+ /// A copy.
+ public Attribute Clone()
+ {
+ return new Attribute
+ {
+ Name = Name,
+ Arguments = Arguments.ToList()
+ };
+ }
}
}
diff --git a/src/Core/Silk.NET.BuildTools/Common/Struct.cs b/src/Core/Silk.NET.BuildTools/Common/Struct.cs
index a9d643963c..6223bcf846 100644
--- a/src/Core/Silk.NET.BuildTools/Common/Struct.cs
+++ b/src/Core/Silk.NET.BuildTools/Common/Struct.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Newtonsoft.Json;
using Silk.NET.BuildTools.Common.Functions;
using Silk.NET.BuildTools.Common.Structs;
@@ -18,30 +19,31 @@ public class Struct : IProfileConstituent
/// The name of the struct.
///
public string Name { get; set; }
-
+
///
/// The name of the struct in the native library.
///
public string NativeName { get; set; }
-
+
///
/// The extension name.
///
public string ExtensionName { get; set; }
+
[JsonIgnore] public string ProfileName { get; set; }
[JsonIgnore] public Version ProfileVersion { get; set; }
[JsonIgnore] public string[] ClangMetadata { get; set; }
-
+
///
/// A list of fields this struct contains.
///
public List Fields { get; set; } = new List();
-
+
///
/// A list of attributes this struct has.
///
public List Attributes { get; set; } = new List();
-
+
///
/// A list of functions this struct has.
///
@@ -51,7 +53,7 @@ public class Struct : IProfileConstituent
/// A list of functions contained in the LpVtbl field.
///
public List Vtbl { get; set; } = new List();
-
+
///
/// A list of interface names which this interface inherits.
///
@@ -61,5 +63,34 @@ public class Struct : IProfileConstituent
/// This struct's UUID attribute.
///
public Guid? Uuid { get; set; }
+
+ ///
+ /// Creates an alias of this structure, by cloning it.
+ ///
+ /// The alias name.
+ /// A copy of this instance.
+ public Struct Clone(string alias = null, string nativeAlias = null)
+ {
+ return new Struct
+ {
+ Name = alias ?? Name,
+ // These properties can simply be copied.
+ NativeName = nativeAlias ?? NativeName,
+ ExtensionName = ExtensionName,
+ ProfileName = ProfileName,
+ ProfileVersion = ProfileVersion,
+ Uuid = Uuid,
+ // WARNING these aren't deep clones so modifying any will modify the alias
+ // This is by design as an alias should not differ in any of these ways, and it means
+ // that modifying the source or the alias will affect both, which is desirable.
+ ClangMetadata = ClangMetadata,
+ Fields = Fields,
+ Functions = Functions,
+ Vtbl = Vtbl,
+ ComBases = ComBases,
+ // Deep clone, as we often want different attributes on an alias
+ Attributes = Attributes.Select(a => a.Clone()).ToList()
+ };
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Core/Silk.NET.BuildTools/Converters/Khronos/StructureDefinition.cs b/src/Core/Silk.NET.BuildTools/Converters/Khronos/StructureDefinition.cs
index 1de1946f5d..af3f9cae25 100644
--- a/src/Core/Silk.NET.BuildTools/Converters/Khronos/StructureDefinition.cs
+++ b/src/Core/Silk.NET.BuildTools/Converters/Khronos/StructureDefinition.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -7,39 +8,57 @@ namespace Silk.NET.BuildTools.Converters.Khronos
public class StructureDefinition
{
public string Name { get; }
+
+ ///
+ /// The alias holds the concrete structure definition's name.
+ ///
+ public string Alias { get; }
public MemberSpec[] Members { get; }
+ public IReadOnlyList Extends { get; }
- public StructureDefinition(string name, MemberSpec[] members)
+ public StructureDefinition(string name, string alias, MemberSpec[] members, string extends)
{
Name = name;
+ Alias = alias;
Members = members;
+ Extends = string.IsNullOrWhiteSpace(extends)
+ ? Array.Empty()
+ : extends.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
}
public static StructureDefinition CreateFromXml(XElement xe)
{
Require.NotNull(xe);
+ var name = xe.GetNameAttribute();
- if (!(xe.Attribute("alias") is null))
+ // Find original definition if this is an alias (this should usually be one step, but we handle gracefully)
+ var aliases = new HashSet { name };
+ string alias = null;
+ string a;
+ while ((a = xe.Attribute("alias")?.Value) is not null)
{
- var ret = CreateFromXml
- (
- xe.Document.Element("registry")
- .Elements("types")
- .Elements("type")
- .Where(typex => typex.HasCategoryAttribute("struct"))
- .FirstOrDefault(x => x.GetNameAttribute() == xe.Attribute("alias").Value) ?? throw new Exception("wat")
- );
-
- return new StructureDefinition(xe.GetNameAttribute(), ret.Members);
+ alias = a;
+ if (aliases.Contains(alias))
+ {
+ throw new Exception($"A loop of aliases was found as we have seen the '{alias}' alias before.");
+ }
+ aliases.Add(alias);
+
+ xe = xe.Document.Element("registry")
+ .Elements("types")
+ .Elements("type")
+ .Where(typex => typex.HasCategoryAttribute("struct"))
+ .FirstOrDefault(x => x.GetNameAttribute() == alias) ??
+ throw new Exception($"Could not find the '{alias}' alias.");
}
- var name = xe.GetNameAttribute();
var members = xe.Elements
("member")
.Where(x => !(x.Element("name") is null))
.Select(memberx => MemberSpec.CreateFromXml(memberx))
.ToArray();
- return new StructureDefinition(name, members);
+ var extends = xe.Attribute("structextends")?.Value;
+ return new StructureDefinition(name, alias, members, extends);
}
public override string ToString()
diff --git a/src/Core/Silk.NET.BuildTools/Converters/Readers/VulkanReader.cs b/src/Core/Silk.NET.BuildTools/Converters/Readers/VulkanReader.cs
index 923bdf914a..54198304d6 100644
--- a/src/Core/Silk.NET.BuildTools/Converters/Readers/VulkanReader.cs
+++ b/src/Core/Silk.NET.BuildTools/Converters/Readers/VulkanReader.cs
@@ -55,47 +55,246 @@ private Dictionary ConvertStructs(VulkanSpecification spec, Bind
{
var prefix = task.FunctionPrefix;
var ret = new Dictionary();
+
+ // Gets all aliases of a struct, no matter where in an alias chain we start
+ // Note this could be simpler if we just assume we only need to check $VKALIASES, but this
+ // version is bombproof.
+ IReadOnlyList GetAllAliasesFromName(string structName)
+ {
+ var todo = new Queue();
+ todo.Enqueue(structName);
+ var result = new Dictionary();
+
+ while (todo.Any())
+ {
+ structName = todo.Dequeue();
+ if (!ret.TryGetValue(structName, out var s))
+ {
+ result[structName] = null;
+ continue;
+ }
+
+ result[structName] = s;
+
+ // Get any aliases
+ var aliasOf = s.Attributes
+ .FirstOrDefault
+ (
+ a => a.Arguments.Count > 1 && a.Name == "BuildToolsIntrinsic" &&
+ a.Arguments[0] == "$VKALIASOF"
+ )
+ ?.Arguments[1];
+ if (!string.IsNullOrWhiteSpace(aliasOf) && !result.ContainsKey(aliasOf))
+ {
+ todo.Enqueue(aliasOf);
+ }
+
+ // Check other way as well
+ foreach (var a in s.Attributes
+ .FirstOrDefault
+ (
+ a => a.Arguments.Count > 1 && a.Name == "BuildToolsIntrinsic" &&
+ a.Arguments[0] == "$VKALIASES"
+ )
+ ?.Arguments
+ .Skip(1)
+ .Where(a => !string.IsNullOrWhiteSpace(a) && !result.ContainsKey(a))
+ .ToArray()
+ ?? Array.Empty())
+ {
+ todo.Enqueue(a);
+ }
+ }
+
+ return result.Values.Where(s => s is not null).ToList();
+ }
+
+ // Opposite way round lookup of what aliases exist for this key-struct
+ // i.e. if VkB is an alias of VkA, and VkC is an alias of VkA, then aliases has [VkA]={VkB,VkC}
+ var aliases = new Dictionary>();
+ // Holds any chain starts for chains we haven't seen yet (should rarely be needed).
+ var chainExtensions = new List<(Struct, IReadOnlyList)>();
foreach (var s in spec.Structures)
{
- ret.Add
- (
- s.Name, new Struct
+ // Build aliases dictionary
+ if (!string.IsNullOrWhiteSpace(s.Alias))
+ {
+ if (!aliases.TryGetValue(s.Alias, out var aList))
{
- Fields = s.Members.Select
- (
- x => new Field
- {
- Count = string.IsNullOrEmpty(x.ElementCountSymbolic)
- ? x.ElementCount != 1 ? new Count(x.ElementCount) : null
- : new Count(x.ElementCountSymbolic, false),
- Name = Naming.Translate(TrimName(x.Name, task), prefix),
- Doc = $"/// {x.Comment}",
- NativeName = x.Name,
- NativeType = x.Type.ToString(),
- Type = ConvertType(x.Type),
- DefaultAssignment =
- (x.Type.Name == "VkStructureType" || x.Type.Name == "XrStructureType")
- && !string.IsNullOrWhiteSpace(x.LegalValues)
- ? "StructureType." + TryTrim
+ aList = new();
+ aliases[s.Alias] = aList;
+ }
+
+ aList.Add(s.Name);
+ continue;
+ }
+
+ var @struct = new Struct
+ {
+ Fields = s.Members.Select
+ (
+ x => new Field
+ {
+ Count = string.IsNullOrEmpty(x.ElementCountSymbolic)
+ ? x.ElementCount != 1 ? new Count(x.ElementCount) : null
+ : new Count(x.ElementCountSymbolic, false),
+ Name = Naming.Translate(TrimName(x.Name, task), prefix),
+ Doc = $"/// {x.Comment}",
+ NativeName = x.Name,
+ NativeType = x.Type.ToString(),
+ Type = ConvertType(x.Type),
+ DefaultAssignment =
+ (x.Type.Name == "VkStructureType" || x.Type.Name == "XrStructureType")
+ && !string.IsNullOrWhiteSpace(x.LegalValues)
+ ? "StructureType." + TryTrim
+ (
+ Naming.Translate
(
- Naming.Translate
- (
- TrimName(x.LegalValues.Split(',').FirstOrDefault(), task),
- task.FunctionPrefix
- ),
- Naming.TranslateLite(TrimName("VkStructureType", task), task.FunctionPrefix)
- )
- : null,
- NumBits = x.NumBits
- }.WithFixedFieldFixup09072020()
- )
- .ToList(),
- Name = Naming.TranslateLite(TrimName(s.Name, task), prefix),
- NativeName = s.Name
+ TrimName(x.LegalValues.Split(',').FirstOrDefault(), task),
+ task.FunctionPrefix
+ ),
+ Naming.TranslateLite(TrimName("VkStructureType", task), task.FunctionPrefix)
+ )
+ : null,
+ NumBits = x.NumBits
+ }.WithFixedFieldFixup09072020()
+ )
+ .ToList(),
+ Name = Naming.TranslateLite(TrimName(s.Name, task), prefix),
+ NativeName = s.Name
+ };
+
+ // Find the STYpe field (and it's position, which is required for IChainable
+ var (sTypeField, sTypeFieldIndex) = @struct.Fields.Select((f, i) => (Field: f, Index: i))
+ .FirstOrDefault(f => f.Field.Name == "SType" && f.Field.Type.Name == "VkStructureType");
+ if (sTypeField is not null)
+ {
+ @struct.Attributes.Add
+ (
+ new()
+ {
+ Name = "BuildToolsIntrinsic",
+ Arguments = new() {"$VKSTRUCTUREDTYPE", sTypeField.DefaultAssignment ?? string.Empty}
+ }
+ );
+
+ // Ensure SType was in position 0, and we have a pointer called PNext in position 1.
+ Field pNextField;
+ if (sTypeFieldIndex == 0 &&
+ @struct.Fields.Count > 1 &&
+ (pNextField = @struct.Fields[1]).Name == "PNext" &&
+ pNextField.Type.IsPointer)
+ {
+ // The type is at least chainable.
+ @struct.Attributes.Add
+ (
+ new()
+ {
+ Name = "BuildToolsIntrinsic",
+ Arguments = new() {"$VKCHAINABLE"}
+ }
+ );
+
+ if (s.Extends.Any())
+ {
+ chainExtensions.Add((@struct, s.Extends));
+ }
+ }
+ }
+
+ ret.Add(s.Name, @struct);
+ }
+
+ // Create Aliases
+ foreach (var (structName, aList) in aliases)
+ {
+ if (!ret.TryGetValue(structName, out var @struct))
+ {
+ continue;
+ }
+
+ foreach (var alias in aList)
+ {
+ var aliasStruct = @struct.Clone(Naming.TranslateLite(TrimName(alias, task), prefix), alias);
+ aliasStruct.Attributes.Add
+ (
+ new()
+ {
+ Name = "BuildToolsIntrinsic",
+ Arguments = new() {"$VKALIASOF", @struct.NativeName}
+ }
+ );
+ // Create a clone for the alias
+ ret.Add(alias, aliasStruct);
+ }
+
+ // Now that we've finished cloning we can add the build intrinsic to the root struct.
+ @struct.Attributes.Add
+ (
+ new()
+ {
+ Name = "BuildToolsIntrinsic",
+ Arguments = new[] {"$VKALIASES"}.Concat(aList).ToList()
}
);
}
-
+
+ // Add chain extensions, we have to do this now to account for aliases, we
+ if (chainExtensions.Any())
+ {
+ foreach (var (@struct, chainNames) in chainExtensions)
+ {
+ // Get all the aliases of this struct (including this one)
+ var allStructs = GetAllAliasesFromName(@struct.NativeName);
+ // Get all the chains this struct extends (including their aliases)
+ var chains = chainNames.SelectMany(n => GetAllAliasesFromName(n)).ToArray();
+
+ // Add $VKEXTENDSCHAIN build tools intrinsic attribute to all versions of this struct
+ Attribute attribute = null;
+ foreach (var s in allStructs)
+ {
+ if (attribute is null)
+ {
+ // Create $VKEXTENDSCHAIN build tools intrinsic attribute
+ attribute = new()
+ {
+ Name = "BuildToolsIntrinsic",
+ Arguments = new[] {"$VKEXTENDSCHAIN"}.Concat(chains.Select(c => c.Name)).ToList()
+ };
+ }
+ else
+ {
+ // Clone existing attribute.
+ attribute = attribute.Clone();
+ }
+
+ s.Attributes.Add(attribute);
+ }
+
+ // Add chain starts to all chains and their aliases
+ attribute = null;
+ foreach (var c in chains)
+ {
+ if (attribute is null)
+ {
+ // Create $VKEXTENDSCHAIN build tools intrinsic attribute
+ attribute = new()
+ {
+ Name = "BuildToolsIntrinsic",
+ Arguments = new[] {"$VKCHAINSTART"}.Concat(allStructs.Select(s => s.Name)).ToList()
+ };
+ }
+ else
+ {
+ // Clone existing attribute.
+ attribute = attribute.Clone();
+ }
+
+ c.Attributes.Add(attribute);
+ }
+ }
+ }
+
foreach (var h in spec.Handles)
{
ret.Add
@@ -103,7 +302,9 @@ private Dictionary ConvertStructs(VulkanSpecification spec, Bind
h.Name, new Struct
{
Fields = new List
- {new Field {Name = "Handle", Type = new Type {Name = h.CanBeDispatched ? "nint" : "ulong"}}},
+ {
+ new Field {Name = "Handle", Type = new Type {Name = h.CanBeDispatched ? "nint" : "ulong"}}
+ },
Name = Naming.TranslateLite(TrimName(h.Name, task), prefix),
NativeName = h.Name
}
@@ -112,13 +313,19 @@ private Dictionary ConvertStructs(VulkanSpecification spec, Bind
foreach (var u in spec.Unions)
{
- ret.Add(u.Name, new Struct
- {
- Attributes = new List{new Attribute{Name = "StructLayout", Arguments = new List{"LayoutKind.Explicit"}}},
- Fields = GetFields(u, task).ToList(),
- Name = Naming.TranslateLite(TrimName(u.Name, task), prefix),
- NativeName = u.Name
- });
+ ret.Add
+ (
+ u.Name, new Struct
+ {
+ Attributes = new List
+ {
+ new Attribute {Name = "StructLayout", Arguments = new List {"LayoutKind.Explicit"}}
+ },
+ Fields = GetFields(u, task).ToList(),
+ Name = Naming.TranslateLite(TrimName(u.Name, task), prefix),
+ NativeName = u.Name
+ }
+ );
}
return ret;
@@ -138,7 +345,7 @@ private IEnumerable GetFields(StructureDefinition union, BindTask task)
Name = $"{Naming.Translate(x.Name, task.FunctionPrefix)}_{i}",
Attributes = new List
{
- new Attribute{Name = "FieldOffset", Arguments = new List {$"{i * fieldSize}"}}
+ new Attribute {Name = "FieldOffset", Arguments = new List {$"{i * fieldSize}"}}
},
Doc = $"/// {x.Comment}",
NativeName = x.Name,
@@ -155,7 +362,7 @@ private IEnumerable GetFields(StructureDefinition union, BindTask task)
Name = $"{Naming.Translate(x.Name, task.FunctionPrefix)}",
Attributes = new List
{
- new Attribute{Name = "FieldOffset", Arguments = new List {"0"}}
+ new Attribute {Name = "FieldOffset", Arguments = new List {"0"}}
},
Doc = $"/// {x.Comment}",
NativeName = x.Name,
@@ -165,7 +372,7 @@ private IEnumerable GetFields(StructureDefinition union, BindTask task)
};
}
}
- }
+ }
private int GetTypeSize(string type, IEnumerable> maps)
{
@@ -237,7 +444,7 @@ public IEnumerable ReadFunctions(object obj, BindTask task)
}
}
}
-
+
foreach (var extension in spec.Extensions)
{
foreach (var name in extension.CommandNames)
@@ -273,14 +480,15 @@ private Dictionary ConvertFunctions(VulkanSpecification spec,
(
function.Name, new Function
{
- Name = Naming.Translate(NameTrimmer.Trim(TrimName(function.Name, task), task.FunctionPrefix), task.FunctionPrefix),
+ Name = Naming.Translate
+ (NameTrimmer.Trim(TrimName(function.Name, task), task.FunctionPrefix), task.FunctionPrefix),
Parameters = function.Parameters.Select
(
x => new Parameter
{
Count = x.IsNullTerminated ? null :
- x.ElementCountSymbolic != null ?
- function.Parameters.Any(y => y.Name == x.ElementCountSymbolic)
+ x.ElementCountSymbolic != null ? function.Parameters.Any
+ (y => y.Name == x.ElementCountSymbolic)
? new(x.ElementCountSymbolic)
: new(x.ElementCountSymbolic.Split(',')) :
new(x.ElementCount),
@@ -322,7 +530,7 @@ public IEnumerable ReadEnums(object obj, BindTask task)
};
}
}
-
+
task.InjectTypeMap(tm);
}
@@ -342,7 +550,7 @@ public IEnumerable ReadConstants(object obj, BindTask task)
ConstantType.Float32 => new Type {Name = "float"},
ConstantType.UInt32 => new Type {Name = "uint"},
ConstantType.UInt64 => new Type {Name = "ulong"},
- _ => new Type{Name = "ulong"}
+ _ => new Type {Name = "ulong"}
},
ExtensionName = "Core"
}
@@ -363,21 +571,23 @@ public IEnumerable ReadConstants(object obj, BindTask task)
}
)
)
- ).Concat
+ )
+ .Concat
(
spec.Extensions.SelectMany
(
- x => x.EnumExtensions.Where(y => y.ExtendedType is null).Select
- (
- y => new Constant
- {
- Name = Naming.Translate(TrimName(y.Name, task), task.FunctionPrefix),
- NativeName = y.Name,
- Value = y.Value,
- Type = new Type {Name = "uint"},
- ExtensionName = TrimName(x.Name, task)
- }
- )
+ x => x.EnumExtensions.Where(y => y.ExtendedType is null)
+ .Select
+ (
+ y => new Constant
+ {
+ Name = Naming.Translate(TrimName(y.Name, task), task.FunctionPrefix),
+ NativeName = y.Name,
+ Value = y.Value,
+ Type = new Type {Name = "uint"},
+ ExtensionName = TrimName(x.Name, task)
+ }
+ )
)
);
}
@@ -395,7 +605,9 @@ public string TrimName(string name, BindTask task)
return name.Remove(0, task.FunctionPrefix.Length + 1);
}
- return name.ToLower().StartsWith(task.FunctionPrefix.ToLower()) ? name.Remove(0, task.FunctionPrefix.Length) : name;
+ return name.ToLower().StartsWith(task.FunctionPrefix.ToLower())
+ ? name.Remove(0, task.FunctionPrefix.Length)
+ : name;
}
private static FlowDirection ConvertFlow(ParameterModifier mod)
@@ -408,7 +620,7 @@ private static FlowDirection ConvertFlow(ParameterModifier mod)
_ => FlowDirection.In
};
}
-
+
private Dictionary ConvertEnums(VulkanSpecification spec, BindTask task)
{
var ret = new Dictionary();
@@ -440,8 +652,8 @@ private Dictionary ConvertEnums(VulkanSpecification spec, BindTask
: new List(),
EnumBaseType = e.BitWidth switch
{
- 64 => new(){Name = "long"},
- _ => new(){Name = "int"}
+ 64 => new() {Name = "long"},
+ _ => new() {Name = "int"}
}
}
);
@@ -458,6 +670,7 @@ private Dictionary ConvertEnums(VulkanSpecification spec, BindTask
}
private static readonly char[] Digits = "1234567890".ToCharArray();
+
private static string TryTrim(string token, string @enum)
{
var trimmed = token.StartsWith(@enum) ? token.Substring(@enum.Length) : token;
diff --git a/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining.Test/PrototypeStructChaining.Test.csproj b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining.Test/PrototypeStructChaining.Test.csproj
new file mode 100644
index 0000000000..c61c1205b8
--- /dev/null
+++ b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining.Test/PrototypeStructChaining.Test.csproj
@@ -0,0 +1,34 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+ true
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining.Test/TestChainMetadata.cs b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining.Test/TestChainMetadata.cs
new file mode 100644
index 0000000000..9e3c26a1f4
--- /dev/null
+++ b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining.Test/TestChainMetadata.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Linq;
+using Silk.NET.Vulkan;
+using Xunit;
+
+namespace PrototypeStructChaining.Test;
+
+public class TestChainMetadata
+{
+ [Fact]
+ public void TestClrType()
+ {
+ Assert.Equal(typeof(DeviceCreateInfo), StructureType.DeviceCreateInfo.ClrType());
+ }
+
+ [Fact]
+ public void TestStructureType()
+ {
+ Assert.Equal(StructureType.DeviceCreateInfo, typeof(DeviceCreateInfo).StructureType());
+ Assert.Null(typeof(PhysicalDeviceFeatures).StructureType());
+ }
+
+ [Fact]
+ public void TestIsChainStart()
+ {
+ Assert.True(StructureType.DeviceCreateInfo.IsChainStart());
+ Assert.True(typeof(DeviceCreateInfo).IsChainStart());
+ Assert.False(StructureType.PhysicalDeviceDescriptorIndexingFeatures.IsChainStart());
+ Assert.False(typeof(PhysicalDeviceDescriptorIndexingFeatures).IsChainStart());
+ }
+
+ [Fact]
+ public void TestIsChainable()
+ {
+ Assert.True(StructureType.DeviceCreateInfo.IsChainable());
+ Assert.True(typeof(DeviceCreateInfo).IsChainable());
+ Assert.True(StructureType.PhysicalDeviceDescriptorIndexingFeatures.IsChainable());
+ Assert.True(typeof(PhysicalDeviceDescriptorIndexingFeatures).IsChainable());
+ }
+
+ [Fact]
+ public void TestCanExtend()
+ {
+ Assert.True(StructureType.PhysicalDeviceFeatures2.CanExtend(StructureType.DeviceCreateInfo));
+ Assert.False(StructureType.DeviceCreateInfo.CanExtend(StructureType.PhysicalDeviceFeatures2));
+ Assert.True(typeof(PhysicalDeviceFeatures2).CanExtend(typeof(DeviceCreateInfo)));
+ Assert.False(typeof(DeviceCreateInfo).CanExtend(typeof(PhysicalDeviceFeatures2)));
+ }
+
+ [Fact]
+ public void TestCanBeExtendedBy()
+ {
+ Assert.False(StructureType.PhysicalDeviceFeatures2.CanBeExtendedBy(StructureType.DeviceCreateInfo));
+ Assert.True(StructureType.DeviceCreateInfo.CanBeExtendedBy(StructureType.PhysicalDeviceFeatures2));
+ Assert.False(typeof(PhysicalDeviceFeatures2).CanBeExtendedBy(typeof(DeviceCreateInfo)));
+ Assert.True(typeof(DeviceCreateInfo).CanBeExtendedBy(typeof(PhysicalDeviceFeatures2)));
+ }
+}
diff --git a/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining/ChainExtensionsMetadata.cs b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining/ChainExtensionsMetadata.cs
new file mode 100644
index 0000000000..8b5b8fb157
--- /dev/null
+++ b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining/ChainExtensionsMetadata.cs
@@ -0,0 +1,200 @@
+using System.Runtime.CompilerServices;
+
+namespace Silk.NET.Vulkan;
+
+public static class ChainExtensionsMetadata
+{
+ ///
+ /// Provides a set of all the s that can be extended by a .
+ ///
+ public static readonly IReadOnlyDictionary> Extensions =
+ new Dictionary>
+ {
+ [Vulkan.StructureType.PhysicalDeviceFeatures2] = new HashSet
+ {
+ Vulkan.StructureType.DeviceCreateInfo
+ },
+ [Vulkan.StructureType.PhysicalDeviceDescriptorIndexingFeatures] = new HashSet
+ {
+ Vulkan.StructureType.PhysicalDeviceFeatures2,
+ Vulkan.StructureType.DeviceCreateInfo
+ },
+ [Vulkan.StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr] = new HashSet
+ {
+ Vulkan.StructureType.PhysicalDeviceFeatures2,
+ Vulkan.StructureType.DeviceCreateInfo
+ }
+ };
+
+ ///
+ /// Provides a set of all the s that can extend a .
+ ///
+ public static readonly IReadOnlyDictionary> Extenders =
+ new Dictionary>
+ {
+ [Vulkan.StructureType.DeviceCreateInfo] = new HashSet
+ {
+ Vulkan.StructureType.PhysicalDeviceFeatures2,
+ Vulkan.StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ Vulkan.StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr
+ },
+ [Vulkan.StructureType.PhysicalDeviceFeatures2] = new HashSet
+ {
+ Vulkan.StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ Vulkan.StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr
+ }
+ };
+
+ ///
+ /// Provides a mapping from the to the corresponding .
+ ///
+ public static readonly IReadOnlyDictionary ClrTypes =
+ new Dictionary
+ {
+ [Vulkan.StructureType.DeviceCreateInfo] = typeof(DeviceCreateInfo),
+ [Vulkan.StructureType.PhysicalDeviceFeatures2] = typeof(PhysicalDeviceFeatures2),
+ [Vulkan.StructureType.PhysicalDeviceDescriptorIndexingFeatures] =
+ typeof(PhysicalDeviceDescriptorIndexingFeatures),
+ [Vulkan.StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr] =
+ typeof(PhysicalDeviceAccelerationStructureFeaturesKHR)
+ };
+
+ ///
+ /// Provides a mapping from the to the corresponding .
+ ///
+ public static readonly IReadOnlyDictionary StructureTypes =
+ new Dictionary
+ {
+ [typeof(DeviceCreateInfo)] = Vulkan.StructureType.DeviceCreateInfo,
+ [typeof(PhysicalDeviceFeatures2)] = Vulkan.StructureType.PhysicalDeviceFeatures2,
+ [typeof(PhysicalDeviceDescriptorIndexingFeatures)] =
+ Vulkan.StructureType.PhysicalDeviceDescriptorIndexingFeatures,
+ [typeof(PhysicalDeviceAccelerationStructureFeaturesKHR)] =
+ Vulkan.StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr
+ };
+
+ ///
+ /// Gets the corresponding for a , if any.
+ ///
+ /// The structure type.
+ /// The corresponding for , if any; otherwise,
+ /// .
+ public static Type ClrType(this StructureType structureType)
+ {
+ return ClrTypes[structureType];
+ }
+
+ ///
+ /// Gets the corresponding for a , if any.
+ ///
+ /// The CLR type.
+ /// The corresponding for , if any; otherwise,
+ /// .
+ public static StructureType? StructureType(this Type type)
+ {
+ return StructureTypes.TryGetValue(type, out var structureType) ? structureType : null;
+ }
+
+ ///
+ /// Whether the can start a chain.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainStart(this StructureType type)
+ {
+ return Extenders.ContainsKey(type);
+ }
+
+ ///
+ /// Whether the can start a chain.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainStart(this Type type)
+ {
+ return StructureTypes.TryGetValue(type, out var structureType) &&
+ Extenders.ContainsKey(structureType);
+ }
+
+ ///
+ /// Whether the is chainable.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainable(this StructureType type)
+ {
+ return Extenders.ContainsKey(type) ||
+ Extensions.ContainsKey(type);
+ }
+
+ ///
+ /// Whether the is chainable.
+ ///
+ /// The to test.
+ /// if the can start a chain; otherwise
+ /// .
+ public static bool IsChainable(this Type type)
+ {
+ return StructureTypes.TryGetValue(type, out var structureType) &&
+ (Extenders.ContainsKey(structureType) || Extensions.ContainsKey(structureType));
+ }
+
+ ///
+ /// Whether the current can extend the .
+ ///
+ /// The to test.
+ /// The of the chain.
+ /// if the can extend the ; otherwise, false.
+ ///
+ public static bool CanExtend(this StructureType next, StructureType chain)
+ {
+ return Extensions.TryGetValue(next, out var extensions) && extensions.Contains(chain);
+ }
+
+ ///
+ /// Whether the current can extend the .
+ ///
+ /// The to test.
+ /// The of the chain.
+ /// if the can extend the ; otherwise, false.
+ ///
+ public static bool CanExtend(this Type next, Type chain)
+ {
+ return
+ StructureTypes.TryGetValue(next, out var nextType) &&
+ StructureTypes.TryGetValue(chain, out var chainType) &&
+ Extensions.TryGetValue(nextType, out var extensions) &&
+ extensions.Contains(chainType);
+ }
+
+ ///
+ /// Whether the current can be extended by the .
+ ///
+ /// The of the chain.
+ /// The to test.
+ /// if the can be extended the ; otherwise, false.
+ ///
+ public static bool CanBeExtendedBy(this StructureType chain, StructureType next)
+ {
+ return Extenders.TryGetValue(chain, out var extenders) && extenders.Contains(next);
+ }
+
+ ///
+ /// Whether the current can be extended by the .
+ ///
+ /// The of the chain.
+ /// The to test.
+ /// if the can extend the ; otherwise, false.
+ ///
+ public static bool CanBeExtendedBy(this Type chain, Type next)
+ {
+ return
+ StructureTypes.TryGetValue(next, out var nextType) &&
+ StructureTypes.TryGetValue(chain, out var chainType) &&
+ Extenders.TryGetValue(chainType, out var extenders) &&
+ extenders.Contains(nextType);
+ }
+}
diff --git a/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining/PrototypeStructChaining.csproj b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining/PrototypeStructChaining.csproj
new file mode 100644
index 0000000000..b5f170722a
--- /dev/null
+++ b/src/Lab/Experiments/PrototypeStructChaining/PrototypeStructChaining/PrototypeStructChaining.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0
+ 10
+ enable
+ true
+ Silk.NET.Vulkan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Lab/Experiments/PrototypeStructChaining/Readme.md b/src/Lab/Experiments/PrototypeStructChaining/Readme.md
new file mode 100644
index 0000000000..2eb68080a7
--- /dev/null
+++ b/src/Lab/Experiments/PrototypeStructChaining/Readme.md
@@ -0,0 +1,393 @@
+# Prototype Struct Chaining
+
+This repository implements a prototype solution for Struct Chaining in [Silk.Net](https://github.com/dotnet/Silk.NET)
+
+## Design Goals
+
+* **Backward compatibility** - the chaining system should not change the existing structs, but add functionality.
+* **Minimal bloat** - the minimum amount of new generated code was sought.
+* **Discoverability** - it should be as easy as possible for a new user to discover
+* **Compile-time Validation** - it should prevent chaining invalid structures (as much as possible) during compilation
+* **Type coercion** - it should set the SType of chained structures.
+* **Compact usage** - it should reduce copy-pasta code.
+* **Avoid the heap** - boxing should be avoided
+* **Well Tested** - tests were added to ensure pointers are correctly set, and compilation failures occur.
+
+All of these goals were met.
+
+## Usage
+
+The proposal provides for the following usage patterns:
+
+### Chain Building
+
+You can happily create the start of a chain as usual, and it's `SType` will be coerced when you start using it as a
+chain:
+
+```csharp
+var createInfo = new DeviceCreateInfo
+{
+ Flags = 1U
+};
+// When you call any chaining method it will set the chain's SType automatically.
+createinfo.AddNext...
+```
+
+-in many cases, we only want to create a default structure for population by the API. To do so, we use the
+static `Chain`
+method like so:
+
+```csharp
+PhysicalDeviceFeatures2.Chain(out var features2)
+```
+
+This has several advantages:
+
+- The method is only available for structures that are valid at the start of a chain; providing compile-time validation.
+- The structure's `SType` will be correctly set immediately.
+- The syntax is fluent, and creates more readable code when used with the other chaining methods (see below).
+
+### AddNext
+
+The most common use case is to add an empty structure to the end of a chain for it to be populated by the Vulkan API,
+this can now be done like so:
+
+```csharp
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ // CreateNext will create an empty struct, with the correct SType (as well as ensuring the
+ // chain's SType is set correctly).
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+```
+
+Each method `out` puts a struct into the local stackframe for querying once populated, and the pointers point to this
+local variable. Despite generics and interfaces being used, the chain methods avoid the heap entirely.
+
+### TryAddNext
+
+You may only want to add a structure if it doesn't already exist in the chain, this can be done with `TryAddNext`, e.g.:
+
+```csharp
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ // As there is already a PhysicalDeviceDescriptorIndexingFeatures structure the following
+ // will not add anything to the chain and `added` will be `false`.
+ .TryAddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures2, out bool added);
+```
+
+### SetNext
+
+Sometimes we may wish to set the initial state of a structure, we can do this with `SetNext`:
+
+```csharp
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR
+{
+ AccelerationStructure = true
+};
+
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ // SetNext accepts an existing struct, note, it will coerce the SType and blank the PNext
+ .SetNext(ref indexingFeatures)
+ .SetNext(ref accelerationStructureFeaturesKhr);
+```
+
+*NOTE* you can mix and match `AddNext` and `SetNext` (and any chaining method) in the same method chain.
+
+By default, `SetNext` will replace any item in the chain with a matching `SType`, this behaviour can be changed by
+setting the optional
+`alwaysAdd` parameter to `true`;
+
+```csharp
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+var indexingFeatures2 = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = false
+};
+var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR
+{
+ AccelerationStructure = true
+};
+
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .SetNext(ref indexingFeatures)
+ // This will add the second 'indexingFeatures' struct, even though one is already present in the chain.
+ .SetNext(ref indexingFeatures2, true);
+```
+
+### IndexOf
+
+Sometimes it's useful to know if a structure you previously supplied is still in a chain, this can be done
+with `IndexOf`, which returns a non-negative index (zero-indexed) if the structure is found, eg.:
+
+```csharp
+PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+// Check indices
+Assert.Equal(1, features2.IndexOf(ref indexingFeatures));
+Assert.Equal(2, features2.IndexOf(ref accelerationStructureFeaturesKhr));
+```
+
+## Features
+
+* All the chaining methods are only available on the structures that implement `IChainStart`, i.e. the structures that
+ the Vulkan specification says can start a chain.
+* All the chaining methods ensure the chain, and any supplied structures, have their `SType` correctly set.
+* All the chaining methods will only accept a structure that is valid in the chain being extended, as per the Vulkan
+ specification.
+* The chaining methods do not box structures, or add anything to the heap.
+
+## Changes required
+
+* Add the new `IStructureType`, `IChainable`, `IChainStart`, `IExtendChain` interfaces.
+* Add the new `Chain` structure.
+* Add the `ChainExtensions` extension methods `SetNext`, `AddNew`, `IndexOf`, `AddNext` and `TryAddNext`.
+* Add the small instance `StructureType IStructuredType.StructureType()` method to `IChainable` structure.
+* Add the `IChainStart` interface and the small static `Chain` method to any structure that can be used as the start of
+ a chain.
+* Add `IExtendsChain` interfaces to any structures that can extend a chain.
+
+Note that the [Vulkan XML](https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/master/registry/vk.xml) does
+include the `structextends` attribute on each structure that directly maps to the `IExtendsChain` interfaces that
+need to be added. However, to add the `IChainStart` interface (and it's associated method), these structures have to be
+marked as a chain start whenever we see a structure extending them.
+
+Although it is possible to not use a design that does not make use of `IChainStart` the following functionality is lost:
+
+* The `Chain` method will appear on all chainable structures, even when they don't represent the start of a chain.
+* All extension methods will appear on all chainable structures, even when they are not the start.
+* Indexing, adding, etc. can't be guaranteed to be scanning from the start of the chain.
+
+As adding `IChainStart` requires relatively simple logic I believe it's worth including.
+
+# Extension
+
+## ManagedChain
+
+Sometimes it is desirable to keep the structures around on the heap. To facilitate that you can use
+the `ManagedChain` types. Like `Tuple` et al, these support up to chain size 16. They must be
+disposed when finished with. Whenever a structure is loaded into the `ManagedChain` it's `SType` and `PNext` are
+forced to be correct, preventing errors. Structures can be replaced at any time, and we be inserted efficiently
+into the chain as an O(1) operation.
+
+### Creation
+
+For example:
+
+```csharp
+using var chain = new ManagedChain();
+
+// Ensure all STypes set correctly
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain.Item2.SType);
+
+// Ensure pointers set correctly
+Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+Assert.Equal((nint) 0, (nint) chain.Item2.PNext);
+```
+
+The structures are held in unmanaged memory, preventing movement by the GC, and ensuring that the ptrs remain fixed.
+
+You can also use the `ManagedChain.Create(...)` static methods to create `ManagedChain`s.
+
+### Modifying values
+
+We can easily modify any value in the `ManagedChain`, and it will maintain the ptrs automatically, e.g.:
+
+```csharp
+using var chain = new ManagedChain(item1: new PhysicalDeviceDescriptorIndexingFeatures
+{
+ // We can set any non-default values, note we do not need to set SType or PNext
+ // indeed they will be overwritten.
+ ShaderInputAttachmentArrayDynamicIndexing = true
+});
+
+// Ensure all STypes set correctly
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain.Item2.SType);
+
+// Ensure pointers set correctly
+Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+Assert.Equal((nint) 0, (nint) chain.Item2.PNext);
+
+// Check our value was set
+Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+var item1Ptr = chain.Item1Ptr;
+
+// Overwrite Item1
+chain.Item1 = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ // Again we do not need to set SType or PNext, which will be set to the correct values
+ ShaderInputAttachmentArrayDynamicIndexing = false
+};
+
+// Check our value was cleared
+Assert.False(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+// Note all the pointers are still correct (and have not changed)
+Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+Assert.Equal((nint) 0, (nint) chain.Item2.PNext);
+
+// As is the SType
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+```
+
+**Note** When we update any item in the chain it overwrites the existing memory, so the ptrs remain fixed. It also
+ensures the PNext value is maintained.
+
+### Appending to a chain
+
+You can call `Append` on a `ManagedChain` to efficiently create a new, larger, `ManagedChain` with a new item appended
+to the end, e.g:
+
+```csharp
+using var chain = new ManagedChain(
+ item1: new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true});
+
+// The new chain, will efficiently copy the old chain and append a new structure to the end
+using var newChain = chain.Append();
+// You will usualy wish to dispose the old chain here, the two chains are now independent of each other.
+
+// Check the flag from the first chain is still set in the new chain.
+Assert.True(newChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+```
+
+### Loading from an unmanaged chain
+
+If you have created an unmanaged chain and would like to load that into a `ManagedChain` you can use one of the
+`ManagedChain.Load` methods:
+
+```csharp
+// Load an unmanaged chain
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+PhysicalDeviceFeatures2
+.Chain(out var unmanagedChain)
+.SetNext(ref indexingFeatures)
+.AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+// Loads a new managed chain from an unmanaged chain
+using var managedChain =
+ ManagedChain.Load(unmanagedChain, out var errors);
+
+// Check we had no loading errors
+Assert.Equal("", errors);
+
+// Check the flag still set
+Assert.True(managedChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+```
+
+The full version of the `Load` method returns an output parameter `errors` as it's first parameter. The `errors`
+parameter will be `string.Empty` if there are no errors, otherwise each line will contain a separate error for each
+issue found during loading. There is also an overload that accepts a single argument `chain` for when you don't care if
+there are any errors. Either method always succeeds, even if the unmanaged chain doesn't match exactly - for example it
+is shorter or longer than the chain being loaded, or if the managed chain has different structure types in any of the
+positions. Any structure type in the expected position will always be loaded into the new `ManagedChain`.
+
+```csharp
+var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+{
+ ShaderInputAttachmentArrayDynamicIndexing = true
+};
+// Load an unmanaged chain
+DeviceCreateInfo
+ .Chain(out var unmanagedChain)
+ .AddNext(out PhysicalDeviceFeatures2 features2)
+ .SetNext(ref indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+// Loads a new managed chain from an unmanaged chain
+using var managedChain =
+ new ManagedChain<
+ DeviceCreateInfo,
+ // Note we are supplied a PhysicalDeviceFeatures2 here from the unmanaged chain
+ PhysicalDeviceAccelerationStructureFeaturesKHR,
+ PhysicalDeviceDescriptorIndexingFeatures,
+ PhysicalDeviceAccelerationStructureFeaturesKHR,
+ // Note that the unmanaged chain did not supply a 5th entry
+ PhysicalDeviceFeatures2>(unmanagedChain, out var errors);
+
+// Check for errors
+Assert.Equal(
+@"The unmanaged chain has a structure type PhysicalDeviceFeatures2Khr at position 2; expected PhysicalDeviceAccelerationStructureFeaturesKhr
+The unmanaged chain was length 4, expected length 5",
+ errors);
+
+// Despite the errors indexing features was at the right location so was loaded
+Assert.True(managedChain.Item2.ShaderInputAttachmentArrayDynamicIndexing);
+```
+
+Notice that the above form use the constructor as an alternative.
+
+### IReadOnlyList
+
+All the fully generic `ManageChain` types extend `ManagedChain` which implements `IDisposable`
+and `IReadOnlyList`. The latter allowing for easy consumption of any `ManagedChain`, e.g.:
+
+```csharp
+using var chain = new ManagedChain();
+
+Assert.Equal(3, chain.Count);
+
+// Ensure all STypes set correctly using indexer
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain[0].StructureType());
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain[1].StructureType());
+Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain[2].StructureType());
+
+Assert.Throws(() => chain[3]);
+
+// Get array using IEnumerable implementation
+IChainable[] structures = chain.ToArray();
+
+// Check concrete types
+Assert.IsType(structures[0]);
+Assert.IsType(structures[1]);
+Assert.IsType(structures[2]);
+```
+
+### Deconstruction
+
+Each `ManageChain` has a corresponding deconstructor for convenience, e.g.:
+
+```csharp
+using var chain = new ManagedChain();
+
+var (physicalDeviceFeatures2, indexingFeatures, accelerationStructureFeaturesKhr) = chain;
+
+// Ensure all STypes set correctly
+Assert.Equal(StructureType.PhysicalDeviceFeatures2, physicalDeviceFeatures2.SType);
+Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, accelerationStructureFeaturesKhr.SType);
+```
+
+### Disposal
+
+As each `ManagedChain` holds the underlying structures in unmanaged memory (to prevent them being moved and their
+pointers being invalidated), then it is critical you dispose them; either by calling `Dispose()` or by using a `using`
+statement.
\ No newline at end of file
diff --git a/src/Vulkan/Silk.NET.Vulkan.Tests/Silk.NET.Vulkan.Tests.csproj b/src/Vulkan/Silk.NET.Vulkan.Tests/Silk.NET.Vulkan.Tests.csproj
new file mode 100644
index 0000000000..d76ae06525
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan.Tests/Silk.NET.Vulkan.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netcoreapp3.1;net5.0
+ true
+ preview
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
diff --git a/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainExtensions.cs b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainExtensions.cs
new file mode 100644
index 0000000000..4e826ada57
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainExtensions.cs
@@ -0,0 +1,182 @@
+using Xunit;
+
+namespace Silk.NET.Vulkan.Tests;
+
+public class TestChainExtensions
+{
+ [Fact]
+ public unsafe void TestAddNext()
+ {
+ PhysicalDeviceFeatures2
+ // The BaseInStructure method, is a convenient static, to provide a consistent syntax.
+ .Chain(out var features2)
+ // AddNext will create an empty struct, with the correct SType (as well as ensuring the
+ // chain's SType is coerced correctly.
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+ // Ensure all pointers set correctly
+ Assert.Equal((nint) (&indexingFeatures), (nint) features2.PNext);
+ Assert.Equal((nint) (&accelerationStructureFeaturesKhr), (nint) indexingFeatures.PNext);
+ Assert.Equal(0, (nint) accelerationStructureFeaturesKhr.PNext);
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, features2.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+ Assert.Equal
+ (
+ StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr,
+ accelerationStructureFeaturesKhr.SType
+ );
+
+ // Check indices
+ Assert.Equal(1, features2.IndexOf(ref indexingFeatures));
+ Assert.Equal(2, features2.IndexOf(ref accelerationStructureFeaturesKhr));
+ }
+
+ [Fact]
+ public unsafe void TestTryAddNext()
+ {
+ PhysicalDeviceFeatures2
+ // The BaseInStructure method, is a convenient static, to provide a consistent syntax.
+ .Chain(out var features2)
+ // AddNext will create an empty struct, with the correct SType (as well as ensuring the
+ // chain's SType is coerced correctly.
+ .AddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures);
+
+ // Ensure all pointers set correctly
+ Assert.Equal((nint) (&indexingFeatures), (nint) features2.PNext);
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, features2.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+
+ features2.TryAddNext(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures2, out var added);
+ Assert.False(added);
+ }
+
+ [Fact]
+ public unsafe void TestSetNext()
+ {
+ var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ };
+ var accelerationStructureFeaturesKhr = new PhysicalDeviceAccelerationStructureFeaturesKHR
+ {
+ AccelerationStructure = true
+ };
+
+ PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ // SetNext accepts an existing struct, note, it will coerce the SType and blank the PNext
+ .SetNext(ref indexingFeatures)
+ .SetNext(ref accelerationStructureFeaturesKhr);
+
+ Assert.Equal((nint) (&indexingFeatures), (nint) features2.PNext);
+ Assert.Equal((nint) (&accelerationStructureFeaturesKhr), (nint) indexingFeatures.PNext);
+ Assert.Equal(0, (nint) accelerationStructureFeaturesKhr.PNext);
+
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, features2.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+ Assert.Equal
+ (
+ StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr,
+ accelerationStructureFeaturesKhr.SType
+ );
+
+ Assert.True(indexingFeatures.ShaderInputAttachmentArrayDynamicIndexing);
+ Assert.True(accelerationStructureFeaturesKhr.AccelerationStructure);
+ }
+
+ [Fact]
+ public unsafe void TestSetNextUpdates()
+ {
+ var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ };
+
+ PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ // SetNext accepts an existing struct, note, it will coerce the SType and blank the PNext
+ .SetNext(ref indexingFeatures);
+
+ Assert.Equal((nint) (&indexingFeatures), (nint) features2.PNext);
+ Assert.Equal(0, (nint) indexingFeatures.PNext);
+
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, features2.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+
+ // Update indexing features
+ var indexingFeatures2 = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = false
+ };
+ features2.SetNext(ref indexingFeatures2);
+
+ Assert.Equal((nint) (&indexingFeatures2), (nint) features2.PNext);
+ Assert.Equal(0, (nint) indexingFeatures2.PNext);
+
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures2.SType);
+
+ Assert.Equal(1, features2.IndexOf(ref indexingFeatures2));
+ Assert.True(features2.IndexOf(ref indexingFeatures) < 0);
+ }
+
+ [Fact]
+ public unsafe void TestSetNextAlwaysAdd()
+ {
+ var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ };
+
+ PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ // SetNext accepts an existing struct, note, it will coerce the SType and blank the PNext
+ .SetNext(ref indexingFeatures);
+
+ Assert.Equal((nint) (&indexingFeatures), (nint) features2.PNext);
+ Assert.Equal(0, (nint) indexingFeatures.PNext);
+
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, features2.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+
+ // Update indexing features
+ var indexingFeatures2 = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = false
+ };
+ features2.SetNext(ref indexingFeatures2, true);
+
+ Assert.Equal((nint) (&indexingFeatures), (nint) features2.PNext);
+ Assert.Equal((nint) (&indexingFeatures2), (nint) indexingFeatures.PNext);
+ Assert.Equal(0, (nint) indexingFeatures2.PNext);
+
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures2.SType);
+
+ Assert.Equal(1, features2.IndexOf(ref indexingFeatures));
+ Assert.Equal(2, features2.IndexOf(ref indexingFeatures2));
+ }
+
+ [Fact]
+ public unsafe void TestWithoutChain()
+ {
+ // We don't have to use the BaseInStructure() pattern, as we can start with an existing struct
+ var createInfo = new DeviceCreateInfo
+ {
+ Flags = 1U
+ };
+ // However, note that AddNext will still coerce the SType of createInfo.
+ createInfo.AddNext(out PhysicalDeviceFeatures2 features2);
+ Assert.Equal((nint) (&features2), (nint) createInfo.PNext);
+ Assert.Equal(0, (nint) features2.PNext);
+
+ // Note, even though we didn't use chain, we have still coerced the SType
+ Assert.Equal(StructureType.DeviceCreateInfo, createInfo.SType);
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, features2.SType);
+
+ Assert.Equal(1U, createInfo.Flags);
+ }
+}
diff --git a/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainExtensionsAny.cs b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainExtensionsAny.cs
new file mode 100644
index 0000000000..49c7fa63d3
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainExtensionsAny.cs
@@ -0,0 +1,29 @@
+using Xunit;
+
+namespace Silk.NET.Vulkan.Tests;
+
+public class TestChainExtensionsAny
+{
+ [Fact]
+ public unsafe void TestAddNextUnchecked()
+ {
+ var accelerationStructureFeatures = new PhysicalDeviceAccelerationStructureFeaturesKHR();
+ accelerationStructureFeatures
+ .AddNextAny(out PhysicalDeviceDescriptorIndexingFeatures indexingFeatures)
+ .AddNextAny(out DeviceCreateInfo deviceCreateInfo);
+
+ // Ensure all pointers set correctly
+ Assert.Equal((nint) (&indexingFeatures), (nint) accelerationStructureFeatures.PNext);
+ Assert.Equal((nint) (&deviceCreateInfo), (nint) indexingFeatures.PNext);
+ Assert.Equal(0, (nint) deviceCreateInfo.PNext);
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, accelerationStructureFeatures.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+ Assert.Equal(StructureType.DeviceCreateInfo, deviceCreateInfo.SType);
+
+ // Check indices
+ Assert.Equal(1, accelerationStructureFeatures.IndexOfAny(ref indexingFeatures));
+ Assert.Equal(2, accelerationStructureFeatures.IndexOfAny(ref deviceCreateInfo));
+ }
+}
diff --git a/src/Vulkan/Silk.NET.Vulkan.Tests/TestChains.cs b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChains.cs
new file mode 100644
index 0000000000..17f092fde9
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChains.cs
@@ -0,0 +1,500 @@
+using System;
+using System.Linq;
+using Xunit;
+
+namespace Silk.NET.Vulkan.Tests;
+
+public class TestChains
+{
+ [Fact]
+ public unsafe void TestChain()
+ {
+ using var chain = Chain.Create
+ (
+ default(PhysicalDeviceFeatures2),
+ default(PhysicalDeviceDescriptorIndexingFeatures),
+ default(PhysicalDeviceAccelerationStructureFeaturesKHR)
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+ Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+ Assert.Equal(0, (nint) chain.Item2.PNext);
+ }
+
+ [Fact]
+ public unsafe void TestChainReplaceHead()
+ {
+ using var chain =
+ Chain.Create
+ (
+ default(DeviceCreateInfo),
+ default(PhysicalDeviceFeatures2),
+ default(PhysicalDeviceDescriptorIndexingFeatures)
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.DeviceCreateInfo, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Item1.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+ Assert.Equal(0, (nint) chain.Item2.PNext);
+
+ Assert.Equal(0U, chain.Head.Flags);
+
+ var headPtr = chain.HeadPtr;
+
+ // Get the current head
+ var head = chain.Head;
+ // Update the flags
+ head.Flags = 1U;
+ // Update the chain
+ chain.Head = head;
+
+ Assert.Equal(1U, chain.Head.Flags);
+
+ // The head ptr should not change, we overwrite it with the new value
+ Assert.Equal((nint) headPtr, (nint) chain.HeadPtr);
+ // But the next pointer should not change
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ }
+
+ [Fact]
+ public unsafe void TestChainReplaceMiddle()
+ {
+ using var chain = Chain.Create
+ (
+ item1: new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ // We can set any non-default values, note we do not need to set SType or PNext
+ // indeed they will be overwritten.
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ }
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+ Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+ Assert.Equal(0, (nint) chain.Item2.PNext);
+
+ // Check our value was set
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ var item1Ptr = chain.Item1Ptr;
+
+ // Overwrite Item1
+ chain.Item1 = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ // Again we do not need to set SType or PNext, which will be set to the correct values
+ ShaderInputAttachmentArrayDynamicIndexing = false
+ };
+
+ // Check our value was cleared
+ Assert.False(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ // Note all the pointers are still correct (and have not changed)
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+ Assert.Equal(0, (nint) chain.Item2.PNext);
+
+ // As is the SType
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+ chain.Dispose();
+ }
+
+ [Fact]
+ public unsafe void TestChainDuplicate()
+ {
+ using var chain = Chain.Create
+ (
+ item1: new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true}
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ using var newChain = chain.Duplicate();
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ // Check we have new copies
+ Assert.NotEqual((nint) chain.HeadPtr, (nint) newChain.HeadPtr);
+ Assert.NotEqual((nint) chain.Item1Ptr, (nint) newChain.Item1Ptr);
+
+ // Test equality
+ Assert.Equal(chain, newChain);
+ Assert.True(chain == newChain);
+
+ // Modify second chain
+ newChain.Item1 = default;
+
+ // Test equality
+ Assert.NotEqual(chain, newChain);
+ Assert.False(chain == newChain);
+ }
+
+ [Fact]
+ public unsafe void TestChainAdd()
+ {
+ using var chain = Chain.Create
+ (
+ item1: new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true}
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ using var newChain = chain.Add(default(PhysicalDeviceAccelerationStructureFeaturesKHR));
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, newChain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, newChain.Item1.SType);
+ Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, newChain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) newChain.Item1Ptr, (nint) newChain.Head.PNext);
+ Assert.Equal((nint) newChain.Item2Ptr, (nint) newChain.Item1.PNext);
+ Assert.Equal(0, (nint) newChain.Item2.PNext);
+
+ // Check flag still set
+ Assert.True(newChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ // Check we have new copies
+ Assert.NotEqual((nint) chain.HeadPtr, (nint) newChain.HeadPtr);
+ Assert.NotEqual((nint) chain.Item1Ptr, (nint) newChain.Item1Ptr);
+ }
+
+ [Fact]
+ public unsafe void TestChainTruncate()
+ {
+ using var chain =
+ Chain.Create<
+ PhysicalDeviceFeatures2,
+ PhysicalDeviceDescriptorIndexingFeatures,
+ PhysicalDeviceAccelerationStructureFeaturesKHR>
+ (
+ item2: new PhysicalDeviceAccelerationStructureFeaturesKHR
+ {AccelerationStructure = true}
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+ Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+ Assert.Equal(0, (nint) chain.Item2.PNext);
+
+ // Check flag set
+ Assert.True(chain.Item2.AccelerationStructure);
+
+ using var newChain = chain.Truncate(out var accelerationStructure);
+
+ Assert.Equal(2, newChain.Count);
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, newChain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, newChain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) newChain.Item1Ptr, (nint) newChain.Head.PNext);
+ Assert.Equal(0, (nint) newChain.Item1.PNext);
+
+ // Check removed type flag
+ Assert.True(accelerationStructure.AccelerationStructure);
+
+ // Check we have new copies
+ Assert.NotEqual((nint) chain.HeadPtr, (nint) newChain.HeadPtr);
+ Assert.NotEqual((nint) chain.Item1Ptr, (nint) newChain.Item1Ptr);
+ }
+
+ [Fact]
+ public unsafe void TestChainLoad()
+ {
+ // Load an unmanaged chain
+ var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ };
+ PhysicalDeviceFeatures2
+ .Chain(out var unmanagedChain)
+ .SetNext(ref indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+ // Loads a new managed chain from an unmanaged chain
+ using var managedChain =
+ Chain.Load(out var errors, unmanagedChain);
+
+ // Check we had no loading errors
+ Assert.Equal("", errors);
+
+ // Check the flag still set
+ Assert.True(managedChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, managedChain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, managedChain.Item1.SType);
+ Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, managedChain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) managedChain.Item1Ptr, (nint) managedChain.Head.PNext);
+ Assert.Equal((nint) managedChain.Item2Ptr, (nint) managedChain.Item1.PNext);
+ Assert.Equal(0, (nint) managedChain.Item2.PNext);
+ }
+
+ [Fact]
+ public void TestChainLoadWithError()
+ {
+ var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ };
+ // Load an unmanaged chain
+ DeviceCreateInfo
+ .Chain(out var unmanagedChain)
+ .AddNext(out PhysicalDeviceFeatures2 features2)
+ .SetNext(ref indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+ // Loads a new managed chain from an unmanaged chain
+ using var managedChain =
+ Chain.Load<
+ DeviceCreateInfo,
+ // Note we are supplied a PhysicalDeviceFeatures2 here from the unmanaged chain
+ PhysicalDeviceAccelerationStructureFeaturesKHR,
+ PhysicalDeviceDescriptorIndexingFeatures,
+ PhysicalDeviceAccelerationStructureFeaturesKHR,
+ // Note that the unmanaged chain did not supply a 5th entry
+ PhysicalDeviceFeatures2>(out var errors, unmanagedChain);
+
+ // Check for errors
+ var errorsArray = errors.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
+ Assert.Equal(2, errorsArray.Length);
+ Assert.Equal
+ (
+ "The unmanaged chain has a structure type PhysicalDeviceFeatures2Khr at position 2; expected PhysicalDeviceAccelerationStructureFeaturesKhr",
+ errorsArray[0]
+ );
+ Assert.Equal
+ (
+ "The unmanaged chain was length 4, expected length 5", errorsArray[1]
+ );
+
+ // Despite the errors indexing features was at the right location so was loaded
+ Assert.True(managedChain.Item2.ShaderInputAttachmentArrayDynamicIndexing);
+ }
+
+ [Fact]
+ public void TestChainLoadWithErrorTooLong()
+ {
+ var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ };
+ // Load an unmanaged chain
+ DeviceCreateInfo
+ .Chain(out var unmanagedChain)
+ .AddNext(out PhysicalDeviceFeatures2 features2)
+ .SetNext(ref indexingFeatures)
+ .AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeaturesKhr);
+
+ // Try loading a shorter managed chain
+ using var managedChain =
+ Chain.Load<
+ DeviceCreateInfo,
+ PhysicalDeviceFeatures2,
+ PhysicalDeviceDescriptorIndexingFeatures>(out var errors, unmanagedChain);
+
+ // Check for errors
+ Assert.Equal(@"The unmanaged chain was longer than the expected length 3", errors);
+
+ // Despite the errors indexing features was at the right location so was loaded
+ Assert.True(managedChain.Item2.ShaderInputAttachmentArrayDynamicIndexing);
+ }
+
+ [Fact]
+ public void TestReadOnlyList()
+ {
+ using var chain = Chain.Create();
+
+ Assert.Equal(3, chain.Count);
+
+ // Ensure all STypes set correctly using indexer
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain[0].StructureType());
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain[1].StructureType());
+ Assert.Equal(StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, chain[2].StructureType());
+
+ Assert.Throws(() => chain[3]);
+
+ // Get array using IEnumerable implementation
+ var structures = chain.ToArray();
+
+ // Check concrete types
+ Assert.IsType(structures[0]);
+ Assert.IsType(structures[1]);
+ Assert.IsType(structures[2]);
+ }
+
+ [Fact]
+ public void TestDeconstructor()
+ {
+ using var chain = Chain.Create();
+
+ var (physicalDeviceFeatures2, indexingFeatures, accelerationStructureFeaturesKhr) = chain;
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, physicalDeviceFeatures2.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, indexingFeatures.SType);
+ Assert.Equal
+ (StructureType.PhysicalDeviceAccelerationStructureFeaturesKhr, accelerationStructureFeaturesKhr.SType);
+ }
+
+ [Fact]
+ public unsafe void TestChainGetHashCode()
+ {
+ using var chain = Chain.Create
+ (
+ item1: new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true}
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ using var newChain = chain.Duplicate();
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1Ptr->SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ // Check we have new copies
+ Assert.NotEqual((nint) chain.HeadPtr, (nint) newChain.HeadPtr);
+ Assert.NotEqual((nint) chain.Item1Ptr, (nint) newChain.Item1Ptr);
+
+ // Test equality
+ Assert.Equal(chain, newChain);
+ Assert.True(chain == newChain);
+ var hashCode = chain.GetHashCode();
+ var newHashCode = newChain.GetHashCode();
+ Assert.Equal(hashCode, newHashCode);
+
+ // Modify second chain
+ newChain.Item1 = default;
+
+ // Test equality
+ Assert.NotEqual(chain, newChain);
+ Assert.False(chain == newChain);
+ Assert.Equal(hashCode, chain.GetHashCode());
+ var newHashCode2 = newChain.GetHashCode();
+ Assert.NotEqual(hashCode, newHashCode2);
+ Assert.NotEqual(newHashCode, newHashCode2);
+ }
+
+ [Fact]
+ public void TestClear()
+ {
+ using var chain = Chain.Create
+ (
+ new PhysicalDeviceFeatures2 {Features = new PhysicalDeviceFeatures {AlphaToOne = true}},
+ new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true},
+ new PhysicalDeviceAccelerationStructureFeaturesKHR {AccelerationStructure = true}
+ );
+
+ Assert.True(chain.Head.Features.AlphaToOne);
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+ Assert.True(chain.Item2.AccelerationStructure);
+
+ // Don't clear the head
+ chain.Clear(false);
+
+ Assert.True(chain.Head.Features.AlphaToOne);
+ Assert.False(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+ Assert.False(chain.Item2.AccelerationStructure);
+
+ // Clear the head
+ chain.Clear();
+
+ Assert.False(chain.Head.Features.AlphaToOne);
+ Assert.False(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+ Assert.False(chain.Item2.AccelerationStructure);
+ }
+
+ [Fact]
+ public unsafe void TestImplicitCastsToPointers()
+ {
+ using var chain = Chain.Create();
+
+ PhysicalDeviceFeatures2* p = chain;
+ Assert.Equal((nint)p, (nint)chain.HeadPtr);
+
+ BaseInStructure* b = chain;
+ Assert.Equal((nint)b, (nint)chain.HeadPtr);
+
+ void* v = chain;
+ Assert.Equal((nint)v, (nint)chain.HeadPtr);
+
+ nint n = chain;
+ Assert.Equal(n, (nint)chain.HeadPtr);
+ }
+}
diff --git a/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainsAny.cs b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainsAny.cs
new file mode 100644
index 0000000000..000299643b
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan.Tests/TestChainsAny.cs
@@ -0,0 +1,190 @@
+using Xunit;
+
+namespace Silk.NET.Vulkan.Tests;
+
+public class TestChainsAny
+{
+ [Fact]
+ public unsafe void TestChainAny()
+ {
+ using var chain = Chain.CreateAny
+ (
+ default(PhysicalDeviceFeatures2),
+ default(PhysicalDeviceDescriptorIndexingFeatures),
+ default(DeviceCreateInfo)
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+ Assert.Equal(StructureType.DeviceCreateInfo, chain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+ Assert.Equal(0, (nint) chain.Item2.PNext);
+ }
+
+ [Fact]
+ public unsafe void TestChainDuplicateAny()
+ {
+ using var chain = Chain.CreateAny
+ (
+ item1: new DeviceCreateInfo {Flags = 1U}
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.DeviceCreateInfo, chain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.Equal(1U, chain.Item1.Flags);
+
+ using var newChain = chain.Duplicate();
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.DeviceCreateInfo, chain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.Equal(1U, chain.Item1.Flags);
+
+ // Check we have new copies
+ Assert.NotEqual((nint) chain.HeadPtr, (nint) newChain.HeadPtr);
+ Assert.NotEqual((nint) chain.Item1Ptr, (nint) newChain.Item1Ptr);
+
+ // Test equality
+ Assert.Equal(chain, newChain);
+ Assert.True(chain == newChain);
+ }
+
+ [Fact]
+ public unsafe void TestChainAddAny()
+ {
+ using var chain = Chain.Create
+ (
+ item1: new PhysicalDeviceDescriptorIndexingFeatures {ShaderInputAttachmentArrayDynamicIndexing = true}
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal(0, (nint) chain.Item1.PNext);
+
+ // Check flag set
+ Assert.True(chain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ using var newChain = chain.AddAny(default(DeviceCreateInfo));
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, newChain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, newChain.Item1.SType);
+ Assert.Equal(StructureType.DeviceCreateInfo, newChain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) newChain.Item1Ptr, (nint) newChain.Head.PNext);
+ Assert.Equal((nint) newChain.Item2Ptr, (nint) newChain.Item1.PNext);
+ Assert.Equal(0, (nint) newChain.Item2.PNext);
+
+ // Check flag still set
+ Assert.True(newChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ // Check we have new copies
+ Assert.NotEqual((nint) chain.HeadPtr, (nint) newChain.HeadPtr);
+ Assert.NotEqual((nint) chain.Item1Ptr, (nint) newChain.Item1Ptr);
+ }
+
+ [Fact]
+ public unsafe void TestChainTruncateAny()
+ {
+ using var chain =
+ Chain.CreateAny<
+ PhysicalDeviceFeatures2,
+ PhysicalDeviceDescriptorIndexingFeatures,
+ DeviceCreateInfo>
+ (
+ item2: new DeviceCreateInfo {Flags = 1U}
+ );
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, chain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, chain.Item1.SType);
+ Assert.Equal(StructureType.DeviceCreateInfo, chain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) chain.Item1Ptr, (nint) chain.Head.PNext);
+ Assert.Equal((nint) chain.Item2Ptr, (nint) chain.Item1.PNext);
+ Assert.Equal(0, (nint) chain.Item2.PNext);
+
+ // Check flag set
+ Assert.Equal(1U, chain.Item2.Flags);
+
+ using var newChain = chain.TruncateAny(out var deviceCreateInfo);
+
+ Assert.Equal(2, newChain.Count);
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, newChain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, newChain.Item1.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) newChain.Item1Ptr, (nint) newChain.Head.PNext);
+ Assert.Equal(0, (nint) newChain.Item1.PNext);
+
+ // Check removed type flag
+ Assert.Equal(1U, deviceCreateInfo.Flags);
+
+ // Check we have new copies
+ Assert.NotEqual((nint) chain.HeadPtr, (nint) newChain.HeadPtr);
+ Assert.NotEqual((nint) chain.Item1Ptr, (nint) newChain.Item1Ptr);
+
+ // NOTE: As the new chain is valid, we can use Truncate on it
+ using var finalChain = newChain.Truncate(out var indexingFeatures);
+ }
+
+ [Fact]
+ public unsafe void TestChainLoadAny()
+ {
+ // Load an unmanaged chain
+ var indexingFeatures = new PhysicalDeviceDescriptorIndexingFeatures
+ {
+ ShaderInputAttachmentArrayDynamicIndexing = true
+ };
+ PhysicalDeviceFeatures2
+ .Chain(out var unmanagedChain)
+ .SetNext(ref indexingFeatures)
+ .AddNextAny(out DeviceCreateInfo deviceCreateInfo);
+
+ // Loads a new managed chain from an unmanaged chain
+ using var managedChain =
+ Chain.LoadAny(out var errors, unmanagedChain);
+
+ // Check we had no loading errors
+ Assert.Equal("", errors);
+
+ // Check the flag still set
+ Assert.True(managedChain.Item1.ShaderInputAttachmentArrayDynamicIndexing);
+
+ // Ensure all STypes set correctly
+ Assert.Equal(StructureType.PhysicalDeviceFeatures2, managedChain.Head.SType);
+ Assert.Equal(StructureType.PhysicalDeviceDescriptorIndexingFeatures, managedChain.Item1.SType);
+ Assert.Equal(StructureType.DeviceCreateInfo, managedChain.Item2.SType);
+
+ // Ensure pointers set correctly
+ Assert.Equal((nint) managedChain.Item1Ptr, (nint) managedChain.Head.PNext);
+ Assert.Equal((nint) managedChain.Item2Ptr, (nint) managedChain.Item1.PNext);
+ Assert.Equal(0, (nint) managedChain.Item2.PNext);
+ }
+}
diff --git a/src/Vulkan/Silk.NET.Vulkan.Tests/TestCompilation.cs b/src/Vulkan/Silk.NET.Vulkan.Tests/TestCompilation.cs
new file mode 100644
index 0000000000..1f2675acc7
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan.Tests/TestCompilation.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace Silk.NET.Vulkan.Tests;
+
+public class TestCompilation
+{
+ private static readonly Lazy> References = new
+ (
+ () =>
+ ((string?) AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? string.Empty)
+ .Split(Path.PathSeparator)
+ .Select(r => MetadataReference.CreateFromFile(r))
+ .Concat(new[] {MetadataReference.CreateFromFile(typeof(StructureType).Assembly.Location)})
+ .ToArray()
+ );
+
+ private static readonly string CodeTemplate = @"
+using System;
+using Silk.NET.Vulkan;
+
+public class Test
+{{
+ public void DoTest()
+ {{
+ {0}
+ }}
+}}";
+
+ private IReadOnlyList CheckCompile(string code)
+ {
+ var assemblyName = Path.GetRandomFileName();
+ var compilation = CSharpCompilation.Create
+ (
+ assemblyName,
+ new[] {CSharpSyntaxTree.ParseText(string.Format(CodeTemplate, code))},
+ References.Value,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
+ );
+
+ using var ms = new MemoryStream();
+ var result = compilation.Emit(ms);
+
+ if (result.Success)
+ {
+ return Array.Empty();
+ }
+
+ return result.Diagnostics.Where
+ (
+ diagnostic =>
+ diagnostic.IsWarningAsError ||
+ diagnostic.Severity == DiagnosticSeverity.Error
+ )
+ .ToArray();
+ }
+
+ [Fact]
+ public void TestCantAddUnsupportedNext()
+ {
+ var diagnostics = CheckCompile
+ (
+ @"PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .AddNext(out DeviceCreateInfo createInfo);"
+ );
+
+ Assert.Single(diagnostics);
+ var error = diagnostics.First();
+ // error CS0315: The type 'Silk.NET.Vulkan.PhysicalDeviceFeatures2' cannot be used as type parameter 'TChain' in the generic type or method 'Chain.AddNext(ref TChain, out TNext)'. There is no boxing conversion from 'Silk.NET.Vulkan.PhysicalDeviceFeatures2' to 'Silk.NET.Vulkan.IChainable'.
+ Assert.Equal("CS0315", error.Id);
+ }
+
+ [Fact]
+ public void TestCanAddUnsupportedNextUsingAny()
+ {
+ var diagnostics = CheckCompile
+ (
+ @"PhysicalDeviceFeatures2
+ .Chain(out var features2)
+ .AddNextAny(out DeviceCreateInfo createInfo);"
+ );
+
+ Assert.Empty(diagnostics);
+ }
+
+ [Fact]
+ public void TestCanAddSupportedNext()
+ {
+ var diagnostics = CheckCompile
+ (
+ @"DeviceCreateInfo
+ .Chain(out var createInfo)
+ .AddNext(out PhysicalDeviceFeatures2 features2);"
+ );
+
+ Assert.Empty(diagnostics);
+ }
+}
diff --git a/src/Vulkan/Silk.NET.Vulkan/Chain.cs b/src/Vulkan/Silk.NET.Vulkan/Chain.cs
new file mode 100644
index 0000000000..321bad70a3
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan/Chain.cs
@@ -0,0 +1,290 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable CS0659, CS0660, CS0661
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Silk.NET.Vulkan;
+
+///
+/// Base class for all Managed Chains.
+///
+public abstract unsafe partial class Chain : IReadOnlyList, IEquatable, IDisposable
+{
+ ///
+ /// Gets a pointer to the current head.
+ ///
+ public abstract BaseInStructure* HeadPtr { get; }
+
+ ///
+ /// Gets the total size (in bytes) of the unmanaged memory, managed by this chain.
+ ///
+ public abstract int Size { get; }
+
+ ///
+ /// Clears this instance by setting each element of the chain to default, whilst maintaining the
+ /// and .
+ ///
+ /// If true will also clear the head of the chain; otherwise, will not touch the
+ /// head if false
+ /// This instance.
+ ///
+ /// Note, unlike many of the other length modifying methods, this does not return a new instance, if you wish
+ /// to create a cleared instance with the same types, first use .
+ ///
+ public abstract Chain Clear(bool includeHead = true);
+
+ ///
+ /// Creates a new by copying this instance.
+ ///
+ /// A duplicate of this instance.
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Chain Duplicate() => DoDuplicate();
+
+ ///
+ /// Creates a new by copying this instance.
+ ///
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ ///
+ ///
+ protected abstract Chain DoDuplicate();
+
+ ///
+ /// Creates a new with 2 items, by appending to
+ /// the end of this instance.
+ ///
+ /// The item to append.
+ /// Type of the item.
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Chain AddAny(T item = default)
+ where T : unmanaged, IChainable
+ => DoAddAny(item);
+
+ ///
+ /// Creates a new with 2 items, by appending to
+ /// the end of this instance.
+ ///
+ /// The item to append.
+ /// Type of the item.
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ protected abstract Chain DoAddAny(T item = default)
+ where T : unmanaged, IChainable;
+
+ ///
+ /// Creates a new by removing the last item from this instance.
+ ///
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Chain TruncateAny() => DoTruncateAny();
+
+ ///
+ /// Creates a new by removing the last item from this instance.
+ ///
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ protected abstract Chain DoTruncateAny();
+
+ ///
+ /// Creates a new by removing the last item from this instance.
+ ///
+ /// The item removed from this instance.
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Chain TruncateAny(out IChainable tail)
+ => DoTruncateAny(out tail);
+
+ ///
+ /// Creates a new by removing the last item from this instance.
+ ///
+ /// The item removed from this instance.
+ ///
+ /// Do not forget to dispose this chain if you are no longer using it.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ protected abstract Chain DoTruncateAny(out IChainable tail);
+
+ ///
+ public abstract IEnumerator GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ public abstract int Count { get; }
+
+ ///
+ public abstract IChainable this[int index] { get; }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(Chain other)
+ {
+ return !ReferenceEquals(null, other) &&
+ (ReferenceEquals(this, other) || other.GetType() == this.GetType() && MemoryEquals(other));
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ return !ReferenceEquals(null, obj) &&
+ (ReferenceEquals(this, obj) || obj.GetType() == this.GetType() && MemoryEquals((Chain) obj));
+ }
+
+ ///
+ /// Equality operator overload.
+ ///
+ ///
+ /// The left value.
+ ///
+ ///
+ /// The right value.
+ ///
+ ///
+ /// true when the instances are equal, otherwise false .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(Chain left, Chain right)
+ {
+ return left?.Equals(right) ?? ReferenceEquals(null, right);
+ }
+
+ ///
+ /// Inequality operator overload.
+ ///
+ ///
+ /// The left value.
+ ///
+ ///
+ /// The right value.
+ ///
+ ///
+ /// true when the instances are not equal, otherwise false .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(Chain left, Chain right)
+ {
+ return !(left?.Equals(right) ?? ReferenceEquals(null, right));
+ }
+
+ ///
+ /// Compares the supplied memory block with this one.
+ ///
+ protected abstract bool MemoryEquals(Chain other);
+
+ ///
+ public void Dispose()
+ {
+ // Dispose of unmanaged resources.
+ Dispose(true);
+ // Suppress finalization.
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Protected implementation of the dispose pattern
+ ///
+ ///
+ protected abstract void Dispose(bool disposing);
+
+ ///
+ /// Combines a hashcode with the contents of a slice.
+ ///
+ ///
+ ///
+ ///
+ protected static void CombineHash(ref int hashCode, ReadOnlySpan slice)
+ {
+ if (slice.Length >= 8)
+ {
+ // Process slice in 8 byte chunks
+ var s8 = MemoryMarshal.Cast(slice);
+ foreach (var l in s8)
+ {
+ hashCode = HashCode.Combine(hashCode, l);
+ }
+
+ slice = slice.Slice(s8.Length * 8);
+ }
+
+ // Process remainder of slice
+ if (slice.Length >= 4)
+ {
+ var s4 = MemoryMarshal.Cast(slice);
+ hashCode = HashCode.Combine(hashCode, s4[0]);
+ slice = slice.Slice(s4.Length * 4);
+ }
+
+ if (slice.Length >= 2)
+ {
+ var s2 = MemoryMarshal.Cast(slice);
+ hashCode = HashCode.Combine(hashCode, s2[0]);
+ slice = slice.Slice(s2.Length * 2);
+ }
+
+ if (slice.Length > 0)
+ {
+ hashCode = HashCode.Combine(hashCode, slice[0]);
+ }
+ }
+
+ ///
+ /// Defines an implicit conversion of a given to a pointer to the head.
+ ///
+ /// A to implicitly convert.
+ /// A nint pointer to the head of the .
+ public static implicit operator nint(Chain chain) => (nint) chain.HeadPtr;
+
+ ///
+ /// Defines an implicit conversion of a given to a pointer to the head.
+ ///
+ /// A to implicitly convert.
+ /// A void* pointer to the head of the .
+ public static implicit operator void*(Chain chain) => (void*) chain.HeadPtr;
+
+ ///
+ /// Defines an implicit conversion of a given to a pointer to the head.
+ ///
+ /// A to implicitly convert.
+ /// A * pointer to the head of the .
+ public static implicit operator BaseInStructure*(Chain chain) => chain.HeadPtr;
+}
diff --git a/src/Vulkan/Silk.NET.Vulkan/Chain.g.cs b/src/Vulkan/Silk.NET.Vulkan/Chain.g.cs
new file mode 100644
index 0000000000..b030feaa63
--- /dev/null
+++ b/src/Vulkan/Silk.NET.Vulkan/Chain.g.cs
@@ -0,0 +1,21342 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ReSharper disable StaticMemberInGenericType
+#pragma warning disable CS0659, CS0660
+using Silk.NET.Core.Native;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+
+namespace Silk.NET.Vulkan;
+
+///
+/// Base class for all Managed Chains.
+///
+public abstract unsafe partial class Chain
+{
+ ///
+ /// Gets the maximum supported chain .
+ ///
+ public static readonly int MaximumCount = 16;
+
+ ///
+ /// Creates a new with 1 items.
+ ///
+ /// The head of the chain.
+ /// The chain type
+ /// A new with 1 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Create(TChain head = default)
+ where TChain : unmanaged, IChainStart
+ => new(head);
+
+ ///
+ /// Creates a new with 1 items.
+ ///
+ /// The head of the chain.
+ /// The chain type
+ /// A new with 1 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain CreateAny(TChain head = default)
+ where TChain : unmanaged, IChainable
+ => new(head);
+
+ ///
+ /// Loads a new with 1 items from an existing unmanaged chain,
+ /// ignoring any errors.
+ ///
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 1 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Load(TChain chain)
+ where TChain : unmanaged, IChainStart
+ => LoadAny(chain);
+
+ ///
+ /// Loads a new with 1 items from an existing unmanaged chain,
+ /// ignoring any errors.
+ ///
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 1 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain LoadAny(TChain chain)
+ where TChain : unmanaged, IChainable
+ {
+ var size = Chain.MemorySize;
+ var newHeadPtr = SilkMarshal.Allocate(size);
+ chain.StructureType();
+ *((TChain*)newHeadPtr) = chain;
+ return new Chain(newHeadPtr);
+ }
+
+ ///
+ /// Loads a new with 1 items from an existing unmanaged chain.
+ ///
+ /// Any errors loading the chain.
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 1 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Load(out string errors, TChain chain)
+ where TChain : unmanaged, IChainStart
+ => LoadAny(out errors, chain);
+
+ ///
+ /// Loads a new with 1 items from an existing unmanaged chain.
+ ///
+ /// Any errors loading the chain.
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 1 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain LoadAny(out string errors, TChain chain)
+ where TChain : unmanaged, IChainable
+ {
+ var size = Chain.MemorySize;
+ var newHeadPtr = SilkMarshal.Allocate(size);
+ chain.StructureType();
+ *((TChain*)newHeadPtr) = chain;
+ errors = string.Empty;
+ return new Chain(newHeadPtr);
+ }
+
+ ///
+ /// Creates a new with 2 items.
+ ///
+ /// The head of the chain.
+ /// Item 1.
+ /// The chain type
+ /// Type of Item 1.
+ /// A new with 2 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Create(TChain head = default, T1 item1 = default)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ => new(head, item1);
+
+ ///
+ /// Creates a new with 2 items.
+ ///
+ /// The head of the chain.
+ /// Item 1.
+ /// The chain type
+ /// Type of Item 1.
+ /// A new with 2 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain CreateAny(TChain head = default, T1 item1 = default)
+ where TChain : unmanaged, IChainable
+ where T1 : unmanaged, IChainable
+ => new(head, item1);
+
+ ///
+ /// Loads a new with 2 items from an existing unmanaged chain,
+ /// ignoring any errors.
+ ///
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 2 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Load(TChain chain)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ => LoadAny(chain);
+
+ ///
+ /// Loads a new with 2 items from an existing unmanaged chain,
+ /// ignoring any errors.
+ ///
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 2 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain LoadAny(TChain chain)
+ where TChain : unmanaged, IChainable
+ where T1 : unmanaged, IChainable
+ {
+ var size = Chain.MemorySize;
+ var newHeadPtr = SilkMarshal.Allocate(size);
+ chain.StructureType();
+ *((TChain*)newHeadPtr) = chain;
+ var existingPtr = (BaseInStructure*) Unsafe.AsPointer(ref chain);
+ var newPtr = (BaseInStructure*) newHeadPtr;
+
+ existingPtr = existingPtr->PNext;
+ newPtr->PNext = (BaseInStructure*) (newHeadPtr + Chain.Item1Offset);
+ newPtr = newPtr->PNext;
+
+ T1 item1 = default;
+ var expectedStructureType = item1.StructureType();
+ if (existingPtr is not null &&
+ existingPtr->SType == expectedStructureType) {
+ if (existingPtr->PNext is not null) {
+ existingPtr->PNext = null;
+ }
+ item1 = Unsafe.AsRef(existingPtr);
+ }
+ *((T1*)newPtr) = item1;
+ return new Chain(newHeadPtr);
+ }
+
+ ///
+ /// Loads a new with 2 items from an existing unmanaged chain.
+ ///
+ /// Any errors loading the chain.
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 2 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Load(out string errors, TChain chain)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ => LoadAny(out errors, chain);
+
+ ///
+ /// Loads a new with 2 items from an existing unmanaged chain.
+ ///
+ /// Any errors loading the chain.
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 2 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain LoadAny(out string errors, TChain chain)
+ where TChain : unmanaged, IChainable
+ where T1 : unmanaged, IChainable
+ {
+ var size = Chain.MemorySize;
+ var newHeadPtr = SilkMarshal.Allocate(size);
+ chain.StructureType();
+ *((TChain*)newHeadPtr) = chain;
+ var errorBuilder = new StringBuilder();
+ var existingPtr = (BaseInStructure*) Unsafe.AsPointer(ref chain);
+ var newPtr = (BaseInStructure*) newHeadPtr;
+
+ existingPtr = existingPtr->PNext;
+ newPtr->PNext = (BaseInStructure*) (newHeadPtr + Chain.Item1Offset);
+ newPtr = newPtr->PNext;
+
+ T1 item1 = default;
+ var expectedStructureType = item1.StructureType();
+ if (existingPtr is null) {
+ errorBuilder.AppendLine("The unmanaged chain was length 1, expected length 2");
+ } else {
+ if (existingPtr->SType != expectedStructureType) {
+ errorBuilder.Append("The unmanaged chain has a structure type ")
+ .Append(existingPtr->SType)
+ .Append(" at position 2; expected ")
+ .Append(expectedStructureType)
+ .AppendLine();
+ } else {
+ if (existingPtr->PNext is not null) {
+ errorBuilder.AppendLine("The unmanaged chain was longer than the expected length 2");
+ existingPtr->PNext = null;
+ }
+ item1 = Unsafe.AsRef(existingPtr);
+ }
+ }
+ *((T1*)newPtr) = item1;
+
+ // Create string of errors
+ errors = errorBuilder.ToString().Trim();
+ return new Chain(newHeadPtr);
+ }
+
+ ///
+ /// Creates a new with 3 items.
+ ///
+ /// The head of the chain.
+ /// Item 1.
+ /// Item 2.
+ /// The chain type
+ /// Type of Item 1.
+ /// Type of Item 2.
+ /// A new with 3 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Create(TChain head = default, T1 item1 = default, T2 item2 = default)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ where T2 : unmanaged, IExtendsChain
+ => new(head, item1, item2);
+
+ ///
+ /// Creates a new with 3 items.
+ ///
+ /// The head of the chain.
+ /// Item 1.
+ /// Item 2.
+ /// The chain type
+ /// Type of Item 1.
+ /// Type of Item 2.
+ /// A new with 3 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain CreateAny(TChain head = default, T1 item1 = default, T2 item2 = default)
+ where TChain : unmanaged, IChainable
+ where T1 : unmanaged, IChainable
+ where T2 : unmanaged, IChainable
+ => new(head, item1, item2);
+
+ ///
+ /// Loads a new with 3 items from an existing unmanaged chain,
+ /// ignoring any errors.
+ ///
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 3 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Load(TChain chain)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ where T2 : unmanaged, IExtendsChain
+ => LoadAny(chain);
+
+ ///
+ /// Loads a new with 3 items from an existing unmanaged chain,
+ /// ignoring any errors.
+ ///
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 3 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain LoadAny(TChain chain)
+ where TChain : unmanaged, IChainable
+ where T1 : unmanaged, IChainable
+ where T2 : unmanaged, IChainable
+ {
+ var size = Chain.MemorySize;
+ var newHeadPtr = SilkMarshal.Allocate(size);
+ chain.StructureType();
+ *((TChain*)newHeadPtr) = chain;
+ var existingPtr = (BaseInStructure*) Unsafe.AsPointer(ref chain);
+ var newPtr = (BaseInStructure*) newHeadPtr;
+
+ existingPtr = existingPtr->PNext;
+ newPtr->PNext = (BaseInStructure*) (newHeadPtr + Chain.Item1Offset);
+ newPtr = newPtr->PNext;
+
+ T1 item1 = default;
+ var expectedStructureType = item1.StructureType();
+ if (existingPtr is not null &&
+ existingPtr->SType == expectedStructureType) {
+ item1 = Unsafe.AsRef(existingPtr);
+ }
+ *((T1*)newPtr) = item1;
+
+ existingPtr = existingPtr->PNext;
+ newPtr->PNext = (BaseInStructure*) (newHeadPtr + Chain.Item2Offset);
+ newPtr = newPtr->PNext;
+
+ T2 item2 = default;
+ expectedStructureType = item2.StructureType();
+ if (existingPtr is not null &&
+ existingPtr->SType == expectedStructureType) {
+ if (existingPtr->PNext is not null) {
+ existingPtr->PNext = null;
+ }
+ item2 = Unsafe.AsRef(existingPtr);
+ }
+ *((T2*)newPtr) = item2;
+ return new Chain(newHeadPtr);
+ }
+
+ ///
+ /// Loads a new with 3 items from an existing unmanaged chain.
+ ///
+ /// Any errors loading the chain.
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 3 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Load(out string errors, TChain chain)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ where T2 : unmanaged, IExtendsChain
+ => LoadAny(out errors, chain);
+
+ ///
+ /// Loads a new with 3 items from an existing unmanaged chain.
+ ///
+ /// Any errors loading the chain.
+ /// The unmanaged chain to use as the basis of this chain.
+ /// A new with 3 items.
+ /// The `Any` versions of chain methods do not validate that items belong in the chain, this is
+ /// useful for situations where the specification does not indicate required chain constraints. You should generally
+ /// try to use the none `Any` version in preference.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain LoadAny(out string errors, TChain chain)
+ where TChain : unmanaged, IChainable
+ where T1 : unmanaged, IChainable
+ where T2 : unmanaged, IChainable
+ {
+ var size = Chain.MemorySize;
+ var newHeadPtr = SilkMarshal.Allocate(size);
+ chain.StructureType();
+ *((TChain*)newHeadPtr) = chain;
+ var errorBuilder = new StringBuilder();
+ var existingPtr = (BaseInStructure*) Unsafe.AsPointer(ref chain);
+ var newPtr = (BaseInStructure*) newHeadPtr;
+
+ existingPtr = existingPtr->PNext;
+ newPtr->PNext = (BaseInStructure*) (newHeadPtr + Chain.Item1Offset);
+ newPtr = newPtr->PNext;
+
+ T1 item1 = default;
+ var expectedStructureType = item1.StructureType();
+ if (existingPtr is null) {
+ errorBuilder.AppendLine("The unmanaged chain was length 1, expected length 3");
+ } else {
+ if (existingPtr->SType != expectedStructureType) {
+ errorBuilder.Append("The unmanaged chain has a structure type ")
+ .Append(existingPtr->SType)
+ .Append(" at position 2; expected ")
+ .Append(expectedStructureType)
+ .AppendLine();
+ } else {
+ item1 = Unsafe.AsRef(existingPtr);
+ }
+ }
+ *((T1*)newPtr) = item1;
+
+ existingPtr = existingPtr->PNext;
+ newPtr->PNext = (BaseInStructure*) (newHeadPtr + Chain.Item2Offset);
+ newPtr = newPtr->PNext;
+
+ T2 item2 = default;
+ expectedStructureType = item2.StructureType();
+ if (existingPtr is null) {
+ errorBuilder.AppendLine("The unmanaged chain was length 2, expected length 3");
+ } else {
+ if (existingPtr->SType != expectedStructureType) {
+ errorBuilder.Append("The unmanaged chain has a structure type ")
+ .Append(existingPtr->SType)
+ .Append(" at position 3; expected ")
+ .Append(expectedStructureType)
+ .AppendLine();
+ } else {
+ if (existingPtr->PNext is not null) {
+ errorBuilder.AppendLine("The unmanaged chain was longer than the expected length 3");
+ existingPtr->PNext = null;
+ }
+ item2 = Unsafe.AsRef(existingPtr);
+ }
+ }
+ *((T2*)newPtr) = item2;
+
+ // Create string of errors
+ errors = errorBuilder.ToString().Trim();
+ return new Chain(newHeadPtr);
+ }
+
+ ///
+ /// Creates a new with 4 items.
+ ///
+ /// The head of the chain.
+ /// Item 1.
+ /// Item 2.
+ /// Item 3.
+ /// The chain type
+ /// Type of Item 1.
+ /// Type of Item 2.
+ /// Type of Item 3.
+ /// A new with 4 items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Chain Create(TChain head = default, T1 item1 = default, T2 item2 = default, T3 item3 = default)
+ where TChain : unmanaged, IChainStart
+ where T1 : unmanaged, IExtendsChain
+ where T2 : unmanaged, IExtendsChain