Sponsored By

Shader resource binding model in Diligent Engine 2.0

This post describes a new shader resource binding model implemented in Diligent Engine 2.0 to take advantages of Direct3D12.

Egor Yusov, Blogger

March 24, 2016

5 Min Read
Game Developer logo in a gray background | Game Developer

To take advantages of Direct3D12, Diligent Engine 2.0 implements a new binding model. The model is based on managing descriptor heaps and descriptor tables, new concepts introduced by Direct3D12, to allow application to change resources efficiently. To better understand the the new model, let’s take a look at resource binding in Direct3D12.

Overview of shader resource binding in Direct3D12

One of the main design goals of the D3D12 resource binding model is to enable apps to change resources frequently without paying significant CPU cost. In D3D11, shader resources were bound directly to the shader registers using methods such as PSSetShaderResources() , PSSetConstantBuffers() , PSSetSamplers() , and CSSetUnorderedAccessViews():


pCtx->PSSetShaderResources(0, NumSRVs, ppSRVs);
pCtx->PSSetConstantBuffers(0, NumBuffers, ppCBs);
pCtx->PSSetSamplers(0, NumSamplers, ppSamplers);
pCtx->CSSetUnorderedAccessViews(0, NumUAVs, ppUAVs, nullptr);

This model does not take into account frame-to-frame coherency usually found in typical GPU command sequences. If some draw command in frame N uses say 8 textures and 2 constant buffers, it is most likely that in the next frame that the same draw command will use the same textures and buffers. It would be efficient to cache all the bindings so that all the resources are set at once rather than individually. D3D12 solves this problem by referencing all resources through descriptors stored in descriptor heaps. A descriptor is a relatively small block of data that fully describes an object to the GPU, in a GPU specific opaque format. Descriptor heap is essentially an array of descriptors. Every pipeline state incorporates a root signature that defines how shader registers are mapped to the descriptors in the bound descriptor heaps. Resource binding is thus a two-stage process: shader register is first mapped to the descriptor in a descriptor heap as defined by the root signature. The descriptor (which may be SRV, UAV, CBV or sampler) then references the resource in GPU memory. The picture below illustrates simplified view of the D3D12 resource binding model.

Note that in practice every shader stage can reference a number of descriptor tables, each being a set of ranges of descriptors of a specific type. The shaders can also reference a few resources and even constants directly through the root signature. In D3D12, it is only necessary to switch the descriptor table within a descriptor heap to change all bindings specified by the table. This is very cheap and fast operation. Note that changing descriptor heaps may in contrast be very expensive, as on some hardware this may require a GPU stall to flush all work that depends on the currently bound descriptor heap.
In D3D12, there are two descriptor heap types that can be referenced by shaders:

  • D3D12_SRV_UAV_CBV_DESCRIPTOR_HEAP can hold Shader Resource Views, Constant Buffer Views, and Unordered Access Views

  • D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER can only store sampler descriptors

In contrast to D3D11, D3D12 requires that the application is responsible for resource lifetime and residency management. The application must make sure that all resources referenced by descriptors in descriptor heaps are alive, valid and resident until draw/compute command is complete. Diligent Engine 2.0 automates descriptor heap management, but provides enough flexibility to allow applications leverage advantages of D3D12.

Resource binding model in Diligent Engine 2.0

D3D12 allows applications to pre-populate descriptor heaps with descriptors required for the draw commands that are known to be issued during rendering of the scene and only set the descriptor table at draw time. Several descriptor tables can be bound to the pipeline at a time, which allows grouping and switching resources at different frequencies. For example, all bindings that never change (such as global frame attributes constant buffer or light attributes constant buffer) can be put into a single descriptor table and shared between all shaders. Resources that are specific to every material (such as diffuse and specular textures) can be referenced from material-specific descriptor tables. Changing materials then would only require switching few descriptor tables. Finally, resources that can be switched frequently can be stored in a separate dynamic descriptor table.
To allow grouping of resources based on the frequency of expected change, Diligent Engine 2.0 introduces classification of shader variables:

  • Static variables are variables that are expected to be set only once. They may not be changed once a resource is bound to the variable. Such variables are intended to hold global constants such as camera attributes or global light attributes constant buffers.

  • Mutable variables define resources that are expected to change on a per-material frequency. Examples may include diffuse textures, normal maps etc.

  • Dynamic variables are expected to change frequently and randomly.

Shader variable type is defined during shader creation and cannot be changed afterwards. For example:


ShaderCreationAttribs attribs;
attribs.Desc.DefaultVariableType = SHADER_VARIABLE_TYPE_STATIC;
attribs.Desc.ShaderType = SHADER_TYPE_VERTEX;
attribs.Desc.Name = "Sprite VS";
ShaderVariableDesc Vars[] = 
{ 
    {"cbCameraAttribs", SHADER_VARIABLE_TYPE_STATIC},
    {"tex2DDiffuse", SHADER_VARIABLE_TYPE_MUTABLE}, 
    {"cbRandomAttribs", SHADER_VARIABLE_TYPE_DYNAMIC} 
};
attribs.Desc.VariableDesc = Vars;
attribs.EntryPoint = "vs";
attribs.FilePath = "vs.hlsl";
attribs.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
pDevice->CreateShader(attribs, &vs);

Shader variable type defines which descriptor table will hold the descriptor. The new model also introduces a new object called Shader Resource Binding (SRB) that encapsulates shader resources specific to a particular material instance. SRB enables setting dynamic and mutable resource bindings. Static resources are bound directly through the shader:


vs->GetShaderVariable("cbCameraAttribs")->Set(pCamAttrbis);

SRB object is created by the pipeline state:


pPSO->CreateShaderResourceBinding(&pSRB);

Dynamic and mutable resources are then bound through SRB object:


pSRB->GetVariable(SHADER_TYPE_VERTEX, "tex2DDiffuse")->Set(pDiffuseTexSRV);
pSRB->GetVariable(SHADER_TYPE_VERTEX, "cbRandomAttribs")->Set(pRandomAttrsCB);

Before a draw call is invoked, shader resources must be explicitly bound to the pipeline:


pDeviceCtxt->CommitShaderResources(pSRB);

CommitShaderResources() method takes SRB object as an argument. If shaders have no mutable and dynamic resources, nullptr may be specified.

The new shader resource binding model in Diligent Engine 2.0 facilitates efficient resource binding on Direct3D12. It also maps well to Direct3D11. Diligent Engine also implements this model on OpenGL and OpenGLES APIs.

Find Diligent Engine on the Web, follow us on Twitter, and Facebook.

References:
Introduction to Resource Binding in Microsoft DirectX* 12 by Wolfgang Engel
Resource Binding in D3D12 on MSDN

 

Read more about:

Blogs

About the Author

Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like