Динамическое индексирование с помощью HLSL 5.1
В примере D3D12DynamicIndexing демонстрируются некоторые новые функции HLSL, доступные в модели шейдера 5.1, в частности динамическое индексирование и неограниченные массивы, для отрисовки одной и той же сетки несколько раз, каждый раз отрисовки с динамически выбранным материалом. Благодаря динамическому индексации шейдеры теперь могут индексироваться в массиве, не зная значения индекса во время компиляции. В сочетании с неограниченными массивами это добавляет еще один уровень косвенного обращения и гибкости для авторов шейдеров и конвейеров рисования.
- Настройка пиксельного шейдера
- Настройка корневой подписи
- Создание текстур
- Отправка данных текстуры
- Загрузка диффузной текстуры
- Создание примера
- Динамическое изменение индекса корневого параметра
- Запуск примера
- Связанные темы
Настройка пиксельного шейдера
Давайте сначала рассмотрим сам шейдер, который для этого примера является пиксельным шейдером.
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);
}
Функция неограниченного массива иллюстрируется массивом g_txMats[]
, так как он не указывает размер массива. Динамическое индексирование используется для индексирования g_txMats[]
с matIndex
помощью , который определяется как корневая константа. Шейдер не имеет сведений о размере, массиве или значении индекса во время компиляции. Оба атрибута определяются в корневой сигнатуре объекта состояния конвейера, используемого с шейдером.
Чтобы воспользоваться преимуществами функций динамического индексирования в HLSL, необходимо скомпилировать шейдер с помощью SM 5.1. Кроме того, чтобы использовать несвязанные массивы, необходимо также использовать флаг /enable_unbounded_descriptor_tables . Для компиляции этого шейдера с помощью средства Компилятора эффектов (FXC) используются следующие параметры командной строки:
fxc /Zi /E"PSSceneMain" /Od /Fo"dynamic_indexing_pixel.cso" /ps"_5_1" /nologo /enable_unbounded_descriptor_tables
Настройка корневой подписи
Теперь давайте рассмотрим определение корневой сигнатуры, в частности, то, как мы определим размер неограниченного массива и свяжите корневую константу с matIndex
. Для шейдера пикселей мы определяем три вещи: таблицу дескрипторов для SRV (наши Texture2Ds), таблицу дескриптора для samplers и одну корневую константу. Таблица дескрипторов для наших SRV содержит CityMaterialCount + 1
записи.
CityMaterialCount
— это константа, определяющая g_txMats[]
длину , а значение + 1 — для g_txDiffuse
. Таблица дескрипторов для образцов содержит только одну запись, и мы определяем только одно 32-разрядное значение корневой константы с помощью InitAsConstants(...) в методе LoadAssets .
// 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)));
}
Создание текстур
Содержимое g_txMats[]
— это процедурно созданные текстуры, созданные в LoadAssets. Каждый город, отрисованный в сцене, имеет одинаковую диффузную текстуру, но каждый из них также имеет собственную процедурную текстуру. Массив текстур охватывает спектр радуги, чтобы легко визуализировать метод индексирования.
// 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;
}
}
}
}
Поток вызовов | Параметры |
---|---|
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) |
Отправка данных текстуры
Данные текстуры передаются в GPU через кучу отправки, а srv создаются для каждого из них и хранятся в куче дескрипторов SRV.
// 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);
}
}
Поток вызовов | Параметры |
---|---|
GetRequiredIntermediateSize | |
CreateCommittedResource | |
D3D12_SUBRESOURCE_DATA | |
UpdateSubresources | |
ResourceBarrier |
Загрузка диффузной текстуры
Диффузная текстура, g_txDiffuse
, передается аналогичным образом и также получает собственный SRV, но данные текстуры уже определены в occcity.bin.
// 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);
}
Поток вызовов | Параметры |
---|---|
D3D12_RESOURCE_DESC | |
CreateCommittedResource | |
GetRequiredIntermediateSize | |
CreateCommittedResource | |
D3D12_SUBRESOURCE_DATA | |
ResourceBarrier |
Создание семплировщика
Наконец, для LoadAssets создается один средство выборки для выборки из диффузной текстуры или массива текстур.
// 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);
}
Динамическое изменение индекса корневого параметра
Если бы мы отрисовали сцену сейчас, все города будут выглядеть одинаково, так как мы не задали значение нашей корневой константы , matIndex
. Каждый пиксельный шейдер будет индексироваться в 0-м слоте , g_txMats
и сцена будет выглядеть следующим образом:
Значение корневой константы задается в FrameResource::P opulateCommandLists. В цикле double for , в котором записывается команда draw для каждого города, мы записываем вызов SetGraphicsRoot32BitConstants , указывая индекс корневого параметра в отношении корневой сигнатуры ( в данном случае 3 — значение динамического индекса и смещение — в данном случае 0). Так как длина g_txMats
равна количеству городов, которые мы отрисовываем, значение индекса устанавливается для каждого города постепенно.
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);
}
}
Поток вызовов | Параметры |
---|---|
SetPipelineState | |
SetGraphicsRoot32BitConstant | |
SetGraphicsRootDescriptorTable | |
DrawIndexedInstanced |
Запуск примера
Теперь при отрисовки сцены каждый город будет иметь разное значение для matIndex
и, таким образом, будет искать другую текстуру, чтобы g_txMats[]
сделать сцену выглядеть следующим образом: