当前位置:网站首页>An article takes you to thoroughly understand descriptors

An article takes you to thoroughly understand descriptors

2022-07-05 04:47:00 Haro3378

First introduced by the description in the dragon book Descriptors The role of .

During rendering ,GPU Resources may be read and written . Before issuing the draw command , We need to bind the resources related to this drawing call to the rendering pipeline . Some resources may change each time a drawing call is made , So we need to update the binding every time we need it . however ,GPU Resources are not directly bound to the rendering pipeline , Instead, it is used through an object called descriptor .

It can be seen that descriptors are used for resource binding , Microsoft is in D3D12 A new concept for resource binding is added in , Include descriptors, descriptor tables, descriptor heaps, and root signatures, Next, let's introduce .( Because these concepts are interrelated , Therefore, it is recommended to read it at least twice to understand the context )

Descriptors

Definition : Resource descriptor , Is a piece used to describe GPU Data of various rendering resources in . essentially ,descriptors In fact, it is an intermediate layer , It describes the address and type information of the resource .

descriptors There are several types of :( Pay attention to d3d12 in ,descriptors And view Synonymous )

 

The size of the descriptor is and gpu Hardware related , It can be through the interface GetDescriptorHandleIncrementSize obtain .

Descriptors are created through the driver layer API complete , For different descriptors, there are different API, But they are basically described by a corresponding structure descriptors The information contained ( Here's the picture )

, The following SRV For example ,SRV The corresponding structure is D3D12_SHADER_RESOURCE_VIEW_DESC,

typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC  
    {  
    DXGI_FORMAT Format;   
    D3D12_SRV_DIMENSION ViewDimension;  
    UINT Shader4ComponentMapping;  
    union   
        {  
        D3D12_BUFFER_SRV Buffer;  
        D3D12_TEX1D_SRV Texture1D;  
        D3D12_TEX1D_ARRAY_SRV Texture1DArray;  
        D3D12_TEX2D_SRV Texture2D;  
        D3D12_TEX2D_ARRAY_SRV Texture2DArray;  
        D3D12_TEX2DMS_SRV Texture2DMS;  
        D3D12_TEX2DMS_ARRAY_SRV Texture2DMSArray;  
        D3D12_TEX3D_SRV Texture3D;  
        D3D12_TEXCUBE_SRV TextureCube;  
        D3D12_TEXCUBE_ARRAY_SRV TextureCubeArray;  
        }   ;  
    }   D3D12_SHADER_RESOURCE_VIEW_DESC;

Format Represents the data format of the map resource .

typedef enum DXGI_FORMAT {
  DXGI_FORMAT_UNKNOWN = 0,
  DXGI_FORMAT_R32G32B32A32_TYPELESS = 1,
  DXGI_FORMAT_R32G32B32A32_FLOAT = 2,
  DXGI_FORMAT_R32G32B32A32_UINT = 3,
  DXGI_FORMAT_R32G32B32A32_SINT = 4,
  DXGI_FORMAT_R32G32B32_TYPELESS = 5,
  DXGI_FORMAT_R32G32B32_FLOAT = 6,
  DXGI_FORMAT_R32G32B32_UINT = 7,
  DXGI_FORMAT_R32G32B32_SINT = 8,
 ···
} ;

ViewDimension Indicates the size of the resource , such as 1D Mapping ,2D Mapping , Cube map, etc .

typedef enum D3D12_SRV_DIMENSION {
  D3D12_SRV_DIMENSION_UNKNOWN = 0,
  D3D12_SRV_DIMENSION_BUFFER = 1,
  D3D12_SRV_DIMENSION_TEXTURE1D = 2,
  D3D12_SRV_DIMENSION_TEXTURE1DARRAY = 3,
  D3D12_SRV_DIMENSION_TEXTURE2D = 4,
  D3D12_SRV_DIMENSION_TEXTURE2DARRAY = 5,
  D3D12_SRV_DIMENSION_TEXTURE2DMS = 6,
  D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY = 7,
  D3D12_SRV_DIMENSION_TEXTURE3D = 8,
  D3D12_SRV_DIMENSION_TEXTURECUBE = 9,
  D3D12_SRV_DIMENSION_TEXTURECUBEARRAY = 10,
  D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE = 11
} ;

Shader4ComponentMapping It means sorting the data returned when sampling the map , Normal use D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING that will do .

Contained in the structure Union It means that only one element can be selected for this resource , If you want to generate 2D Map resources are selected D3D12_TEX2D_SRV Texture2D.

typedef struct D3D12_TEX2D_SRV {
  UINT  MostDetailedMip; // Identify the most detailed mipmap Indexes , Range [0,Mipmap - 1]
  UINT  MipLevels; //mimap Number 
  UINT  PlaneSlice; // Flat index 
  FLOAT ResourceMinLODClamp; // The smallest available mipmap Grade ,0 Indicates that all levels can be obtained 
} D3D12_TEX2D_SRV;

  Finally, complete creation :

D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = bricksTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = bricksTex->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.f;
md3dDevice->CreateShaderResourceView(bricksTex.Get(), &srvDesc, hDescriptor);

Descriptor Heaps

  Definition : Descriptor heap , There are a series of descriptors ( Think of it as a descriptor heap ), In essence, it is a piece of memory for storing a specific type descriptor in a user program . Each descriptor stack can store only one specific descriptor (SRV/CBV/UAV It can be regarded as a type ).

According to Microsoft documentation , The maximum storage capacity of a descriptor heap 100 Ten thousand descriptors ( It depends on the hardware ). 

Then follow Descriptors Heap To understand the creation process , We store Sampler For example .

Be similar to Descriptors The creation of , It is to fill a structure and then pass through the driving layer API To create .

// Describe and create a sampler descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC samplerHeapDesc = {};
samplerHeapDesc.NumDescriptors = 1;
samplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
samplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&samplerHeapDesc, IID_PPV_ARGS(&m_samplerHeap)));

