Simulazione di gravità n motore multi-motore

L'esempio D3D12nBodyGravity illustra come eseguire il lavoro di calcolo in modo asincrono. L'esempio genera un numero di thread ognuno con una coda di comandi di calcolo e pianifica il lavoro di calcolo sulla GPU che esegue una simulazione di gravità n corpo. Ogni thread opera su due buffer completi di dati di posizione e velocità. Con ogni iterazione, il compute shader legge i dati di posizione e velocità correnti da un buffer e scrive l'iterazione successiva nell'altro buffer. Al termine dell'iterazione, lo shader di calcolo scambia il buffer che rappresenta il buffer SRV per la lettura dei dati di posizione/velocità e che è l'UAV per la scrittura degli aggiornamenti di posizione/velocità modificando lo stato della risorsa in ogni buffer.

Creare le firme radice

Si inizia creando una grafica e una firma radice di calcolo nel metodo LoadAssets . Entrambe le firme radice hanno una visualizzazione del buffer costante radice (CBV) e una tabella di descrittore SRV (Shader Resource View). La firma radice di calcolo include anche una tabella descrittore UAV (Unrdered Access View).

 // Create the root signatures.
       {
              CD3DX12_DESCRIPTOR_RANGE ranges[2];
              ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
              ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0);

              CD3DX12_ROOT_PARAMETER rootParameters[RootParametersCount];
              rootParameters[RootParameterCB].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_ALL);
              rootParameters[RootParameterSRV].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_VERTEX);
              rootParameters[RootParameterUAV].InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_ALL);

              // The rendering pipeline does not need the UAV parameter.
              CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
              rootSignatureDesc.Init(_countof(rootParameters) - 1, rootParameters, 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 compute signature. Must change visibility for the SRV.
              rootParameters[RootParameterSRV].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;

              CD3DX12_ROOT_SIGNATURE_DESC computeRootSignatureDesc(_countof(rootParameters), rootParameters, 0, nullptr);
              ThrowIfFailed(D3D12SerializeRootSignature(&computeRootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));

              ThrowIfFailed(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&m_computeRootSignature)));
       }
Flusso di chiamata Parametri
CD3DX12_DESCRIPTOR_RANGE D3D12_DESCRIPTOR_RANGE_TYPE
CD3DX12_ROOT_PARAMETER D3D12_SHADER_VISIBILITY
CD3DX12_ROOT_SIGNATURE_DESC D3D12_ROOT_SIGNATURE_FLAGS
ID3DBlob
D3D12SerializeRootSignature D3D_ROOT_SIGNATURE_VERSION
CreateRootSignature
CD3DX12_ROOT_SIGNATURE_DESC
D3D12SerializeRootSignature D3D_ROOT_SIGNATURE_VERSION
CreateRootSignature

 

Creare i buffer SRV e UAV

I buffer SRV e UAV sono costituiti da una matrice di dati di posizione e velocità.

 // Position and velocity data for the particles in the system.
       // Two buffers full of Particle data are utilized in this sample.
       // The compute thread alternates writing to each of them.
       // The render thread renders using the buffer that is not currently
       // in use by the compute shader.
       struct Particle
       {
              XMFLOAT4 position;
              XMFLOAT4 velocity;
       };
Flusso di chiamata Parametri
XMFLOAT4

 

Creare i buffer CBV e vertex

Per la pipeline grafica, CBV è uno struct contenente due matrici usate dal geometry shader. Il geometry shader accetta la posizione di ogni particella nel sistema e genera un quad per rappresentarlo usando queste matrici.

 struct ConstantBufferGS
       {
              XMMATRIX worldViewProjection;
              XMMATRIX inverseView;

              // Constant buffers are 256-byte aligned in GPU memory. Padding is added
              // for convenience when computing the struct's size.
              float padding[32];
       };
Flusso di chiamata Parametri
XMMATRIX

 

Di conseguenza, il buffer del vertice usato dal vertex shader in realtà non contiene dati posizionaali.

 // "Vertex" definition for particles. Triangle vertices are generated 
       // by the geometry shader. Color data will be assigned to those 
       // vertices via this struct.
       struct ParticleVertex
       {
              XMFLOAT4 color;
       };
Flusso di chiamata Parametri
XMFLOAT4

 

Per la pipeline di calcolo, CBV è uno struct contenente alcune costanti usate dalla simulazione di gravità n-body nel compute shader.

 struct ConstantBufferCS
       {
              UINT param[4];
              float paramf[4];
       };

Sincronizzare i thread di rendering e calcolo

