Compartilhar via


Criando e gravando listas de comandos e pacotes

Este tópico descreve a gravação de listas de comandos e pacotes em aplicativos Direct3D 12. Listas de comandos e pacotes permitem que os aplicativos registrem chamadas de desenho ou alteração de estado para execução posterior na GPU (unidade de processamento de gráficos).

Além das listas de comandos, a API explora a funcionalidade presente no hardware de GPU adicionando um segundo nível de listas de comandos, que são conhecidas como pacotes. A finalidade dos pacotes é permitir que os aplicativos agrupem um pequeno número de comandos de API para execução posterior. No momento da criação do pacote, o driver executará o máximo de pré-processamento possível para torná-los baratos para serem executados posteriormente. Os pacotes são projetados para serem usados e usados novamente várias vezes. As listas de comandos, por outro lado, normalmente são executadas apenas uma única vez. No entanto, uma lista de comandos pode ser executada várias vezes (desde que o aplicativo garanta que as execuções anteriores tenham sido concluídas antes de enviar novas execuções).

Normalmente, porém, a criação de chamadas de API em pacotes, e chamadas de API e pacotes em listas de comandos, e listas de comandos em um único quadro, é mostrada no diagrama a seguir, observando a reutilização do Pacote 1 na Lista de comandos 1 e na Lista de comandos 2, e que os nomes dos métodos de API no diagrama são apenas exemplos; muitas chamadas de API diferentes podem ser usadas.

criar comandos, pacotes e listas de comandos em quadros

Há restrições diferentes para criar e executar pacotes e listas de comandos diretos, e essas diferenças são observadas ao longo deste tópico.

Criando listas de comandos

Listas de comandos e pacotes diretos são criados chamando ID3D12Device::CreateCommandList ou ID3D12Device4::CreateCommandList1.

Use ID3D12Device4::CreateCommandList1 para criar uma lista de comandos fechada, em vez de criar uma nova lista e fechá-la imediatamente. Isso impede que a ineficiência crie uma lista com um alocador e um PSO sem usá-los.

ID3D12Device::CreateCommandList usa os seguintes parâmetros como entrada:

D3D12_COMMAND_LIST_TYPE

A enumeração D3D12_COMMAND_LIST_TYPE indica o tipo de lista de comandos que está sendo criada. Pode ser uma lista de comandos direta, um pacote, uma lista de comandos de computação ou uma lista de comandos de cópia.

ID3D12CommandAllocator

Um alocador de comandos permite que o aplicativo gerencie a memória alocada para listas de comandos. O alocador de comandos é criado chamando CreateCommandAllocator. Ao criar uma lista de comandos, o tipo de lista de comandos do alocador, especificado por D3D12_COMMAND_LIST_TYPE, deve corresponder ao tipo de lista de comandos que está sendo criada. Um determinado alocador pode estar associado a no máximo uma lista de comandos atualmente gravando por vez, embora um alocador de comandos possa ser usado para criar qualquer número de objetos GraphicsCommandList.

Para recuperar a memória alocada por um alocador de comandos, um aplicativo chama ID3D12CommandAllocator::Reset. Isso permite que o alocador seja reutilizado para novos comandos, mas não reduzirá seu tamanho subjacente. Mas antes de fazer isso, o aplicativo deve garantir que a GPU não esteja mais executando nenhuma lista de comandos associada ao alocador; caso contrário, a chamada falhará. Além disso, observe que essa API não possui execução livre entre threads e, portanto, não pode ser chamada no mesmo alocador simultaneamente por vários threads.

ID3D12PipelineState

O estado inicial do pipeline para a lista de comandos. No Microsoft Direct3D 12, a maioria do estado do pipeline gráfico é definida dentro de uma lista de comandos usando o objeto ID3D12PipelineState. Um aplicativo criará um grande número deles, normalmente durante a inicialização do aplicativo e, em seguida, o estado é atualizado alterando o objeto de estado associado no momento usando ID3D12GraphicsCommandList::SetPipelineState. Para obter mais informações sobre objetos de estado de pipeline, consulte Gerenciando o estado do pipeline de gráficos no Direct3D 12.

Observe que os pacotes não herdam o estado do pipeline definido por chamadas anteriores em listas de comandos diretos que são seus pais.

Se esse parâmetro for NULL, um estado padrão será usado.

Listas de comandos de gravação

Imediatamente após a criação, as listas de comandos estão no estado de gravação. Você também pode reutilizar uma lista de comandos existente chamando ID3D12GraphicsCommandList::Reset, que também deixa a lista de comandos no estado de gravação. Ao contrário de ID3D12CommandAllocator::Reset, você pode chamar Reset enquanto a lista de comandos ainda estiver sendo executada. Um padrão típico é enviar uma lista de comandos e redefini-la imediatamente para reutilizar a memória alocada para outra lista de comandos. Observe que apenas uma lista de comandos associada a cada alocador de comandos pode estar em um estado de gravação ao mesmo tempo.

