Skip to main content
Version: v3.0.0-preview

Vulkan Chaining

Table of Contents

BaseInStructure

The Vulkan Specification provides a machine-readable XML version 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 type in Silk.NET, which contains 2 fields:

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 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:

/// <summary>
/// Base interface for any struct that has can set the next value.
/// </summary>
/// <remarks><para>Note that any structure marked <see cref="IChainable"/> must start with a
/// <see cref="StructureType"/> and a <c>void*</c> field, in that order. This is so that a pointer to it can be coerced
/// to a pointer to a <see cref="BaseInStructure"/>.</para></remarks>
public interface IChainable : IStructuredType
{
/// <summary>
/// Points to the next <see cref="IChainable"/> in this chain, if any; otherwise <see langword="null"/>.
/// </summary>
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.:

public void* PNext;

/// <inheritdoc />
unsafe BaseInStructure* IChainable.PNext
{
get => (BaseInStructure*) PNext;
set => PNext = value;
}

IStructuredType

You may also note that IChainable extends IStructuredType which is defined here:

/// <summary>
/// Base interface for any struct that has a <see cref="StructureType"/> field called `SType`, that must be correctly
/// set when passing into the Vulkan API.
/// </summary>
public interface IStructuredType
{
/// <summary>
/// Gets the structured type's <see cref="Vulkan.StructureType"/> enum value.
/// </summary>
/// <remarks>
/// Retrieving the <see cref="Vulkan.StructureType"/> also ensures it is set to the correct value (if any).
/// </remarks>
StructureType StructureType();
}

Although this is currently never implemented directly (as all such structures in Vulkan are Chainable), 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:

[NativeName("Name", "VkDeviceCreateInfo")]
public unsafe partial struct DeviceCreateInfo : IChainStart
{
...
/// <inheritdoc />
StructureType IStructuredType.StructureType()
{
return SType = StructureType.DeviceCreateInfo; // Assigns correct value AND returns it
}
...
}

IExtendsChain<TChain>

For some chainable structures, the Vulkan Specification goes further and specifies which chains the structure can be used in. For example, take the VkPhysicalDeviceVariablePointersFeatures structure, which maps to PhysicalDeviceVariablePointersFeatures . It is defined in the specification as:


<type category="struct"
name="VkPhysicalDeviceVariablePointersFeatures"
structextends="VkPhysicalDeviceFeatures2,VkDeviceCreateInfo">
...
</type>

Which, BuildTools converts to:

[NativeName("Name", "VkPhysicalDeviceVariablePointersFeatures")]
[NativeName("Aliases", "VkPhysicalDeviceVariablePointersFeaturesKHR, VkPhysicalDeviceVariablePointerFeaturesKHR, VkPhysicalDeviceVariablePointerFeatures")]
public unsafe partial struct PhysicalDeviceVariablePointersFeatures :
IExtendsChain<PhysicalDeviceFeatures2>,
IExtendsChain<PhysicalDeviceFeatures2KHR>,
IExtendsChain<DeviceCreateInfo>
{ ... }

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:

/// <summary>
/// Marks a <see cref="IChainable">chainable</see> struct indicating which <see cref="IChainStart">chain</see> this type
/// extends.
/// </summary>
/// <typeparam name="TChain">A chain start structure.</typeparam>
public interface IExtendsChain<out TChain> : 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<PhysicalDeviceFeatures2KHR> come from? As the API only lists 2 structures in the structextends attribute? Well a clue can be found in the PhysicalDeviceFeatures2 structure here:

[NativeName("Name", "VkPhysicalDeviceFeatures2")]
[NativeName("Aliases", "VkPhysicalDeviceFeatures2KHR")]
public unsafe partial struct PhysicalDeviceFeatures2 : IChainStart, IExtendsChain<DeviceCreateInfo>
{ ... }

As you can see this indicates that the specification defines an alias for VkPhysicalDeviceFeatures2 called VkPhysicalDeviceFeatures2KHR, which maps to the PhysicalDeviceFeatures2KHR structure found here.

IChainStart

From PhysicalDeviceFeatures2 we can see another new interface called IChainStart, similar to IExtendsChain<TChain>, it also implements IChainable and is defined here:

/// <summary>
/// Marks a <see cref="IChainable">chainable</see> struct as being allowed at the start of a chain.
/// </summary>
/// <remarks><para>Any <see cref="IChainStart"/> will have a corresponding static `BaseInStructure(out var chain)`
/// convenience method.</para></remarks>
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<TChain> types throughout the chain), *Any methods only require IChainable structures throughout.

Class Diagram

The above structures can be visualized below:

Class Diagram