Dynamische Indizierung mit HLSL 5.1
Das D3D12DynamicIndexing-Beispiel veranschaulicht einige der neuen HLSL-Features, die in Shader Model 5.1 verfügbar sind – insbesondere dynamische Indizierung und ungebundene Arrays –, um dasselbe Gittermodell mehrmals zu rendern, jedes Mal, wenn es mit einem dynamisch ausgewählten Material gerendert wird. Mit der dynamischen Indizierung können Shader jetzt in ein Array indizieren, ohne den Wert des Indexes zur Kompilierzeit zu kennen. In Kombination mit ungebundenen Arrays bietet dies eine weitere Ebene der Deskription und Flexibilität für Shaderautoren und Artpipelines.
- Einrichten des Pixel-Shaders
- Einrichten der Stammsignatur
- Erstellen der Texturen
- Hochladen der Texturdaten
- Laden der diffusen Textur
- Erstellen eines Samplers
- Dynamisches Ändern des Stammparameterindexes
- Ausführen des Beispiels
- Zugehörige Themen
Einrichten des Pixel-Shaders
Sehen wir uns zunächst den Shader selbst an, bei dem es sich in diesem Beispiel um einen Pixel-Shader handelt.
Texture2D g_txDiffuse : register(t0);
Texture2D g_txMats[] : register(t1);
SamplerState g_sampler : register(s0);
struct PSSceneIn
{
float4 pos : SV_Position;
float2 tex : TEXCOORD0;
};
struct MaterialConstants
{
uint matIndex; // Dynamically set index for looking up from g_txMats[].
};
ConstantBuffer<MaterialConstants> materialConstants : register(b0, space0);
float4 PSSceneMain(PSSceneIn input) : SV_Target
{
float3 diffuse = g_txDiffuse.Sample(g_sampler, input.tex).rgb;
float3 mat = g_txMats[materialConstants.matIndex].Sample(g_sampler, input.tex).rgb;
return float4(diffuse * mat, 1.0f);
}
Das Feature für ein ungebundenes Array wird durch das Array veranschaulicht, da g_txMats[] es keine Arraygröße an gibt. Die dynamische Indizierung wird verwendet, um in g_txMats[] mit zu indizieren, matIndex die als Stammkonst constant definiert ist. Der Shader hat keine Kenntnis der Größe, des Arrays oder des Indexwerts zur Kompilierzeit. Beide Attribute werden in der Stammsignatur des Pipelinezustandsobjekts definiert, das mit dem Shader verwendet wird.
Um die dynamischen Indizierungsfeatures in HLSL nutzen zu können, muss der Shader mit SM 5.1 kompiliert werden. Darüber hinaus muss auch das Flag /enable _ unbounded _ descriptor _ tables verwendet werden, um ungebundene Arrays zu verwenden. Die folgenden Befehlszeilenoptionen werden verwendet, um diesen Shader mit dem Effect-Compiler Tool (FXC) zu kompilieren:
fxc /Zi /E"PSSceneMain" /Od /Fo"dynamic_indexing_pixel.cso" /ps"_5_1" /nologo /enable_unbounded_descriptor_tables
Einrichten der Stammsignatur
Sehen wir uns nun die Definition der Stammsignatur an, insbesondere, wie wir die Größe des ungebundenen Arrays definieren und eine Stammkonst constant mit matIndex verknüpfen. Für den Pixel-Shader definieren wir drei Dinge: eine Deskriptortabelle für SRVs (unsere Texture2Ds), eine Deskriptortabelle für Sampler und eine einzelne Stammkonst constant. Die Deskriptortabelle für unsere SRVs enthält CityMaterialCount + 1 Einträge. CityMaterialCount ist eine Konstante, die die Länge von g_txMats[] definiert, und + 1 ist für g_txDiffuse . Die Deskriptortabelle für unsere Samplers enthält nur einen Eintrag, und wir definieren nur einen 32-Bit-Stammkonstantenwert über InitAsConstants(...). in der LoadAssets-Methode.
// Create the root signature.
{
CD3DX12_DESCRIPTOR_RANGE ranges[3];
ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1 + CityMaterialCount, 0); // Diffuse texture + array of materials.
ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
ranges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
CD3DX12_ROOT_PARAMETER rootParameters[4];
rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[2].InitAsDescriptorTable(1, &ranges[2], D3D12_SHADER_VISIBILITY_VERTEX);
rootParameters[3].InitAsConstants(1, 0, 0, D3D12_SHADER_VISIBILITY_PIXEL);
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(_countof(rootParameters), rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
ThrowIfFailed(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature)));
}
Erstellen der Texturen
Der Inhalt von g_txMats[] sind prozedural generierte Texturen, die in LoadAssets erstellt werden. Jede in der Szene gerenderte Stadt hat dieselbe diffuse Textur, aber jede verfügt auch über eine eigene prozedural generierte Textur. Das Array von Texturen erstreckt sich über das Gesamtexte, um die Indizierungstechnik einfach zu visualisieren.
// Create the textures and sampler.
{
// Procedurally generate an array of textures to use as city materials.
{
// All of these materials use the same texture desc.
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.Width = CityMaterialTextureWidth;
textureDesc.Height = CityMaterialTextureHeight;
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
// The textures evenly span the color rainbow so that each city gets
// a different material.
float materialGradStep = (1.0f / static_cast<float>(CityMaterialCount));
// Generate texture data.
vector<vector<unsigned char>> cityTextureData;
cityTextureData.resize(CityMaterialCount);
for (int i = 0; i < CityMaterialCount; ++i)
{
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
ThrowIfFailed(m_device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&m_cityMaterialTextures[i])));
// Fill the texture.
float t = i * materialGradStep;
cityTextureData[i].resize(CityMaterialTextureWidth * CityMaterialTextureHeight * CityMaterialTextureChannelCount);
for (int x = 0; x < CityMaterialTextureWidth; ++x)
{
for (int y = 0; y < CityMaterialTextureHeight; ++y)
{
// Compute the appropriate index into the buffer based on the x/y coordinates.
int pixelIndex = (y * CityMaterialTextureChannelCount * CityMaterialTextureWidth) + (x * CityMaterialTextureChannelCount);
// Determine this row's position along the rainbow gradient.
float tPrime = t + ((static_cast<float>(y) / static_cast<float>(CityMaterialTextureHeight)) * materialGradStep);
// Compute the RGB value for this position along the rainbow
// and pack the pixel value.
XMVECTOR hsl = XMVectorSet(tPrime, 0.5f, 0.5f, 1.0f);
XMVECTOR rgb = XMColorHSLToRGB(hsl);
cityTextureData[i][pixelIndex + 0] = static_cast<unsigned char>((255 * XMVectorGetX(rgb)));
cityTextureData[i][pixelIndex + 1] = static_cast<unsigned char>((255 * XMVectorGetY(rgb)));
cityTextureData[i][pixelIndex + 2] = static_cast<unsigned char>((255 * XMVectorGetZ(rgb)));
cityTextureData[i][pixelIndex + 3] = 255;
}
}
}
}
| Anruffluss | Parameter |
|---|---|
| D3D12_RESOURCE_DESC |
D3D12_RESOURCE_FLAGS [D3D12_RESOURCE_DIMENSION] (/windows/desktop/api/d3d12/ne-d3d12-d3d12_resource_dimension) |
| CreateCommittedResource |
D3D12_HEAP_TYPE [D3D12_HEAP_FLAG] (/windows/desktop/api/d3d12/ne-d3d12-d3d12_heap_flags) CD3DX12_RESOURCE_DESC [D3D12_RESOURCE_STATES] (/windows/desktop/api/d3d12/ne-d3d12-d3d12_resource_states) |
| XMVECTOR |
[XMColorHSLToRGB] (/windows/desktop/api/directxmath/nf-directxmath-xmcolorhsltorgb) |
Hochladen der Texturdaten
Texturdaten werden über einen Upload-Heap in die GPU hochgeladen, und SRVs werden für jeden erstellt und in einem SRV-Deskriptor-Heap gespeichert.
// Upload texture data to the default heap resources.
{
const UINT subresourceCount = textureDesc.DepthOrArraySize * textureDesc.MipLevels;
const UINT64 uploadBufferStep = GetRequiredIntermediateSize(m_cityMaterialTextures[0].Get(), 0, subresourceCount); // All of our textures are the same size in this case.
const UINT64 uploadBufferSize = uploadBufferStep * CityMaterialCount;
CD3DX12_HEAP_PROPERTIES uploadHeap(D3D12_HEAP_TYPE_UPLOAD);
auto uploadDesc = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);
ThrowIfFailed(m_device->CreateCommittedResource(
&uploadHeap,
D3D12_HEAP_FLAG_NONE,
&uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&materialsUploadHeap)));
for (int i = 0; i < CityMaterialCount; ++i)
{
// Copy data to the intermediate upload heap and then schedule
// a copy from the upload heap to the appropriate texture.
D3D12_SUBRESOURCE_DATA textureData = {};
textureData.pData = &cityTextureData[i][0];
textureData.RowPitch = static_cast<LONG_PTR>((CityMaterialTextureChannelCount * textureDesc.Width));
textureData.SlicePitch = textureData.RowPitch * textureDesc.Height;
UpdateSubresources(m_commandList.Get(), m_cityMaterialTextures[i].Get(), materialsUploadHeap.Get(), i * uploadBufferStep, 0, subresourceCount, &textureData);
auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_cityMaterialTextures[i].Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
m_commandList->ResourceBarrier(1, &barrier);
}
}
| Anruffluss | Parameter |
|---|---|
| GetRequiredIntermediateSize | |
| CreateCommittedResource | |
| D3D12_SUBRESOURCE_DATA | |
| UpdateSubresources | |
| ResourceBarrier |
Laden der diffusen Textur
Die diffuse Textur g wird auf ähnliche Weise hochgeladen und erhält auch einen eigenen SRV, aber die Texturdaten sind _ txDiffuse bereits in occcity.bin definiert.
// Load the occcity diffuse texture with baked-in ambient lighting.
// This texture will be blended with a texture from the materials
// array in the pixel shader.
{
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.MipLevels = SampleAssets::Textures[0].MipLevels;
textureDesc.Format = SampleAssets::Textures[0].Format;
textureDesc.Width = SampleAssets::Textures[0].Width;
textureDesc.Height = SampleAssets::Textures[0].Height;
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
ThrowIfFailed(m_device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&m_cityDiffuseTexture)));
const UINT subresourceCount = textureDesc.DepthOrArraySize * textureDesc.MipLevels;
const UINT64 uploadBufferSize = GetRequiredIntermediateSize(m_cityDiffuseTexture.Get(), 0, subresourceCount);
CD3DX12_HEAP_PROPERTIES uploadHeap(D3D12_HEAP_TYPE_UPLOAD);
auto uploadDesc = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);
ThrowIfFailed(m_device->CreateCommittedResource(
&uploadHeap,
D3D12_HEAP_FLAG_NONE,
&uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&textureUploadHeap)));
// Copy data to the intermediate upload heap and then schedule
// a copy from the upload heap to the diffuse texture.
D3D12_SUBRESOURCE_DATA textureData = {};
textureData.pData = pMeshData + SampleAssets::Textures[0].Data[0].Offset;
textureData.RowPitch = SampleAssets::Textures[0].Data[0].Pitch;
textureData.SlicePitch = SampleAssets::Textures[0].Data[0].Size;
UpdateSubresources(m_commandList.Get(), m_cityDiffuseTexture.Get(), textureUploadHeap.Get(), 0, 0, subresourceCount, &textureData);
auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_cityDiffuseTexture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
m_commandList->ResourceBarrier(1, &barrier);
}
| Anruffluss | Parameter |
|---|---|
| D3D12_RESOURCE_DESC | |
| CreateCommittedResource | |
| GetRequiredIntermediateSize | |
| CreateCommittedResource | |
| D3D12_SUBRESOURCE_DATA | |
| ResourceBarrier |
Erstellen eines Samplers
Schließlich wird für LoadAssets ein einzelner Sampler erstellt, um eine Stichprobe aus der diffusen Textur oder dem Texturarray zu erstellen.
// Describe and create a sampler.
D3D12_SAMPLER_DESC samplerDesc = {};
samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
m_device->CreateSampler(&samplerDesc, m_samplerHeap->GetCPUDescriptorHandleForHeapStart());
// Create SRV for the city's diffuse texture.
CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(m_cbvSrvHeap->GetCPUDescriptorHandleForHeapStart(), 0, m_cbvSrvDescriptorSize);
D3D12_SHADER_RESOURCE_VIEW_DESC diffuseSrvDesc = {};
diffuseSrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
diffuseSrvDesc.Format = SampleAssets::Textures->Format;
diffuseSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
diffuseSrvDesc.Texture2D.MipLevels = 1;
m_device->CreateShaderResourceView(m_cityDiffuseTexture.Get(), &diffuseSrvDesc, srvHandle);
srvHandle.Offset(m_cbvSrvDescriptorSize);
// Create SRVs for each city material.
for (int i = 0; i < CityMaterialCount; ++i)
{
D3D12_SHADER_RESOURCE_VIEW_DESC materialSrvDesc = {};
materialSrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
materialSrvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
materialSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
materialSrvDesc.Texture2D.MipLevels = 1;
m_device->CreateShaderResourceView(m_cityMaterialTextures[i].Get(), &materialSrvDesc, srvHandle);
srvHandle.Offset(m_cbvSrvDescriptorSize);
}
Dynamisches Ändern des Stammparameterindexes
Wenn wir die Szene jetzt rendern würden, würden alle Städte gleich aussehen, da wir den Wert unserer Stammkonstante , nicht festgelegt matIndex haben. Jeder Pixel-Shader wird in den 0. Slot von g_txMats indiziert, und die Szene würde wie folgt aussehen:

