Skip to content

Descriptor Binding Interface

manas-kulkarni edited this page Feb 27, 2024 · 3 revisions

Overview

Descriptor binding is handled through the DescriptorSet interface. Descriptor set represents a bunch of VkDescriptorSet (Vulkan), D3D12_GPU_DESCRIPTOR_HANDLE (D3D12), or MTLArgumentBuffer (Metal) There are three ways to bind descriptors:

  • Descriptor sets
  • Push constants
    • To specify a shader resource as a push constant, the word "rootconstant" (case insensitive) should be somewhere in the shader resource name
    • Example:
    • Push constants are useful for specifying per draw data such as a draw id
    • To bind push constants, use the cmdBindPushConstants function
  • Root descriptors / Dynamic uniform buffers
    • To specify a shader resource as a root descriptor / dynamic uniform buffer, the word "rootcbv" (case insensitive) should be somewhere in the shader resource name
    • Example:
    • Root descriptors enable us to bind different offsets of the same constant buffer/uniform buffer to the shader without having to call vkUpdateDescriptorSet / CopyDescriptorsSimple
    • They are also generally faster than regular uniform buffers since they get user data placement
    • Use cmdBindDescriptorSetWithRootCbvs to update and bind the new offsets of the root descriptors specified as arguments to the function

Specifying Update Frequencies for descriptors in the shader

  • One of the most important ways for the user to optimize performance for graphics is grouping the descriptors based on the frequency at which they are updated. In Forge, there are 4 update frequencies
    • UPDATE_FREQ_NONE - Descriptor gets hardly updated in the lifetime of the application
    • UPDATE_FREQ_PER_FRAME - Descriptor gets updated roughly once per frame
    • UPDATE_FREQ_PER_BATCH - Descriptor gets updated per arbitrary batch (batch is application dependent. commonly this is per material)
    • UPDATE_FREQ_PER_DRAW - Descriptor gets updated per draw call. (information which changes per draw call, ... usually with modern APIs, it is better to avoid this update frequency by using the bindless model and using push constants instead)
  • To specify the update frequency of a descriptor inside the shader code, use one of the values from above point (UPDATE_FREQ_NONE, UPDATE_FREQ_PER_FRAME, ...) as the register space in HLSL or the set layout modifier in GLSL or the argument buffer index in MSL.
  • FSL Example
CBUFFER(gMaterialCBuffer, UPDATE_FREQ_PER_BATCH, b0, binding = 0)
RES(Tex2D(float4), gEnvironmentMap, UPDATE_FREQ_NONE, t0, binding = 0);
RES(ByteBuffer, gLightingTiles, UPDATE_FREQ_PER_FRAME, t0, binding = 0);
  • HLSL Example
ConstantBuffer<MaterialUniforms> gMaterialCBuffer : register(b0, UPDATE_FREQ_PER_BATCH);
Texture2D gEnvironmentMap : register(t0, UPDATE_FREQ_NONE);
ByteAddressBuffer gLightingTiles : register(t0, UPDATE_FREQ_PER_FRAME);
  • GLSL Example
layout(UPDATE_FREQ_PER_BATCH, binding = 0) uniform gMaterialCBuffer
{
    uniform MaterialUniforms gMaterialCBufferData;
};
layout(UPDATE_FREQ_NONE, binding = 0) uniform texture2D gEnvironmentMap;
layout(UPDATE_FREQ_PER_FRAME, binding = 0) buffer gLightingTiles
{
    uint gLightingTilesData[];
};
  • MSL Example
struct ArgDataPerBatch
{
    constant MaterialUniforms& mMaterialCBuffer [[id(0)]];
    // Add more per batch data here
};

struct ArgDataStatic
{
    texture2d<float> mEnvironmentMap; // Id is optional
    // Add more static data here
};

struct ArgDataPerFrame
{
    device uint* mLightingTiles;
};

fragment stageMain(
    constant ArgDataStatic& gArgBufferStatic [[buffer(UPDATE_FREQ_NONE)]];
    constant ArgDataPerFrame& gArgBufferPerFrame [[buffer(UPDATE_FREQ_PER_FRAME)]],
    constant ArgDataPerBatch& gArgBufferPerBatch [[buffer(UPDATE_FREQ_PER_BATCH)]],
    // Root constants bound directly without the need of an argument buffer
    constant uint& gDrawIdRootConstant [[buffer(UPDATE_FREQ_USER)]]
)