Dopo aver inizializzato tutti i buffer, il rendering e il lavoro di calcolo inizieranno. Il thread di calcolo cambierà lo stato dei due buffer di posizione/velocità indietro e indietro tra SRV e UAV man mano che esegue l'iterazione della simulazione e il thread di rendering deve garantire che funzioni sulla pipeline grafica che opera sulla SRV. Le recinzioni vengono usate per sincronizzare l'accesso ai due buffer.

Nel thread di rendering:

// Render the scene.
void D3D12nBodyGravity::OnRender()
{
       // Let the compute thread know that a new frame is being rendered.
       for (int n = 0; n < ThreadCount; n++)
       {
              InterlockedExchange(&m_renderContextFenceValues[n], m_renderContextFenceValue);
       }

       // Compute work must be completed before the frame can render or else the SRV 
       // will be in the wrong state.
       for (UINT n = 0; n < ThreadCount; n++)
       {
              UINT64 threadFenceValue = InterlockedGetValue(&m_threadFenceValues[n]);
              if (m_threadFences[n]->GetCompletedValue() < threadFenceValue)
              {
                     // Instruct the rendering command queue to wait for the current 
                     // compute work to complete.
                     ThrowIfFailed(m_commandQueue->Wait(m_threadFences[n].Get(), threadFenceValue));
              }
       }

       // 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(0, 0));

       MoveToNextFrame();
}
Flusso di chiamata Parametri
InterlockedExchange
InterlockedGetValue
GetCompletedValue
Aspettare
ID3D12CommandList
ExecuteCommandLists
IDXGISwapChain1::Present1

 

Per semplificare l'esempio, il thread di calcolo attende il completamento di ogni iterazione della GPU prima di pianificare più operazioni di calcolo. In pratica, le applicazioni vogliono mantenere la coda di calcolo piena per ottenere prestazioni massime dalla GPU.

Nel thread di calcolo:

DWORD D3D12nBodyGravity::AsyncComputeThreadProc(int threadIndex)
{
       ID3D12CommandQueue* pCommandQueue = m_computeCommandQueue[threadIndex].Get();
       ID3D12CommandAllocator* pCommandAllocator = m_computeAllocator[threadIndex].Get();
       ID3D12GraphicsCommandList* pCommandList = m_computeCommandList[threadIndex].Get();
       ID3D12Fence* pFence = m_threadFences[threadIndex].Get();

       while (0 == InterlockedGetValue(&m_terminating))
       {
              // Run the particle simulation.
              Simulate(threadIndex);

              // Close and execute the command list.
              ThrowIfFailed(pCommandList->Close());
              ID3D12CommandList* ppCommandLists[] = { pCommandList };

              pCommandQueue->ExecuteCommandLists(1, ppCommandLists);

              // Wait for the compute shader to complete the simulation.
              UINT64 threadFenceValue = InterlockedIncrement(&m_threadFenceValues[threadIndex]);
              ThrowIfFailed(pCommandQueue->Signal(pFence, threadFenceValue));
              ThrowIfFailed(pFence->SetEventOnCompletion(threadFenceValue, m_threadFenceEvents[threadIndex]));
              WaitForSingleObject(m_threadFenceEvents[threadIndex], INFINITE);

              // Wait for the render thread to be done with the SRV so that
              // the next frame in the simulation can run.
              UINT64 renderContextFenceValue = InterlockedGetValue(&m_renderContextFenceValues[threadIndex]);
              if (m_renderContextFence->GetCompletedValue() < renderContextFenceValue)
              {
                     ThrowIfFailed(pCommandQueue->Wait(m_renderContextFence.Get(), renderContextFenceValue));
                     InterlockedExchange(&m_renderContextFenceValues[threadIndex], 0);
              }

              // Swap the indices to the SRV and UAV.
              m_srvIndex[threadIndex] = 1 - m_srvIndex[threadIndex];

              // Prepare for the next frame.
              ThrowIfFailed(pCommandAllocator->Reset());
              ThrowIfFailed(pCommandList->Reset(pCommandAllocator, m_computeState.Get()));
       }

       return 0;
}
Flusso di chiamata Parametri
ID3D12CommandQueue
ID3D12CommandAllocator
ID3D12GraphicsCommandList
ID3D12Fence
InterlockedGetValue
Chiudi
ID3D12CommandList
ExecuteCommandLists
InterlockedIncrement
Segnale
SetEventOnCompletion
WaitForSingleObject
InterlockedGetValue
GetCompletedValue
Aspettare
InterlockedExchange
ID3D12CommandAllocator::Reset
ID3D12GraphicsCommandList::Reset

 

Eseguire l'esempio

schermata della simulazione di gravità del corpo finale n

Procedura dettagliata del codice D3D12

Sincronizzazione multi-motore