-
[DirectX11] StructuredBuffer2024년 03월 12일
- 유니얼
-
작성자
-
2024.03.12.:35
728x90DirectX 11로 게임 엔진 아키텍처 만들기
DirectX11을 통한 게임 엔진 아키텍처의 설계와 구현 과정에서, 데이터의 효율적인 관리와 처리는 중요한 요소 중 하나입니다. 특히, GPU에서의 데이터 처리를 최적화하기 위해 StructuredBuffer의 이해와 활용은 필수적입니다. 이번 블로그 포스트에서는 StructuredBuffer에 대해 알아보고자 합니다.
참고강의 링크:
StructuredBuffer 소개
StructuredBuffer는 DirectX11에서 제공하는 고급 데이터 버퍼 유형 중 하나로, 구조화된 데이터를 저장하고 GPU에서 직접 접근할 수 있도록 설계되었습니다. 이는 복잡한 데이터 구조를 효율적으로 GPU에 전달하고, 컴퓨트 셰이더 등에서 이를 활용할 수 있게 해줍니다. 예를 들어, 3D 모델의 정점 데이터, 물리 시뮬레이션에서의 입자 정보, 또는 렌더링에 사용되는 다양한 파라미터 등이 StructuredBuffer를 통해 관리될 수 있습니다.
StructuredBuffer의 특징
- 구조화된 데이터 저장: StructuredBuffer는 각종 구조체 형태의 데이터를 저장할 수 있어, 데이터의 의미와 사용 목적에 따라 효과적으로 구성할 수 있습니다.
- GPU에서의 직접 접근: 컴퓨트 셰이더를 포함한 다양한 셰이더 단계에서 StructuredBuffer에 저장된 데이터에 직접 접근할 수 있습니다. 이를 통해 GPU에서 복잡한 계산과 처리가 가능해집니다.
- 높은 성능: StructuredBuffer는 GPU 메모리를 효율적으로 활용하여 높은 성능의 데이터 접근과 처리를 가능하게 합니다.
StructuredBuffer 활용 예제
StructuredBuffer를 사용하여 간단한 데이터 구조를 정의하고, 이를 컴퓨트 셰이더에서 활용하는 예제를 살펴보겠습니다.
위 예제에서는 Particle이라는 간단한 구조체를 정의하고, 이를 저장하는 StructuredBuffer를 선언합니다. 컴퓨트 셰이더에서는 StructuredBuffer에 저장된 각 Particle 데이터에 접근하여 필요한 계산을 수행할 수 있습니다.
// 입력 데이터 구조체 정의: 4x4 행렬 struct InputDesc { matrix input; }; // 출력 데이터 구조체 정의: 연산 결과를 담을 4x4 행렬 struct OutputDesc { matrix result; }; // 입력 버퍼: 구조화된 버퍼로 정의된 InputDesc 구조체의 인스턴스들을 저장 StructuredBuffer<InputDesc> Input; // 출력 버퍼: 쓰기 가능한 구조화된 버퍼로 OutputDesc 구조체의 인스턴스들을 저장 RWStructuredBuffer<OutputDesc> Output; // 컴퓨트 셰이더의 엔트리 포인트 // numthreads 어트리뷰트는 한 번에 실행될 쓰레드 그룹의 크기를 지정 (이 경우, x축으로 500개 쓰레드) [numthreads(500, 1, 1)] void CS(uint id : SV_GroupIndex) // 각 쓰레드의 고유 ID { // 입력 버퍼에서 id에 해당하는 행렬을 가져와 2배로 확장 matrix result = Input[id].input * 2; // 계산된 결과를 출력 버퍼의 해당 위치에 저장 Output[id].result = result; } // 셰이더 기술 세트 정의 technique11 T0 { pass P0 { // 이 테크닉에서는 버텍스 셰이더와 픽셀 셰이더는 사용하지 않으므로 NULL로 설정 SetVertexShader(NULL); SetPixelShader(NULL); // 컴파일된 컴퓨트 셰이더를 사용 SetComputeShader(CompileShader(cs_5_0, CS())); } };
StructuredBuffer
StructuredBuffer 클래스는 Direct3D 11을 사용하여 구조화된 버퍼를 생성하고 관리하는 클래스입니다. 이 클래스는 GPU에서 처리할 데이터의 입력 및 출력을 위한 버퍼를 만듭니다. 주요 기능으로는 입력 데이터를 GPU에 전달하고 처리 결과를 읽을 수 있는 메커니즘을 제공합니다.
StructuredBuffer.h
#pragma once class TextureBuffer { public: // 생성자: 원본 텍스처를 인자로 받습니다. TextureBuffer(ComPtr<ID3D11Texture2D> src); // 소멸자: 자원을 해제합니다. ~TextureBuffer(); public: // 버퍼를 생성하는 주 함수입니다. void CreateBuffer(); private: // 입력 텍스처를 설정합니다. void CreateInput(ComPtr<ID3D11Texture2D> src); // 입력 텍스처에 대한 셰이더 리소스 뷰(SRV)를 생성합니다. void CreateSRV(); // 출력 텍스처를 생성합니다. void CreateOutput(); // 출력 텍스처에 대한 비순차적 액세스 뷰(UAV)를 생성합니다. void CreateUAV(); // 처리 결과를 저장할 텍스처를 생성합니다. void CreateResult(); public: // 텍스처의 너비, 높이, 배열 크기를 반환하는 접근자 함수들입니다. uint32 GetWidth() { return _width; } uint32 GetHeight() { return _height; } uint32 GetArraySize() { return _arraySize; } // 출력 텍스처와 그에 대한 SRV를 반환하는 접근자 함수입니다. ComPtr<ID3D11Texture2D> GetOutput() { return (ID3D11Texture2D*)_output.Get(); } ComPtr<ID3D11ShaderResourceView> GetOutputSRV() { return _outputSRV; } public: // 입력 및 출력 텍스처에 대한 SRV와 UAV를 반환하는 접근자 함수입니다. ComPtr<ID3D11ShaderResourceView> GetSRV() { return _srv; } ComPtr<ID3D11UnorderedAccessView> GetUAV() { return _uav; } private: // 입력 및 출력 텍스처와 관련 자원을 저장하는 멤버 변수입니다. ComPtr<ID3D11Texture2D> _input; ComPtr<ID3D11ShaderResourceView> _srv; // 입력 텍스처의 SRV ComPtr<ID3D11Texture2D> _output; ComPtr<ID3D11UnorderedAccessView> _uav; // 출력 텍스처의 UAV private: // 텍스처의 너비, 높이, 배열 크기 및 포맷을 저장하는 멤버 변수입니다. uint32 _width = 0; uint32 _height = 0; uint32 _arraySize = 0; DXGI_FORMAT _format; ComPtr<ID3D11ShaderResourceView> _outputSRV; // 출력 텍스처의 SRV };
StructuredBuffer.cpp
#include "pch.h" #include "StructuredBuffer.h" // 생성자: 입력 데이터, 데이터의 구조 크기, 갯수 등을 초기화하고 버퍼 생성 함수를 호출합니다. StructuredBuffer::StructuredBuffer(void* inputData, uint32 inputStride, uint32 inputCount, uint32 outputStride, uint32 outputCount) : _inputData(inputData), _inputStride(inputStride), _inputCount(inputCount), _outputStride(outputStride), _outputCount(outputCount) { // 출력 스트라이드나 카운트가 지정되지 않았다면 입력 스트라이드와 카운트를 사용합니다. if (outputStride == 0 || outputCount == 0) { _outputStride = inputStride; _outputCount = inputCount; } // 버퍼 생성 함수 호출 CreateBuffer(); } // 소멸자 StructuredBuffer::~StructuredBuffer() { // Direct3D 자원 해제는 ComPtr에 의해 자동으로 관리되므로 별도로 할 작업이 없습니다. } // 버퍼 생성 전체 과정을 담당하는 함수 void StructuredBuffer::CreateBuffer() { CreateInput(); // 입력 데이터에 대한 버퍼 생성 CreateSRV(); // 입력 버퍼에 대한 SRV 생성 CreateOutput(); // 출력 데이터를 위한 버퍼 생성 CreateUAV(); // 출력 버퍼에 대한 UAV 생성 CreateResult(); // 결과 데이터를 위한 버퍼 생성 (주로 CPU에서 읽기 위함) } // 입력 버퍼 생성 void StructuredBuffer::CreateInput() { // 버퍼 설명 구조체 초기화 D3D11_BUFFER_DESC desc; ZeroMemory(&desc, sizeof(desc)); // 입력 버퍼의 바이트 크기, 바인딩 옵션, 구조체 바이트 크기 설정 desc.ByteWidth = GetInputByteWidth(); // 입력 데이터 전체 크기 계산 desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; desc.StructureByteStride = _inputStride; // 개별 요소의 크기 desc.Usage = D3D11_USAGE_DYNAMIC; // CPU가 쓰기 가능 desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // CPU 쓰기 액세스 허용 // 입력 데이터가 있는 경우 해당 데이터로 초기화 D3D11_SUBRESOURCE_DATA subResource = { 0 }; subResource.pSysMem = _inputData; // 입력 버퍼 생성 if (_inputData != nullptr) CHECK(DEVICE->CreateBuffer(&desc, &subResource, _input.GetAddressOf())); else CHECK(DEVICE->CreateBuffer(&desc, nullptr, _input.GetAddressOf())); } // 입력 버퍼에 대한 SRV 생성 void StructuredBuffer::CreateSRV() { // 입력 버퍼의 설명을 가져와서 SRV를 생성합니다. D3D11_BUFFER_DESC desc; _input->GetDesc(&desc); D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_UNKNOWN; // 구조화된 버퍼이므로 포맷은 알 수 없음 srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX; // 확장 버퍼 뷰 사용 srvDesc.BufferEx.NumElements = _inputCount; // 요소의 갯수 // SRV 생성 CHECK(DEVICE->CreateShaderResourceView(_input.Get(), &srvDesc, _srv.GetAddressOf())); } // 출력 버퍼 생성 void StructuredBuffer::CreateOutput() { // 출력 버퍼의 설명 구조체를 설정합니다. D3D11_BUFFER_DESC desc; ZeroMemory(&desc, sizeof(desc)); desc.ByteWidth = GetOutputByteWidth(); // 출력 데이터의 전체 크기 desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS; // UAV에 바인딩될 예정 desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; desc.StructureByteStride = _outputStride; // 출력 구조체의 바이트 크기 // 출력 버퍼 생성 CHECK(DEVICE->CreateBuffer(&desc, nullptr, _output.GetAddressOf())); } // 출력 버퍼에 대한 UAV 생성 void StructuredBuffer::CreateUAV() { // 출력 버퍼의 설명을 가져옵니다. D3D11_BUFFER_DESC desc; _output->GetDesc(&desc); D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc; ZeroMemory(&uavDesc, sizeof(uavDesc)); uavDesc.Format = DXGI_FORMAT_UNKNOWN; // 포맷은 알 수 없음 uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; // 버퍼 뷰 uavDesc.Buffer.NumElements = _outputCount; // 출력 요소의 갯수 // UAV 생성 CHECK(DEVICE->CreateUnorderedAccessView(_output.Get(), &uavDesc, _uav.GetAddressOf())); } // 결과 버퍼 생성 void StructuredBuffer::CreateResult() { // 출력 버퍼를 기반으로 결과 버퍼의 설명 구조체를 설정합니다. D3D11_BUFFER_DESC desc; _output->GetDesc(&desc); desc.Usage = D3D11_USAGE_STAGING; // CPU에서 읽을 수 있도록 설정 desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; // CPU 읽기 액세스 desc.BindFlags = 0; // 바인딩 없음 desc.MiscFlags = 0; // 추가 플래그 없음 // 결과 버퍼 생성 CHECK(DEVICE->CreateBuffer(&desc, NULL, _result.GetAddressOf())); } // 입력 버퍼로 데이터를 복사하는 함수 void StructuredBuffer::CopyToInput(void* data) { D3D11_MAPPED_SUBRESOURCE subResource; DC->Map(_input.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource); // 입력 버퍼 매핑 { memcpy(subResource.pData, data, GetInputByteWidth()); // 데이터 복사 } DC->Unmap(_input.Get(), 0); // 매핑 해제 } // 출력 버퍼에서 데이터를 복사하여 가져오는 함수 void StructuredBuffer::CopyFromOutput(void* data) { DC->CopyResource(_result.Get(), _output.Get()); // 출력 버퍼에서 결과 버퍼로 복사 D3D11_MAPPED_SUBRESOURCE subResource; DC->Map(_result.Get(), 0, D3D11_MAP_READ, 0, &subResource); // 결과 버퍼 매핑 { memcpy(data, subResource.pData, GetOutputByteWidth()); // 데이터 복사 } DC->Unmap(_result.Get(), 0); // 매핑 해제 }
프로젝트 호출
StructuredBufferDemo.h
#pragma once #include "IExecute.h" class StructuredBufferDemo : public IExecute { public: void Init() override; void Update() override; void Render() override; private: shared_ptr<Shader> _shader; };
StructuredBufferDemo.cpp
#include "pch.h" #include "StructuredBufferDemo.h" #include "RawBuffer.h" #include "StructuredBuffer.h" void StructuredBufferDemo::Init() { _shader = make_shared<Shader>(L"27. StructuredBufferDemo.fx"); vector<Matrix> inputs(500, Matrix::Identity); auto buffer = make_shared<StructuredBuffer>(inputs.data(), sizeof(Matrix), 500, sizeof(Matrix), 500); _shader->GetSRV("Input")->SetResource(buffer->GetSRV().Get()); _shader->GetUAV("Output")->SetUnorderedAccessView(buffer->GetUAV().Get()); _shader->Dispatch(0, 0, 1, 1, 1); vector<Matrix> outputs(500); buffer->CopyFromOutput(outputs.data()); } void StructuredBufferDemo::Update() { } void StructuredBufferDemo::Render() { }
결론
StructuredBuffer는 DirectX11에서 제공하는 강력한 데이터 버퍼 유형으로, 복잡한 구조의 데이터를 효율적으로 GPU에 전달하고 활용할 수 있게 해줍니다. 이를 통해 개발자는 더욱 복잡하고 세밀한 그래픽 및 계산 작업을 구현할 수 있으며, 게임 엔진의 성능과 기능을 향상시킬 수 있습니다. StructuredBuffer의 이해와 적절한 활용은 DirectX11 기반의 게임 엔진 아키텍처 설계와 개발에 있어 중요한 역량 중 하나입니다.
반응형다음글이전글이전 글이 없습니다.댓글