Compartir a través de


Simulación de la gravedad de n-cuerpos de varios motores

En el ejemplo D3D12nBodyGravity se muestra cómo realizar el trabajo de proceso de forma asincrónica. El ejemplo pone en marcha una serie de subprocesos cada uno con una cola de comandos de proceso y programa el trabajo de proceso en la GPU que realiza una simulación de gravedad de n cuerpos. Cada subproceso funciona en dos búferes llenos de datos de posición y velocidad. Con cada iteración, el sombreador de proceso lee la posición actual y los datos de velocidad de un búfer y escribe la siguiente iteración en el otro búfer. Cuando se completa la iteración, el sombreador de proceso intercambia qué búfer es el SRV para leer los datos de posición/velocidad y cuál es el UAV para escribir actualizaciones de posición y velocidad cambiando el estado del recurso en cada búfer.

Creación de las firmas raíz

Empezamos creando un gráfico y una firma raíz de proceso, en el método LoadAssets . Ambas firmas raíz tienen una vista de búfer de constante raíz (CBV) y una tabla de descriptores de vista de recursos de sombreador (SRV). La firma raíz de proceso también tiene una tabla de descriptores de vista de acceso sin ordenar (UAV).

 // 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)));
       }
Flujo de llamadas Parámetros
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

 

Creación de los búferes SRV y UAV

Los búferes SRV y UAV constan de una matriz de datos de posición y velocidad.

 // 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;
       };
Flujo de llamadas Parámetros
XMFLOAT4

 

Creación de los búferes de vértices y CBV

Para la canalización de gráficos, el CBV es un struct que contiene dos matrices usadas por el sombreador de geometría. El sombreador de geometría toma la posición de cada partícula del sistema y genera un quad para representarlo mediante estas matrices.

 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];
       };
Flujo de llamadas Parámetros
XMMATRIX

 

Como resultado, el búfer de vértices usado por el sombreador de vértices realmente no contiene datos posicionales.

 // "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;
       };
Flujo de llamadas Parámetros
XMFLOAT4

 

Para la canalización de proceso, el CBV es un struct que contiene algunas constantes usadas por la simulación de gravedad n-body en el sombreador de proceso.

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

Sincronización de los subprocesos de representación y proceso

Una vez inicializados todos los búferes, se iniciará el trabajo de representación y proceso. El subproceso de proceso cambiará el estado de los dos búferes de posición/velocidad entre SRV y UAV, ya que itera en la simulación y el subproceso de representación debe asegurarse de que programa el trabajo en la canalización de gráficos que funciona en SRV. Las vallas se usan para sincronizar el acceso a los dos búferes.

En el subproceso render:

// 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();
}
Flujo de llamadas Parámetros
InterlockedExchange
InterlockedGetValue
GetCompletedValue
Esperar
ID3D12CommandList
ExecuteCommandLists
IDXGISwapChain1::Present1

 

Para simplificar el ejemplo un poco, el subproceso de proceso espera a que la GPU complete cada iteración antes de programar más trabajo de proceso. En la práctica, es probable que las aplicaciones quieran mantener la cola de proceso llena para lograr el máximo rendimiento de la GPU.

En el subproceso de proceso:

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;
}
Flujo de llamadas Parámetros
ID3D12CommandQueue
ID3D12CommandAllocator
ID3D12GraphicsCommandList
ID3D12Fence
InterlockedGetValue
Cerrar
ID3D12CommandList
ExecuteCommandLists
InterlockedIncrement
Señal
SetEventOnCompletion
Waitforsingleobject
InterlockedGetValue
GetCompletedValue
Esperar
InterlockedExchange
ID3D12CommandAllocator::Reset
ID3D12GraphicsCommandList::Reset

 

Ejecución del ejemplo

captura de pantalla de la simulación final de gravedad del cuerpo n

Tutoriales de código D3D12

Sincronización de varios motores