-
[DirectX11] RawBuffer2024년 03월 12일
- 유니얼
-
작성자
-
2024.03.12.:06
728x90DirectX 11로 게임 엔진 아키텍처 만들기
DirectX11 학습 과정에서, RawBuffer는 게임 엔진 아키텍처 내에서 중요한 역할을 하는 개념입니다. RawBuffer, 종종 구조화되지 않은 버퍼(Unstructured Buffer)라고도 불리며, 다양한 유형의 데이터를 GPU에 효율적으로 전송하는 데 사용됩니다. 이 블로그 포스트에서는 RawBuffer의 개념, 사용 사례, 그리고 DirectX11을 사용한 간단한 예제 코드를 통해 이를 구현하는 방법을 살펴보겠습니다.
참고강의 링크:
RawBuffer란?
RawBuffer는 그 이름에서 알 수 있듯이, 어떠한 특정 데이터 구조에 국한되지 않는 범용 데이터 버퍼입니다. 이는 텍스처 데이터, 정점 데이터, 또는 컴퓨트 셰이더에서 사용되는 임의의 데이터와 같이, 다양한 형태와 크기의 데이터를 저장할 수 있습니다. RawBuffer의 가장 큰 장점은 데이터의 유연한 처리와 GPU에서의 고속 접근이 가능하다는 것입니다.
RawBuffer의 이해와 활용
DirectX11에서 RawBuffer는 매우 유연한 데이터 저장 및 접근 방식을 제공합니다. 특히, RWByteAddressBuffer는 읽기와 쓰기가 가능한 RawBuffer의 한 형태로, 개발자가 버퍼 내 임의의 위치에 접근하여 데이터를 읽고 쓸 수 있게 해줍니다. 이는 컴퓨트 셰이더에서 다양한 계산 작업을 수행할 때 유용하게 사용될 수 있습니다.
RWByteAddressBuffer 개요
RWByteAddressBuffer는 UAV(Unordered Access View)로 사용되며, 이는 GPU에서의 비순차적인 메모리 접근을 가능하게 합니다. 다른 버퍼 타입과 달리, RWByteAddressBuffer를 사용하면 개발자는 버퍼 내의 정확한 바이트 주소를 지정하여 데이터를 읽고 쓸 수 있습니다. 이는 매우 낮은 수준의 데이터 관리를 가능하게 하며, 복잡한 데이터 구조를 GPU에서 효율적으로 처리하는 데 필수적입니다.
사용 사례
RawBuffer는 다음과 같은 다양한 사용 사례에서 활용될 수 있습니다:
- 컴퓨트 셰이더 데이터 처리: 대량의 데이터를 처리하고, 결과를 저장하기 위해 컴퓨트 셰이더에서 사용됩니다.
- 렌더링 파이프라인 간의 데이터 공유: 정점 셰이더, 픽셀 셰이더 등 다양한 셰이더 스테이지 간에 데이터를 공유할 때 사용됩니다.
- GPU 기반 시뮬레이션: 입자 시스템, 유체 시뮬레이션 등 GPU를 활용한 다양한 시뮬레이션에 필요한 데이터 저장소로 사용됩니다.
DirectX11에서의 RawBuffer 구현 예제
DirectX11에서 RawBuffer를 구현하기 위해서는 ID3D11Buffer 인터페이스를 사용하여 버퍼를 생성하고, 적절한 버퍼 설명자(D3D11_BUFFER_DESC)와 서브리소스 데이터(D3D11_SUBRESOURCE_DATA)를 설정해야 합니다. 다음은 간단한 RawBuffer 생성 예제 코드입니다:
RWByteAddressBuffer Output; // 데이터 쓰기가 가능한 UAV 선언 struct ComputeInput { uint3 groupID : SV_GroupID; // 디스패치 호출 시 사용된 그룹 ID uint3 groupThreadID : SV_GroupThreadID; // 현재 스레드 그룹 내 스레드 ID uint3 dispatchThreadID : SV_DispatchThreadID; // 전체 디스패치에서의 스레드 ID uint groupIndex : SV_GroupIndex; // 스레드 그룹 내의 스레드 인덱스 }; [numthreads(10, 8, 3)] void CS(ComputeInput input) { uint index = input.groupIndex; uint outAddress = index * 10 * 4; // 버퍼 내 쓰기 시작 위치 계산 // 입력 정보를 버퍼에 저장 Output.Store3(outAddress + 0, input.groupID); Output.Store3(outAddress + 12, input.groupThreadID); Output.Store3(outAddress + 24, input.dispatchThreadID); Output.Store(outAddress + 36, input.groupIndex); } technique11 T0 { pass P0 { SetVertexShader(NULL); SetPixelShader(NULL); SetComputeShader(CompileShader(cs_5_0, CS())); } };
이 예제에서는 컴퓨트 셰이더를 사용하여 각 스레드에 대한 정보(그룹 ID, 스레드 ID 등)를 RawBuffer에 저장합니다. Store3 및 Store 메서드를 사용하여 3개의 uint 또는 단일 uint 데이터를 버퍼의 특정 바이트 주소에 저장합니다. 이 과정에서 버퍼 내 데이터의 정확한 위치를 계산하여, 각 스레드의 정보를 구분하여 저장합니다.
RawBuffer
RawBuffer클래스는 Direct3D 11을 사용하여 GPU 계산에 필요한 데이터를 관리하고, 결과를 CPU로 다시 가져오는 과정을 캡슐화합니다. 생성자에서는 입력 데이터와 그 크기, 그리고 출력 데이터의 크기를 받습니다. 이 클래스는 데이터를 GPU로 전송하기 위한 입력 버퍼, 쉐이더에서 데이터를 읽기 위한 쉐이더 리소스 뷰(SRV), 처리 결과를 저장하기 위한 출력 버퍼, 출력 데이터에 대한 접근을 허용하는 비순차적 액세스 뷰(UAV), 그리고 최종 결과를 CPU로 다시 읽어오기 위한 결과 버퍼를 생성하고 관리합니다.
RawBuffer.h
#pragma once class RawBuffer { public: // 생성자: 초기 입력 데이터, 입력 및 출력 데이터의 바이트 크기를 인자로 받음 RawBuffer(void* inputData, uint32 inputByte, uint32 outputByte); // 소멸자: 생성된 D3D11 자원 해제 ~RawBuffer(); public: // 버퍼 생성 함수: 입력, 출력 및 결과 버퍼 생성 void CreateBuffer(); // 입력 데이터를 GPU로 복사 void CopyToInput(void* data); // GPU 처리 결과를 CPU로 복사 void CopyFromOutput(void* data); public: // 쉐이더 리소스 뷰(SRV) 접근자 ComPtr<ID3D11ShaderResourceView> GetSRV() { return _srv; } // 비순차적 액세스 뷰(UAV) 접근자 ComPtr<ID3D11UnorderedAccessView> GetUAV() { return _uav; } private: // 입력 버퍼 생성 void CreateInput(); // 쉐이더 리소스 뷰 생성 void CreateSRV(); // 출력 버퍼 생성 void CreateOutput(); // 비순차적 액세스 뷰 생성 void CreateUAV(); // 결과 버퍼 생성 void CreateResult(); private: ComPtr<ID3D11Buffer> _input; // 입력 버퍼 ComPtr<ID3D11ShaderResourceView> _srv; // 입력 데이터의 SRV ComPtr<ID3D11Buffer> _output; // 출력 버퍼 ComPtr<ID3D11UnorderedAccessView> _uav; // 출력 데이터의 UAV ComPtr<ID3D11Buffer> _result; // 결과 데이터 버퍼 private: void* _inputData; // 초기 입력 데이터 uint32 _inputByte = 0; // 입력 데이터 크기 uint32 _outputByte = 0; // 출력 데이터 크기 };
RawBuffer.cpp
#include "pch.h" #include "RawBuffer.h" RawBuffer::RawBuffer(void* inputData, uint32 inputByte, uint32 outputByte) : _inputData(inputData), _inputByte(inputByte), _outputByte(outputByte) { CreateBuffer(); } RawBuffer::~RawBuffer() { } // 버퍼 생성 관련 여러 함수를 순서대로 호출 void RawBuffer::CreateBuffer() { CreateInput(); // 입력 버퍼 생성 CreateSRV(); // 쉐이더 리소스 뷰 생성 CreateOutput(); // 출력 버퍼 생성 CreateUAV(); // 비순차적 액세스 뷰 생성 CreateResult(); // 결과 버퍼 생성 } // 입력 버퍼에 데이터를 복사 void RawBuffer::CopyToInput(void* data) { D3D11_MAPPED_SUBRESOURCE subResource; // 입력 버퍼를 맵핑하여 CPU에서 데이터를 쓸 수 있도록 준비 DC->Map(_input.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource); { // 맵핑된 메모리에 데이터 복사 memcpy(subResource.pData, data, _inputByte); } // 맵핑 해제 DC->Unmap(_input.Get(), 0); } // 출력 버퍼에서 데이터를 복사 void RawBuffer::CopyFromOutput(void* data) { // 출력 버퍼의 데이터를 결과 버퍼로 복사 DC->CopyResource(_result.Get(), _output.Get()); D3D11_MAPPED_SUBRESOURCE subResource; // 결과 버퍼를 맵핑하여 CPU에서 데이터를 읽을 수 있도록 준비 DC->Map(_result.Get(), 0, D3D11_MAP_READ, 0, &subResource); { // 맵핑된 메모리로부터 데이터 복사 memcpy(data, subResource.pData, _outputByte); } // 맵핑 해제 DC->Unmap(_result.Get(), 0); } // 입력 버퍼 생성 로직 void RawBuffer::CreateInput() { if (_inputByte == 0) return; // 입력 바이트가 0이면 함수 종료 D3D11_BUFFER_DESC desc; ZeroMemory(&desc, sizeof(desc)); desc.ByteWidth = _inputByte; // 버퍼 크기 설정 desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; // SRV로 사용될 것임을 명시 desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS; // RAW 버퍼 허용 desc.Usage = D3D11_USAGE_DYNAMIC; // CPU에서 쓰기 가능하며 GPU에서 읽기 가능 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 RawBuffer::CreateSRV() { if (_inputByte == 0) return; // 입력 바이트가 0이면 함수 종료 D3D11_BUFFER_DESC desc; _input->GetDesc(&desc); // 입력 버퍼의 설명 가져오기 D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_R32_TYPELESS; // 포맷은 쉐이더에서 해석될 것임을 명시 srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX; // 확장된 버퍼 뷰 srvDesc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW; // RAW 플래그 설정 srvDesc.BufferEx.NumElements = desc.ByteWidth / 4; // 원소 개수 설정 // SRV 생성 CHECK(DEVICE->CreateShaderResourceView(_input.Get(), &srvDesc, _srv.GetAddressOf())); } // 출력 버퍼 생성 로직 void RawBuffer::CreateOutput() { D3D11_BUFFER_DESC desc; ZeroMemory(&desc, sizeof(desc)); desc.ByteWidth = _outputByte; // 버퍼 크기 설정 desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS; // UAV로 사용될 것임을 명시 desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS; // RAW 버퍼 허용 // 버퍼 생성 CHECK(DEVICE->CreateBuffer(&desc, NULL, _output.GetAddressOf())); } // 비순차적 액세스 뷰(UAV) 생성 로직 void RawBuffer::CreateUAV() { D3D11_BUFFER_DESC desc; _output->GetDesc(&desc); // 출력 버퍼의 설명 가져오기 D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc; ZeroMemory(&uavDesc, sizeof(uavDesc)); uavDesc.Format = DXGI_FORMAT_R32_TYPELESS; // 포맷은 쉐이더에서 해석될 것임을 명시 uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; // 버퍼 뷰 uavDesc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; // RAW 플래그 설정 uavDesc.Buffer.NumElements = desc.ByteWidth / 4; // 원소 개수 설정 // UAV 생성 CHECK(DEVICE->CreateUnorderedAccessView(_output.Get(), &uavDesc, _uav.GetAddressOf())); } // 결과 버퍼 생성 로직 void RawBuffer::CreateResult() { D3D11_BUFFER_DESC desc; _output->GetDesc(&desc); // 출력 버퍼의 설명을 다시 가져오기 // 결과 버퍼를 CPU에서 읽을 수 있도록 설정 변경 desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; desc.BindFlags = 0; // 더 이상 UAV로 바인드되지 않음 desc.MiscFlags = 0; // 결과 버퍼 생성 CHECK(DEVICE->CreateBuffer(&desc, nullptr, _result.GetAddressOf())); }
프로젝트 호출
RawBufferDemo.h
#pragma once #include "IExecute.h" class RawBufferDemo :public IExecute { struct Output { uint32 groupID[3]; uint32 groupThreadID[3]; uint32 dispatchThreadID[3]; uint32 groupIndex; }; public: void Init() override; void Update() override; void Render() override; private: shared_ptr<Shader> _shader; private: };
RawBufferDemo.cpp
#include "pch.h" #include "RawBufferDemo.h" #include "RawBuffer.h" //sv-groupindex 설명 링크 //https://learn.microsoft.com/ko-kr/windows/win32/direct3dhlsl/sv-groupindex void RawBufferDemo::Init() { _shader = make_shared<Shader>(L"24. RawBufferDemo.fx"); // 하나의 쓰레드 그룹 내에서, 운영할 쓰레드 개수 uint32 count = 10 * 8 * 3; shared_ptr<RawBuffer> rawBuffer = make_shared<RawBuffer>(nullptr, 0, sizeof(Output) * count); _shader->GetUAV("Output")->SetUnorderedAccessView(rawBuffer->GetUAV().Get()); // x, y, z 쓰레드 그룹 _shader->Dispatch(0, 0, 1, 1, 1); vector<Output> outputs(count); rawBuffer->CopyFromOutput(outputs.data()); FILE* file; ::fopen_s(&file, "../RawBuffer.csv", "w"); ::fprintf ( file, "GroupID(X),GroupID(Y),GroupID(Z),GroupThreadID(X),GroupThreadID(Y),GroupThreadID(Z),DispatchThreadID(X),DispatchThreadID(Y),DispatchThreadID(Z),GroupIndex\n" ); for (uint32 i = 0; i < count; i++) { const Output& temp = outputs[i]; ::fprintf ( file, "%d,%d,%d, %d,%d,%d, %d,%d,%d, %d\n", temp.groupID[0], temp.groupID[1], temp.groupID[2], temp.groupThreadID[0], temp.groupThreadID[1], temp.groupThreadID[2], temp.dispatchThreadID[0], temp.dispatchThreadID[1], temp.dispatchThreadID[2], temp.groupIndex ); } ::fclose(file); } void RawBufferDemo::Update() { } void RawBufferDemo::Render() { }
결론
RawBuffer는 DirectX11 게임 엔진 아키텍쳐에서 다양한 용도로 사용될 수 있는 유연하고 강력한 데이터 저장소입니다. 이를 통해 개발자들은 GPU에서의 데이터 처리와 렌더링 성능을 최적화할 수 있으며, 더 복잡하고 다양한 시각적 효과와 시뮬레이션을 실시간으로 구현할 수 있습니다.
반응형다음글이전글이전 글이 없습니다.댓글