Creación y grabación de listas y agrupaciones de comandos

En este tema se describen las listas y agrupaciones de comandos de grabación en aplicaciones de Direct3D 12. Las listas de comandos y agrupaciones permiten a las aplicaciones grabar llamadas de dibujo o cambio de estado para su ejecución posterior en la unidad de procesamiento de gráficos (GPU).

Más allá de las listas de comandos, la API aprovecha la funcionalidad presente en el hardware de GPU mediante la adición de un segundo nivel de listas de comandos, a la que se hace referencia como agrupaciones. El propósito de las agrupaciones es permitir que las aplicaciones agrupe un pequeño número de comandos de API para su posterior ejecución. En el momento de la creación del lote, el controlador realizará tanto procesamiento previo como sea posible para que estos sean baratos para ejecutarse más adelante. Los paquetes están diseñados para usarse y volver a usar cualquier número de veces. Por otro lado, las listas de comandos se ejecutan normalmente solo una sola vez. Sin embargo, una lista de comandos se puede ejecutar varias veces (siempre que la aplicación garantice que las ejecuciones anteriores se hayan completado antes de enviar nuevas ejecuciones).

Sin embargo, la compilación de llamadas API en agrupaciones y llamadas API y agrupaciones en listas de comandos, y listas de comandos en un solo marco, se muestra en el diagrama siguiente, teniendo en cuenta la reutilización del lote 1 en la lista de comandos 1 y la lista de comandos 2, y que los nombres de método de API en el diagrama son como ejemplos, se pueden usar muchas llamadas API diferentes.

compilar comandos, agrupaciones y listas de comandos en marcos

Hay diferentes restricciones para crear y ejecutar agrupaciones y listas de comandos directas, y estas diferencias se indican en este tema.

Creación de listas de comandos

Las listas y agrupaciones de comandos directos se crean llamando a ID3D12Device::CreateCommandList o ID3D12Device4::CreateCommandList1.

Use ID3D12Device4::CreateCommandList1 para crear una lista de comandos cerrada, en lugar de crear una nueva lista y cerrarla inmediatamente. Esto evita la ineficacia de la creación de una lista con un asignador y UN ARCHIVO, pero no usarlos.

ID3D12Device::CreateCommandList toma los parámetros siguientes como entrada:

D3D12_COMMAND_LIST_TYPE

La enumeración D3D12_COMMAND_LIST_TYPE indica el tipo de lista de comandos que se está creando. Puede ser una lista de comandos directa, una agrupación, una lista de comandos de proceso o una lista de comandos de copia.

ID3D12CommandAllocator

Un asignador de comandos permite a la aplicación administrar la memoria asignada para las listas de comandos. El asignador de comandos se crea mediante una llamada a CreateCommandAllocator. Al crear una lista de comandos, el tipo de lista de comandos del asignador, especificado por D3D12_COMMAND_LIST_TYPE, debe coincidir con el tipo de lista de comandos que se va a crear. Un asignador determinado se puede asociar a no más de una lista de comandos que está grabando actualmente a la vez, aunque se puede usar un asignador de comandos para crear cualquier número de objetos GraphicsCommandList .

Para reclamar la memoria asignada por un asignador de comandos, una aplicación llama a ID3D12CommandAllocator::Reset. Esto permite que el asignador se reutilice para los nuevos comandos, pero no reducirá su tamaño subyacente. Pero antes de hacerlo, la aplicación debe asegurarse de que la GPU ya no está ejecutando ninguna lista de comandos asociada al asignador; de lo contrario, se producirá un error en la llamada. Además, tenga en cuenta que esta API no está libre y, por lo tanto, no se puede llamar a en el mismo asignador al mismo tiempo desde varios subprocesos.

ID3D12PipelineState

Estado inicial de la canalización para la lista de comandos. En Microsoft Direct3D 12, la mayoría del estado de la canalización de gráficos se establece dentro de una lista de comandos mediante el objeto ID3D12PipelineState . Una aplicación creará un gran número de estas, normalmente durante la inicialización de la aplicación y, a continuación, el estado se actualiza cambiando el objeto de estado enlazado actualmente mediante ID3D12GraphicsCommandList::SetPipelineState. Para obtener más información sobre los objetos de estado de canalización, consulta Administrar el estado de canalización de gráficos en Direct3D 12.

