본문 바로가기
공부

[CS] 컴퓨트 쉐이더(Compute Shader)

by MY블로그 2023. 7. 11.

컴퓨트 쉐이더?

출처 : https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-dispatchthreadid

컴퓨트 쉐이더는 그래픽 처리장치(GPU)에서 병렬 계산을 수행하기 위한 프로그램 유닛 입니다.

기존에는 그래픽 렌더링 파이프라인에서 정점(Vertex), 픽셀(Pixel)등을 처리하기 위한 버텍스 쉐이더(VS. Vertex Shader)와 픽셀 쉐이더(PS , Pixel Shader)가 주로 사용되었으나, 컴퓨트 쉐이더는 그래픽 처리(렌더링파이프라인) 외의 일반적인 계산에 GPU를 활용하기 위해 도입되었습니다.

즉, 그래픽스와 별도로 사용이 가능 합니다.

 

컴퓨트 쉐이더는 주로 GPGPU(아래의 링크를 참고)작업을 수행하는 데 사용됩니다.

 

[CS] GPGPU

GPGPU ? GPGPU는 "General Purpose Computing on Graphics Processing Unit" 의 약어 이며, 그래픽 처리 장치(GPU)를 일반 목적(연산)의 작업에 사용하는 기술을 의미합니다. (즉, CPU에서 하던 작업의 일부를 GPU에서 처

rhksgml78.tistory.com

그래픽 카드의 병렬 처리 능력을 활용하여 대규모 데이터 집합의 처리, 복잡한 계산 작업, 머신러닝 알고리즘의 학습 및 추론 등을 가속화하는데 사용됩니다.

 

컴퓨트 쉐이더는 C-like 언어인 HLSL(Hight Level Shading Language) 혹은, GLSL(Graphics Library Shading Language)와 같은 언어로 작성 됩니다.

이 쉐이더 프로그램은 병렬 처리 작업을 위해 수행 되는 하나 이상의 스레드 그룹(Thread Group)으로 구성되고, 각 스레드 그룹은 작업을 독립적으로 처리하며, 각 스레드는 입력 데이터에 대해 동일한 코드를 실행하여 병렬 처리를 수행하게 됩니다.

 

컴퓨트 쉐이더를 사용하면 그래픽 카드의 수천 개의 작은 계산 코어를 활용하여 병렬 계산을 수행할 수 있습니다. 이를 통해 데이터 집합의 처리 속도를 대폭 향상시킬 수 있고, CPU와 비교하여 더 높은 성능과 처리량을 얻을 수 있습니다.


DirectX 에서의 컴퓨트 쉐이더 장단점

장점

1. 병렬 처리 및 가속화

컴퓨트 쉐이더를 사용하여 병렬 계산을 수행하면 그래픽 카드의 다수의 계산 코어를 사용하기때문에 대규모 데이터 집합의 처리를 빠르게 진행 할 수 있습니다. 

2. 그래픽 카드의 성능 활용

그래픽 카드는 그래픽 처리를 위하여 설계된 강력한 하드웨어를 가지고 있습니다.

컴퓨트 쉐이더를 사용하여 그래픽 카드의 성능을 최대까지 활용 할 수 있습니다.

(원래 GPU의 역활이었던 그래픽 처리와 동시에 작업이 가능합니다)

3. 다양한 응요 분야

GPGPU작업을 사용하는데 쓰이기때문에 GPGPU를 필요로하는 다양한 분야에 사용됩니다.

데이터 처리, 과학 및 공학연구, 기계 학습, 암호 해독, 의료 영상처리 등

 

단점

1. API 및 개발 환경의 복잡성

DirectX는 복잡한 API의 세트로 구성되어 있기떄문에 컴퓨트 쉐이더를 사용하기위해서는 적절한 API 호출 및 설정이 필요하게 됩니다.

또한 HLSL 언어를 사용하여 쉐이더를 작성해야하기 때문에 초기 학습과 개발에 많은 시간이 필요합니다.

2. 호환성 문제

컴퓨트 쉐이더는 DirectX11이상의 버전에서 지원이 됩니다.

모든 그래픽 카드에서 동일한 수준의 지원을 제공하지 않을 수 있습니다.

따라서 사용자의 그래픽 카드의 드라이버 지원 및 하드웨어 제한 사항등을 고려해야 합니다.

3. 입출력 데이터의 전송 및 동기화 오버헤드

주로 그래픽 카드의 메모리를 사용하기때문에 입력 및 출력 데이터의 전송이 필요합니다.

이는 CPU와 GPU간의 데이터 전송 및 동기화 오버헤드를 초래할 수 있습니다.

4. 범용성의 한계

컴퓨트 쉐이더는 주로 병렬 처리를 위하여 최적화 되어 있기 때문에 순차적인 작업이나 간단한 계산 작업등에는 효율직이지 못합니다.


DirectX 에서 컴퓨트 쉐이더 사용 예제

DirectX 환경에서 컴퓨트 쉐이더를 사용하는 간단한 에제를 정리해 봅니다.

우선 컴퓨트 쉐이더를 사용하기 위해서는 hlsl 형식의 쉐이더 파일이 필요합니다.

HLSL 쉐이더 파일 / ComputeShader.hlsl
// 컴퓨트 쉐이더 상수 버퍼 정의
cbuffer ConstantBuffer : register(b0)
{
    float4 constantData;  // 상수 데이터
};

// 출력 버퍼 정의
RWTexture2D<float4> outputBuffer : register(u0);

// 컴퓨트 쉐이더의 진입점
[numthreads(16, 16, 1)]  // 스레드 그룹 크기 설정
void CSMain(uint3 dispatchThreadID : SV_DispatchThreadID)
{
    // 여기에 병렬 계산 작업을 수행하는 코드를 작성합니다.

    // 예시: 상수 데이터와 스레드 ID를 이용하여 결과 계산
    float4 result = constantData + float4(dispatchThreadID.xy, 0.0, 0.0);

    // 결과를 출력 버퍼에 저장합니다.
    outputBuffer[dispatchThreadID.xy] = result;
}
main.cpp 에서 사용하기
#include <d3d11.h>
#include <d3dcompiler.h>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")

// 컴파일된 쉐이더 데이터
extern const char* g_ComputeShaderCode;

int main()
{
    // DirectX 초기화 및 디바이스 생성

    ID3D11Device* device = nullptr;
    ID3D11DeviceContext* context = nullptr;

    D3D11CreateDevice(
        nullptr, 
        D3D_DRIVER_TYPE_HARDWARE, 
        nullptr, 
        0, 
        nullptr, 
        0, 
        D3D11_SDK_VERSION, 
        &device, 
        nullptr, 
        &context
    );

    // 컴퓨트 쉐이더 컴파일 및 생성

    ID3D11ComputeShader* computeShader = nullptr;

    ID3DBlob* shaderBlob = nullptr;
    ID3DBlob* errorBlob = nullptr;

    D3DCompile(
        g_ComputeShaderCode,                   // HLSL 쉐이더 코드
        strlen(g_ComputeShaderCode),            // 코드 길이
        nullptr,                                // 파일명 (없음)
        nullptr,                                // 매크로 정의 (없음)
        nullptr,                                // 헤더 파일 포인터 (없음)
        "CSMain",                               // 진입점 함수 이름
        "cs_5_0",                               // 프로파일 버전
        0,                                      // 컴파일 옵션 (기본값)
        0,                                      // 플래그 (기본값)
        &shaderBlob,                            // 컴파일된 쉐이더 코드
        &errorBlob                              // 오류 메시지
    );

    device->CreateComputeShader(
        shaderBlob->GetBufferPointer(),         // 컴파일된 쉐이더 코드
        shaderBlob->GetBufferSize(),            // 코드 길이
        nullptr,                                // 클래스 인스턴스 (없음)
        &computeShader                          // 컴퓨트 쉐이더 객체
    );

    // 출력 버퍼 생성

    D3D11_TEXTURE2D_DESC outputBufferDesc;
    outputBufferDesc.Width = 256;               // 가로 해상도
    outputBufferDesc.Height = 256;              // 세로 해상도
    outputBufferDesc.MipLevels = 1;
    outputBufferDesc.ArraySize = 1;
    outputBufferDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;  // 출력 포맷
    outputBufferDesc.SampleDesc.Count = 1;
    outputBufferDesc.SampleDesc.Quality = 0;
    outputBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    outputBufferDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;  // 언오더드 액세스
    outputBufferDesc.CPUAccessFlags = 0;
    outputBufferDesc.MiscFlags = 0;

    ID3D11Texture2D* outputBuffer = nullptr;
    device->CreateTexture2D(&outputBufferDesc, nullptr, &outputBuffer);

    D3D11_UNORDERED_ACCESS_VIEW_DESC outputBufferUAVDesc;
    outputBufferUAVDesc.Format = outputBufferDesc.Format;
    outputBufferUAVDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
    outputBufferUAVDesc.Texture2D.MipSlice = 0;

    ID3D11UnorderedAccessView* outputBufferUAV = nullptr;
    device->CreateUnorderedAccessView(outputBuffer, &outputBufferUAVDesc, &outputBufferUAV);

    // 컴퓨트 쉐이더 실행

    context->CSSetShader(computeShader, nullptr, 0);
    context->CSSetUnorderedAccessViews(0, 1, &outputBufferUAV, nullptr);

    context->Dispatch(16, 16, 1);  // 스레드 그룹 실행

    // 결과 확인 및 후속 처리

    // ...

    // 자원 해제

    computeShader->Release();
    shaderBlob->Release();
    errorBlob->Release();
    outputBuffer->Release();
    outputBufferUAV->Release();
    context->Release();
    device->Release();

    return 0;
}

위의 예제는 D3D11을 사용하여 컴퓨트 쉐이더를 컴파일하고 실행하는 에제 입니다.

C++ 코드에서 컴파일된 쉐이더코드를 가져와 컴퓨트 쉐이더 객체를 생성합니다.

컴퓨트 쉐이더를 실행하기 전에 컨텍스트에 쉐이더와 출력 버퍼를 설정하고, 이후 'Dispatch' 함수를 사용하여 스레드 그룹을 실행하고 결과를 확인하고 후속 처리를 진행 합니다.

마지막으로 생성된 자원들을 해제해 줍니다.

위의 코드는 간단히 보기위한 예제이며 실제 사용시에는 오류 처리, 리소스 관리, 입력 데이터 처리 등등 다양한 부분들을 구현해야 합니다.

 


정리

D3D를 사용하여 게임을 제작할때에 맵을 만들거나 수정할때 계산의 성능을 올리기 위하여 CPU에서만 진행하던 계산을 GPU에서 작업을 하는것을 GPGPU라고 합니다.

그 GPGPU를 사용하기 위해서는 컴퓨트 쉐이더가 필요합니다.

댓글