이번 챕터에서 주된 공부할 내용은 CPU에서 하던 일을 GPU에게 떠넘기는 것이다.
CPU는 할일이 많은 반면(우리가 짠 코드 이외에도 여러 일을 담당) GPU는 할 일이 많이 없는 경우가 생긴다. 그래서 우리의 목표는 CPU도 바쁘게 GPU도 더 많은 일을 할당 받아 바쁘게 돌아가게 하는 것이다.
GPU한테 어떻게 일을 분산 시킬 것인가? 에 대한 대답은 자연스레 알게 될 것이다.
RawBuffer
RawBuffer는 Byte Address Buffer인데 주소에 대한 직접적인 연산해서 접근하는 Buffer로, C++로 따지면 Byte형 포인터를 다루는 것과 동일하다. RawBuffer라는 이름이 붙은 이유는 정말 날것을 취급하기 때문이다.
이 친구도 결국 Buffer이기 때문에 Rescource가 필요하다.
그전에 작업한 내용을 상기 해보면 Rescource Type을 만든 후 ResourceView를 생성해 값을 채운 후 View를 Gpu에게 넘겨주는 형식이었다. 이는 RawBuffer도 마찬가지이다.
RawBuffer를 통해서 입력을 받아주고 ComputeShader에 넘겨주고 연산을 진행한다음 다시 RawBuffer에 값을 다시 받아주게 할 것이다.
#pragma once
class RawBuffer
{
public:
RawBuffer(void* inputData, uint32 inputByte, uint32 outputByte);
~RawBuffer();
public:
void CreateBuffer();
void CopyToInput(void* data);
void CopyFromOutput(void* data);
private:
void CreateInput();
void CreateSRV();
void CreateOutput();
void CreateUAV();
void CreateResult();
private:
ComPtr<ID3D11Buffer> _input;
ComPtr<ID3D11ShaderResourceView> _srv;
ComPtr<ID3D11Buffer> _output;
ComPtr<ID3D11UnorderedAccessView> _uav;
ComPtr<ID3D11Buffer> _result;
private:
void* _inputData;
uint32 _inputByte = 0;
uint32 _outputByte = 0;
};
#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;
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)
{
// 출력 데이터 -> result에 복사
DC->CopyResource(_result.Get(), _output.Get());
D3D11_MAPPED_SUBRESOURCE subResource;
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;
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.ByteWidth = _inputByte;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS; // RAW_BUFFER
desc.Usage = D3D11_USAGE_DYNAMIC; // CPU-WRITE, GPU-READ
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 RawBuffer::CreateSRV()
{
if (_inputByte == 0)
return;
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; // SRV_FLAG_RAW
srvDesc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW;
srvDesc.BufferEx.NumElements = desc.ByteWidth / 4; // 전체 데이터 개수
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;
desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
CHECK(DEVICE->CreateBuffer(&desc, NULL, _output.GetAddressOf()));
}
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;
uavDesc.Buffer.NumElements = desc.ByteWidth / 4;
CHECK(DEVICE->CreateUnorderedAccessView(_output.Get(), &uavDesc, _uav.GetAddressOf()));
}
void RawBuffer::CreateResult()
{
D3D11_BUFFER_DESC desc;
_output->GetDesc(&desc);
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.BindFlags = D3D11_USAGE_DEFAULT; // UAV가 연결되려면, USAGE는 DEFAULT여야 함.
desc.MiscFlags = 0;
CHECK(DEVICE->CreateBuffer(&desc, nullptr, _result.GetAddressOf()));
}
이것을 쉐이더를 만들어서 테스트 해보면 다음과 같다.
RWByteAddressBuffer Output; // UAV
struct ComputeInput
{
uint3 groupID : SV_GroupID;
uint3 groupThreadID : SV_GroupThreadID;
uint3 dispatchThreadID : SV_DispatchThreadID;
uint3 groupIndex : SV_GroupIndex;
};
// 10 * 8 * 3 개의 쓰레드를 이용하겠다
[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.Store3(outAddress + 36, input.groupIndex);
}
technique11 T0
{
pass P0
{
SetVertexShader(NULL);
SetPixelShader(NULL);
SetComputeShader(CompileShader(cs_5_0, CS()));
}
};
Cpu의 경우 쓰레드를 만들어서 공용 데이터를 락을 걸어서 관리했다면 Gpu의 경우 고용할 쓰레드들이 너무 많기 때문에 각 쓰레드 마다 고유한 넘버링이 적용된다는 것을 확인할 수 있다. 즉 어떠한 쓰레드 그룹에 내가 몇번째 쓰레드인지 알고 있다는 것이고 그렇기 때문에 우리가 데이터를 밀어넣을 때, 원하는 쓰레드에 접근할 수 있다는 것이다.
System Value 분석
sv_groupindex에 대한 공식 문서를 보면 다음과 같다.
이는 마치 군단, 군대와 같다. 0,0,0 과 같이 하나의 셀은 하나의 병사를 의미한다고 생각하고 그 병사들이 오와열을 맞춰 나열해 3차원 공간을 만든 것이다. 이 처럼 하나의 셀이 하나의 쓰레드이다. 예제에서 사용한것 처럼 10 * 8 * 3 명의 병사들이 모여 하나의 부대가 되는 것이다. 그리고 Dispatch에서 5, 3, 2 로 설정한 것만큼의 각각의 부대가 그룹화 되어 있는 것이다.
즉, Disptach에서 설정한 3차원 크기 하나당 쓰레드 들이 3차원 공간을 또 이루고 있다는 것이다.
그러면 이 부분이 뭘 의미하는지 금방 알 수 있다.
- SV_GroupThreadID : 하나의 그룹 내 자신의 좌표를 의미한다.(그룹 내 고유함. 타 그룹이랑은 중복)
- SV_GroupID : 특정 그룹 좌표를 나타낸다.(유일한)
- SV_DispatchThreadID : 속한 그룹 좌표와 그룹의 사이즈를 곱하고 자신의 좌표를 더해 "고유" 한 자신만의 쓰레드 번호이다.
- SV_GroupIndex : 하나의 그룹 내 자신의 3차원 좌표를 1차원으로 변환한 값으로 그룹 내 자신의 쓰레드 번호이다.(2차원 배열에서 1차원 배열로 바꾸는 것과 동일)
이처럼 우리가 이러한 넘버링을 통해 어떤 GPU 쓰레드에 어떤 일을 시킬지 정하게 되는 것이다.
TextureBuffer
RawBuffer는 정말 이해를 위해 단순하게 사용해본 것이다.
미래를 위해 텍스처를 가공하는 Buffer를 만들어보자. 기존에 사용했던 베이가 이미지를 색상을 변환해서 돌려받는 식으로 테스트 해보자.
#pragma once
class TextureBuffer
{
public:
TextureBuffer(ComPtr<ID3D11Texture2D> src);
~TextureBuffer();
public:
void CreateBuffer();
private:
void CreateInput(ComPtr<ID3D11Texture2D> src);
void CreateSRV();
void CreateOutput();
void CreateUAV();
void CreateResult();
public:
uint32 GetWidth() { return _width; }
uint32 GetHeight() { return _height; }
uint32 GetArraySize() { return _arraySize; }
ComPtr<ID3D11Texture2D> GetOutput() { return (ID3D11Texture2D*)_output.Get(); }
ComPtr<ID3D11ShaderResourceView> GetOutputSRV() { return _outputSRV; }
public:
ComPtr<ID3D11ShaderResourceView> GetSRV() { return _srv; }
ComPtr<ID3D11UnorderedAccessView> GetUAV() { return _uav; }
private:
ComPtr<ID3D11Texture2D> _input;
ComPtr<ID3D11ShaderResourceView> _srv; // Input
ComPtr<ID3D11Texture2D> _output;
ComPtr<ID3D11UnorderedAccessView> _uav; // Output
private:
uint32 _width = 0;
uint32 _height = 0;
uint32 _arraySize = 0;
DXGI_FORMAT _format;
ComPtr<ID3D11ShaderResourceView> _outputSRV;
};
#include "pch.h"
#include "TextureBuffer.h"
TextureBuffer::TextureBuffer(ComPtr<ID3D11Texture2D> src)
{
CreateInput(src);
CreateBuffer();
}
TextureBuffer::~TextureBuffer()
{
}
void TextureBuffer::CreateBuffer()
{
CreateSRV();
CreateOutput();
CreateUAV();
CreateResult();
}
void TextureBuffer::CreateInput(ComPtr<ID3D11Texture2D> src)
{
D3D11_TEXTURE2D_DESC srcDesc;
src->GetDesc(&srcDesc);
_width = srcDesc.Width;
_height = srcDesc.Height;
_arraySize = srcDesc.ArraySize;
_format = srcDesc.Format;
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Width = _width;
desc.Height = _height;
desc.ArraySize = _arraySize;
desc.Format = _format;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
CHECK(DEVICE->CreateTexture2D(&desc, NULL, _input.GetAddressOf()));
DC->CopyResource(_input.Get(), src.Get());
}
void TextureBuffer::CreateSRV()
{
D3D11_TEXTURE2D_DESC desc;
_input->GetDesc(&desc);
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
srvDesc.Format = desc.Format;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
srvDesc.Texture2DArray.MipLevels = 1;
srvDesc.Texture2DArray.ArraySize = _arraySize;
CHECK(DEVICE->CreateShaderResourceView(_input.Get(), &srvDesc, _srv.GetAddressOf()));
}
void TextureBuffer::CreateOutput()
{
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
desc.Width = _width;
desc.Height = _height;
desc.ArraySize = _arraySize;
desc.Format = _format;
desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
CHECK(DEVICE->CreateTexture2D(&desc, nullptr, _output.GetAddressOf()));
}
void TextureBuffer::CreateUAV()
{
D3D11_TEXTURE2D_DESC desc;
_output->GetDesc(&desc);
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
ZeroMemory(&uavDesc, sizeof(D3D11_UNORDERED_ACCESS_VIEW_DESC));
uavDesc.Format = DXGI_FORMAT_UNKNOWN;
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
uavDesc.Texture2DArray.ArraySize = _arraySize;
CHECK(DEVICE->CreateUnorderedAccessView(_output.Get(), &uavDesc, _uav.GetAddressOf()));
}
void TextureBuffer::CreateResult()
{
D3D11_TEXTURE2D_DESC desc;
_output->GetDesc(&desc);
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
srvDesc.Format = desc.Format;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
srvDesc.Texture2DArray.MipLevels = 1;
srvDesc.Texture2DArray.ArraySize = _arraySize;
CHECK(DEVICE->CreateShaderResourceView(_output.Get(), &srvDesc, _outputSRV.GetAddressOf()));
}
하나의 쓰레드가 하나의 픽셀을 맡아서 텍스처를 변형하게 된다.
Texture2DArray<float4> Input;
RWTexture2DArray<float4> Output;
[numthreads(32, 32, 1)]
void CS(uint3 id : SV_DispatchThreadID)
{
float4 color = Input.Load(int4(id, 0));
//Output[id] = color;
Output[id] = 1.0f - color;
//Output[id] = (color.r + color.g + color.b) / 3.0f;
}
technique11 T0
{
pass P0
{
SetVertexShader(NULL);
SetPixelShader(NULL);
SetComputeShader(CompileShader(cs_5_0, CS()));
}
};
StructureBuffer
실질적으로 데이터를 주고 받을 때 배열의 형태나 구조체 형태로 넘겨주는 경우가 많다. 그래서 StructureBuffer를 구현해보자.
RawBuffer와 매우 비슷하다.
#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);
}
struct InputDesc
{
matrix input;
};
struct OutputDesc
{
matrix result;
};
StructuredBuffer<InputDesc> Input;
RWStructuredBuffer<OutputDesc> Output;
[numthreads(500, 1, 1)]
void CS(uint id : SV_GroupIndex)
{
matrix result = Input[id].input * 2;
Output[id].result = result;
}
technique11 T0
{
pass P0
{
SetVertexShader(NULL);
SetPixelShader(NULL);
SetComputeShader(CompileShader(cs_5_0, CS()));
}
};
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 물방울 책 - 랜더링 파이프라인, 그리기 연산 (0) | 2024.10.23 |
---|---|
[DirectX] GPU - ViewPort, Collider, Picking (0) | 2024.10.17 |
[DirectX] 인스턴싱 - 드로우 콜 (1) | 2024.10.10 |
[DirectX] 애니메이션 - 모델 애니메이션 (0) | 2024.10.09 |
[DirectX] 애니메이션 - 스키닝, 데이터 (1) | 2024.10.02 |