Uma vez que uma lista de comandos esteja no estado de gravação, você simplesmente chama os métodos da interface ID3D12GraphicsCommandList para adicionar comandos à lista. Muitos desses métodos habilitam a funcionalidade comum do Direct3D que será familiar para desenvolvedores do Microsoft Direct3D 11; outras APIs são novas para Direct3D 12.

Depois de adicionar comandos à lista de comandos, você faz a transição da lista de comandos para fora do estado de gravação chamando Fechar.

Os alocadores de comandos podem aumentar, mas não diminuir. O pooling e a reutilização de alocadores devem ser considerados para maximizar a eficiência do seu aplicativo. Você pode registrar várias listas no mesmo alocador antes de ser redefinido, desde que apenas uma lista esteja sendo registrada em um determinado alocador ao mesmo tempo. Você pode visualizar cada lista como se cada uma possuísse uma parte do alocador, o que indica qual ID3D12CommandQueue::ExecuteCommandLists será executado.

Uma estratégia de pooling de alocador simples deve ter como objetivo aproximadamente numCommandLists * MaxFrameLatency alocadores. Por exemplo, se você gravar seis listas e permitir até 3 quadros latentes, poderá esperar razoavelmente de 18 a 20 alocadores. Uma estratégia de pooling mais avançada, que reutiliza alocadores para várias listas no mesmo thread, poderia ter como objetivo numRecordingThreads * MaxFrameLatency alocadores. Usando o exemplo anterior, se 2 listas foram registradas no thread A, 2 no thread B, 1 no thread C e 1 no thread D, realisticamente, você poderia mirar em entre 12 e 14 alocadores.

Use uma barreira para determinar quando um determinado alocador pode ser reutilizado.

Como as listas de comandos podem ser imediatamente redefinidas após a execução, elas podem ser agrupadas trivialmente, adicionando-as de volta ao pool após cada chamada para ID3D12CommandQueue::ExecuteCommandLists.

Exemplo

Os snippets de código a seguir ilustram a criação e a gravação de uma lista de comandos. Observe que este exemplo inclui os seguintes recursos do Direct3D 12:

  • Objetos de estado de pipeline – eles são usados para definir a maioria dos parâmetros de estado do pipeline de renderização em uma lista de comandos. Para obter mais informações, consulte Gerenciando o estado do pipeline de gráficos no Direct3D 12.
  • Heap de descritores - os aplicativos usam heaps de descritores para gerenciar o vínculo do pipeline aos recursos de memória.
  • Barreira de recursos - isso é usado para gerenciar a transição de recursos de um estado para outro, como de um modo de exibição de destino de renderização para um modo de exibição de recurso de sombreador. Para obter mais informações, consulte Usando barreiras de recursos para sincronizar estados de recursos.

Por exemplo

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();
    }
}

Depois que uma lista de comandos tiver sido criada e gravada, ela poderá ser executada usando uma fila de comandos. Para obter mais informações, consulte Executando e sincronizando listas de comandos.

Contagem de referência

A maioria das APIs D3D12 continua a usar contagem de referências seguindo as convenções COM. Uma exceção notável a isso são as APIs da lista de comandos gráficos D3D12. Todas as APIs no ID3D12GraphicsCommandList não contêm referências aos objetos passados para essas APIs. Isso significa que os aplicativos são responsáveis por garantir que uma lista de comandos nunca seja enviada para execução que faça referência a um recurso destruído.

Erros de lista de comandos

A maioria das APIs em ID3D12GraphicsCommandList não retornam erros. Os erros encontrados durante a criação da lista de comandos são adiados até ID3D12GraphicsCommandList::Close. A única exceção é DXGI_ERROR_DEVICE_REMOVED, que é adiado ainda mais. Observe que isso é diferente do D3D11, em que muitos erros de validação de parâmetro são silenciosamente descartados e nunca retornados ao chamador.

Os aplicativos podem esperar encontrar erros DXGI_DEVICE_REMOVED nas seguintes chamadas da API:

Restrições de API da lista de comandos

Algumas APIs de lista de comandos só podem ser chamadas em determinados tipos de listas de comandos. A tabela a seguir mostra quais APIs de lista de comandos são válidas para chamar em cada tipo de lista de comandos. Ele também mostra quais APIs são válidas para chamar em uma passagem de renderização D3D12.