Interface

void addDescriptorSet(Renderer* pRenderer, const DescriptorSetDesc* pDesc, DescriptorSet** ppDescriptorSet)

  • Allocates a descriptor set with the given description
  • Sets all the descriptor handles to their default values (default texture, default buffer, ...)

void removeDescriptorSet(Renderer* pRenderer, DescriptorSet* pDescriptorSet)

  • Frees the descriptor set

void updateDescriptorSet(Renderer* pRenderer, uint32_t index, DescriptorSet* pDescriptorSet, uint32_t paramCount, DescriptorData* pParams)

  • Updates the descriptor set at index with the provided array of descriptor data
  • Safe to call from any thread as long as the the threads are accessing different index

void cmdBindDescriptorSet(Cmd* pCmd, uint32_t index, DescriptorSet* pDescriptorSet)

  • Binds the descriptor set at index to the command buffer
  • Safe to call from any thread

void cmdBindDescriptorSetWithRootCbvs(Cmd* pCmd, uint32_t index, DescriptorSet* pDescriptorSet, uint32_t count, const DescriptorData* pParams)

  • cmdBindDescriptorSet plus updates and binds the root descriptors (pParams) at new offsets specified through DescriptorData::pOffs

void cmdBindPushConstants(Cmd* pCmd, RootSignature* pRootSignature, uint32_t paramIndex, void* pData)

  • Binds a push constant / root constant at the input index in pRootSignature to the command buffer
  • Use getDescriptorIndexFromName to get the index
  • Safe to call from any thread

Structs

DescriptorSetDesc

  • RootSignature* pRootSignature
    • Root signature to use to create the descriptor set
  • DescriptorUpdateFrequency mUpdateFrequency;
    • Update frequency of the descriptor set (set index in Vulkan GLSL, space in HLSL, argument buffer index in MSL)
  • uint32_t mMaxSets
    • Number of descriptor sets to allocate
  • uint32_t mNodeIndex
    • The GPU node index to use to allocate the descriptor sets (only applicable in multi GPU scenarios. leave it as 0 for default GPU)

DescriptorDataRange

  • Used to bind a range of a buffer
  • uint32_t mOffset
    • Offset of the buffer descriptor. Example: VkDescriptorBufferInfo::offset
  • uint32_t mSize
    • Size of the buffer descriptor. Example: VkDescriptorBufferInfo::range
  • uint32_t mStructStride
    • Specify different structured buffer stride (ignored for raw buffer - ByteAddressBuffer)

DescriptorData

  • const char* pName
    • Name of the descriptor. Can be left NULL if mBindByIndex is true (mIndex must be valid)
  • uint32_t mCount
    • Number of array entries to update (array size of ppTextures/ppBuffers/...)
  • uint32_t mArrayOffset
    • Dst offset into the array descriptor (useful for updating few entries in a large array)
    • Example: to update 6th entry in a array of texture descriptor, mArrayOffset will be 6 and mCount will be 1)
  • uint32_t mIndex
    • Index in pRootSignature->pDescriptors array - Cache index using getDescriptorIndexFromName to avoid using string checks at runtime
  • uint32_t mBindByIndex
    • Specifies whether to use pName or mIndex as look up input for the descriptor
  • uint32_t mUavMipSlice
    • When binding UAV, control the mip slice to to bind for UAV (example - generating mipmaps in a compute shader)
  • bool mBindStencilResource
    • Binds stencil only descriptor instead of color/depth
  • bool mBindMipChain
    • Binds entire mip chain as array of UAV
  • bool mBindICB
    • Bind MTLIndirectCommandBuffer along with the MTLBuffer (Metal only)
  • const char* pICBName
    • Name of the ICB descriptor. Can be left NULL if mICBIndex is valid
  • uint32_t mICBIndex
    • Index in pRootSignature->pDescriptors array - Cache index using getDescriptorIndexFromName to avoid using string checks at runtime
  • Texture** ppTextures
    • Array of texture descriptors (srv and uav textures)
  • Sampler** ppSamplers
    • Array of sampler descriptors
  • Buffer** ppBuffers
    • Array of buffer descriptors (srv, uav and cbv buffers)
  • AccelerationStructure** ppAccelerationStructures
    • Array of acceleration structure descriptors (raytracing)