It was quite a bit of boilerplate to setup switching between different feature requirements.
I'm sure everyone has their own preferences on abstraction shape, but here's a little helper struct that handles linking and such so you can simply pre-define collections of
Hope it's useful for someone!
I'm sure everyone has their own preferences on abstraction shape, but here's a little helper struct that handles linking and such so you can simply pre-define collections of
VKPhysicalDeviceFeatures into a VkFeatureChain struct and use where you need it.Hope it's useful for someone!
The Primary Struct
C#
/// <summary>
/// VkFeature structs used to define feature chain queries and device feature sets. Useful for declaring a set
/// of feature profiles. Features are linked through their Next pointers.
/// </summary>
private unsafe struct VkFeatureChain
{
// Every feature struct starts with sType and Next, followed by Bool32 flags
private const int HeaderSize = 16;
public VkPhysicalDeviceFeatures2 Core;
public VkPhysicalDeviceVulkan12Features V12;
public VkPhysicalDeviceVulkan13Features V13;
public VkPhysicalDeviceVulkan14Features V14;
// Constructors are called explicitly so each struct gets its sType
public VkFeatureChain()
{
Core = new();
V12 = new();
V13 = new();
V14 = new();
}
/// <summary>
/// Links the Next pointers in place. The chain must not be copied or moved after linking.
/// </summary>
public void Link()
{
Core.Next = Unsafe.AsPointer(ref V12);
V12.Next = Unsafe.AsPointer(ref V13);
V13.Next = Unsafe.AsPointer(ref V14);
V14.Next = null;
}
/// <summary>
/// Returns true if every flag set in <paramref name="required"/> is also set in this chain.
/// </summary>
public readonly bool Supports(in VkFeatureChain required) =>
IsSubset(required.Core, Core) &&
IsSubset(required.V12, V12) &&
IsSubset(required.V13, V13) &&
IsSubset(required.V14, V14);
/// <summary>
/// Compares the Bool32 flags after the header as raw uints.
/// </summary>
private static bool IsSubset<T>(in T required, in T supported) where T : unmanaged
{
var req = MemoryMarshal.Cast<byte, uint>(MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in required))[HeaderSize..]);
var sup = MemoryMarshal.Cast<byte, uint>(MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in supported))[HeaderSize..]);
for (var i = 0; i < req.Length; i++)
{
if ((req[i] & ~sup[i]) != 0)
return false;
}
return true;
}
}
Usage
C#
/// <summary>
/// Physical Device Feature Requirements
/// </summary>
private static readonly VkFeatureChain RequiredVkFeatures = new()
{
V12 = { TimelineSemaphore = true },
V13 = { DynamicRendering = true, Synchronization2 = true },
};
private static readonly VkFeatureChain VkFeaturesBaseline = new()
{
V12 = { TimelineSemaphore = true },
V13 = { DynamicRendering = true, Synchronization2 = true },
};
private static readonly VkFeatureChain VkFeaturesExtended = new()
{
V13 =
{
Maintenance4 = true,
ShaderDemoteToHelperInvocation = true,
PipelineCreationCacheControl = true,
SubgroupSizeControl = true,
ComputeFullSubgroups = true,
ShaderZeroInitializeWorkgroupMemory = true,
},
V12 = { HostQueryReset = true, SeparateDepthStencilLayouts = true },
};
/// <summary>
/// Create the logical device for the physical device
/// </summary>
private unsafe bool CreateLogicalDevice()
{
// Query the features this physical device supports
var supported = new VkFeatureChain();
supported.Link();
_physicalDevice!.GetFeatures2(ref supported.Core);
if (!supported.Supports(RequiredVkFeatures))
{
Console.WriteLine("Physical device doesn't meet the feature requirements.");
return false;
}
// Grab the features we want to use and link
var enabledFeatures = VkFeaturesBaseline;
enabledFeatures.Link();
/* ------- Your Extension / Queue logic ------- */
// Create the logical device
_logicalDevice = _physicalDevice!.CreateDevice(new Device.CreateInfo
{
Extensions = extensions,
QueueCreateInfos = new ReadOnlySpan<VkDeviceQueueCreateInfo>(in graphicsQueueInfo),
Next = &enabledFeatures.Core,
});
}