Bagikan melalui


Membuat dan merekam daftar perintah dan bundel

Topik ini menjelaskan daftar perintah perekaman dan bundel di aplikasi Direct3D 12. Daftar perintah dan bundel memungkinkan aplikasi merekam menggambar atau mengubah status panggilan untuk eksekusi nanti pada unit pemrosesan grafis (GPU).

Di luar daftar perintah, API mengeksploitasi fungsionalitas yang ada di perangkat keras GPU dengan menambahkan tingkat kedua daftar perintah, yang disebut sebagai bundel. Tujuan bundel adalah untuk memungkinkan aplikasi mengelompokkan sejumlah kecil perintah API bersama-sama untuk eksekusi nanti. Pada waktu pembuatan bundel, driver akan melakukan pra-pemrosesan sebanyak mungkin untuk membuat ini murah untuk dieksekusi nanti. Bundel dirancang untuk digunakan dan digunakan kembali beberapa kali. Daftar perintah, di sisi lain, biasanya hanya dijalankan satu kali. Namun, daftar perintah dapat dijalankan beberapa kali (selama aplikasi memastikan bahwa eksekusi sebelumnya telah selesai sebelum mengirimkan eksekusi baru).

Biasanya, penyusunan panggilan API menjadi bundel, dan panggilan API dan bundel ke dalam daftar perintah, dan daftar perintah ke dalam satu bingkai, ditampilkan dalam diagram berikut, mencatat penggunaan kembali Bundel 1 di daftar Perintah 1 dan daftar Perintah 2, dan bahwa nama metode API dalam diagram sama seperti contoh, banyak panggilan API yang berbeda dapat digunakan.

membangun perintah, bundel, dan daftar perintah ke dalam bingkai

Ada batasan yang berbeda untuk membuat dan menjalankan bundel dan daftar perintah langsung, dan perbedaan ini dicatat di seluruh topik ini.

Membuat daftar perintah

Daftar perintah dan bundel langsung dibuat dengan memanggil ID3D12Device::CreateCommandList atau ID3D12Device4::CreateCommandList1.

Gunakan ID3D12Device4::CreateCommandList1 untuk membuat daftar perintah tertutup, daripada membuat daftar baru dan segera menutupnya. Ini menghindari ketidakefisienan membuat daftar dengan alokator dan PSO tetapi tidak menggunakannya.

ID3D12Device::CreateCommandList mengambil parameter berikut sebagai input:

D3D12_COMMAND_LIST_TYPE

Enumerasi D3D12_COMMAND_LIST_TYPE menunjukkan jenis daftar perintah yang sedang dibuat. Ini bisa berupa daftar perintah langsung, bundel, daftar perintah komputasi, atau daftar perintah salin.

ID3D12CommandAllocator

Alokator perintah memungkinkan aplikasi mengelola memori yang dialokasikan untuk daftar perintah. Alokator perintah dibuat dengan memanggil CreateCommandAllocator. Saat membuat daftar perintah, tipe daftar perintah alokator, yang ditentukan oleh D3D12_COMMAND_LIST_TYPE, harus cocok dengan jenis daftar perintah yang sedang dibuat. Alokator tertentu dapat dikaitkan dengan tidak lebih dari satu daftar perintah perekaman saat ini pada satu waktu, meskipun satu alokator perintah dapat digunakan untuk membuat sejumlah objek GraphicsCommandList .

Untuk mengklaim kembali memori yang dialokasikan oleh alokator perintah, aplikasi memanggil ID3D12CommandAllocator::Reset. Ini memungkinkan alokator digunakan kembali untuk perintah baru, tetapi tidak akan mengurangi ukuran yang mendasarnya. Tetapi sebelum melakukannya, aplikasi harus memastikan bahwa GPU tidak lagi menjalankan daftar perintah apa pun yang terkait dengan alokator; jika tidak, panggilan akan gagal. Selain itu, perhatikan bahwa API ini tidak berulir bebas dan oleh karena itu tidak dapat dipanggil pada alokator yang sama pada saat yang sama dari beberapa utas.

