This tutorial shows how to implement bindless resources, a technique that leverages dynamic shader resource indexing feature enabled by the next-gen APIs to significantly improve rendering performance.
In old APIs (Direct3D11, OpenGL/GLES), when an application needed to render multiple objects using different shader resources, it had to run the following loop:
There are multiple rechinques to make this loop more efficient such as instancing (see Tutorial 4), texture arrays (see Tutorial 5), etc. All these methods, however, are very limited.
Next-generation APIs (Direct3D12, Vulkan, Metal) enable a more efficient way: instead of binding new resources every time next object is rendered, all required resources can be bound once, while shaders can dynamically access required resources using the draw call information.
This tutorial is based on Tutorial 5. However, while original tutorial used a texture array object that required all array slices to be identical (same size, format, number of mip levels, etc.), this tutorial binds textures as an array of shader resources. Unlike texture array object, all resources in an array of resources may have completely different parameters. The pixel shader is able to dynamically select the texture to sample using the texture index it receives from the vertex shader (that reads it from the instance data buffer):
Notice the NonUniformResourceIndex
pseudo-function. It tells the shader compiler that the texture index is not uniform, e.g. it may be different in every pixel. Using it is essential, or the shader may behave incorrectly.
There are no differences in pipeline state initialization for bindless shaders. Shader resource binding objects are also initialized the same way, with the only difference that we bind an array of objects using SetArray
method:
As we discussed earlier, to render all objects, we bind all resources once:
And then render each object using its geometry properties. Texture index will be fetched from the instance data buffer and passed over to the pixel shader.
Notice that we use DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT
flag. This flag informs the engine that none of the dynamic buffers have been modified since the last draw command, which saves extra work the engine would have to perform otherwise.