Nome da API Gráficos Calcular Copiar Grupo Na Passagem de Renderização
AtomicCopyBufferUINT ✓ Válido ✓ Válido ✓ Válido
AtomicCopyBufferUINT64 ✓ Válido ✓ Válido ✓ Válido
IniciarConsulta ✓ Válido ✓ Válido
BeginRenderPass ✓ Válido
BuildRaytracingAccelerationStructure ✓ Válido ✓ Válido
ClearDepthStencilView ✓ Válido
ClearRenderTargetView ✓ Válido
ClearState ✓ Válido ✓ Válido
ClearUnorderedAccessViewFloat ✓ Válido ✓ Válido
ClearUnorderedAccessViewUint ✓ Válido ✓ Válido
CopyBufferRegion ✓ Válido ✓ Válido ✓ Válido
CopyRaytracingAccelerationStructure ✓ Válido ✓ Válido
CopyResource ✓ Válido ✓ Válido ✓ Válido
CopyTextureRegion ✓ Válido ✓ Válido ✓ Válido
CopyTiles ✓ Válido ✓ Válido ✓ Válido
DescartarRecurso ✓ Válido ✓ Válido
Despachar ✓ Válido ✓ Válido ✓ Válido
DispatchRays ✓ Válido ✓ Válido ✓ Válido
DrawIndexedInstanced ✓ Válido ✓ Válido ✓ Válido
DrawInstanced ✓ Válido ✓ Válido ✓ Válido
EmitRaytracingAccelerationStructurePostbuildInfo ✓ Válido ✓ Válido
FinalizarConsulta ✓ Válido ✓ Válido ✓ Válido ✓ Válido
EndRenderPass ✓ Válido ✓ Válido
ExecuteBundle ✓ Válido ✓ Válido
ExecuteIndirect ✓ Válido ✓ Válido ✓ Válido ✓ Válido
ExecuteMetaCommand ✓ Válido ✓ Válido
IASetIndexBuffer ✓ Válido ✓ Válido ✓ Válido
IASetPrimitiveTopology ✓ Válido ✓ Válido ✓ Válido
IASetVertexBuffers ✓ Válido ✓ Válido ✓ Válido
InitializeMetaCommand ✓ Válido ✓ Válido
OMSetBlendFactor ✓ Válido ✓ Válido ✓ Válido
OMSetDepthBounds ✓ Válido ✓ Válido ✓ Válido
OMSetRenderTargets ✓ Válido
OMSetStencilRef ✓ Válido ✓ Válido ✓ Válido
ResolveQueryData ✓ Válido ✓ Válido ✓ Válido
ResolveSubresource ✓ Válido
ResolveSubresourceRegion ✓ Válido
ResourceBarrier ✓ Válido ✓ Válido ✓ Válido ✓ Válido
RSSetScissorRects ✓ Válido ✓ Válido
RSSetShadingRate ✓ Válido ✓ Válido ✓ Válido
RSSetShadingRateImage ✓ Válido ✓ Válido ✓ Válido
RSSetViewports ✓ Válido ✓ Válido
SetComputeRoot32BitConstant ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetComputeRoot32BitConstants ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetComputeRootConstantBufferView ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetComputeRootDescriptorTable ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetComputeRootShaderResourceView ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetComputeRootSignature ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetComputeRootUnorderedAccessView ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetDescriptorHeaps ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetGraphicsRoot32BitConstant ✓ Válido ✓ Válido ✓ Válido
SetGraphicsRoot32BitConstants ✓ Válido ✓ Válido ✓ Válido
SetGraphicsRootConstantBufferView ✓ Válido ✓ Válido ✓ Válido
SetGraphicsRootDescriptorTable ✓ Válido ✓ Válido ✓ Válido
SetGraphicsRootShaderResourceView ✓ Válido ✓ Válido ✓ Válido
SetGraphicsRootSignature ✓ Válido ✓ Válido ✓ Válido
SetGraphicsRootUnorderedAccessView ✓ Válido ✓ Válido ✓ Válido
SetPipelineState ✓ Válido ✓ Válido ✓ Válido ✓ Válido
SetPipelineState1 ✓ Válido ✓ Válido ✓ Válido
SetPredication ✓ Válido ✓ Válido ✓ Válido
SetProtectedResourceSession ✓ Válido ✓ Válido ✓ Válido
SetSamplePositions ✓ Válido ✓ Válido ✓ Válido
SetViewInstanceMask ✓ Válido ✓ Válido ✓ Válido
SOSetTargets ✓ Válido ✓ Válido
WriteBufferImmediate ✓ Válido ✓ Válido ✓ Válido ✓ Válido ✓ Válido

Restrições de pacote

As restrições permitem que os drivers Direct3D 12 façam a maior parte do trabalho associado a pacotes em tempo recorde, permitindo que o ExecuteBundle API seja executado com pouca sobrecarga. Todos os objetos de estado do pipeline referenciados por um pacote devem ter os mesmos formatos de destino de renderização, formato de buffer de profundidade e descrições de exemplo.

As seguintes chamadas à API de lista de comandos não são permitidas em listas de comandos criadas com o tipo: D3D12_COMMAND_LIST_TYPE_BUNDLE:

SetDescriptorHeaps pode ser chamado em um pacote, mas os heaps dos descritores de pacotes devem corresponder ao heap do descritor de lista de comandos de chamada.

Se qualquer uma dessas APIs for chamada em um pacote, o runtime removerá a chamada. A camada de depuração emitirá um erro sempre que isso ocorrer.

Envio de trabalho no Direct3D 12