Search
Duplicate

Raster Grpahics Pipeline(레터라이제이션 파이프라인)

레스터라이제이션 파이프라인은 GPU를 사용해서 만들어진 파이프라인중 제일 오래된 파이프라인입니다. 원하는 메쉬를 가장 빠르고 값싸게 만들려면 해당 파이프라인이 제일 제격입니다. 나중에 시간이 지나고 GPU의 성능이 월등히 좋아진다면 레스터라이제이션이 제일먼저 폐지 될수도 있습니다. 메쉬 쉐이더, 레이트레이싱등이 대체 될 수도 있죠.

Descriptor Heaps

디스크립터 힙은 그래픽 오브젝트에 할당된 오브젝트를 설명을 저장해둔 공간입니다.
(Descriptor Heap은 Descriptor들이 저장된 GPU힙 메모리를 의미한다.)
"셰이더(Shader)가 사용하는 GPU 리소스들(텍스처, 버퍼 등)의 **설명서(Descriptor)**를
GPU가 읽을 수 있도록 메모리에 연속적으로 저장한 공간"이라고 생각하면 됩니다.

예전(D3D11)과의 차이점

Direct3D 11
Direct3D 12
뷰(View)를 자동으로 관리
뷰를 직접 생성하고 힙에 저장해야 함
시스템이 리소스 바인딩 처리
개발자가 명시적으로 디스크립터 바인딩
ID3D12DescriptorHeap* renderTargetViewHeap; D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; rtvHeapDesc.NumDescriptors = backbufferCount; rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; ThrowIfFailed(device->CreateDescriptorHeap( &rtvHeapDesc, IID_PPV_ARGS(&renderTargetViewHeap)));
C++
복사

Root Signature

루트 시그니처는 리소스 유형을 정의합니다. 예를들어 상수버퍼, 구조화버퍼, 샘플러, 텍스처 등등을 나타냅니다.
셰이더(Shader)가 사용할 수 있는 리소스들의 레이아웃(구조와 종류)을 GPU에게 알려주는 계약서 같은 것
“셰이더가 어떤 자원(CBV, SRV, UAV, 샘플러 등)을 쓸 건지, 어디에 배치돼 있을지를 정의”하는 구조

RTV(Render Target View)는 루트 시그니처로 정의하지 않는다.

왜 그런가?
루트 시그니처는 GPU의 셰이더 프로그램이 접근할 리소스(SRV, UAV, CBV, Sampler 등) 를 정의하는 "셰이더 바인딩 구조"야.
반면에 RTV는 셰이더에서 직접 접근하지 않아.
RTV는 렌더 타겟으로 출력되는 대상, 즉 파이프라인의 출력 스테이지(Output Merger, OM) 에서만 사용돼.
셰이더는 RTV에 직접 접근하지 않고, 셰이더가 계산한 픽셀 결과를 GPU가 RTV에 쓰는 것이야.
// 👋 Declare Handles ID3D12RootSignature* rootSignature; // 🔎 Determine if we can get Root Signature Version 1.1: D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {}; featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1; if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData)))) { featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0; } // 📂 Individual GPU Resources D3D12_DESCRIPTOR_RANGE1 ranges[1]; ranges[0].BaseShaderRegister = 0; ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; ranges[0].NumDescriptors = 1; ranges[0].RegisterSpace = 0; ranges[0].OffsetInDescriptorsFromTableStart = 0; ranges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE; //🗄️ Groups of GPU Resources D3D12_ROOT_PARAMETER1 rootParameters[1]; rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; rootParameters[0].DescriptorTable.NumDescriptorRanges = 1; rootParameters[0].DescriptorTable.pDescriptorRanges = ranges; // 🏢 Overall Layout D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc; rootSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; rootSignatureDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; rootSignatureDesc.Desc_1_1.NumParameters = 1; rootSignatureDesc.Desc_1_1.pParameters = rootParameters; rootSignatureDesc.Desc_1_1.NumStaticSamplers = 0; rootSignatureDesc.Desc_1_1.pStaticSamplers = nullptr; ID3DBlob* signature; ID3DBlob* error; try { // 🌱 Create the root signature ThrowIfFailed(D3D12SerializeVersionedRootSignature(&rootSignatureDesc, &signature, &error)); ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature))); rootSignature->SetName(L"Hello Triangle Root Signature"); } catch (std::exception e) { const char* errStr = (const char*)error->GetBufferPointer(); std::cout << errStr; error->Release(); error = nullptr; } if (signature) { signature->Release(); signature = nullptr; }
C++
복사

Heaps(Resource)

