Descriptor Indexing with Bindless Rendering¶
This example shows how to use descriptor indexing (also called "bindless rendering") to access arrays of resources using non-uniform indices in shaders. Traditional Vulkan requires binding specific descriptors before each draw call; descriptor indexing allows binding large arrays once and indexing into them dynamically in shaders. This dramatically reduces CPU overhead and enables efficient material systems, texture atlases, and data-driven rendering.
The example uses the KDGpuExample helper API for simplified setup.
Overview¶
What this example demonstrates:
- Enabling VK_EXT_descriptor_indexing extension and required features
- Creating descriptor sets with large arrays of uniform buffers
- Using
nonuniformEXTqualifier in shaders for dynamic indexing - Drawing multiple objects with different materials/transforms
- Variable-length descriptor arrays sized at runtime
Use cases:
- Material systems (hundreds/thousands of materials in one array)
- Texture streaming and mega-textures
- GPU-driven rendering (indirect draws selecting resources)
- Bindless vertex/index buffers
- Efficient multi-material rendering
Vulkan Requirements¶
- Vulkan Version: 1.2+ (descriptor indexing promoted to core)
- Extensions: VK_EXT_descriptor_indexing (core in 1.2)
- Features:
shaderUniformBufferArrayNonUniformIndexingruntimeDescriptorArraydescriptorBindingVariableDescriptorCountdescriptorBindingPartiallyBound(optional but recommended)
- Shader: SPIR-V 1.3+ or GLSL 450+ with
GL_EXT_nonuniform_qualifier
Key Concepts¶
Traditional Descriptor Binding:
1 2 3 4 5 | |
Every material requires a descriptor set bind, which has CPU cost.
Descriptor Indexing / Bindless:
1 2 3 4 5 6 7 8 | |
Shader:
1 2 3 4 5 6 7 8 9 | |
Benefits:
- Massively reduced bind calls
- GPU-driven resource selection
- Simplified render loop
Spec: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html
NonUniform Indexing:
The nonuniformEXT qualifier tells the compiler that the index can vary between shader invocations (non-uniform control flow). Without this:
- Index must be compile-time constant, or
- Index must be uniform across all invocations in a subgroup
With nonuniformEXT:
- Each triangle/pixel can use different index
- Enables material-per-object, texture-per-pixel selection
- May have small performance cost on some hardware
Variable-Length Arrays:
Traditional Vulkan requires compile-time array sizes. Descriptor indexing allows:
1 2 3 | |
The size is determined at runtime by descriptorCount in VkDescriptorSetLayoutBinding.
Implementation¶
Allocating Descriptor Array Buffers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Filename: bindgroup_indexing/bindgroup_indexing.cpp
This example creates an array of uniform buffers, each holding a rotation matrix. The shader will index into this array.
Storage Buffer for Frame Counter:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Filename: bindgroup_indexing/bindgroup_indexing.cpp
A storage buffer (SSBO) tracks frame count, which the vertex shader uses to compute rotation angles.
Descriptor Set Layout with Variable Array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
Filename: bindgroup_indexing/bindgroup_indexing.cpp
Key configuration:
count = TransformsCount: Array of N buffersVariableBindGroupEntriesCountBit: Variable-length array in shader- Shader can access as
uniform Transforms {} transforms[]
Creating Descriptor Array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Filename: bindgroup_indexing/bindgroup_indexing.cpp
The descriptor set binds all array elements at once.
Storage Buffer Bind Group:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Filename: bindgroup_indexing/bindgroup_indexing.cpp
Separate bind group for the frame counter buffer.
Rendering with Descriptor Arrays:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Filename: bindgroup_indexing/bindgroup_indexing.cpp
Bind pipeline, vertex buffer, and both descriptor sets. Then draw - the shader dynamically selects which transform to use based on computed angle.
When it comes to our shader, we do a few things. We start by defining the extension:
1 | |
Then we declare our bind groups and push constant blocks.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
And we can finally perform a non uniform indexing into the Transform bind group.
1 2 3 4 5 6 7 8 9 10 | |
Performance Notes¶
Benefits:
- CPU: 10-100Ă— reduction in descriptor set binds (major bottleneck in complex scenes)
- CPU: Simplified render loop (bind once, draw many)
- Memory: No need to duplicate descriptors per-object
Costs:
- GPU: NonUniform indexing may reduce SIMD efficiency (divergent execution)
- Memory: All descriptors in array must be valid or use
descriptorBindingPartiallyBound - Cache: Scattered resource access may reduce cache hit rate
Best Practices:
- Group draws by similar resource usage to improve cache coherency
- Use partially bound arrays to avoid allocating unused descriptors
- Profile: gains depend on CPU/GPU balance (CPU-bound benefits most)
- Consider indirect draws for fully GPU-driven rendering
Hardware Considerations:
- NVIDIA: Excellent support, minimal overhead
- AMD: Good support, watch for subgroup divergence
- Mobile: Varies; check vendor documentation
- Intel: Good in recent GPUs
See Also¶
- Partially Bound Descriptors - Using partially bound descriptor arrays
- Buffer Device Address (GPU Pointers) - GPU pointers (VK_KHR_buffer_device_address)
- Dynamic Uniform Buffer - Dynamic uniform buffer offsets (simpler alternative)
- VK_EXT_descriptor_indexing - Official specification
- GL_EXT_nonuniform_qualifier - GLSL extension spec
Further Reading¶
- GPU-Driven Rendering - Modern rendering techniques
- Descriptor Indexing Best Practices - Vulkan guide
Updated on 2026-03-31 at 00:02:07 +0000