From 1ba1442cc4101d4ec754e98c41a7f413c80443be Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Wed, 30 Sep 2026 15:51:33 -0700 Subject: [PATCH] Require GPU-driven device features Enable descriptor indexing (bindless), buffer device address, scalar block layout, indirect draw count, shader draw parameters, timeline semaphores and synchronization2 alongside dynamic rendering. A single feature list drives both physical device selection and device creation. The VMA allocator is created with buffer device address support. Co-Authored-By: Claude Opus 5.5 --- engine/src/vulkan/device.cpp | 87 +++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/engine/src/vulkan/device.cpp b/engine/src/vulkan/device.cpp index fe1fbaa..6b25081 100644 --- a/engine/src/vulkan/device.cpp +++ b/engine/src/vulkan/device.cpp @@ -57,6 +57,76 @@ void destroyDebugUtilsMessengerEXT(VkInstance instance, } } +using DeviceFeatureChain = vk::StructureChain< + vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan11Features, + vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceVulkan13Features>; + +// Single source of truth for the device features Sophia requires. Calls +// fn(featureRef, name) for each one, so the same list drives both the +// suitability check and device creation. +template +static void forEachRequiredFeature(DeviceFeatureChain& chain, Fn&& fn) { + auto& f10 = chain.get().features; + auto& f11 = chain.get(); + auto& f12 = chain.get(); + auto& f13 = chain.get(); + +#define SOPHIA_FEATURE(features, name) fn(features.name, #name) + // Indirect drawing + SOPHIA_FEATURE(f10, multiDrawIndirect); + SOPHIA_FEATURE(f10, drawIndirectFirstInstance); + SOPHIA_FEATURE(f11, shaderDrawParameters); + SOPHIA_FEATURE(f12, drawIndirectCount); + + // Bindless descriptors + SOPHIA_FEATURE(f12, descriptorIndexing); + SOPHIA_FEATURE(f12, runtimeDescriptorArray); + SOPHIA_FEATURE(f12, descriptorBindingPartiallyBound); + SOPHIA_FEATURE(f12, descriptorBindingVariableDescriptorCount); + SOPHIA_FEATURE(f12, descriptorBindingUpdateUnusedWhilePending); + SOPHIA_FEATURE(f12, descriptorBindingSampledImageUpdateAfterBind); + SOPHIA_FEATURE(f12, descriptorBindingStorageImageUpdateAfterBind); + SOPHIA_FEATURE(f12, descriptorBindingStorageBufferUpdateAfterBind); + SOPHIA_FEATURE(f12, shaderSampledImageArrayNonUniformIndexing); + SOPHIA_FEATURE(f12, shaderStorageImageArrayNonUniformIndexing); + SOPHIA_FEATURE(f12, shaderStorageBufferArrayNonUniformIndexing); + + // Buffer pointers + SOPHIA_FEATURE(f12, bufferDeviceAddress); + SOPHIA_FEATURE(f12, scalarBlockLayout); + + // Synchronization + SOPHIA_FEATURE(f12, timelineSemaphore); + SOPHIA_FEATURE(f13, synchronization2); + + // Rendering + SOPHIA_FEATURE(f13, dynamicRendering); +#undef SOPHIA_FEATURE +} + +static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) { + vk::PhysicalDeviceProperties properties = device.getProperties(); + if (properties.apiVersion < SOPHIA_VULKAN_API_VERSION) { + log::verbose("Skipping ", properties.deviceName, + ": Vulkan 1.3 not supported"); + return false; + } + + auto supported = device.getFeatures2< + vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan11Features, + vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceVulkan13Features>(); + + bool allSupported = true; + forEachRequiredFeature(supported, [&](vk::Bool32 has, const char* name) { + if (!has) { + log::verbose("Skipping ", properties.deviceName, + ": missing device feature ", name); + allSupported = false; + } + }); + return allSupported; +} + Device::Device(Window* window) : headless{window == nullptr}, enabledExtensions{headless ? std::vector{} @@ -386,6 +456,10 @@ bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) { bool extensionsSupported = checkDeviceExtensionSupport(device); + if (!checkDeviceFeatureSupport(device)) { + return false; + } + if (this->headless) { return indices.graphicsFamily.has_value() && extensionsSupported; } @@ -443,15 +517,17 @@ void Device::createLogicalDevice() { {vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority}); } - vk::PhysicalDeviceVulkan13Features vulkan13Features{}; - vulkan13Features.dynamicRendering = vk::True; + DeviceFeatureChain enabledFeatures; + forEachRequiredFeature(enabledFeatures, [](vk::Bool32& feature, const char*) { + feature = vk::True; + }); - auto deviceFeatures = vk::PhysicalDeviceFeatures(); auto createInfo = vk::DeviceCreateInfo( vk::DeviceCreateFlags(), static_cast(queueCreateInfos.size()), queueCreateInfos.data()); - createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.pNext = &vulkan13Features; + // Core features are passed through PhysicalDeviceFeatures2 in the pNext + // chain, so pEnabledFeatures must stay null. + createInfo.pNext = &enabledFeatures.get(); createInfo.enabledExtensionCount = static_cast(this->enabledExtensions.size()); @@ -473,6 +549,7 @@ void Device::createLogicalDevice() { void Device::createAllocator() { VmaAllocatorCreateInfo createInfo = {}; + createInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; createInfo.vulkanApiVersion = SOPHIA_VULKAN_API_VERSION; createInfo.instance = this->instance.get(); createInfo.physicalDevice = this->physicalDevice;