힙은 여러 오브젝트들을 감싸고 있는 GPU 메모리입니다. 정점 버퍼같은 리소스를 업로드하거나 또는 텍스처 리소스를 업로드 하는데 사용됩니다.
// 🔼 Upload: // 👋 Declare Handles ID3D12Resource* uploadBuffer; std::vector<unsigned char> sourceData; D3D12_HEAP_PROPERTIES uploadHeapProps = {D3D12_HEAP_TYPE_UPLOAD, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1u, 1u}; D3D12_RESOURCE_DESC uploadBufferDesc = {D3D12_RESOURCE_DIMENSION_BUFFER, 65536ull, 65536ull, 1u, 1, 1, DXGI_FORMAT_UNKNOWN, {1u, 0u}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE}; result = device->CreateCommittedResource( &uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadBufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, __uuidof(ID3D12Resource), ((void**)&uploadBuffer)); uint8_t* data = nullptr; D3D12_RANGE range{0, SIZE_T(chunkSize)}; auto hr = spStaging -> Map(0, &range, reinterpret_cast<void**>(&data)); if (FAILED(hr)) { std::cout << "Could not map resource"; } // Copy data if (resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) { memcpy(data, sourceData.data(), sourceData.size()); } // 📖 Readback: // 👋 Declare Handles ID3D12Resource* readbackBuffer; D3D12_HEAP_PROPERTIES heapPropsRead = {D3D12_HEAP_TYPE_READBACK, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1u, 1u}; D3D12_RESOURCE_DESC resourceDescDimBuffer = { D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, 2725888ull, 1u, 1, 1, DXGI_FORMAT_UNKNOWN, {1u, 0u}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE}; result = device->CreateCommittedResource( &heapPropsRead, D3D12_HEAP_FLAG_NONE, &resourceDescDimBuffer, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, __uuidof(ID3D12Resource), ((void**)&readbackBuffer));
C++
복사
자체적으로 관리하는 GPU Heap 을 생성하면 메모리를 더 효율적으로 활용할수도 있습니다. 다만 메모리 관리라는것이 생각보다 번거로운 작업이기 떄문에 라이브러리를 활용하는걸 추천합니다.

Vertex Buffer

정점 버퍼는 정점셰이더에서 사용 할수 있는 정점 데이터를 저장해둔 공간입니다. 버텍스 버퍼, 인덱스 버퍼, 상수 버퍼 등 모든 버퍼 오브젝트는 Directx12에서 ID3D12Resource 입니다.
// 💾 Declare Data struct Vertex { float position[3]; float color[3]; }; Vertex vertexBufferData[3] = {{{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, {{-1.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, {{0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}}; // 👋 Declare Handles ID3D12Resource* vertexBuffer; D3D12_VERTEX_BUFFER_VIEW vertexBufferView; const UINT vertexBufferSize = sizeof(vertexBufferData); D3D12_HEAP_PROPERTIES heapProps; heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; D3D12_RESOURCE_DESC vertexBufferResourceDesc; vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; vertexBufferResourceDesc.Alignment = 0; vertexBufferResourceDesc.Width = vertexBufferSize; vertexBufferResourceDesc.Height = 1; vertexBufferResourceDesc.DepthOrArraySize = 1; vertexBufferResourceDesc.MipLevels = 1; vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN; vertexBufferResourceDesc.SampleDesc.Count = 1; vertexBufferResourceDesc.SampleDesc.Quality = 0; vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ThrowIfFailed(device->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &vertexBufferResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&vertexBuffer))); // 📄 Copy the triangle data to the vertex buffer. UINT8* pVertexDataBegin; // 🙈 We do not intend to read from this resource on the CPU. D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin))); memcpy(pVertexDataBegin, vertexBufferData, sizeof(vertexBufferData)); vertexBuffer->Unmap(0, nullptr); // 👀 Initialize the vertex buffer view. vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress(); vertexBufferView.StrideInBytes = sizeof(Vertex); vertexBufferView.SizeInBytes = vertexBufferSize;
C++
복사

Index Buffer

