Compute Shader

 

  • CPU의 ALU(산술 논리 장치)의 수가 적지만, GPU는 ALU의 수가 CPU에 비해 상당히 많다. (코어 자체의 성능은 CPU가 훨씬 좋다.)

    ALU가 많은 GPU는 CPU에 비해 단순계산에 상당히 특화되어 있다고 볼 수 있다.

    따라서 CPU가 해줄 일반적인 연산을 GPU에게도 넘겨주어 수행하게끔 하는 것이 가능하다. (GPGPU프로그래밍)

    수행하는 일이 서로 병렬처리가 가능할 때(서로 독립적일 때) GPU의 수행이 가장 유용해진다.



  • Compute Shader는 DirectX가 제공하는 프로그램 가능 쉐이더의 하나로 랜더링 파이프라인에 포함되지 않으며
    렌더링을 위한 쉐이더가 아닌 계산을 위한 쉐이더로 GPGPU프로그래밍을 할 때 주로 사용된다.

 

Rendering Pipeline.

 

  • GPGPU(범용 GPU, General Purpose GPU)
    • 정의 : 대규모 병렬 처리에 적합한 GPU를 비 그래픽 분야에 응용하는것.
      GPU자원의 자료를 직접 읽고 쓰기가 가능하다.
      독립적으로 연산을 하여 보내주고, 결과물을 받아가고 하는 방식으로 동작한다.
    • 장점 : 많은양의 원소에 비슷한 연산을 병렬적으로 처리하는데 적합
      • ex : 화면에 그려지는 픽셀 단편(pixel fragment), 파티클 시스템(particle system)
    • 단점 : 그래픽 연산과는 달리 GPU의 연산 결과를 다시 CPU로 보내야함
      • GPU를 통한 연산 속도가 단점을 충분히 보완

 

 

  • CUDA(Compute Unified Device Architecture)의 메모리 구조는 커널, 그리드, 스레드, 그룹으로 이루어져 있다.
    • 커널
      • Kernel은 GPU에서 처리하는 동작 즉 함수를 의미한다. (GPU에서 실행되는 함수)
      • GPU가 처리하는 Function 코드라고 볼 수 있다.
      • 레지스터는 커널에 선언되는 변수가 저장되는 메모리다.

    • 그리드
      • 커널 호출에 의해 생성된 모든 스레드
      • 그리드는 많은 스레드 블록(Block)으로 구성된다.

    • 그룹 (블록)
      • 동시에 쓰레드들을 실행하는 단위
      • 쓰레드 그룹이라고 불린다.
      • 그룹 역시 동시 실행이 가능하며 그룹 또한 3차원으로 구성이 된다.
      • 그룹의 개수는 컴퓨트 쉐이더 객체를 .Dispatch(kernalIndex, X, Y, Z)로 실행할 때 인자로 지정한다.
      • Dispatch(int kernalIndex, int x, int y, int z) : Compute Shader를 실행시킨다.

    • 쓰레드
      • 커널을 실행하는 단위, 하나의 쓰레드가 하나의 커널을 실행한다.
      • 쓰레드는 3차원(x,y,z)로 구성되어 있다.
      • 커널 함수 선언부 상단에 [numthreads(x, y, z)]를 붙여 스레드의 개수를 3차원으로 지정한다.
      • 1024 * 1 * 1로 1차원으로 지정하는 경우도 있고, 32 * 32 * 1처럼 2차원으로, 혹은 3차원으로 지정하는 경우도 있다. 이는 계산할 데이터의 차원에 따라 결정한다.



CUDA의 메모리 구조

 

  • GroupThreadID : 그룹내 쓰레드의 좌표
  • GroupID : 그리드 내 그룹의 좌표
  • DispathThreadID : [GroupID * numthreads] + GroupThreadID

 

 

 


 

 

<Compute.fx>

 

 

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef _COMPUTE_FX_
#define _COMPUTE_FX_
 
#include "params.fx"
 
RWTexture2D<float4> g_rwtex_0 : register(u0);
 
// 쓰레드 그룹당 쓰레드 개수
// max : 1024 (CS_5.0)
// - 하나의 쓰레드 그룹은 하나의 다중처리기에서 실행
[numthreads(102411)]
void CS_Main(int3 threadIndex : SV_DispatchThreadID)
{
    if (threadIndex.y % 2 == 0)
        g_rwtex_0[threadIndex.xy] = float4(1.f, 0.f, 0.f, 1.f);
    else
        g_rwtex_0[threadIndex.xy] = float4(0.f, 1.f, 0.f, 1.f);
}
 
#endif
cs

 

 

  • RWTexture2D<float4> g_rwtex_0 : register(u0)
    => unordered acess view. Compute Shader 전용으로 활용하는 register.
    => Read, Write가 모두 가능하다.

  • [numthreads()] 
    =>하나의 그룹 안에서 지닐 수 있는 쓰레드 개수를 정해주는 함수.

  • CS_Main()
    => g_rwtex_0의 텍스쳐를 DispatchThreadID의 y좌표가 2로 나뉘어진다면 (짝수라면) Red, 나눠지지 않는다면 Green으로 텍스쳐를 생성한다.
    => 사실상 GroupID에 따라 색이 달라진다고 보면 된다.

 

 

<ComputeShader 생성>

 

 

더보기
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma region ComputeShader
    {
        // UAV 용 Texture 생성
        shared_ptr<Texture> texture = GET_SINGLE(Resources)->CreateTexture(L"UAVTexture",
            DXGI_FORMAT_R8G8B8A8_UNORM, 10241024,
            CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE,
            D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
 
        shared_ptr<Material> material = GET_SINGLE(Resources)->Get<Material>(L"ComputeShader");
 
        GEngine->GetComputeDescHeap()->SetUAV(texture->GetUAVHandle(), UAV_REGISTER::u0);
 
        // 쓰레드 그룹 (1 * 1024 * 1)
        material->Dispatch(110241);
    }
#pragma endregion
cs

 

  • SceneManager 일부를 가져온 코드이다.
  • ComputeShader를 Resources에서 꺼내서 사용해주고, 위에서 설명한 UAV를 사용하기 위한 UAV Texture를 따로 생성해준다.
  • 이후 ComputeShader Material 또한 Resources에서 꺼내주고 texture를 u0 레지스터에 세팅해준 뒤
    (1 * 1024 * 1 크기로) dispatch 해준다.

 

 


 

 

결과

 

 

  • 상단에 RT가 하나 추가되어 새로운 Texture (g_rwtex_0) 가 보이는것을 확인할 수 있다.
  • 텍스쳐 생김새가 저런 이유는 Compute.fx에서 수행했던 연산 때문인데, ThreadID의 y좌표가 짝수인 부분은 빨갛게, 홀수인 부분은 초록색으로 텍스쳐를 만들어주었기 때문이다.

+ Recent posts