Поделиться через


Создание и запись списков команд и пакетов

В этом разделе описывается запись списков команд и пакетов в приложениях 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.

После добавления команд в список команд можно перевести список команд из состояния записи, вызвав метод Close.

Распределители команд могут увеличиваться, но не сжиматься. Следует учитывать возможность объединения и повторного использования распределителей, чтобы максимально повысить эффективность приложения. Вы можете записать несколько списков в один и тот же распределитель перед его сбросом при условии, что только один список одновременно записывается в данный распределитель. Вы можете визуализировать каждый список как владеющий частью распределителя, который указывает, что ID3D12CommandQueue::ExecuteCommandLists будет выполняться.

Простая стратегия объединения распределителей должна быть направлена на приблизительно numCommandLists * MaxFrameLatency распределители. Например, если записать 6 списков и разрешить до 3 скрытых кадров, можно ожидать 18–20 распределителей. Более расширенная стратегия объединения, которая повторно использует распределители для нескольких списков в одном потоке, может быть направлена на numRecordingThreads * MaxFrameLatency выделение. В предыдущем примере, если 2 списка были записаны в потоке A, 2 в потоке B, 1 в потоке C и 1 в потоке D, можно реально стремиться к 12–14 распределителям.

Используйте ограждение, чтобы определить, когда данный распределитель можно использовать повторно.

Так как списки команд могут быть немедленно сброшены после выполнения, их можно тривиально объединить в пул, добавляя их обратно в пул после каждого вызова ID3D12CommandQueue::ExecuteCommandLists.

Пример

Следующие фрагменты кода иллюстрируют создание и запись списка команд. Обратите внимание, что этот пример включает следующие функции 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:

Ограничения API списка команд

Некоторые API списков команд можно вызывать только для определенных типов списков команд. В таблице ниже показано, какие API списка команд допустимы для вызова для каждого типа списка команд. В нем также показано, какие API допустимы для вызова в проходе отрисовки D3D12.

Имя API Графика Вычисления Копировать Bundle В проходе отрисовки
AtomicCopyBufferUINT
AtomicCopyBufferUINT64
BeginQuery
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

Ограничения пакетов

Ограничения позволяют драйверам Direct3D 12 выполнять большую часть работы, связанной с пакетами, в рекордное время, что позволяет запускать API ExecuteBundle с небольшими затратами. Все объекты состояния конвейера, на которые ссылается пакет, должны иметь одинаковые целевые форматы отрисовки, формат буфера глубины и описания примеров.

Следующие вызовы API списков команд не допускаются в списках команд, созданных с типом : D3D12_COMMAND_LIST_TYPE_BUNDLE:

SetDescriptorHeaps можно вызывать в пакете, но куча дескриптора пакета должна соответствовать куче дескриптора списка вызывающих команд.

Если какой-либо из этих API вызывается в пакете, среда выполнения откатит вызов. Уровень отладки будет выдавать ошибку всякий раз, когда это произойдет.

Отправка работы в Direct3D 12