Création d’un composant Direct3D 12 de base
Notes
Consultez le dépôt DirectX-Graphics-Samples pour les exemples graphiques DirectX 12 qui montrent comment créer des applications gourmandes en graphismes pour Windows 10.
Cette rubrique décrit le flux d’appels pour créer un composant Direct3D 12 de base.
- Flux de code pour une application simple
- Exemple de code pour une application simple
- Rubriques connexes
Flux de code pour une application simple
La boucle la plus externe d’un programme D3D 12 suit un processus graphique très standard :
Conseil
Les nouvelles fonctionnalités de Direct3D 12 sont suivies d’une note.
- Initialiser
- Répéter
- Détruire
Initialize
L’initialisation implique d’abord la configuration des variables et classes globales, et une fonction initialize doit préparer le pipeline et les ressources.
- Initialisez le pipeline.
Activez la couche de débogage.
Créez l’appareil.
Créez la file d’attente de commandes.
Créez la chaîne d’échange.
Créez un tas de descripteur de vue cible de rendu (RTV).
Notes
Un tas de descripteurs peut être considéré comme un tableau de descripteurs. Où chaque descripteur décrit entièrement un objet au GPU.
Créez des ressources frame (une vue cible de rendu pour chaque image).
Créez un allocateur de commande.
Notes
Un allocateur de commandes gère le stockage sous-jacent pour les listes de commandes et les bundles.
- Initialisez les ressources.
Créez une signature racine vide.
Notes
Une signature racine graphique définit les ressources liées au pipeline graphique.
Compilez les nuanceurs.
Créez la disposition d’entrée de vertex.
Créez une description de l’objet d’état de pipeline , puis créez l’objet .
Notes
Un objet d’état de pipeline conserve l’état de tous les nuanceurs actuellement définis, ainsi que certains objets d’état de fonction fixe (tels que l’assembleur d’entrée, le tsselator, le rastériseur et la fusion de sortie).
Créez la liste de commandes.
Fermez la liste des commandes.
Créez et chargez les tampons de vertex.
Créez les vues de mémoire tampon de vertex.
Créez une clôture.
Notes
Une clôture est utilisée pour synchroniser le processeur avec le GPU (voir Synchronisation multi-moteur).
Créez un handle d’événement.
Attendez que le GPU se termine.
Notes
Vérifiez sur la clôture!
Reportez-vous à la classe D3D12HelloTriangle, OnInit, LoadPipeline et LoadAssets.
Update
Mettez à jour tout ce qui doit changer depuis le dernier frame.
- Modifiez la constante, le vertex, les mémoires tampons d’index et tout le reste, si nécessaire.
Reportez-vous à OnUpdate.
Rendu
Dessinez le nouveau monde.
- Remplissez la liste des commandes.
Réinitialisez l’allocateur de liste de commandes.
Notes
Réutiliser la mémoire associée à l’allocateur de commande.
Réinitialisez la liste des commandes.
Définissez la signature racine du graphique.
Notes
Définit la signature racine graphique à utiliser pour la liste de commandes actuelle.
Définissez les rectangles de fenêtre d’affichage et de ciseaux.
Définissez une barrière de ressources, indiquant que la mémoire tampon d’arrière-mémoire doit être utilisée comme cible de rendu.
Notes
Les barrières de ressources sont utilisées pour gérer les transitions de ressources.
Enregistrez les commandes dans la liste des commandes.
Indiquez que la mémoire tampon d’arrière-mémoire sera utilisée pour présenter une fois la liste de commandes exécutée.
Notes
Un autre appel pour définir une barrière de ressources.
Fermez la liste de commandes pour un enregistrement ultérieur.
- Exécutez la liste de commandes.
- Présentez le cadre.
- Attendez que le GPU se termine.
Notes
Continuez à mettre à jour et à vérifier la clôture.
Reportez-vous à OnRender.
Détruire
Fermez proprement l’application.
Attendez que le GPU se termine.
Notes
Dernière case activée sur la clôture.
Fermez le handle d’événement.
Reportez-vous à OnDestroy.
Exemple de code pour une application simple
Le code suivant développe le flux de code ci-dessus pour inclure le code requis pour un exemple de base.
classe D3D12HelloTriangle
Commencez par définir la classe dans un fichier d’en-tête, y compris une fenêtre d’affichage, un rectangle de scissor et une mémoire tampon de vertex à l’aide des structures :
#include "DXSample.h"
using namespace DirectX;
using namespace Microsoft::WRL;
class D3D12HelloTriangle : public DXSample
{
public:
D3D12HelloTriangle(UINT width, UINT height, std::wstring name);
virtual void OnInit();
virtual void OnUpdate();
virtual void OnRender();
virtual void OnDestroy();
private:
static const UINT FrameCount = 2;
struct Vertex
{
XMFLOAT3 position;
XMFLOAT4 color;
};
// Pipeline objects.
D3D12_VIEWPORT m_viewport;
D3D12_RECT m_scissorRect;
ComPtr<IDXGISwapChain3> m_swapChain;
ComPtr<ID3D12Device> m_device;
ComPtr<ID3D12Resource> m_renderTargets[FrameCount];
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
ComPtr<ID3D12CommandQueue> m_commandQueue;
ComPtr<ID3D12RootSignature> m_rootSignature;
ComPtr<ID3D12DescriptorHeap> m_rtvHeap;
ComPtr<ID3D12PipelineState> m_pipelineState;
ComPtr<ID3D12GraphicsCommandList> m_commandList;
UINT m_rtvDescriptorSize;
// App resources.
ComPtr<ID3D12Resource> m_vertexBuffer;
D3D12_VERTEX_BUFFER_VIEW m_vertexBufferView;
// Synchronization objects.
UINT m_frameIndex;
HANDLE m_fenceEvent;
ComPtr<ID3D12Fence> m_fence;
UINT64 m_fenceValue;
void LoadPipeline();
void LoadAssets();
void PopulateCommandList();
void WaitForPreviousFrame();
};
OnInit()
Dans les projets main fichier source, commencez à initialiser les objets :
void D3D12HelloTriangle::OnInit()
{
LoadPipeline();
LoadAssets();
}
LoadPipeline()
Le code suivant crée les principes de base d’un pipeline graphique. Le processus de création d’appareils et de chaînes d’échange est très similaire à Direct3D 11.
- Activez la couche de débogage, avec des appels à :
D3D12GetDebugInterface
ID3D12Debug::EnableDebugLayer
- Créez l’appareil :
CreateDXGIFactory1
D3D12CreateDevice
- Renseignez une description de la file d’attente de commandes, puis créez la file d’attente de commandes :
D3D12_COMMAND_QUEUE_DESC
ID3D12Device::CreateCommandQueue
- Renseignez une description de la chaîne d’échange, puis créez la chaîne d’échange :
DXGI_SWAP_CHAIN_DESC
IDXGIFactory::CreateSwapChain
IDXGISwapChain3::GetCurrentBackBufferIndex
- Renseignez une description du tas. créez ensuite un tas de descripteur :
D3D12_DESCRIPTOR_HEAP_DESC
ID3D12Device::CreateDescriptorHeap
ID3D12Device::GetDescriptorHandleIncrementsize
- Créez l’affichage cible de rendu :
CD3DX12_CPU_DESCRIPTOR_HANDLE
GetCPUDescriptorHandleForHeapStart
IDXGISwapChain::GetBuffer
ID3D12Device::CreateRenderTargetView
- Créez l’allocateur de commande : ID3D12Device::CreateCommandAllocator.
Dans les étapes ultérieures, les listes de commandes sont obtenues à partir de l’utilitaire de commandes et envoyées à la file d’attente de commandes.
Chargez les dépendances du pipeline de rendu (notez que la création d’un appareil WARP logiciel est entièrement facultative).
void D3D12HelloTriangle::LoadPipeline()
{
#if defined(_DEBUG)
// Enable the D3D12 debug layer.
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
}
}
#endif
ComPtr<IDXGIFactory4> factory;
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&factory)));
if (m_useWarpDevice)
{
ComPtr<IDXGIAdapter> warpAdapter;
ThrowIfFailed(factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter)));
ThrowIfFailed(D3D12CreateDevice(
warpAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&m_device)
));
}
else
{
ComPtr<IDXGIAdapter1> hardwareAdapter;
GetHardwareAdapter(factory.Get(), &hardwareAdapter);
ThrowIfFailed(D3D12CreateDevice(
hardwareAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&m_device)
));
}
// Describe and create the command queue.
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ThrowIfFailed(m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue)));
// Describe and create the swap chain.
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
swapChainDesc.BufferCount = FrameCount;
swapChainDesc.BufferDesc.Width = m_width;
swapChainDesc.BufferDesc.Height = m_height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.OutputWindow = Win32Application::GetHwnd();
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = TRUE;
ComPtr<IDXGISwapChain> swapChain;
ThrowIfFailed(factory->CreateSwapChain(
m_commandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.
&swapChainDesc,
&swapChain
));
ThrowIfFailed(swapChain.As(&m_swapChain));
// This sample does not support fullscreen transitions.
ThrowIfFailed(factory->MakeWindowAssociation(Win32Application::GetHwnd(), DXGI_MWA_NO_ALT_ENTER));
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
// Create descriptor heaps.
{
// Describe and create a render target view (RTV) descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = FrameCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap)));
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
}
// Create frame resources.
{
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
// Create a RTV for each frame.
for (UINT n = 0; n < FrameCount; n++)
{
ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n])));
m_device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);
rtvHandle.Offset(1, m_rtvDescriptorSize);
}
}
ThrowIfFailed(m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator)));
}
LoadAssets()
Le chargement et la préparation des ressources sont un processus long. Bon nombre de ces phases sont similaires à D3D 11, même si certaines sont nouvelles avec D3D 12.
Dans Direct3D 12, l’état de pipeline requis est attaché à une liste de commandes via un objet d’état de pipeline (PSO). Cet exemple montre comment créer un fournisseur d’alimentation. Vous pouvez stocker l’authentification unique en tant que variable membre et la réutiliser autant de fois que nécessaire.
Un segment de descripteur définit les vues et la façon d’accéder aux ressources (par exemple, une vue cible de rendu).
Avec l’allocateur de liste de commandes et l’authentification unique, vous pouvez créer la liste de commandes réelle, qui sera exécutée ultérieurement.
Les API et processus suivants sont appelés successivement.
- Créez une signature racine vide à l’aide de la structure d’assistance disponible :
CD3DX12_ROOT_SIGNATURE_DESC
D3D12SerializeRootSignature
ID3D12Device::CreateRootSignature
- Chargez et compilez les nuanceurs : D3DCompileFromFile.
- Créez la disposition d’entrée de vertex : D3D12_INPUT_ELEMENT_DESC.
- Renseignez une description de l’état du pipeline à l’aide des structures d’assistance disponibles, puis créez l’état du pipeline graphique :
D3D12_GRAPHICS_PIPELINE_STATE_DESC
CD3DX12_RASTERIZER_DESC
CD3DX12_BLEND_DESC
ID3D12Device::CreateGraphicsPipelineState
- Créez, puis fermez une liste de commandes :
ID3D12Device::CreateCommandList
ID3D12GraphicsCommandList::Close
- Créez la mémoire tampon de vertex : ID3D12Device::CreateCommittedResource.
- Copiez les données de vertex dans la mémoire tampon de vertex :
ID3D12Resource::Map
ID3D12Resource::Unmap
- Initialisez l’affichage de la mémoire tampon de vertex : GetGPUVirtualAddress.
- Créez et initialisez la clôture : ID3D12Device::CreateFence.
- Créez un handle d’événement à utiliser avec la synchronisation d’images.
- Attendez que le GPU se termine.
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.
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);
auto desc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
ThrowIfFailed(m_device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&desc,
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();
}
}
OnUpdate()
Pour un exemple simple, rien n’est mis à jour.
void D3D12HelloTriangle::OnUpdate()
{
}
OnRender()
Pendant la configuration, la variable membre m_commandList a été utilisée pour enregistrer et exécuter toutes les commandes de configuration. Vous pouvez maintenant réutiliser ce membre dans la boucle de rendu main.
Le rendu implique un appel pour remplir la liste de commandes, puis la liste de commandes peut être exécutée et la mémoire tampon suivante dans la chaîne d’échange présentée :
- Remplissez la liste de commandes.
- Exécutez la liste de commandes : ID3D12CommandQueue::ExecuteCommandLists.
- IDXGISwapChain1::P resent1 le cadre.
- Attendez que le GPU se termine.
void D3D12HelloTriangle::OnRender()
{
// Record all the commands we need to render the scene into the command list.
PopulateCommandList();
// Execute the command list.
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
// Present the frame.
ThrowIfFailed(m_swapChain->Present(1, 0));
WaitForPreviousFrame();
}
PopulateCommandList()
Vous devez réinitialiser l’allocateur de la liste de commandes et la liste de commandes elle-même avant de pouvoir les réutiliser. Dans des scénarios plus avancés, il peut être judicieux de réinitialiser l’allocation toutes les images. La mémoire est associée à l’allocateur qui ne peut pas être libéré immédiatement après l’exécution d’une liste de commandes. Cet exemple montre comment réinitialiser l’allocateur après chaque image.
À présent, réutilisez la liste de commandes pour le cadre actuel. Rattachez à nouveau la fenêtre d’affichage à la liste de commandes (ce qui doit être effectué chaque fois qu’une liste de commandes est réinitialisée et avant l’exécution de la liste de commandes), indiquez que la ressource sera utilisée comme cible de rendu, enregistrez les commandes, puis indiquez que la cible de rendu sera utilisée pour présenter lorsque la liste de commandes est terminée.
Le remplissage des listes de commandes appelle à son tour les méthodes et processus suivants :
- Réinitialisez l’allocateur de commandes et la liste de commandes :
ID3D12CommandAllocator::Reset
ID3D12GraphicsCommandList::Reset
- Définissez les rectangles de signature racine, de fenêtre d’affichage et de ciseaux :
ID3D12GraphicsCommandList::SetGraphicsRootSignature
ID3D12GraphicsCommandList::RSSetViewports
ID3D12GraphicsCommandList::RSSetScissorRects
- Indiquez que la mémoire tampon arrière doit être utilisée comme cible de rendu :
ID3D12GraphicsCommandList::ResourceBarrier
ID3D12DescriptorHeap::GetCPUDescriptorHandleForHeapStart
ID3D12GraphicsCommandList::OMSetRenderTargets
- Enregistrez les commandes :
ID3D12GraphicsCommandList::ClearRenderTargetView
ID3D12GraphicsCommandList::IASetPrimitiveTopology
ID3D12GraphicsCommandList::IASetVertexBuffers
ID3D12GraphicsCommandList::D rawInstanced
- Indiquez que la mémoire tampon d’arrière-mémoire sera maintenant utilisée pour présenter : ID3D12GraphicsCommandList::ResourceBarrier.
- Fermez la liste de commandes : ID3D12GraphicsCommandList::Close.
void D3D12HelloTriangle::PopulateCommandList()
{
// Command list allocators can only be reset when the associated
// command lists have finished execution on the GPU; apps should use
// fences to determine GPU execution progress.
ThrowIfFailed(m_commandAllocator->Reset());
// However, when ExecuteCommandList() is called on a particular command
// list, that command list can then be reset at any time and must be before
// re-recording.
ThrowIfFailed(m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get()));
// Set necessary state.
m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
m_commandList->RSSetViewports(1, &m_viewport);
m_commandList->RSSetScissorRects(1, &m_scissorRect);
// Indicate that the back buffer will be used as a render target.
auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
m_commandList->ResourceBarrier(1, &barrier);
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
// Record commands.
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList->DrawInstanced(3, 1, 0, 0);
// Indicate that the back buffer will now be used to present.
barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
m_commandList->ResourceBarrier(1, &barrier);
ThrowIfFailed(m_commandList->Close());
}
WaitForPreviousFrame()
Le code suivant montre une utilisation trop simplifiée des clôtures.
Notes
L’attente de la fin d’une trame est trop inefficace pour la plupart des applications.
Les API et processus suivants sont appelés dans l’ordre :
- ID3D12CommandQueue::Signal
- ID3D12Fence::GetCompletedValue
- ID3D12Fence::SetEventOnCompletion
- Attendez l’événement.
- Mettez à jour l’index de frame : IDXGISwapChain3::GetCurrentBackBufferIndex.
void D3D12HelloTriangle::WaitForPreviousFrame()
{
// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
// This is code implemented as such for simplicity. More advanced samples
// illustrate how to use fences for efficient resource usage.
// Signal and increment the fence value.
const UINT64 fence = m_fenceValue;
ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), fence));
m_fenceValue++;
// Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence)
{
ThrowIfFailed(m_fence->SetEventOnCompletion(fence, m_fenceEvent));
WaitForSingleObject(m_fenceEvent, INFINITE);
}
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
OnDestroy()
Fermez proprement l’application.
- Attendez que le GPU se termine.
- Fermez l’événement.
void D3D12HelloTriangle::OnDestroy()
{
// Wait for the GPU to be done with all resources.
WaitForPreviousFrame();
CloseHandle(m_fenceEvent);
}
Rubriques connexes