Jorge
Jorge Autor de Aprende Unreal Engine 4.

DirectX 12: Triángulo

DirectX 12: Triángulo

Vamos a usar todo lo aprendido para pintar un triángulo por pantalla. Nuestro Hello World en DX12.

Tabla de contenidos

Parte 1. Window

Parte 2. Adapter

Parte 3. Conceptos

Parte 4. Queue

Parte 5. Resources

Parte 6. Descriptores

Parte 7. Pipeline State

Parte 8. Swapchain

» Parte 9. Triángulo

Parte 10. Root Signature

Parte 11. Texturas

Pipeline & Root Signature

Partiendo del código del tutorial anterior, vamos a completar el código con los métodos para crear pipeline y RootSignature:

// En el tutorial referente al pipeline explicamos todo este código.

ComPtr<ID3DBlob> DemoApp::LoadShader(LPCWSTR Filename, LPCSTR EntryPoint, LPCSTR Target)
{
  HRESULT hr;

  /* Shaders */
	
  ComPtr<ID3DBlob> ShaderBlob;
  hr = D3DCompileFromFile(
    Filename, // FileName
    nullptr, nullptr, // MacroDefines, Includes, 		
    EntryPoint, // FunctionEntryPoint
    Target, // Target: "vs_5_0", "ps_5_0", "vs_5_1", "ps_5_1"
    D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, // Compile flags
    0, // Flags2
    &ShaderBlob, // Code
    nullptr // Error
  );
  if (FAILED(hr))
  {
    OutputDebugString("[ERROR] D3DCompileFromFile -- Vertex shader");
  }

  return ShaderBlob;
}

void DemoApp::CreateRootSignature()
{
  /* RootSignature vacío, no le pasamos decriptores 
    (ni texturas, ni constant buffer, etc.,) a los shaders */
  
  D3D12_ROOT_SIGNATURE_DESC SignatureDesc{};
  SignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;	
  SignatureDesc.NumParameters = 0;
  SignatureDesc.NumStaticSamplers = 0;

  ComPtr<ID3DBlob> Signature;
  ComPtr<ID3DBlob> Error;

  D3D12SerializeRootSignature(&SignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_0, &Signature, &Error);
  Device->CreateRootSignature(0, Signature->GetBufferPointer(), Signature->GetBufferSize(), IID_PPV_ARGS(&RootSignature));
}

