Skip to content

Commit

Permalink
Vulkan Explicit layer extensions + Fix SPIRV-Cross native libraries (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Beyley authored Jul 10, 2023
1 parent 022236a commit 7c657c9
Show file tree
Hide file tree
Showing 13 changed files with 78 additions and 52 deletions.
6 changes: 6 additions & 0 deletions build/nuke/Native/SPIRVCross.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ pub fn build(b: *std.Build) void {
//Export the C API symbols
lib.defineCMacro(""SPVC_EXPORT_SYMBOLS"", ""1"");
//On windows, we need to specify `__declspec(dllexport)` ourselves
//else SPIRV-Cross knows this is a GNU toolchain and uses the wrong attribute in this case
if (target.isWindows()) {
lib.defineCMacro(""SPVC_PUBLIC_API"", ""__declspec(dllexport)"");
}
//If we arent in debug, defined NDEBUG
if (mode != .Debug)
lib.defineCMacro(""NDEBUG"", ""1"");
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
124 changes: 72 additions & 52 deletions src/Vulkan/Silk.NET.Vulkan/Vk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Silk.NET.Core;
using Silk.NET.Core.Attributes;
Expand Down Expand Up @@ -107,14 +108,15 @@ protected override void PostInit()
/// </summary>
/// <param name="instance">The instance to load the extension from.</param>
/// <param name="ext">The loaded instance extension, or null if load failed.</param>
/// <param name="layer">The instance layer name.</param>
/// <typeparam name="T">The instance extension to load.</typeparam>
/// <remarks>
/// This function doesn't check that the extension is enabled - you will get an error later on if you attempt
/// to call an extension function from an extension that isn't loaded.
/// </remarks>
/// <returns>Whether the extension is available and loaded.</returns>
public bool TryGetInstanceExtension<T>(Instance instance, out T ext) where T : NativeExtension<Vk> =>
!((ext = IsInstanceExtensionPresent(ExtensionAttribute.GetExtensionAttribute(typeof(T)).Name)
public bool TryGetInstanceExtension<T>(Instance instance, out T ext, string layer = null) where T : NativeExtension<Vk> =>
!((ext = IsInstanceExtensionPresent(ExtensionAttribute.GetExtensionAttribute(typeof(T)).Name, layer)
? (T) Activator.CreateInstance
(typeof(T), new LamdaNativeContext(x => GetInstanceProcAddr(instance, x)))
: null) is null);
Expand All @@ -125,15 +127,16 @@ public bool TryGetInstanceExtension<T>(Instance instance, out T ext) where T : N
/// <param name="instance">The instance to load the extension from.</param>
/// <param name="device">The device to load the extension from.</param>
/// <param name="ext">The loaded device extension, or null if load failed.</param>
/// <param name="layer">The instance layer name.</param>
/// <typeparam name="T">The device extension to load.</typeparam>
/// <remarks>
/// This function doesn't check that the extension is enabled - you will get an error later on if you attempt
/// to call an extension function from an extension that isn't loaded.
/// </remarks>
/// <returns>Whether the extension is available and loaded.</returns>
public bool TryGetDeviceExtension<T>
(Instance instance, Device device, out T ext) where T : NativeExtension<Vk> =>
!((ext = IsDeviceExtensionPresent(instance, ExtensionAttribute.GetExtensionAttribute(typeof(T)).Name)
(Instance instance, Device device, out T ext, string layer = null) where T : NativeExtension<Vk> =>
!((ext = IsDeviceExtensionPresent(instance, ExtensionAttribute.GetExtensionAttribute(typeof(T)).Name, layer)
? (T) Activator.CreateInstance
(typeof(T), new LamdaNativeContext(x => GetDeviceProcAddr(device, x)))
: null) is null);
Expand All @@ -142,10 +145,10 @@ public bool TryGetDeviceExtension<T>
[Obsolete("Use IsInstanceExtensionPresent instead.", true)]
public override bool IsExtensionPresent(string extension) => IsInstanceExtensionPresent(extension);

private HashSet<string> _cachedInstanceExtensions;
private HashSet<(string extension, string layer)> _cachedInstanceExtensions;

// Contains strings in form of '<IntPtr>|<Extension name>' for each extension and '<IntPtr>' to indicate an extension has been loaded.
private HashSet<string> _cachedDeviceExtensions = new HashSet<string>();
private Dictionary<PhysicalDevice, HashSet<string>> _cachedDeviceExtensions = new Dictionary<PhysicalDevice, HashSet<string>>();
private ReaderWriterLockSlim _cachedDeviceExtensionsLock = new ReaderWriterLockSlim();

private ConcurrentDictionary<Instance, PhysicalDevice[]> _cachedPhyscialDevices = new ConcurrentDictionary<Instance, PhysicalDevice[]>();
Expand All @@ -154,59 +157,65 @@ public bool TryGetDeviceExtension<T>
/// Checks whether the given instance extension is available.
/// </summary>
/// <param name="extension">The instance extension name.</param>
/// <param name="layer">The instance layer name.</param>
/// <returns>Whether the instance extension is available.</returns>
public unsafe bool IsInstanceExtensionPresent(string extension)
public unsafe bool IsInstanceExtensionPresent(string extension, string layer = null)
{
// Note we use optimistic code to avoid locks, if this is called on multiple threads
// then multiple initialisations can happen, only one thread will succeed in initialising the cache though.
var cachedInstanceExtensions = _cachedInstanceExtensions;
if (cachedInstanceExtensions is null)
{
// Get count of properties
var instanceExtPropertiesCount = 0u;
EnumerateInstanceExtensionProperties((byte*) 0, &instanceExtPropertiesCount, null);
fixed (byte* layerPtr = layer is null ? Array.Empty<byte>() : Encoding.UTF8.GetBytes(layer)) {
// Get count of properties
var instanceExtPropertiesCount = 0u;
EnumerateInstanceExtensionProperties(layerPtr, &instanceExtPropertiesCount, null);

// Initialise return structure
using var mem = GlobalMemory.Allocate((int) instanceExtPropertiesCount * sizeof(ExtensionProperties));
var props = (ExtensionProperties*) Unsafe.AsPointer(ref mem.GetPinnableReference());
// Initialise return structure
var propsArr = new ExtensionProperties[(int)instanceExtPropertiesCount];

// Get properties
EnumerateInstanceExtensionProperties((byte*) 0, &instanceExtPropertiesCount, props);
fixed(ExtensionProperties* props = propsArr) {
// Get properties
EnumerateInstanceExtensionProperties(layerPtr, &instanceExtPropertiesCount, props);

cachedInstanceExtensions = new HashSet<string>();
for (var p = 0; p < instanceExtPropertiesCount; p++)
{
cachedInstanceExtensions.Add(Marshal.PtrToStringAnsi((nint) props[p].ExtensionName));
}
cachedInstanceExtensions = new HashSet<(string, string)>();
for (var p = 0; p < instanceExtPropertiesCount; p++)
{
cachedInstanceExtensions.Add((Marshal.PtrToStringAnsi((nint) props[p].ExtensionName), layer));
}

// Thread-safe, only one initialisation will actually succeed.
cachedInstanceExtensions = Interlocked.CompareExchange(ref _cachedInstanceExtensions, cachedInstanceExtensions, null) ??
cachedInstanceExtensions;
// Thread-safe, only one initialisation will actually succeed.
cachedInstanceExtensions = Interlocked.CompareExchange(ref _cachedInstanceExtensions, cachedInstanceExtensions, null) ??
cachedInstanceExtensions;
}
}
}

return cachedInstanceExtensions.Contains(extension);
return cachedInstanceExtensions.Contains((extension, layer));
}

/// <summary>
/// Checks whether the given device extension is available on any physical devices.
/// </summary>
/// <param name="instance">The Vulkan instance.</param>
/// <param name="extension">The extension to check for.</param>
/// <param name="layer">The layer to check for the extension in.</param>
/// <returns>Whether the device extension is available.</returns>
public bool IsDeviceExtensionPresent(Instance instance, string extension)
=> IsDeviceExtensionPresent(instance, extension, out _);
public bool IsDeviceExtensionPresent(Instance instance, string extension, string layer = null)
=> IsDeviceExtensionPresent(instance, extension, out _, layer);

/// <summary>
/// Checks whether the given device extension is available on the given physical device.
/// </summary>
/// <param name="device">The physical device.</param>
/// <param name="extension">The extension to check for.</param>
/// <param name="layer">The layer to check for the extension in.</param>
/// <returns>Whether the device extension is available.</returns>
public unsafe bool IsDeviceExtensionPresent(PhysicalDevice device, string extension)
public unsafe bool IsDeviceExtensionPresent(PhysicalDevice device, string extension, string layer = null)
{
var prefix = device.Handle.ToString();
var prefix_sep = prefix + '|';
var fullKey = prefix_sep + extension;
var prefixSep = prefix + '|';
var fullKey = prefixSep + extension + '|' + layer;
var result = false;

// We place a devices handle into the hashset to indicate it has been previously loaded.
Expand All @@ -221,13 +230,20 @@ public unsafe bool IsDeviceExtensionPresent(PhysicalDevice device, string extens
// no real additional cost.
_cachedDeviceExtensionsLock.EnterUpgradeableReadLock();

//If we do not have a cache for this specific device,
if(!_cachedDeviceExtensions.ContainsKey(device))
{
//Create one
_cachedDeviceExtensions[device] = new HashSet<string>();
}

// We check for the extension first to avoid 2 lookups
if (_cachedDeviceExtensions.Contains(fullKey))
if (_cachedDeviceExtensions[device].Contains(fullKey))
{
// We found the extension
result = true;
}
else if (!_cachedDeviceExtensions.Contains(prefix))
else if (!_cachedDeviceExtensions[device].Contains(prefix))
{
// The lack of the device handle indicates we've not been previously initialised. We now need a write lock.
_cachedDeviceExtensionsLock.EnterWriteLock();
Expand All @@ -236,25 +252,28 @@ public unsafe bool IsDeviceExtensionPresent(PhysicalDevice device, string extens
{
var deviceExtPropertiesCount = 0u;

// Get number of properties
EnumerateDeviceExtensionProperties(device, (byte*) 0, &deviceExtPropertiesCount, null);

// Initialise return structure
mem = GlobalMemory.Allocate((int) deviceExtPropertiesCount * sizeof(ExtensionProperties));
var props = (ExtensionProperties*) Unsafe.AsPointer(ref mem.GetPinnableReference());

// Get properties
EnumerateDeviceExtensionProperties(device, (byte*) 0, &deviceExtPropertiesCount, props);
for (int j = 0; j < deviceExtPropertiesCount; j++)
{
// Prefix the extension name
var newKey = prefix_sep + Marshal.PtrToStringAnsi((nint) props[j].ExtensionName);
_cachedDeviceExtensions.Add(newKey);
if (!result && string.Equals(newKey, fullKey))
{
// We found the extension (no need to do another lookup as we're scanning anyway)
// As such this has taken 2 lookups + initialisation scan.
result = true;
fixed (byte* layerPtr = layer is null ? Array.Empty<byte>() : Encoding.UTF8.GetBytes(layer)) {
// Get number of properties
EnumerateDeviceExtensionProperties(device, (byte*) layerPtr, &deviceExtPropertiesCount, null);

// Initialise return structure
var propsArr = new ExtensionProperties[(int)deviceExtPropertiesCount];

fixed(ExtensionProperties* props = propsArr) {
// Get properties
EnumerateDeviceExtensionProperties(device, (byte*) layerPtr, &deviceExtPropertiesCount, props);
for (int j = 0; j < deviceExtPropertiesCount; j++)
{
// Prefix the extension name
var newKey = prefixSep + Marshal.PtrToStringAnsi((nint) props[j].ExtensionName);
_cachedDeviceExtensions[device].Add(newKey);
if (!result && string.Equals(newKey, fullKey))
{
// We found the extension (no need to do another lookup as we're scanning anyway)
// As such this has taken 2 lookups + initialisation scan.
result = true;
}
}
}
}
}
Expand All @@ -276,10 +295,11 @@ public unsafe bool IsDeviceExtensionPresent(PhysicalDevice device, string extens
/// <param name="instance">The Vulkan instance to use.</param>
/// <param name="extension">The extension to check for.</param>
/// <param name="device">The first physical device that provides the extension.</param>
/// <param name="layer">The layer to check for the extension in.</param>
/// <returns>Whether the device extension is available.</returns>
public unsafe bool IsDeviceExtensionPresent(Instance instance, string extension, out PhysicalDevice device)
public unsafe bool IsDeviceExtensionPresent(Instance instance, string extension, out PhysicalDevice device, string layer = null)
{
device = GetPhysicalDevices(instance).FirstOrDefault(pd => IsDeviceExtensionPresent(pd, extension));
device = GetPhysicalDevices(instance).FirstOrDefault(pd => IsDeviceExtensionPresent(pd, extension, layer));
return device.Handle != 0;
}

Expand Down

0 comments on commit 7c657c9

Please sign in to comment.