[vulkan] Initial implementation on Bindless Buffer/Descriptors (#4251)

Adds a VK_EXT_descriptor_buffer, VK_KHR_device_buffer_address path alongside push descriptors and descriptor sets. The ring is chunked and sized from the device limits, so drivers with a small sampler range (QCOM reports 128 KiB) get more, chunks per frame instead of losing the feature. A pipeline uses one path or the other, never both, since some drivers do not support bufferless push descriptors. The chunk binding is only re-emitted when it changes, and a draw that repeats the previous payload reuses its allocation instead of rewriting it. This whole change will help to reduce the constant use of descriptors on the slower path, improves the "slow implementation" on QCOM drivers (A7xx and older) for push descriptors, improves latency when compiling pipelines. The implementation on indexing descriptors was also refined.

_Special Thanks_

1.- Meowly The Bindless Smol (@Gidoly)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4251
This commit is contained in:
CamilleLaVey
2026-08-05 16:58:43 +02:00
committed by crueter
parent 5e1d5e82dc
commit 49a0ca6d5d
25 changed files with 852 additions and 81 deletions
@@ -506,6 +506,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (is_qualcomm) {
must_emulate_scaled_formats = true;
LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation.");
has_broken_descriptor_aliasing = true;
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken descriptor aliasing.");
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken custom border color.");
RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
@@ -713,6 +715,37 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
// Descriptors feature list
{
auto& descriptor_indexing = features.descriptor_indexing;
descriptor_indexing.shaderInputAttachmentArrayDynamicIndexing = false;
descriptor_indexing.shaderUniformTexelBufferArrayDynamicIndexing = false;
descriptor_indexing.shaderStorageTexelBufferArrayDynamicIndexing = false;
descriptor_indexing.shaderUniformBufferArrayNonUniformIndexing = false;
descriptor_indexing.shaderStorageBufferArrayNonUniformIndexing = false;
descriptor_indexing.shaderInputAttachmentArrayNonUniformIndexing = false;
descriptor_indexing.descriptorBindingUniformBufferUpdateAfterBind = false;
descriptor_indexing.descriptorBindingSampledImageUpdateAfterBind = false;
descriptor_indexing.descriptorBindingStorageImageUpdateAfterBind = false;
descriptor_indexing.descriptorBindingStorageBufferUpdateAfterBind = false;
descriptor_indexing.descriptorBindingUniformTexelBufferUpdateAfterBind = false;
descriptor_indexing.descriptorBindingStorageTexelBufferUpdateAfterBind = false;
descriptor_indexing.descriptorBindingUpdateUnusedWhilePending = false;
descriptor_indexing.descriptorBindingVariableDescriptorCount = false;
descriptor_indexing.runtimeDescriptorArray = false;
}
// VK_EXT_descriptor_buffer requires VK_KHR_buffer_device_address
if (extensions.descriptor_buffer && !features.buffer_device_address.bufferDeviceAddress) {
LOG_WARNING(Render_Vulkan, "Descriptor buffer needs buffer device address, disabling.");
RemoveExtensionFeature(extensions.descriptor_buffer, features.descriptor_buffer,
VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME);
}
if (!extensions.descriptor_buffer) {
RemoveExtensionFeature(extensions.buffer_device_address, features.buffer_device_address,
VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
}
logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions), first_next, dld);
graphics_queue = logical.GetQueue(graphics_family);
@@ -726,6 +759,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (extensions.memory_budget) {
flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
if (extensions.buffer_device_address) {
flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
}
const VmaAllocatorCreateInfo allocator_info{
.flags = flags,
.physicalDevice = physical,
@@ -972,6 +1008,10 @@ bool Device::GetSuitability(bool requires_swapchain) {
CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
if (instance_version < VK_API_VERSION_1_2) {
CHECK_EXTENSION(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
}
#undef LOG_EXTENSION
#undef CHECK_EXTENSION
@@ -1082,6 +1122,11 @@ bool Device::GetSuitability(bool requires_swapchain) {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR;
SetNext(next, properties.push_descriptor);
}
if (extensions.descriptor_buffer) {
properties.descriptor_buffer.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT;
SetNext(next, properties.descriptor_buffer);
}
if (extensions.subgroup_size_control || features.subgroup_size_control.subgroupSizeControl) {
properties.subgroup_size_control.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES;
@@ -1213,6 +1258,11 @@ void Device::RemoveUnsuitableExtensions() {
RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
// VK_EXT_descriptor_buffer
extensions.descriptor_buffer = features.descriptor_buffer.descriptorBuffer;
RemoveExtensionFeatureIfUnsuitable(extensions.descriptor_buffer, features.descriptor_buffer,
VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME);
// VK_EXT_extended_dynamic_state
extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState;
RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state,
+32 -1
View File
@@ -36,6 +36,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, DescriptorIndexing, DESCRIPTOR_INDEXING, descriptor_indexing) \
FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \
FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \
FEATURE(KHR, BufferDeviceAddress, BUFFER_DEVICE_ADDRESS, buffer_device_address) \
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore)
#define FOR_EACH_VK_FEATURE_1_3(FEATURE) \
@@ -55,6 +56,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \
FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \
FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \
FEATURE(EXT, DescriptorBuffer, DESCRIPTOR_BUFFER, descriptor_buffer) \
FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
@@ -175,6 +177,8 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE_NAME(depth_bias_control, depthBiasControl) \
FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \
FEATURE_NAME(depth_bias_control, depthBiasExact) \
FEATURE_NAME(descriptor_indexing, descriptorBindingPartiallyBound) \
FEATURE_NAME(descriptor_indexing, shaderSampledImageArrayNonUniformIndexing) \
FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
FEATURE_NAME(robust_image_access, robustImageAccess) \
@@ -383,7 +387,7 @@ FN_MAX_LIMIT_LIST
/// Returns true if descriptor aliasing is natively supported.
bool IsDescriptorAliasingSupported() const {
return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
return !has_broken_descriptor_aliasing;
}
bool IsSampledImageArrayNonUniformIndexingSupported() const {
@@ -466,6 +470,26 @@ FN_MAX_LIMIT_LIST
return properties.push_descriptor.maxPushDescriptors;
}
/// Returns true if robust buffer access is enabled on the device.
bool IsRobustBufferAccessEnabled() const {
return features.features.robustBufferAccess == VK_TRUE;
}
/// Returns true if the device supports descriptor buffers.
bool IsExtDescriptorBufferSupported() const {
return extensions.descriptor_buffer;
}
/// Returns the descriptor buffer properties of the device.
const VkPhysicalDeviceDescriptorBufferPropertiesEXT& DescriptorBufferProperties() const {
return properties.descriptor_buffer;
}
/// Returns true if the device supports buffer device address.
bool IsBufferDeviceAddressSupported() const {
return extensions.buffer_device_address;
}
/// Returns true if formatless image load is supported.
bool IsFormatlessImageLoadSupported() const {
return features.features.shaderStorageImageReadWithoutFormat;
@@ -818,6 +842,11 @@ FN_MAX_LIMIT_LIST
return extensions.astc_decode_mode;
}
/// Returns true if descriptor bindings is partially bound.
bool IsDescriptorBindingPartiallyBoundSupported() const {
return features.descriptor_indexing.descriptorBindingPartiallyBound;
}
bool HasTimelineSemaphore() const;
/// Returns true if the device supports VK_KHR_synchronization2.
@@ -1109,6 +1138,7 @@ private:
VkPhysicalDeviceSubgroupProperties subgroup_properties{};
VkPhysicalDeviceFloatControlsProperties float_controls{};
VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor{};
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{};
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
@@ -1133,6 +1163,7 @@ private:
bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
bool has_broken_compute{}; ///< Compute shaders can cause crashes
bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
bool has_broken_descriptor_aliasing{}; ///< Miscompiles descriptors aliased on one binding
bool has_broken_parallel_compiling{}; ///< Has broken parallel shader compiling.
bool has_renderdoc{}; ///< Has RenderDoc attached
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
@@ -237,6 +237,12 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkUnmapMemory);
X(vkUpdateDescriptorSetWithTemplate);
X(vkUpdateDescriptorSets);
X(vkGetBufferDeviceAddress);
X(vkGetDescriptorSetLayoutSizeEXT);
X(vkGetDescriptorSetLayoutBindingOffsetEXT);
X(vkGetDescriptorEXT);
X(vkCmdBindDescriptorBuffersEXT);
X(vkCmdSetDescriptorBufferOffsetsEXT);
X(vkWaitForFences);
X(vkWaitSemaphores);
@@ -352,6 +352,12 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkSetDebugUtilsObjectTagEXT vkSetDebugUtilsObjectTagEXT{};
PFN_vkUnmapMemory vkUnmapMemory{};
PFN_vkUpdateDescriptorSetWithTemplate vkUpdateDescriptorSetWithTemplate{};
PFN_vkGetBufferDeviceAddress vkGetBufferDeviceAddress{};
PFN_vkGetDescriptorSetLayoutSizeEXT vkGetDescriptorSetLayoutSizeEXT{};
PFN_vkGetDescriptorSetLayoutBindingOffsetEXT vkGetDescriptorSetLayoutBindingOffsetEXT{};
PFN_vkGetDescriptorEXT vkGetDescriptorEXT{};
PFN_vkCmdBindDescriptorBuffersEXT vkCmdBindDescriptorBuffersEXT{};
PFN_vkCmdSetDescriptorBufferOffsetsEXT vkCmdSetDescriptorBufferOffsetsEXT{};
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets{};
PFN_vkWaitForFences vkWaitForFences{};
PFN_vkWaitSemaphores vkWaitSemaphores{};
@@ -793,6 +799,11 @@ public:
return !mapped.empty();
}
/// Returns true if host writes are visible to the device.
bool IsHostCoherent() const noexcept {
return is_coherent;
}
void Flush() const;
void Invalidate() const;
@@ -1087,6 +1098,34 @@ public:
dld->vkUpdateDescriptorSetWithTemplate(handle, set, update_template, data);
}
[[nodiscard]] VkDeviceAddress GetBufferDeviceAddress(VkBuffer buffer) const noexcept {
const VkBufferDeviceAddressInfo info{
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
.pNext = nullptr,
.buffer = buffer,
};
return dld->vkGetBufferDeviceAddress(handle, &info);
}
[[nodiscard]] VkDeviceSize GetDescriptorSetLayoutSizeEXT(
VkDescriptorSetLayout layout) const noexcept {
VkDeviceSize size{};
dld->vkGetDescriptorSetLayoutSizeEXT(handle, layout, &size);
return size;
}
[[nodiscard]] VkDeviceSize GetDescriptorSetLayoutBindingOffsetEXT(
VkDescriptorSetLayout layout, u32 binding) const noexcept {
VkDeviceSize offset{};
dld->vkGetDescriptorSetLayoutBindingOffsetEXT(handle, layout, binding, &offset);
return offset;
}
void GetDescriptorEXT(const VkDescriptorGetInfoEXT& info, size_t size,
void* descriptor) const noexcept {
dld->vkGetDescriptorEXT(handle, &info, size, descriptor);
}
VkResult AcquireNextImageKHR(VkSwapchainKHR swapchain, u64 timeout, VkSemaphore semaphore,
VkFence fence, u32* image_index) const noexcept {
return dld->vkAcquireNextImageKHR(handle, swapchain, timeout, semaphore, fence,
@@ -1400,6 +1439,18 @@ public:
PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, {}, image_barrier);
}
void BindDescriptorBuffersEXT(Span<VkDescriptorBufferBindingInfoEXT> bindings) const noexcept {
dld->vkCmdBindDescriptorBuffersEXT(handle, bindings.size(), bindings.data());
}
void SetDescriptorBufferOffsetsEXT(VkPipelineBindPoint bind_point, VkPipelineLayout layout,
u32 first_set, Span<u32> buffer_indices,
Span<VkDeviceSize> offsets) const noexcept {
dld->vkCmdSetDescriptorBufferOffsetsEXT(handle, bind_point, layout, first_set,
buffer_indices.size(), buffer_indices.data(),
offsets.data());
}
void CopyBufferToImage(VkBuffer src_buffer, VkImage dst_image, VkImageLayout dst_image_layout,
Span<VkBufferImageCopy> regions) const noexcept {
dld->vkCmdCopyBufferToImage(handle, src_buffer, dst_image, dst_image_layout, regions.size(),