DirectX

76_StructureBuffer

devRiripong 2024. 3. 23. 12:01
반응형

byte address 즉 raw buffer를 통해서 실질적으로 compute shader 랑 작업하는 것을 알아봤고,

texture buffer를 이용해서 texture를 조작하는 것을 알아봤다.

 

대부분의 경우는 raw buffer 보다는 structured buffer라는 것을 이용해서 원하는 구조체의 배열을 만들어서 관리하는 게 쉽다.

 

1. StructuredBuffer 클래스 만들기

Engine/05. ComputeShader/Buffer 필터에 StructuredBuffer 클래스를 만든다.

 

#pragma once
class StructuredBuffer
{
public:
	StructuredBuffer(void* inputData, uint32 inputStride, uint32 inputCount, uint32 outputStride = 0, uint32 outputCount = 0);
	~StructuredBuffer();

public:
	void CreateBuffer();

private:
	void CreateInput();
	void CreateSRV();
	void CreateOutput();
	void CreateUAV();
	void CreateResult();

public:
	uint32 GetInputByteWidth() { return _inputStride * _inputCount; }
	uint32 GetOutputByteWidth() { return _outputStride * _outputCount; }

	void CopyToInput(void* data);
	void CopyFromOutput(void* data);

public:
	ComPtr<ID3D11ShaderResourceView> GetSRV() { return _srv; }
	ComPtr<ID3D11UnorderedAccessView> GetUAV() { return _uav; }

private:
	ComPtr<ID3D11Buffer> _input;
	ComPtr<ID3D11ShaderResourceView> _srv; // Input
	ComPtr<ID3D11Buffer> _output;
	ComPtr<ID3D11UnorderedAccessView> _uav; // Output
	ComPtr<ID3D11Buffer> _result;

private:
	void* _inputData;
	uint32 _inputStride = 0;
	uint32 _inputCount = 0;
	uint32 _outputStride = 0;
	uint32 _outputCount = 0;
};
#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()
{

}

void StructuredBuffer::CreateBuffer()
{
	CreateInput();
	CreateSRV();
	CreateOutput();
	CreateUAV();
	CreateResult();
}

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;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

	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()));
}

void StructuredBuffer::CreateSRV()
{
	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;

	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;
	desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
	desc.StructureByteStride = _outputStride;

	CHECK(DEVICE->CreateBuffer(&desc, nullptr, _output.GetAddressOf()));
}

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;

	CHECK(DEVICE->CreateUnorderedAccessView(_output.Get(), &uavDesc, _uav.GetAddressOf()));
}

void StructuredBuffer::CreateResult()
{
	D3D11_BUFFER_DESC desc;
	_output->GetDesc(&desc);

	desc.Usage = D3D11_USAGE_STAGING;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
	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);
}

RawBuffer와 대칭적이고 동일하다.

 

2. 27. StructuredBufferDemo.fx 만들기

이걸 간단하게 사용하는 예제를 만들어 본다.

26. TextureBufferDemo.fx를 복붙하고 이름을 27. StructuredBufferDemo.fx 라 한다.

Client/Shaders/Week4 필터에 넣는다.

 

먼저 27. StructuredBufferDemo.fx부터 작업을 한다.

struct InputDesc
{
    matrix input; 
};

struct OutputDesc
{
    matrix result; 
};

StructuredBuffer<InputDesc> Input; // vector 처럼 같은 타임의 데이터가 여러개 있는 거 
RWStructuredBuffer<OutputDesc> Output;

[numthreads(500, 1, 1)] 
void CS(uint id : SV_GroupIndex)
{    
    matrix result = Input[id].input*2; // 가공을 2를 곱하는 걸로
    
    Output[id].result = result; 
}

technique11 T0
{
    pass P0
    {
        SetVertexShader(NULL); 
        SetPixelShader(NULL); 
        SetComputeShader(CompileShader(cs_5_0, CS())); 
    }
};

 

3. StructuredBufferDemo 클래스를 만들어서 테스트하기

rawBufferDemo 클래스를 복붙 해서 이름을 StructuredBufferDemo라고 하고, Client/Game 필터에 넣는다. 코드를 StructuredBufferDemo에 맞게 수정한다.

	_shader = make_shared<Shader>(L"27. StructuredBufferDemo.fx");

로그를 찍을 것이 아니라서 필요 없는 부분은 삭제한다.

 

Main에 가서도 세팅을 한다.

 

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()); 

}

메모리에 성공적으로 들어가는지 확인하기 위해 StructuredBufferDemo 끝에 break point를 잡고

실행을 해보면

Dispatch를 호출하는 순간에 GPU에 떠넘겨가지고 500개의 스레드를 고용해 가지고

일감을 실행한 상태가 되는 것이고,

각기의 스레드들은 하나의 자기가 담당하는 메트릭스를 담당해서 2를 곱하는 작업만 해 놓은 상태다.

 

Identity에 2가 곱해져서 2가 들어가 있다.

성공적으로 데이터가 가공되어 있는 걸 확인할 수 있다.

 

사용할 때는 raw buffer를 사용할 때처럼 원시적인 데이터를 넣어줄 수 있을 것이고, 정해준 형태로 동일한 데이터를 여러 개 사용하는 방식으로 할 수도 있다. 대부분의 경우는 의미 없이 offset을 정해서 하기보다는 이런 식으로 고정된 데이터를 여러 개를 넣어 주는 경우가 많을 테니까 instancing을 할 때는 matrix 거나 그 본 행렬 정보를 넣어줘서 그거를 instancing 한 개수만큼 뿌려서 넣어준다거나 이런 식으로 사용하는 경우가 대부분일 테니까 그런 경우에는 structured buffer를 사용하는 걸 고려할 수 있다.

 

GPU에다가 CPU 대신 연산하는 걸 시키고 싶을 때는 버퍼를 만들어 주는 코드는 재사용하면 될 것이고, StructuredBufferDemo::Init의 부분만 만들어 주면 효율적으로 코드를 만들 수 있다.

 

GPUPU compute shader를 사용하는 방식 중 마지막 방식이었으니까 앞으로 콘텐츠를 만나게 되면 이걸 활용하는 작업을 해본다.

반응형