ID3D12PipelineState

Status alur awal untuk daftar perintah. Di Microsoft Direct3D 12, sebagian besar status alur grafis diatur dalam daftar perintah menggunakan objek ID3D12PipelineState . Aplikasi akan membuat sejumlah besar ini, biasanya selama inisialisasi aplikasi, dan kemudian status diperbarui dengan mengubah objek status yang saat ini terikat menggunakan ID3D12GraphicsCommandList::SetPipelineState. Untuk informasi selengkapnya tentang objek status alur, lihat Mengelola status alur grafis di Direct3D 12.

Perhatikan bahwa bundel tidak mewarisi status alur yang ditetapkan oleh panggilan sebelumnya dalam daftar perintah langsung yang merupakan induknya.

Jika parameter ini NULL, status default akan digunakan.

Daftar perintah perekaman

Segera setelah dibuat, daftar perintah berada dalam status perekaman. Anda juga dapat menggunakan kembali daftar perintah yang ada dengan memanggil ID3D12GraphicsCommandList::Reset, yang juga meninggalkan daftar perintah dalam status perekaman. Tidak seperti ID3D12CommandAllocator::Reset, Anda dapat memanggil Reset saat daftar perintah masih dijalankan. Pola umumnya adalah mengirimkan daftar perintah dan kemudian segera mengatur ulang untuk menggunakan kembali memori yang dialokasikan untuk daftar perintah lain. Perhatikan bahwa hanya satu daftar perintah yang terkait dengan setiap alokator perintah yang mungkin berada dalam status perekaman pada satu waktu.

Setelah daftar perintah berada dalam status perekaman, Anda cukup memanggil metode antarmuka ID3D12GraphicsCommandList untuk menambahkan perintah ke daftar. Banyak dari metode ini memungkinkan fungsionalitas Direct3D umum yang akan akrab dengan pengembang Microsoft Direct3D 11; API lainnya baru untuk Direct3D 12.

Setelah menambahkan perintah ke daftar perintah, Anda mentransisikan daftar perintah keluar dari status perekaman dengan memanggil Tutup.

Alokator perintah dapat tumbuh tetapi tidak menyusut - mengumpulkan dan menggunakan kembali alokator harus dipertimbangkan untuk memaksimalkan efisiensi aplikasi Anda. Anda dapat merekam beberapa daftar ke alokator yang sama sebelum direset, asalkan hanya satu daftar yang merekam ke alokator tertentu pada satu waktu. Anda dapat memvisualisasikan setiap daftar sebagai pemilik sebagian alokator yang menunjukkan ID3D12CommandQueue::ExecuteCommandLists yang akan dijalankan.

Strategi pengumpulan alokator sederhana harus bertujuan untuk sekitar numCommandLists * MaxFrameLatency alokator. Misalnya, jika Anda merekam 6 daftar dan mengizinkan hingga 3 bingkai laten, Anda dapat mengharapkan 18-20 alokator secara wajar. Strategi pengumpulan yang lebih canggih, yang menggunakan kembali alokator untuk beberapa daftar pada utas yang sama, dapat bertujuan untuk numRecordingThreads * MaxFrameLatency alokator. Menggunakan contoh sebelumnya, jika 2 daftar direkam pada utas A, 2 pada utas B, 1 pada utas C, dan 1 pada utas D, Anda dapat secara realistis bertujuan untuk 12-14 alokator.

Gunakan pagar untuk menentukan kapan alokator tertentu dapat digunakan kembali.

Karena daftar perintah dapat segera diatur ulang setelah eksekusi, mereka dapat dikumpulkan secara sepele, menambahkannya kembali ke kumpulan setelah setiap panggilan ke ID3D12CommandQueue::ExecuteCommandLists.

Contoh