void DemoApp::CreatePipeline()
{
  /* Shaders */

  ComPtr<ID3DBlob> VertexBlob = LoadShader(L"shaders.hlsl", "VSMain", "vs_5_1");

  D3D12_SHADER_BYTECODE VertexShaderBytecode;
  VertexShaderBytecode.pShaderBytecode = VertexBlob->GetBufferPointer();
  VertexShaderBytecode.BytecodeLength = VertexBlob->GetBufferSize();

  ComPtr<ID3DBlob> PixelBlob = LoadShader(L"shaders.hlsl", "PSMain", "ps_5_1");

  D3D12_SHADER_BYTECODE PixelShaderBytecode;
  PixelShaderBytecode.pShaderBytecode = PixelBlob->GetBufferPointer();
  PixelShaderBytecode.BytecodeLength = PixelBlob->GetBufferSize();

  /* Input Layout */
    // vamos a usar un Vertex con position y color
  D3D12_INPUT_ELEMENT_DESC pInputElementDescs[] = {
  // SemanticName; SemanticIndex; Format; InputSlot; AlignedByteOffset; InputSlotClass; InstanceDataStepRate;
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
    {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 3 * 4, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
  };

  D3D12_INPUT_LAYOUT_DESC InputLayout{};
  InputLayout.NumElements = 2;
  InputLayout.pInputElementDescs = pInputElementDescs;

  /* Rasterizer Stage */

  D3D12_RASTERIZER_DESC RasterizerState{};
  RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
  RasterizerState.CullMode = D3D12_CULL_MODE_BACK;
  RasterizerState.FrontCounterClockwise = FALSE;
  RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
  RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
  RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
  RasterizerState.DepthClipEnable = TRUE;
  RasterizerState.MultisampleEnable = FALSE;
  RasterizerState.AntialiasedLineEnable = FALSE;
  RasterizerState.ForcedSampleCount = 0;
  RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;

  /* Blend State */

  D3D12_BLEND_DESC BlendState{};
  BlendState.AlphaToCoverageEnable = FALSE;
  BlendState.IndependentBlendEnable = FALSE;
  const D3D12_RENDER_TARGET_BLEND_DESC DefaultRenderTargetBlendDesc =
  {
    FALSE,FALSE,
    D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
    D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
    D3D12_LOGIC_OP_NOOP,
    D3D12_COLOR_WRITE_ENABLE_ALL,
  };
  for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
  {
    BlendState.RenderTarget[i] = DefaultRenderTargetBlendDesc;
  } 
	
  /* Depth Stencil */

  D3D12_DEPTH_STENCIL_DESC DepthStencilState{};
  DepthStencilState.DepthEnable = FALSE;
  DepthStencilState.StencilEnable = FALSE;

  /* Pipeline */

  D3D12_GRAPHICS_PIPELINE_STATE_DESC PSODesc{};
  PSODesc.InputLayout = InputLayout;
  PSODesc.pRootSignature = RootSignature.Get();
  PSODesc.VS = VertexShaderBytecode;
  PSODesc.PS = PixelShaderBytecode;
  PSODesc.RasterizerState = RasterizerState;
  PSODesc.BlendState = BlendState;
  PSODesc.DepthStencilState = DepthStencilState;
  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;

  Device->CreateGraphicsPipelineState(&PSODesc, IID_PPV_ARGS(&PipelineState));
}

Si necesitas repasar este es el tutorial sobre el pipeline dónde se explica todo este código.

Crear el Vertex Buffer

Añade los siguientes métodos:

// DemoApp.h
class DemoApp
{
  // ...

  /* Vertex Buffers */
  struct Vertex
  {
    XMFLOAT3 Position;
    XMFLOAT4 Color;
  };
  ComPtr<ID3D12Resource> VertexBuffer;
  D3D12_VERTEX_BUFFER_VIEW VertexBufferView;
  void CreateVertexBuffer();

};
// DemoApp.cpp
void DemoApp::CreateVertexBuffer()
{
  Vertex Vertices[] = {
    // { POS, COLOR }
    { {-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f, 1.0f} },
    { {0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f} },
    { {0.0f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f, 1.0f} }
  };

  VertexBuffer = GPUMem::Buffer(Device.Get(), sizeof(Vertices), D3D12_HEAP_TYPE_DEFAULT);

  ComPtr<ID3D12Resource> UploadBuffer = GPUMem::Buffer(Device.Get(), sizeof(Vertices), D3D12_HEAP_TYPE_UPLOAD);
	
  UINT8* pData;
  D3D12_RANGE ReadRange{ 0, 0 };
  UploadBuffer->Map(0, &ReadRange, reinterpret_cast<void**>(&pData));
  memcpy(pData, Vertices, sizeof(Vertices));
  UploadBuffer->Unmap(0, nullptr);

  CommandAllocator->Reset();
  CommandList->Reset(CommandAllocator.Get(), nullptr);

  GPUMem::ResourceBarrier(CommandList.Get(), VertexBuffer.Get(), D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_COPY_DEST);
  CommandList->CopyResource(VertexBuffer.Get(), UploadBuffer.Get());	
  CommandList->Close();

  ID3D12CommandList* const pCommandList[] = { CommandList.Get() };
  CommandQueue->ExecuteCommandLists(1, pCommandList);

  VertexBufferView.BufferLocation = VertexBuffer->GetGPUVirtualAddress();
  VertexBufferView.SizeInBytes = sizeof(Vertices);
  VertexBufferView.StrideInBytes = sizeof(Vertex);
    
  FlushAndWait();
}

Y no olvidar llamar a los métodos en el constructor:

DemoApp::DemoApp(HWND hWnd, UINT Width, UINT Height)
{	
  CreateDevice();
  CreateQueues();
  CreateFence();

  CreateSwapchain(hWnd, Width, Height);
  CreateRenderTargets();

  CreateRootSignature();
  CreatePipeline();

  CreateVertexBuffer();
}

Todo este código lo explicamos en el tutorial sobre recursos y descriptores.

Comandos para pintar

Modificamos la función RecordCommandList con comandos para pintar, principalmente:

  1. Indicarle cuál es el RenderTarget
  2. Pasarle el Vertex Buffer

Antes estamos obligados a indicar que región de la pantalla vamos a pintar:

// DemoApp.h
class DemoApp
{
    // ...
private:
    // ...
    
	/* Viewport and Scissor */
	D3D12_VIEWPORT Viewport;
	D3D12_RECT ScissorRect;
	void SetViewportAndScissorRect(int Width, int Height);  
};


// DemoApp.cpp
void DemoApp::SetViewportAndScissorRect(int Width, int Height)
{
  Viewport.TopLeftX = 0;
  Viewport.TopLeftY = 0;
  Viewport.Width = static_cast<FLOAT>(Width);
  Viewport.Height = static_cast<FLOAT>(Height);
  Viewport.MinDepth = D3D12_MIN_DEPTH;
  Viewport.MaxDepth = D3D12_MAX_DEPTH;

  ScissorRect.left = 0;
  ScissorRect.top = 0;
  ScissorRect.right = Width;
  ScissorRect.bottom = Height;
}

El constructor quedaría así:

DemoApp::DemoApp(HWND hWnd, UINT Width, UINT Height)
{	
  CreateDevice();
  CreateQueues();
  CreateFence();

  CreateSwapchain(hWnd, Width, Height);
  CreateRenderTargets();

  CreateRootSignature();
  CreatePipeline();

  SetViewportAndScissorRect(Width, Height);
  CreateVertexBuffer();
}

Y actualizamos la lista de comandos:

void DemoApp::RecordCommandList()
{
	const UINT BackFrameIndex = Swapchain->GetCurrentBackBufferIndex();

	CommandAllocator->Reset();
	CommandList->Reset(CommandAllocator.Get(), PipelineState.Get());

	D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetDescriptor = RenderTargetViewHeap->GetCPUDescriptorHandleForHeapStart();
	RenderTargetDescriptor.ptr += ((SIZE_T)BackFrameIndex) * Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

	GPUMem::ResourceBarrier(CommandList.Get(), RenderTargets[BackFrameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);

	const FLOAT ClearValue[] = { 0.02f, 0.02f, 0.15f, 1.0f };
	CommandList->ClearRenderTargetView(RenderTargetDescriptor, ClearValue, 0, nullptr);
	CommandList->OMSetRenderTargets(1, &RenderTargetDescriptor, FALSE, nullptr);
	
	CommandList->SetGraphicsRootSignature(RootSignature.Get());
	CommandList->RSSetViewports(1, &Viewport);
	CommandList->RSSetScissorRects(1, &ScissorRect);
	
	CommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	CommandList->IASetVertexBuffers(0, 1, &VertexBufferView);	
	
	CommandList->DrawInstanced(3, 1, 0, 0);

	GPUMem::ResourceBarrier(CommandList.Get(), RenderTargets[BackFrameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);

	CommandList->Close();
}

Y, por supuesto, no olvidar el shader:

// shaders.hlsl
struct VS2PS
{
    float4 position : SV_Position;
    float4 color : COLOR;
};

VS2PS VSMain(float3 position : POSITION, float4 color : COLOR)
{
    VS2PS vs2ps;
    vs2ps.position = float4(position, 1.0);
    vs2ps.color = color;
    return vs2ps;
}

float4 PSMain(VS2PS vs2ps) : SV_Target
{
    return vs2ps.color;
}

Si le das a ejecutar tendremos un bonito hola mundo en DirectX 12:

DirectX Pipeline

Descargar código fuente

Puedes descargar el proyecto en Visual Studio 2019 aquí.

comments powered by Disqus