인덱스 버퍼는 그려지는 메쉬의 정점의 순서가 담겨져 있습니다. 정점을 그리는 순서라고 생각하면 됩니다.
// 💾 Declare Data uint32_t indexBufferData[3] = {0, 1, 2}; // 👋 Declare Handles ID3D12Resource* indexBuffer; D3D12_INDEX_BUFFER_VIEW indexBufferView; const UINT indexBufferSize = sizeof(indexBufferData); D3D12_HEAP_PROPERTIES heapProps; heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; D3D12_RESOURCE_DESC vertexBufferResourceDesc; vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; vertexBufferResourceDesc.Alignment = 0; vertexBufferResourceDesc.Width = indexBufferSize; vertexBufferResourceDesc.Height = 1; vertexBufferResourceDesc.DepthOrArraySize = 1; vertexBufferResourceDesc.MipLevels = 1; vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN; vertexBufferResourceDesc.SampleDesc.Count = 1; vertexBufferResourceDesc.SampleDesc.Quality = 0; vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ThrowIfFailed(device->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &vertexBufferResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&indexBuffer))); // 📄 Copy data to DirectX 12 driver memory: UINT8* pVertexDataBegin; // 🙈 We do not intend to read from this resource on the CPU. D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(indexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin))); memcpy(pVertexDataBegin, indexBufferData, sizeof(indexBufferData)); indexBuffer->Unmap(0, nullptr); // 👀 Initialize the index buffer view. indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress(); indexBufferView.Format = DXGI_FORMAT_R32_UINT; indexBufferView.SizeInBytes = indexBufferSize;
C++
복사

Constant Buffer

상수버퍼는 CPU → GPU 쉐이더 단계로 전송할수 있는 버퍼입니다. 일반적으론 행렬 정보나, 색상, 위치 등등 프로그래머가 원하는 값을 전달 할 수 있습니다.
// 💾 Declare Data struct { glm::mat4 projectionMatrix; glm::mat4 modelMatrix; glm::mat4 viewMatrix; } cbVS; // 👋 Declare Handles ID3D12Resource* constantBuffer; ID3D12DescriptorHeap* constantBufferHeap; UINT8* mappedConstantBuffer; // 🧊 Create the Constant Buffer D3D12_HEAP_PROPERTIES heapProps; heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; heapDesc.NumDescriptors = 1; heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; ThrowIfFailed(device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&constantBufferHeap))); D3D12_RESOURCE_DESC cbResourceDesc; cbResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; cbResourceDesc.Alignment = 0; cbResourceDesc.Width = (sizeof(cbVS) + 255) & ~255; cbResourceDesc.Height = 1; cbResourceDesc.DepthOrArraySize = 1; cbResourceDesc.MipLevels = 1; cbResourceDesc.Format = DXGI_FORMAT_UNKNOWN; cbResourceDesc.SampleDesc.Count = 1; cbResourceDesc.SampleDesc.Quality = 0; cbResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; cbResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ThrowIfFailed(device->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &cbResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&constantBuffer))); constantBufferHeap->SetName(L"Constant Buffer Upload Resource Heap"); // 👓 Create our Constant Buffer View D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; cbvDesc.BufferLocation = constantBuffer->GetGPUVirtualAddress(); cbvDesc.SizeInBytes = (sizeof(cbVS) + 255) & ~255; // CB size is required to be 256-byte aligned. D3D12_CPU_DESCRIPTOR_HANDLE cbvHandle(constantBufferHeap->GetCPUDescriptorHandleForHeapStart()); cbvHandle.ptr = cbvHandle.ptr + device->GetDescriptorHandleIncrementSize( D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) * 0; device->CreateConstantBufferView(&cbvDesc, cbvHandle); // 🙈 We do not intend to read from this resource on the CPU. D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(constantBuffer->Map( 0, &readRange, reinterpret_cast<void**>(&mappedConstantBuffer))); memcpy(mappedConstantBuffer, &cbVS, sizeof(cbVS)); constantBuffer->Unmap(0, &readRange);
C++
복사

Vertex Shader

정점 쉐이더는 정점(vertex)별로 실행되며 주어진 오브젝트를 변형하고 움직이게 하는 작업들을 주로 실행합니다.
cbuffer cb : register(b0) { row_major float4x4 projectionMatrix : packoffset(c0); row_major float4x4 modelMatrix : packoffset(c4); row_major float4x4 viewMatrix : packoffset(c8); }; struct VertexInput { float3 inPos : POSITION; float3 inColor : COLOR; }; struct VertexOutput { float3 color : COLOR; float4 position : SV_Position; }; VertexOutput main(VertexInput vertexInput) { float3 inColor = vertexInput.inColor; float3 inPos = vertexInput.inPos; float3 outColor = inColor; float4 position = mul(float4(inPos, 1.0f), mul(modelMatrix, mul(viewMatrix, projectionMatrix))); VertexOutput output; output.position = position; output.color = outColor; return output; }
GLSL
복사
외부라이브러리를 사용하여 컴파일 하는 경우도 있지만 우리예제에서는 Dx12자체에서 제공해주는 함수를 사용 하겠습니다.
DirectXShaderCompiler
microsoft
UINT compileFlags = 0; #if defined(_DEBUG) compileFlags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #endif ComPtr<ID3DBlob> shaderBlob; ComPtr<ID3DBlob> errorBlob; HRESULT hr = D3DCompileFromFile( filename.c_str(), // 파일 경로 nullptr, // 매크로 정의 D3D_COMPILE_STANDARD_FILE_INCLUDE, // include 허용 entryPoint.c_str(), // 엔트리 포인트 (예: "VSMain") target.c_str(), // 타겟 프로파일 (예: "vs_5_0") compileFlags, // 컴파일 플래그 0, // 효과 컴파일 없음 &shaderBlob, &errorBlob ); if (FAILED(hr)) { if (errorBlob) { std::cerr << "Shader Compile Error: " << (char*)errorBlob->GetBufferPointer() << std::endl; } throw std::runtime_error("Shader compilation failed."); } return shaderBlob;
GLSL
복사