NumDescriptors From the name, you can know that it specifies the number of descriptors that can be stored in the heap ,Type Represents the stored descriptor type ,

typedef enum D3D12_DESCRIPTOR_HEAP_TYPE
{
    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,    // Constant buffer/Shader resource/Unordered access views
    D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,        // Samplers
    D3D12_DESCRIPTOR_HEAP_TYPE_RTV,            // Render target view
    D3D12_DESCRIPTOR_HEAP_TYPE_DSV,            // Depth stencil view
    D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES       // Simply the number of descriptor heap types
} D3D12_DESCRIPTOR_HEAP_TYPE;

Pay attention to the following parameters Flags, Is the option of descriptor heap , There are two values to choose from ,

typedef enum D3D12_DESCRIPTOR_HEAP_FLAGS {
  D3D12_DESCRIPTOR_HEAP_FLAG_NONE = 0,
  D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE = 0x1
} ;

This mark is not explained in detail in the dragon book , After reading the document, I found that this is the key to understand the whole resource binding process ( By mistake .

  First look at the description in the document :

The flag D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE can optionally be set on a descriptor heap to indicate it is be bound on a command list for reference by shaders. Descriptor heaps created without this flag allow applications the option to stage descriptors in CPU memory before copying them to a shader visible descriptor heap, as a convenience.

In human words, it means ,D3D12_DESCRIPTOR_HEAP_FLAGS Indicates the of the descriptor heap storage ,D3D12_DESCRIPTOR_HEAP_FLAG_NONE Indicates that the heap is stored in the system memory ( Description Fu Mingming is related GPU Resources , Why can heaps be stored in system memory ? I'll explain later ),D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE Indicates that the descriptor heap is stored in GPU In the memory .

With the above concept , We can classify descriptors , For better understanding .

First , Put it in GPU The descriptors in the descriptor heap are called GPU Descriptors, Can only Used to store SRV/CBV/UAV/SAMPLER, Because these resources are gpu Medium shader You can quote , It can also be called Shader Visible.

Again , stay CPU The descriptor in the descriptor heap is CPU Desciptors, All type descriptors can be stored , But because it can't be Shader Direct access to , So called Non Shader Visible, What needs special explanation is , Only CPU Descriptors You can perform the assignment operation of the display .

There is also a special descriptor Transparent Descriptors, It can be understood that it is stored hidden in D3D12 Pipeline Inside GPU Descriptor heap , There is no need for the application layer to maintain an additional descriptor heap , Its internal implementation may be better than GPU Descriptor heap is simpler , Therefore, it can be used to bind in Pipeline Resources on , for example :Vertex Buffer View、Index Buffer View、Stream Output View And stored directly in Root Signature Of CBV\SRV\UAV\Sampler The descriptor .

There may be a problem here , Why should descriptor heap distinguish GPU and CPU Well ? In fact, it was mentioned in the introduction of descriptor heap above , Each descriptor heap can store up to 100 Ten thousand descriptors , Then we can't put all the resources in the game GPU Descriptor heap ( I don't think I can use it up at all ?), Since you can't put gpu Then put it in the system memory , This space is bigger than gpu There is too much memory , But note that this is not the stored resource data , It's a reference to resources , The data is always GPU in , When rendering is needed, start from CPU The descriptor heap calls the reference of resources into GPU Descriptor or through CommandList to GPU, This explains the problem that the previous heap can be stored in the system memory .

Shader Visible/Non Shader Visible It's from GPU visual angle To classify , Can also from CPU visual angle classification ,CPU Descriptors It must be CPU Visible Of , But in fact GPU Descriptors It's also CPU Visible, because GPU Descriptor Heap Upper sum CPU Visible Corresponding descriptor , Can pass GetCPUDescriptorHandleForHeapStart Function to get the start CPU Descriptor address .

After creating the descriptor heap , Next is Write descriptor to descriptor heap .

Descriptor heap can be understood as an array , If it is a regular array , There are two ways for us to read and write the content . The first is to use subscripts to access , unfortunately d3d12 No such interface is provided , Then there is the second way to pass through the pointer , Yes C or C++ You know the array in , Arrays are actually pointers , We can use a pointer to the first element to access all elements by offset , This method is used in descriptor heap . The header pointer of the descriptor heap is encapsulated into another structure ,Descriptor Handle, Corresponding CPU and GPU The descriptor heap has CPU handle and GPU handle.

GetCPUDescriptorHandleForHeapStart Return the corresponding CPU visible Type descriptor heap CPU handle, If not CPU visible Then the return NULL,, following API Need to use CPU handle:

GetGPUDescriptorHandleForHeapStart Return the corresponding shader visible Type descriptor heap GPU handle, If not shader visible Then the return NULL, following API Use GPU handle:

 

ID3D12Device::GetDescriptorHandleIncrementSize  Returns the incremental size in the descriptor heap , combination handle You can traverse all descriptors in the descriptor heap .

Create a descriptor heap and write descriptors :

 D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
    cbvHeapDesc.NumDescriptors = 1;
    cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	cbvHeapDesc.NodeMask = 0;
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
        IID_PPV_ARGS(&mCbvHeap)));

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