Der Wert der Stammkonstante wird in FrameResource::P opulateCommandLists festgelegt. In der doppelten for-Schleife, in der ein Draw-Befehl für jede Stadt aufgezeichnet wird, erfassen wir einen Aufruf von SetGraphicsRoot32BitConstants, der unseren Stammparameterindex in Bezug auf die Stammsignatur angibt – in diesem Fall 3 – den Wert des dynamischen Indexes und einen Offset – in diesem Fall 0. Da die Länge von g_txMats gleich der Anzahl der gerenderten Städte ist, wird der Wert des Indexes für jede Stadt inkrementell festgelegt.
for (UINT i = 0; i < m_cityRowCount; i++)
{
for (UINT j = 0; j < m_cityColumnCount; j++)
{
pCommandList->SetPipelineState(pPso);
// Set the city's root constant for dynamically indexing into the material array.
pCommandList->SetGraphicsRoot32BitConstant(3, (i * m_cityColumnCount) + j, 0);
// Set this city's CBV table and move to the next descriptor.
pCommandList->SetGraphicsRootDescriptorTable(2, cbvSrvHandle);
cbvSrvHandle.Offset(cbvSrvDescriptorSize);
pCommandList->DrawIndexedInstanced(numIndices, 1, 0, 0, 0);
}
}
| Aufruffluss | Parameter |
|---|---|
| SetPipelineState | |
| SetGraphicsRoot32BitConstant | |
| SetGraphicsRootDescriptorTable | |
| DrawIndexedInstanced |
Ausführen des Beispiels
Wenn wir nun die Szene rendern, hat jede Stadt einen anderen Wert für matIndex und sucht daher eine andere Textur, als die Szene wie folgt g_txMats[] aussieht:
