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 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 15:51:33 -07:00
parent 7d87503eba
commit 1ba1442cc4
+82 -5
View File
@@ -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 <typename Fn>
static void forEachRequiredFeature(DeviceFeatureChain& chain, Fn&& fn) {
auto& f10 = chain.get<vk::PhysicalDeviceFeatures2>().features;
auto& f11 = chain.get<vk::PhysicalDeviceVulkan11Features>();
auto& f12 = chain.get<vk::PhysicalDeviceVulkan12Features>();
auto& f13 = chain.get<vk::PhysicalDeviceVulkan13Features>();
#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) Device::Device(Window* window)
: headless{window == nullptr}, : headless{window == nullptr},
enabledExtensions{headless ? std::vector<const char*>{} enabledExtensions{headless ? std::vector<const char*>{}
@@ -386,6 +456,10 @@ bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) {
bool extensionsSupported = checkDeviceExtensionSupport(device); bool extensionsSupported = checkDeviceExtensionSupport(device);
if (!checkDeviceFeatureSupport(device)) {
return false;
}
if (this->headless) { if (this->headless) {
return indices.graphicsFamily.has_value() && extensionsSupported; return indices.graphicsFamily.has_value() && extensionsSupported;
} }
@@ -443,15 +517,17 @@ void Device::createLogicalDevice() {
{vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority}); {vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority});
} }
vk::PhysicalDeviceVulkan13Features vulkan13Features{}; DeviceFeatureChain enabledFeatures;
vulkan13Features.dynamicRendering = vk::True; forEachRequiredFeature(enabledFeatures, [](vk::Bool32& feature, const char*) {
feature = vk::True;
});
auto deviceFeatures = vk::PhysicalDeviceFeatures();
auto createInfo = vk::DeviceCreateInfo( auto createInfo = vk::DeviceCreateInfo(
vk::DeviceCreateFlags(), static_cast<uint32_t>(queueCreateInfos.size()), vk::DeviceCreateFlags(), static_cast<uint32_t>(queueCreateInfos.size()),
queueCreateInfos.data()); queueCreateInfos.data());
createInfo.pEnabledFeatures = &deviceFeatures; // Core features are passed through PhysicalDeviceFeatures2 in the pNext
createInfo.pNext = &vulkan13Features; // chain, so pEnabledFeatures must stay null.
createInfo.pNext = &enabledFeatures.get<vk::PhysicalDeviceFeatures2>();
createInfo.enabledExtensionCount = createInfo.enabledExtensionCount =
static_cast<uint32_t>(this->enabledExtensions.size()); static_cast<uint32_t>(this->enabledExtensions.size());
@@ -473,6 +549,7 @@ void Device::createLogicalDevice() {
void Device::createAllocator() { void Device::createAllocator() {
VmaAllocatorCreateInfo createInfo = {}; VmaAllocatorCreateInfo createInfo = {};
createInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
createInfo.vulkanApiVersion = SOPHIA_VULKAN_API_VERSION; createInfo.vulkanApiVersion = SOPHIA_VULKAN_API_VERSION;
createInfo.instance = this->instance.get(); createInfo.instance = this->instance.get();
createInfo.physicalDevice = this->physicalDevice; createInfo.physicalDevice = this->physicalDevice;