Pixel Shader

픽쉘 쉐이더는 모니터에 출력될 픽셀들을 연산하는 쉐이더 입니다.
struct PixelInput { float3 color : COLOR; }; struct PixelOutput { float4 attachment0 : SV_Target0; }; PixelOutput main(PixelInput pixelInput) { float3 inColor = pixelInput.color; PixelOutput output; output.attachment0 = float4(inColor, 1.0f); return output; }
GLSL
복사

Pipeline State

레스터라이제이션 파이프 라인입니다. 각각의 실행단계가 어떤 순서로 실행되는지 볼수 있습니다.
11에서는 context 에 파이프라인을 세팅해주면 끝났습니다. 하지만 12에서는 파이프라인 객체(PSO)를 만들어서 사용해야 합니다.

DX12 파이프라인 설계 시 주의할 점

1.
PSO는 상태가 조금이라도 다르면 새로 만들어야 합니다.
(ex: 픽셀 셰이더만 바뀌어도 PSO를 다시 생성해야 함)
2.
루트 시그니처는 PSO에 포함되므로 셰이더가 사용하는 모든 리소스를 고려해서 설계해야 합니다.
3.
PSO 생성은 비용이 크므로 초기화 시에 모두 생성하고, 실행 중에는 재사용하는 것이 좋습니다.
// 👋 Declare handles ID3D12PipelineState* pipelineState; // ⚗️ Define the Graphics Pipeline D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; // 🔣 Input Assembly D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}}; psoDesc.InputLayout = {inputElementDescs, _countof(inputElementDescs)}; // 🦄 Resources psoDesc.pRootSignature = rootSignature; // 🔺 Vertex Shader D3D12_SHADER_BYTECODE vsBytecode; vsBytecode.pShaderBytecode = vertexShaderBlob->GetBufferPointer(); vsBytecode.BytecodeLength = vertexShaderBlob->GetBufferSize(); psoDesc.VS = vsBytecode; // 🖌️ Pixel Shader D3D12_SHADER_BYTECODE psBytecode; psBytecode.pShaderBytecode = pixelShaderBlob->GetBufferPointer(); psBytecode.BytecodeLength = pixelShaderBlob->GetBufferSize(); psoDesc.PS = psBytecode; // 🟨 Rasterization D3D12_RASTERIZER_DESC rasterDesc; rasterDesc.FillMode = D3D12_FILL_MODE_SOLID; rasterDesc.CullMode = D3D12_CULL_MODE_NONE; rasterDesc.FrontCounterClockwise = FALSE; rasterDesc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; rasterDesc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; rasterDesc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; rasterDesc.DepthClipEnable = TRUE; rasterDesc.MultisampleEnable = FALSE; rasterDesc.AntialiasedLineEnable = FALSE; rasterDesc.ForcedSampleCount = 0; rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; psoDesc.RasterizerState = rasterDesc; psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; // 🌀 Color/Blend D3D12_BLEND_DESC blendDesc; blendDesc.AlphaToCoverageEnable = FALSE; blendDesc.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) blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc; psoDesc.BlendState = blendDesc; // 🌑 Depth/Stencil State psoDesc.DepthStencilState.DepthEnable = FALSE; psoDesc.DepthStencilState.StencilEnable = FALSE; psoDesc.SampleMask = UINT_MAX; // 🖼️ Output psoDesc.NumRenderTargets = 1; psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; psoDesc.SampleDesc.Count = 1; // 🌟 Create the raster pipeline state try { ThrowIfFailed(device->CreateGraphicsPipelineState( &psoDesc, IID_PPV_ARGS(&pipelineState))); } catch (std::exception e) { std::cout << "Failed to create Graphics Pipeline!"; }
GLSL
복사