Cuplikan kode berikut mengilustrasikan pembuatan dan perekaman daftar perintah. Perhatikan bahwa contoh ini mencakup fitur Direct3D 12 berikut:

  • Objek status alur - Ini digunakan untuk mengatur sebagian besar parameter status alur render dari dalam daftar perintah. Untuk informasi selengkapnya, lihat Mengelola status alur grafis di Direct3D 12.
  • Tumpukan deskriptor - Aplikasi menggunakan tumpukan deskriptor untuk mengelola pengikatan alur ke sumber daya memori.
  • Penghambat sumber daya - Ini digunakan untuk mengelola transisi sumber daya dari satu status ke status lainnya, seperti dari tampilan target render ke tampilan sumber daya shader. Untuk informasi selengkapnya, lihat Menggunakan penghalang sumber daya untuk menyinkronkan status sumber daya.

Misalnya,

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

Setelah daftar perintah dibuat dan direkam, daftar perintah dapat dijalankan menggunakan antrean perintah. Untuk informasi selengkapnya, lihat Menjalankan dan menyinkronkan daftar perintah.

Perhitungan referensi

Sebagian besar API D3D12 terus menggunakan penghitungan referensi mengikuti konvensi COM. Pengecualian penting untuk ini adalah API daftar perintah grafis D3D12. Semua API di ID3D12GraphicsCommandList tidak menyimpan referensi ke objek yang diteruskan ke API tersebut. Ini berarti aplikasi bertanggung jawab untuk memastikan bahwa daftar perintah tidak pernah dikirimkan untuk eksekusi yang mereferensikan sumber daya yang dihancurkan.

Kesalahan daftar perintah

Sebagian besar API di ID3D12GraphicsCommandList tidak mengembalikan kesalahan. Kesalahan yang ditemui selama pembuatan daftar perintah ditangguhkan hingga ID3D12GraphicsCommandList::Close. Satu pengecualian adalah DXGI_ERROR_DEVICE_REMOVED, yang ditangguhkan lebih jauh. Perhatikan bahwa ini berbeda dari D3D11, di mana banyak kesalahan validasi parameter diam-diam dihilangkan dan tidak pernah dikembalikan ke pemanggil.

Aplikasi dapat mengharapkan untuk melihat kesalahan DXGI_DEVICE_REMOVED dalam panggilan API berikut:

Pembatasan API daftar perintah

Beberapa API daftar perintah hanya dapat dipanggil pada jenis daftar perintah tertentu. Tabel di bawah ini memperlihatkan API daftar perintah mana yang valid untuk dipanggil pada setiap jenis daftar perintah. Ini juga menunjukkan API mana yang valid untuk dipanggil dalam pass render D3D12.

Nama API Grafik Compute Menyalin Bundel Di Render Pass
AtomicCopyBufferUINT
AtomicCopyBufferUINT64
BeginQuery
BeginRenderPass
BuildRaytracingAccelerationStructure
ClearDepthStencilView
ClearRenderTargetView
ClearState
ClearUnorderedAccessViewFloat
ClearUnorderedAccessViewUint
CopyBufferRegion
CopyRaytracingAccelerationStructure
CopyResource
CopyTextureRegion
CopyTiles
DiscardResource
Pengiriman
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

Pembatasan bundel

Pembatasan memungkinkan driver Direct3D 12 untuk melakukan sebagian besar pekerjaan yang terkait dengan bundel pada waktu rekaman, sehingga memungkinkan EXECUTEBundle API dijalankan dengan overhead rendah. Semua objek status alur yang direferensikan oleh bundel harus memiliki format target render yang sama, format buffer kedalaman, dan deskripsi sampel.

Panggilan API daftar perintah berikut tidak diizinkan pada daftar perintah yang dibuat dengan jenis: D3D12_COMMAND_LIST_TYPE_BUNDLE:

SetDescriptorHeaps dapat dipanggil pada bundel, tetapi tumpukan deskriptor bundel harus cocok dengan tumpukan deskriptor daftar perintah panggilan.

Jika salah satu API ini dipanggil pada bundel, runtime akan menghilangkan panggilan. Lapisan debug akan mengeluarkan kesalahan setiap kali ini terjadi.

Pengiriman Kerja di Direct3D 12