Compute Shader는 기존의 렌더링 파이프라인에서 벗어나 GPU의 병렬 처리 능력을 범용 연산(GPGPU: General-Purpose computing on Graphics Processing Units)에 사용할 수 있게 해주는 쉐이더 단계다.
CPU는 연산이 복잡한 작업에 적합하고, GPU는 대량의 단순한 작업을 병렬로 빠르게 처리할 수 있다. 이러한 특성을 활용하여 CPU의 부담을 줄이고 GPU의 자원을 효율적으로 활용하는 것이 목적이다.
RawBuffer, 혹은 Byte Address Buffer는 DirectX11에서 도입된 버퍼의 한 형태로, 특정 데이터 구조가 아닌 바이트 단위의 주소를 직접 연산하여 접근하는 방식이다. 마치 C++의 byte* 포인터처럼 동작하며, 데이터 형식에 구애받지 않는 범용성이 특징이다.
RawBuffer 클래스는 GPU와 데이터를 주고받기 위한 리소스를 캡슐화한 구조다. 입력 데이터를 GPU에 전달하고, GPU가 처리한 결과를 다시 CPU로 가져오기까지의 전체 과정을 담당한다.
_input: GPU에 전달할 입력 버퍼_srv: 입력 버퍼를 읽기 위한 SRV_output: GPU 연산 결과를 저장할 버퍼_uav: 결과 버퍼에 접근하기 위한 UAV_result: CPU가 읽을 수 있는 결과용 스테이징 버퍼RawBuffer::CopyToInput(void* data)
// CPU → GPU로 데이터를 복사
RawBuffer::CopyFromOutput(void* data)
// GPU → CPU로 결과 데이터를 복사
CreateInput(); // 입력 버퍼 생성
CreateSRV(); // 입력용 SRV 생성
CreateOutput(); // 출력 버퍼 생성
CreateUAV(); // 출력용 UAV 생성
CreateResult(); // 결과 버퍼 생성
RWByteAddressBuffer Output; // 결과값 저장 UAV
[numthreads(10, 8, 3)]
void CS(ComputeInput input)
{
uint index = input.groupIndex;
uint outAddress = index * 40;
Output.Store3(outAddress + 0, input.groupID);
Output.Store3(outAddress + 12, input.groupThreadID);
Output.Store3(outAddress + 24, input.dispatchThreadID);
Output.Store(outAddress + 36, input.groupIndex);
}
numthreads(10,8,3)는 총 240개의 스레드를 의미한다. 각각의 스레드는 고유의 groupID, threadID, dispatchThreadID, groupIndex 값을 갖고 있으며, 이 정보를 이용해 출력 버퍼의 자신만의 주소를 계산하고 데이터를 저장한다.
struct Output
{
uint32 groupID[3];
uint32 groupThreadID[3];
uint32 dispatchThreadID[3];
uint32 groupIndex;
};
// 쓰레드 수 계산
uint32 count = 10 * 8 * 3;
// RawBuffer 생성 (입력 없음)
shared_ptr<RawBuffer> rawBuffer = make_shared<RawBuffer>(nullptr, 0, sizeof(Output) * count);
// UAV 설정 및 디스패치 호출
_shader->GetUAV("Output")->SetUnorderedAccessView(rawBuffer->GetUAV().Get());
_shader->Dispatch(0, 0, 1, 1, 1); // 스레드 그룹 1개
// GPU 결과를 CPU로 복사
vector<Output> outputs(count);
rawBuffer->CopyFromOutput(outputs.data());
FILE* file;
::fopen_s(&file, "../RawBuffer.csv", "w");
::fprintf(file, "GroupID(X),...,GroupIndex\n");
for (uint32 i = 0; i < count; i++) {
const Output& temp = outputs[i];
::fprintf(file, "%d,%d,%d,...,%d\n", ...);
}
::fclose(file);
결과 파일을 보면 각 쓰레드가 규칙적으로 자신의 고유 인덱스를 갖고 있는 것을 확인할 수 있다. 이는 GPU의 병렬처리가 규칙적이고 예측 가능한 구조로 이루어진다는 핵심 포인트를 설명해준다.
| 개념 | 설명 |
|---|---|
| Compute Shader | GPU에 일반 연산을 시키는 쉐이더 단계 |
| RawBuffer | 바이트 주소 기반 GPU 리소스 |
| SRV / UAV | 읽기/쓰기 전용 뷰 객체 |
| Dispatch(x,y,z) | x×y×z 개수의 쓰레드 그룹을 실행 |
| SV_GroupIndex | 그룹 내 고유 스레드 인덱스 |
| Store() / Store3() | 주소 기반 데이터 쓰기 함수 |