Encoding Commands

Draw 를 호출하려면 명령을 작성할 공간이 필요합니다. Command List는 베리어, 루트 시그니처, 파이프라인 커맨드등 여러명령을 담을 수 있는 객체입니다.
// 👋 Declare handles ID3D12CommandAllocator* commandAllocator; ID3D12PipelineState* initialPipelineState; ID3D12GraphicsCommandList* commandList; // 📃 Create the command list. ThrowIfFailed(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator, initialPipelineState, IID_PPV_ARGS(&commandList)));
GLSL
복사
// 🚿 Reset the command list and add new commands. ThrowIfFailed(commandAllocator->Reset()); // 🖌️ Begin using the Raster Graphics Pipeline ThrowIfFailed(commandList->Reset(commandAllocator, pipelineState)); // 🔳 Setup Resources commandList->SetGraphicsRootSignature(rootSignature); ID3D12DescriptorHeap* pDescriptorHeaps[] = {constantBufferHeap}; commandList->SetDescriptorHeaps(_countof(pDescriptorHeaps), pDescriptorHeaps); D3D12_GPU_DESCRIPTOR_HANDLE cbvHandle(constantBufferHeap->GetGPUDescriptorHandleForHeapStart()); commandList->SetGraphicsRootDescriptorTable(0, cbvHandle); // 🖼️ Indicate that the back buffer will be used as a render target. D3D12_RESOURCE_BARRIER renderTargetBarrier; renderTargetBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; renderTargetBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; renderTargetBarrier.Transition.pResource = renderTargets[frameIndex]; renderTargetBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; renderTargetBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; renderTargetBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; commandList->ResourceBarrier(1, &renderTargetBarrier); D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart()); rtvHandle.ptr = rtvHandle.ptr + (frameIndex * rtvDescriptorSize); commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); // 🎥 Record raster commands. const float clearColor[] = {0.2f, 0.2f, 0.2f, 1.0f}; commandList->RSSetViewports(1, &viewport); commandList->RSSetScissorRects(1, &surfaceSize); commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); commandList->IASetVertexBuffers(0, 1, &vertexBufferView); commandList->IASetIndexBuffer(&indexBufferView); commandList->DrawIndexedInstanced(3, 1, 0, 0, 0); // 🖼️ Indicate that the back buffer will now be used to present. D3D12_RESOURCE_BARRIER presentBarrier; presentBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; presentBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; presentBarrier.Transition.pResource = renderTargets[frameIndex]; presentBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; presentBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; presentBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; commandList->ResourceBarrier(1, &presentBarrier); ThrowIfFailed(commandList->Close());
GLSL
복사

Render

렌더링은 상수버퍼 데이터를 변경하고, 실행할 command list들을 제출하고, win32에서 출력가능하도록 스왑체인을 사용하여 화면에 그려주면 됩니다.
// 👋 declare handles std::chrono::time_point<std::chrono::steady_clock> tStart, tEnd; float elapsedTime = 0.0f; void render() { // ⌚ Frame limit set to 60 fps tEnd = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration<float, std::milli>(tEnd - tStart).count(); if (time < (1000.0f / 60.0f)) { return; } tStart = std::chrono::high_resolution_clock::now(); // 🎛️ Update Uniforms elapsedTime += 0.001f * time; elapsedTime = fmodf(elapsedTime, 6.283185307179586f); cbVS.modelMatrix = Matrix4::rotationY(elapsedTime); D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(constantBuffer->Map( 0, &readRange, reinterpret_cast<void**>(&mappedConstantBuffer))); memcpy(mappedConstantBuffer, &cbVS, sizeof(cbVS)); constantBuffer->Unmap(0, &readRange); setupCommands(); ID3D12CommandList* ppCommandLists[] = {commandList}; commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); // 🎥 Present, then wait till finished to continue execution swapchain->Present(1, 0); const UINT64 fence = fenceValue; ThrowIfFailed(commandQueue->Signal(fence, fence)); fenceValue++; if (fence->GetCompletedValue() < fence) { ThrowIfFailed(fence->SetEventOnCompletion(fence, fenceEvent)); WaitForSingleObject(fenceEvent, INFINITE); } frameIndex = swapchain->GetCurrentBackBufferIndex(); }
GLSL
복사

Destroy Handles

마지막으로 렌더링에 사용된 모든객체들의 메모리를 해제해주면 됩니다.
ComPtr<T> 를 사용한 경우 따로 해제를 하지 않아도 원할하게 메모리 해제가 가능합니다.