🧠 1. Compute Shader란?

Compute Shader는 기존의 렌더링 파이프라인에서 벗어나 GPU의 병렬 처리 능력을 범용 연산(GPGPU: General-Purpose computing on Graphics Processing Units)에 사용할 수 있게 해주는 쉐이더 단계다.

CPU는 연산이 복잡한 작업에 적합하고, GPU는 대량의 단순한 작업을 병렬로 빠르게 처리할 수 있다. 이러한 특성을 활용하여 CPU의 부담을 줄이고 GPU의 자원을 효율적으로 활용하는 것이 목적이다.

✨ Compute Shader의 활용 예시

  • 대규모 텍스처 데이터 가공
  • 실시간 영상 처리 / 합성
  • 물리 기반 애니메이션 보간
  • AI, 암호학, 채굴 연산

📦 2. RawBuffer란?

RawBuffer, 혹은 Byte Address Buffer는 DirectX11에서 도입된 버퍼의 한 형태로, 특정 데이터 구조가 아닌 바이트 단위의 주소를 직접 연산하여 접근하는 방식이다. 마치 C++의 byte* 포인터처럼 동작하며, 데이터 형식에 구애받지 않는 범용성이 특징이다.

  • SRV(Shader Resource View) → GPU가 데이터를 읽는 뷰
  • UAV(Unordered Access View) → GPU가 데이터를 쓰는 뷰
  • Staging Buffer → GPU에서 계산된 결과를 CPU가 읽기 위한 중간 버퍼

🏗️ 3. RawBuffer 클래스 구성

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();  // 결과 버퍼 생성

🧪 4. Compute Shader 작성: RawBufferDemo.fx

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 값을 갖고 있으며, 이 정보를 이용해 출력 버퍼의 자신만의 주소를 계산하고 데이터를 저장한다.


🧪 5. 실제 사용 예: RawBufferDemo

✅ Output 구조체 정의

struct Output
{
    uint32 groupID[3];
    uint32 groupThreadID[3];
    uint32 dispatchThreadID[3];
    uint32 groupIndex;
};

✅ Init 단계 코드 흐름

// 쓰레드 수 계산
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());

✅ 결과를 엑셀(csv)로 저장

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 ShaderGPU에 일반 연산을 시키는 쉐이더 단계
RawBuffer바이트 주소 기반 GPU 리소스
SRV / UAV읽기/쓰기 전용 뷰 객체
Dispatch(x,y,z)x×y×z 개수의 쓰레드 그룹을 실행
SV_GroupIndex그룹 내 고유 스레드 인덱스
Store() / Store3()주소 기반 데이터 쓰기 함수

profile
李家네_공부방

0개의 댓글