Tenga en cuenta que los conjuntos no heredan el estado de canalización establecido por llamadas anteriores en listas de comandos directas que son sus elementos primarios.

Si este parámetro es NULL, se usa un estado predeterminado.

Listas de comandos de grabación

Inmediatamente después de crearse, las listas de comandos están en estado de grabación. También puede volver a usar una lista de comandos existente llamando a ID3D12GraphicsCommandList::Reset, que también deja la lista de comandos en el estado de grabación. A diferencia de ID3D12CommandAllocator::Reset, puede llamar a Reset mientras se sigue ejecutando la lista de comandos. Un patrón típico es enviar una lista de comandos y, a continuación, restablecerla inmediatamente para reutilizar la memoria asignada para otra lista de comandos. Tenga en cuenta que solo una lista de comandos asociada a cada asignador de comandos puede estar en un estado de grabación a la vez.

Una vez que una lista de comandos está en estado de grabación, basta con llamar a métodos de la interfaz ID3D12GraphicsCommandList para agregar comandos a la lista. Muchos de estos métodos permiten una funcionalidad común de Direct3D que resultará familiar para los desarrolladores de Microsoft Direct3D 11; otras API son nuevas para Direct3D 12.

Después de agregar comandos a la lista de comandos, cambie la lista de comandos fuera del estado de grabación llamando a Close.

Los asignadores de comandos pueden crecer, pero no reducir: se deben considerar agrupación y reutilización de asignadores para maximizar la eficacia de la aplicación. Puede grabar varias listas en el mismo asignador antes de que se restablezca, siempre que solo se registre una lista en un asignador determinado a la vez. Puede visualizar cada lista como propietaria de una parte del asignador, que indica qué se ejecutará ID3D12CommandQueue::ExecuteCommandLists .

Una estrategia sencilla de agrupación de asignadores debe tener como objetivo aproximadamente numCommandLists * MaxFrameLatency asignadores. Por ejemplo, si registra 6 listas y permite hasta 3 fotogramas latentes, podría esperar razonablemente 18-20 asignadores. Una estrategia de agrupación más avanzada, que reutiliza los asignadores para varias listas en el mismo subproceso, podría apuntar a numRecordingThreads * MaxFrameLatency asignadores. Con el ejemplo anterior, si se registraron 2 listas en el subproceso A, 2 en el subproceso B, 1 en el subproceso C y 1 en el subproceso D, podría apuntar de forma realista a los asignadores de 12 a 14.

Use una barrera para determinar cuándo se puede reutilizar un asignador determinado.

Como las listas de comandos se pueden restablecer inmediatamente después de la ejecución, se pueden agrupar trivialmente, agregándolos de nuevo al grupo después de cada llamada a ID3D12CommandQueue::ExecuteCommandLists.

Ejemplo

Los fragmentos de código siguientes muestran la creación y grabación de una lista de comandos. Tenga en cuenta que en este ejemplo se incluyen las siguientes características de Direct3D 12:

  • Objetos de estado de canalización: se usan para establecer la mayoría de los parámetros de estado de la canalización de representación desde dentro de una lista de comandos. Para obtener más información, consulta Administrar el estado de canalización de gráficos en Direct3D 12.
  • Montón de descriptores: las aplicaciones usan montones de descriptores para administrar el enlace de canalización a los recursos de memoria.
  • Barrera de recursos: se usa para administrar la transición de recursos de un estado a otro, como desde una vista de destino de representación a una vista de recursos del sombreador. Para más información, consulte Uso de barreras de recursos para sincronizar los estados de los recursos.

Por ejemplo,

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

Una vez creada y registrada una lista de comandos, se puede ejecutar mediante una cola de comandos. Para obtener más información, vea Ejecutar y sincronizar listas de comandos.

Recuento de referencias