md3dDevice->CreateConstantBufferView(
	&cbvDesc,
mCbvHeap->GetCPUDescriptorHandleForHeapStart());

Note that creating descriptors in the heap requires CPU handle, This is cpu First establish gpu Address and cpu Association of descriptors , Wait until the rendering needs to be passed in gpu handle for shader Read .

Root Signatures

Recall the quotation at the beginning of the article again ,“ Usually before the drawing call starts execution , We should bind various types of resources required by different shader programs to the rendering pipeline ”, The descriptor heap we mentioned earlier is just a reference to resources , Not yet related to Shader, So how to do it ?

stay d3d11 This part of the package is relatively simple , Just bind the resource directly to the plug-in operation , however d3d12 Leave this part of the work to the user , And provides a new feature Root Signatures( Root signature ), To map the resources bound to the rendering pipeline to the register corresponding to the shader . If you treat the shader program as a function , Treat the input resource as a function argument passed into the shader , The root signature defines the function parameters , So the root signature must be compatible with shaders ( This involves another concept Assembly line status , Because it does not affect understanding , So I won't write it for the time being ).

In the root signature, each Shader Accessible data items are called Root Parameter( Root parameter ), It can be divided into the following types :

  • Root Constants Root constant , With 32 Constant data of bit aligned dimensions , Directly stored in the root parameter , No overhead , So the access cost is 0
  • Root Descriptor Root descriptor , The resource descriptor stored directly in the root parameter , Point to a specific resource or resource array , Limit can only store SRV/CBV/UAV, Because what is stored is resources handle, So there is an extra layer of access overhead .
  • Descriptor Table Descriptor table , What is specified is a continuous area in the descriptor heap where descriptors are stored , It can be understood from the following figure , This descriptor heap must be GPU Visible Of , Because it stores Table Of handle, So there are two levels of overhead .

 

The first two root parameters Constant and Descriptor It's easy to understand , Here we focus on the descriptor table .

The descriptor table is actually a range of descriptors in the descriptor heap , Because the memory allocation of descriptors is completed by the descriptor heap , So creating a descriptor table to reference some of them is a very efficient operation . The root signature is by reference to the descriptor table , Then it is indirectly referenced to the descriptor heap , The starting position and length of the descriptor table are saved in the root signature .

Create root signature :

Similar to the previous creation , The creation of the root signature requires filling the structure D3D12_ROOT_SIGNATURE_DESC

typedef struct D3D12_ROOT_SIGNATURE_DESC {
  UINT                            NumParameters;
  const D3D12_ROOT_PARAMETER      *pParameters;
  UINT                            NumStaticSamplers;
  const D3D12_STATIC_SAMPLER_DESC *pStaticSamplers;
  D3D12_ROOT_SIGNATURE_FLAGS      Flags;
} D3D12_ROOT_SIGNATURE_DESC;

Each slot type in the root signature is defined by D3D12_ROOT_PARAMETER and D3D12_ROOT_PARAMETER_TYPE constitute ,

typedef struct D3D12_ROOT_PARAMETER {
  D3D12_ROOT_PARAMETER_TYPE ParameterType;
  union {
    D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable;
    D3D12_ROOT_CONSTANTS        Constants;
    D3D12_ROOT_DESCRIPTOR       Descriptor;
  };
  D3D12_SHADER_VISIBILITY   ShaderVisibility;
} D3D12_ROOT_PARAMETER;

typedef enum D3D12_ROOT_PARAMETER_TYPE {
  D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE = 0,
  D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
  D3D12_ROOT_PARAMETER_TYPE_CBV,
  D3D12_ROOT_PARAMETER_TYPE_SRV,
  D3D12_ROOT_PARAMETER_TYPE_UAV
} ;

among , Parameters ShaderVisibility Indicates which render pipeline stages are visible to the slots bound by the current root parameter , If it is Compute Shaders in one stage are set to _All.

typedef enum D3D12_SHADER_VISIBILITY {
  D3D12_SHADER_VISIBILITY_ALL = 0,
  D3D12_SHADER_VISIBILITY_VERTEX = 1,
  D3D12_SHADER_VISIBILITY_HULL = 2,
  D3D12_SHADER_VISIBILITY_DOMAIN = 3,
  D3D12_SHADER_VISIBILITY_GEOMETRY = 4,
  D3D12_SHADER_VISIBILITY_PIXEL = 5,
  D3D12_SHADER_VISIBILITY_AMPLIFICATION = 6,
  D3D12_SHADER_VISIBILITY_MESH = 7
} ;

The root signature passes through the interface ID3D12Device::CreateRootSignature establish , But should pay attention to , This just creates the root signature , In order to make the rendering pipeline recognize , Need to PSO object , Here's how Complete creation and use process

  The content corresponding to each slot of the root signature we created is as follows ,

Here's a tip , The root parameter is essentially a block that can be used in GPU Memory accessed in , Every Draw\Compute The root parameters modified between will be saved by the driver or hardware as a separate data , This is called versioning of root parameters . So if the root parameter is large , And each change is relatively small , It will cause redundant storage of most unchanged data , Increased hardware overhead . Some hardware will optimize the storage of these changes , Reduce the redundancy of unchanging parts . in addition , Hardware to ensure API The drawing semantics of is correct , When the root parameter exceeds the size of the hardware storage , It will overflow to slower system memory or other slower storage mechanisms inside the hardware ( Overflow access cost plus 1), In this case, if you can change frequently RP Preserved RP Overflow range , It can greatly reduce the overhead caused by overflow access to slower storage , This is the same. D3D12 Suggestions will change frequently RP Set it as close as possible to RP The most essential reason for the pile head .

// Define the scope of the descriptor table and the corresponding descriptor type 
CD3DX12_DESCRIPTOR_RANGE1 DescRange[6];

DescRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV,6,2); // t2-t7
DescRange[1].Init(D3D12_DESCRIPTOR_RANGE_UAV,4,0); // u0-u3
DescRange[2].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER,2,0); // s0-s1
DescRange[3].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,8, 0,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); // t8-unbounded
DescRange[4].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,0,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); 
                                                            // (t0,space1)-unbounded
