Создание и запись списков команд и пакетов
В этом разделе описаны списки и пакеты команд записи в приложениях Direct3D 12. Списки команд и пакеты позволяют приложениям записывать вызовы рисования или изменения состояния для последующего выполнения на графическом модуле обработки (GPU).
Помимо списков команд, API использует функциональность, присутствующую в оборудовании GPU, добавляя второй уровень списков команд, которые называются пакетами. Цель пакетов — разрешить приложениям группировать небольшое количество команд API для последующего выполнения. Во время создания набора драйвер будет выполнять столько предварительной обработки, сколько это возможно, чтобы удешевить их последующее выполнение. Пакеты предназначены для использования и повторного использования любого количества раз. С другой стороны, списки команд обычно выполняются только один раз. Однако список команд может выполняться несколько раз (если приложение гарантирует, что предыдущие выполнения завершены перед отправкой новых выполнений).
Как правило, процесс сборки вызовов API в пакеты, последующей сборки вызовов и пакетов API в списки команд, а списков команд — в единый кадр, показан на следующей схеме. Обратите внимание на повторное использование пакета 1 в списке команд 1 и списке команд 2. Имена методов API на схеме представлены как примеры, и множество различных вызовов API могут быть использованы.
Существуют различные ограничения для создания и выполнения пакетов и прямых списков команд, и эти различия отмечаются в этой статье.
Создание списков команд
Прямые списки команд и пакеты создаются путем вызова ID3D12Device::CreateCommandList или ID3D12Device4::CreateCommandList1.
Используйте ID3D12Device4::CreateCommandList1 для создания закрытого списка команд, а не создания нового списка и немедленного закрытия. Это позволяет избежать неэффективности создания списка с распределителем и PSO без их последующего использования.
ID3D12Device::CreateCommandList принимает следующие параметры в качестве входных данных:
D3D12_COMMAND_LIST_TYPE
Перечисление D3D12_COMMAND_LIST_TYPE указывает тип создаваемого списка команд. Это может быть прямой список команд, пакет, список команд вычислений или список команд копирования.
ID3D12CommandAllocator
Распределитель команд позволяет приложению управлять памятью, выделенной для списков команд. Распределитель команд создается путем вызова CreateCommandAllocator. При создании списка команд тип списка команд распределителя, указанного параметром D3D12_COMMAND_LIST_TYPE, должен совпадать с типом создаваемого списка команд. Данный распределитель можно связать не более чем с одним в настоящий момент выполняющим запись списком команд, хотя для создания любого количества объектов GraphicsCommandList можно использовать один распределитель команд.
Чтобы освободить память, выделенную командой, приложение вызывает ID3D12CommandAllocator::Reset. Это позволяет повторно использовать распределитель для новых команд, но не уменьшит его базовый размер. Но перед этим приложение должно убедиться, что GPU больше не выполняет списки команд, связанные с распределителем; В противном случае вызов завершится ошибкой. Кроме того, обратите внимание, что этот API не поддерживает свободную потоковую обработку и поэтому не может вызываться одновременно из нескольких потоков на одном и том же аллокаторе.
ID3D12PipelineState
Начальное состояние потока для списка команд. В Microsoft Direct3D 12 большинство состояний графического конвейера задаются в списке команд с помощью объекта ID3D12PipelineState. Приложение создаст большое количество этих объектов, обычно во время инициализации приложения, а затем состояние обновляется путем изменения объекта состояния, привязанного в данный момент, с помощью ID3D12GraphicsCommandList::SetPipelineState. Дополнительные сведения о объектах состояния конвейера см. в разделе Управление состоянием графического конвейера в Direct3D 12.
Обратите внимание, что пакеты не наследуют состояние конвейера, заданное предыдущими вызовами в прямых списках команд, которые являются их родителями.
Если этот параметр имеет значение NULL, используется состояние по умолчанию.
Запись списков команд
Сразу после создания списки команд находятся в состоянии записи. Вы также можете повторно использовать существующий список команд, вызвав ID3D12GraphicsCommandList::Reset, при этом список команд останется в режиме записи. В отличие от ID3D12CommandAllocator::Reset, можно вызывать Reset, даже пока список команд ещё выполняется. Типичный паттерн — отправить список команд, а затем немедленно сбросить его, чтобы повторно использовать выделенную память для другого списка команд. Обратите внимание, что только один список команд, связанный с каждым распределителем команд, может находиться в состоянии записи одновременно.
После того как список команд находится в состоянии записи, вы просто вызовите методы интерфейса ID3D12GraphicsCommandList, чтобы добавить команды в список. Многие из этих методов позволяют использовать общие функции Direct3D, знакомые разработчикам Microsoft Direct3D 11; другие API являются новыми для Direct3D 12.
После добавления команд в список команд, переведите его из состояния записи путем вызова Закрыть.
Распределители команд могут увеличиваться, но не уменьшаться — объединение в пул и повторное использование распределителей следует учитывать, чтобы максимизировать эффективность вашего приложения. Вы можете записать несколько списков в один и тот же аллокатор перед его сбросом, при условии, что одновременно в указанный аллокатор записывается только один список. Вы можете визуализировать каждый список, как часть распределителя ресурсов, показывающую, что будет выполняться ID3D12CommandQueue::ExecuteCommandLists.
Простая стратегия объединения распределителей должна нацелиться примерно на numCommandLists * MaxFrameLatency
распределителей. Например, если вы записываете 6 списков и разрешаете до 3 латентных фреймов, можно ожидать 18-20 распределителей. Более продвинутая стратегия пула, которая повторно использует аллокаторы для нескольких списков в одном потоке, может стремиться к тому, чтобы иметь numRecordingThreads * MaxFrameLatency
аллокаторов. Используя предыдущий пример, если 2 списка были записаны в потокЕ A, 2 в потоке B, 1 в потоке C и 1 в потоке D, можно реалистично стремиться к 12-14 распределителям.
Используйте забор, чтобы определить, когда данный распределитель может быть использован повторно.
Так как списки команд можно сразу восстановить после выполнения, их можно без особых усилий объединить, добавив их обратно в пул после каждого вызова ID3D12CommandQueue::ExecuteCommandLists.
Пример
Приведенные ниже фрагменты кода иллюстрируют создание и запись списка команд. Обратите внимание, что этот пример включает следующие функции Direct3D 12:
- Объекты состояния конвейера — они используются для задания большинства параметров состояния графического конвейера из списка команд. Дополнительные сведения см. в статье Управление состоянием графического конвейера в Direct3D 12.
- Кучи дескриптора. Приложения используют кучи дескрипторов для управления привязкой конвейера к ресурсам памяти.
- Барьер ресурсов используется для управления переходом ресурсов из одного состояния в другое, например, из представления целевой поверхности к представлению шейдерных ресурсов. Дополнительные сведения см. в статье Использование барьеров ресурсов для синхронизации состояний ресурсов.
Например
void D3D12HelloTriangle::LoadAssets()
{
// Create an empty root signature.
{
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(0, nullptr, 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)));
}
// Create the pipeline state, which includes compiling and loading shaders.
{
ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;
#if defined(_DEBUG)
// Enable better shader debugging with the graphics debugging tools.
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif
ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr));
ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr));
// Define the vertex input layout.
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// Describe and create the graphics pipeline state object (PSO).
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = m_rootSignature.Get();
psoDesc.VS = { reinterpret_cast<UINT8*>(vertexShader->GetBufferPointer()), vertexShader->GetBufferSize() };
psoDesc.PS = { reinterpret_cast<UINT8*>(pixelShader->GetBufferPointer()), pixelShader->GetBufferSize() };
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
ThrowIfFailed(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_pipelineState)));
}
// Create the command list.
ThrowIfFailed(m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.Get(), m_pipelineState.Get(), IID_PPV_ARGS(&m_commandList)));
// Command lists are created in the recording state, but there is nothing
// to record yet. The main loop expects it to be closed, so close it now.
ThrowIfFailed(m_commandList->Close());
// Create the vertex buffer.
{
// Define the geometry for a triangle.
Vertex triangleVertices[] =
{
{ { 0.0f, 0.25f * m_aspectRatio, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.25f, -0.25f * m_aspectRatio, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.25f, -0.25f * m_aspectRatio, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};
const UINT vertexBufferSize = sizeof(triangleVertices);
// Note: using upload heaps to transfer static data like vert buffers is not
// recommended. Every time the GPU needs it, the upload heap will be marshalled
// over. Please read up on Default Heap usage. An upload heap is used here for
// code simplicity and because there are very few verts to actually transfer.
ThrowIfFailed(m_device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&m_vertexBuffer)));
// Copy the triangle data to the vertex buffer.
UINT8* pVertexDataBegin;
CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
ThrowIfFailed(m_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
m_vertexBuffer->Unmap(0, nullptr);
// Initialize the vertex buffer view.
m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
m_vertexBufferView.StrideInBytes = sizeof(Vertex);
m_vertexBufferView.SizeInBytes = vertexBufferSize;
}
// Create synchronization objects and wait until assets have been uploaded to the GPU.
{
ThrowIfFailed(m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
m_fenceValue = 1;
// Create an event handle to use for frame synchronization.
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (m_fenceEvent == nullptr)
{
ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
}
// Wait for the command list to execute; we are reusing the same command
// list in our main loop but for now, we just want to wait for setup to
// complete before continuing.
WaitForPreviousFrame();
}
}
После создания и записи списка команд его можно выполнить с помощью очереди команд. Дополнительные сведения см. в разделе Выполнение и синхронизация списков команд.
Подсчет ссылок
Большинство API D3D12 продолжают использовать подсчет ссылок в соответствии с соглашениями COM. Заметным исключением из этого является API списка графических команд D3D12. Все API на ID3D12GraphicsCommandList не содержат ссылок на объекты, передаваемые этим API. Это означает, что приложения несут ответственность за то, чтобы список команд, ссылающийся на уничтоженный ресурс, никогда не отправлялся на выполнение.
Ошибки списка команд
Большинство API ID3D12GraphicsCommandList не возвращают ошибки. Ошибки, возникающие во время создания списка команд, откладываются до момента закрытия ID3D12GraphicsCommandList::Close. Одно из исключений — это DXGI_ERROR_DEVICE_REMOVED, которое задерживается еще дольше. Обратите внимание, что это отличается от D3D11, где многие ошибки проверки параметров тихо игнорируются и никогда не возвращаются вызывающей стороне.
Приложения могут столкнуться с ошибками DXGI_DEVICE_REMOVED при следующих вызовах API:
- Любой метод создания ресурсов
- ID3D12Resource::Map
- IDXGISwapChain1::Present1
- ПричинаУдаленияУстройства
Ограничения списка команд API
Некоторые API списка команд могут вызываться только в определенных типах списков команд. В таблице ниже показано, какие API списка команд допустимы для вызова каждого типа списка команд. В нем также показано, какие API-функции допустимы для вызова в D3D12 проходе отрисовки.
Имя API | Графика | Вычислять | Копировать | Пакет | В процессе рендеринга |
---|---|---|---|---|---|
AtomicCopyBufferUINT | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
AtomicCopyBufferUINT64 | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
НачатьЗапрос | ✓ Действительный | ✓ Действительный | |||
BeginRenderPass | ✓ Действительный | ||||
СоздатьСтруктуруУскоренияТрассировкиЛучи | ✓ Действительный | ✓ Действительный | |||
ClearDepthStencilView | ✓ Действительный | ||||
ClearRenderTargetView | ✓ Действительный | ||||
ClearState | ✓ Действительный | ✓ Действительный | |||
ClearUnorderedAccessViewFloat | ✓ Действительный | ✓ Действительный | |||
ОчиститьНеупорядоченныйДоступUint | ✓ Действительный | ✓ Действительный | |||
КопироватьРегионБуфера | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
КопироватьСтруктуруУскоренияТрассировкиЛучей | ✓ Действительный | ✓ Действительный | |||
CopyResource | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
CopyTextureRegion | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
CopyTiles | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
УдалитьРесурс | ✓ Действительный | ✓ Действительный | |||
Отправка | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
DispatchRays | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
DrawIndexedInstanced | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
DrawInstanced | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
СгенерироватьИнформациюОПостроенииСтруктурыУскоренияТрассировкиЛучей | ✓ Действительный | ✓ Действительный | |||
ЗавершитьЗапрос | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
EndRenderPass | ✓ Действительный | ✓ Действительный | |||
ExecuteBundle | ✓ Действительный | ✓ Действительный | |||
ExecuteIndirect | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
Выполнить метакоманду | ✓ Действительный | ✓ Действительный | |||
IASetIndexBuffer | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
IASetPrimitiveTopology | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
IASetVertexBuffers | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
ИнициализироватьМетакоманду | ✓ Действительный | ✓ Действительный | |||
OMSetBlendFactor | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
OMSetDepthBounds | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
OMSetRenderTargets (функция устанавливает целевые объекты для рендеринга) | ✓ Действительный | ||||
OMSetStencilRef | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
ResolveQueryData | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
ResolveSubresource | ✓ Действительный | ||||
ResolveSubresourceRegion | ✓ Действительный | ||||
БарьерРесурсов | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
RSSetScissorRects | ✓ Действительный | ✓ Действительный | |||
RSSetShadingRate | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
RSSetShadingRateImage | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
RSSetViewports | ✓ Действительный | ✓ Действительный | |||
SetComputeRoot32BitConstant | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
SetComputeRoot32BitConstants | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
SetComputeRootConstantBufferView | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
УстановитьТаблицуКорневыхДескрипторовВычислений | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
SetComputeRootShaderResourceView | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
УстановитьКорневуюПодписьВычислений | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
SetComputeRootUnorderedAccessView | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
SetDescriptorHeaps | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
SetGraphicsRoot32BitConstant | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetGraphicsRoot32BitConstants | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetGraphicsRootConstantBufferView | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetGraphicsRootDescriptorTable | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetGraphicsRootShaderResourceView | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetGraphicsRootSignature | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
УстановитьГрафическийКореньНеупорядоченногоДоступа | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
УстановитьСостояниеКонвейера | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | |
SetPipelineState1 | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
СетПредиказание | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetProtectedResourceSession | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetSamplePositions | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SetViewInstanceMask | ✓ Действительный | ✓ Действительный | ✓ Действительный | ||
SOSetTargets | ✓ Действительный | ✓ Действительный | |||
МгновеннаяЗаписьБуфера | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный | ✓ Действительный |
Ограничения пакета
Ограничения позволяют драйверам Direct3D 12 выполнять основную часть работы, связанной с пакетами, на этапе записи, что позволяет запускать API ExecuteBundle с низкими накладными расходами. Все объекты состояния конвейера, на которые ссылается пакет, должны иметь одинаковые целевые форматы отрисовки, формат буфера глубины и примеры описаний.
Следующие вызовы API списка команд не допускаются для списков команд, созданных с типом: D3D12_COMMAND_LIST_TYPE_BUNDLE:
- Метод очистки любой
- Любой метод копирования
- Отменить ресурс
- ExecuteBundle
- РесурсныйБарьер (ResourceBarrier)
- ResolveSubresource
- SetPredication
- BeginQuery
- ЗавершитьЗапрос
- SOSetTargets
- OMSetRenderTargets
- RSSetViewports
- RSSetScissorRects
SetDescriptorHeaps можно вызывать в пакете, но куча дескрипторов пакета должна соответствовать кучи дескриптора списка команд вызовов.
Если любой из этих API вызывается в пакете, среда выполнения удаляет вызов. Уровень отладки будет выдавать ошибку всякий раз, когда это происходит.