La mayoría de las API D3D12 siguen usando el recuento de referencias siguiendo las convenciones COM. Una excepción notable a esto es las API de lista de comandos de gráficos D3D12. Todas las API de ID3D12GraphicsCommandList no contienen referencias a los objetos pasados a esas API. Esto significa que las aplicaciones son responsables de garantizar que nunca se envíe una lista de comandos para su ejecución que haga referencia a un recurso destruido.

Errores de lista de comandos

La mayoría de las API de ID3D12GraphicsCommandList no devuelven errores. Los errores detectados durante la creación de la lista de comandos se aplazan hasta ID3D12GraphicsCommandList::Close. La única excepción es DXGI_ERROR_DEVICE_REMOVED, que se aplaza aún más. Tenga en cuenta que esto es diferente de D3D11, donde muchos errores de validación de parámetros se quitan silenciosamente y nunca se devuelven al autor de la llamada.

Las aplicaciones pueden esperar ver errores DXGI_DEVICE_REMOVED en las siguientes llamadas API:

Restricciones de API de lista de comandos

Algunas API de lista de comandos solo se pueden llamar en determinados tipos de listas de comandos. En la tabla siguiente se muestra qué API de lista de comandos son válidas para llamar a en cada tipo de lista de comandos. También muestra qué API son válidas para llamar a en un pase de representación D3D12.

Nombre de la API Gráficos Proceso Copiar Bundle En Pase de representación
AtomicCopyBufferUINT
AtomicCopyBufferUINT64
BeginEvent
BeginRenderPass
BuildRaytracingAccelerationStructure
ClearDepthStencilView
ClearRenderTargetView
ClearState
ClearUnorderedAccessViewFloat
ClearUnorderedAccessViewUint
CopyBufferRegion
CopyRaytracingAccelerationStructure
CopyResource
CopyTextureRegion
CopyTiles
DiscardResource
Dispatch
DispatchRays
DrawIndexedInstanced
DrawInstanced
EmitRaytracingAccelerationStructurePostbuildInfo
EndQuery
EndRenderPass
ExecuteBundle
ExecuteIndirect
ExecuteMetaCommand
IASetIndexBuffer
IASetPrimitiveTopology
IASetVertexBuffers
InitializeMetaCommand
OMSetBlendFactor
OMSetDepthBounds
OMSetRenderTargets
OMSetStencilRef
ResolveQueryData
ResolveSubresource
ResolveSubresourceRegion
ResourceBarrier
RSSetScissorRects
RSSetShadingRate
RSSetShadingRateImage
RSSetViewports
SetComputeRoot32BitConstant
SetComputeRoot32BitConstants
SetComputeRootConstantBufferView
SetComputeRootDescriptorTable
SetComputeRootShaderResourceView
SetComputeRootSignature
SetComputeRootUnorderedAccessView
SetDescriptorHeaps
SetGraphicsRoot32BitConstant
SetGraphicsRoot32BitConstants
SetGraphicsRootConstantBufferView
SetGraphicsRootDescriptorTable
SetGraphicsRootShaderResourceView
SetGraphicsRootSignature
SetGraphicsRootUnorderedAccessView
SetPipelineState
SetPipelineState1
SetPredication
SetProtectedResourceSession
SetSamplePositions
SetViewInstanceMask
SOSetTargets
WriteBufferImmediate

Restricciones de agrupación

Las restricciones permiten que los controladores de Direct3D 12 realicen la mayor parte del trabajo asociado a las agrupaciones en tiempo de registro, lo que permite que la API ExecuteBundle se ejecute con una sobrecarga baja. Todos los objetos de estado de canalización a los que hace referencia una agrupación deben tener los mismos formatos de destino de representación, formato de búfer de profundidad y descripciones de ejemplo.

No se permiten las siguientes llamadas API de lista de comandos en las listas de comandos creadas con el tipo : D3D12_COMMAND_LIST_TYPE_BUNDLE:

Se puede llamar a SetDescriptorHeaps en una agrupación, pero los montones del descriptor de agrupación deben coincidir con el montón del descriptor de lista de comandos que realiza la llamada.

Si se llama a cualquiera de estas API en una agrupación, el tiempo de ejecución quitará la llamada. La capa de depuración emitirá un error cada vez que esto ocurra.

Envío de trabajo en Direct3D 12