DescRange[5].Init(D3D12_DESCRIPTOR_RANGE_CBV,1,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC); // b1
// Define root parameters 
CD3DX12_ROOT_PARAMETER1 RP[7];
// [0] Slots are defined as Constant
RP[0].InitAsConstants(3,2); // 3 constants at b2
// [1] Slots are defined as description tables 
RP[1].InitAsDescriptorTable(2,&DescRange[0]); // 2 ranges t2-t7 and u0-u3
// [2] Slots are defined as CBV
RP[2].InitAsConstantBufferView(0, 0, 
                               D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC); // b0
RP[3].InitAsDescriptorTable(1,&DescRange[2]); // s0-s1
RP[4].InitAsDescriptorTable(1,&DescRange[3]); // t8-unbounded
RP[5].InitAsDescriptorTable(1,&DescRange[4]); // (t0,space1)-unbounded
RP[6].InitAsDescriptorTable(1,&DescRange[5]); // b1
// establish static sampler
CD3DX12_STATIC_SAMPLER StaticSamplers[1];
StaticSamplers[0].Init(3, D3D12_FILTER_ANISOTROPIC); // s3
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC RootSig(7,RP,1,StaticSamplers);
ID3DBlob* pSerializedRootSig;
CheckHR(D3D12SerializeVersionedRootSignature(&RootSig,pSerializedRootSig)); 
// Create root signature 
ID3D12RootSignature* pRootSignature;
hr = CheckHR(pDevice->CreateRootSignature(
    pSerializedRootSig->GetBufferPointer(),pSerializedRootSig->GetBufferSize(),
    __uuidof(ID3D12RootSignature),
    &pRootSignature));

// Finally, we pass the resources in the descriptor heap into the root signature 
// Each incoming resource must match the root parameter type defined above 
ID3D12DescriptorHeap* pHeaps[2] = {pCommonHeap, pSamplerHeap};
pGraphicsCommandList->SetDescriptorHeaps(2,pHeaps);
pGraphicsCommandList->SetGraphicsRootSignature(pRootSignature);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        6,heapOffsetForMoreData,DescRange[5].NumDescriptors);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(5,heapOffsetForMisc,5000); 
pGraphicsCommandList->SetGraphicsRootDescriptorTable(4,heapOffsetForTerrain,20000);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        3,heapOffsetForSamplers,DescRange[2].NumDescriptors);
pGraphicsCommandList->SetComputeRootConstantBufferView(2,pDynamicCBHeap,&CBVDesc);
pGraphicsCommandList->SetSetGraphicsRoot32BitConstants(
                        0,&stuff,0,RTSlot[0].Constants.Num32BitValues);

for(UINT i = 0; i < numObjects; i++)
{
    pGraphicsCommandList->SetPipelineState(PSO[i]);
    pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                    1,heapOffsetForFooAndBar[i],DescRange[1].NumDescriptors);
    pGraphicsCommandList->SetGraphicsRoot32BitConstant(0,&i,1,drawIDOffset);
    SetMyIndexBuffers(i);
    pGraphicsCommandList->DrawIndexedInstanced(...);
}

原网站

版权声明
本文为[Haro3378]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202140629341935.html