GPU는 임의 메모리 접근을 염두에 두고 설계된 CPU와 대조적으로 하나의 저장 장소 또는 연속된 저장 장소들에서 대량의 메모리를 읽어서 처리하는 작업에 최적화되어 있다.

 

계산 셰이더는 렌더링 파이프라인 옆에 따로 존재하며, GPU 자원의 자료를 직접 읽어 들이거나 GPU 자원에 직접 자료를 기록할 수 있다.

 

페르미 아키텍처는 SM을 16개 사용

SM(Streaming Multiprocessor) = 다중 처리기

ㄴ SP 32개를 사용

 

SP(Streaming Processor) = CUDA 코어

ㄴ 코어 하나당 스레드하나 담당

 

GPGPU 프로그래밍 (general purpose GPU) 

ㄴ GPU를 비그래픽 분야에 응용하는 것

ㄴ GPU가 계산한 결과를 CPU에서 읽어야 하는 경우가 많다.

ㄴ 자료 병렬성이 있는 알고리즘을 사용해야 한다.

 

13.1 스레드와 스레드 그룹

다중 처리기당 적어도 두 개의 스레드 그룹을 두는 것이 바람직

각 스레드 그룹에는 그룹의 모든 스레드가 접근할 수 있는 공유 메모리가 주어짐

다중 처리기는 하나의 워프(32개 스레드)를 SIMD32 단위로 처리함.

스레드 그룹의 크기는 워프 크기(32)의 배수인 것이 바람직하다. (AMD의 경우 웨이브프런트(64)의 배수)

 

13.2 계산 셰이더

스레드 그룹을 격자 형태로 분배 (cmdlist->Dispatch)

스레드 그룹 하나의 스레드 개수 구성 (계산 셰이더)

계산 셰이더는 D3D12_COMPUTE_PIPELINE_STATE_DESC 구조체를 서술

 

텍스처자원은 cmdlist->SetGraphicsRootDescriptorTable

UAV자원은 cmdlist->SetComputRootDescriptorTable

 

계산 셰이더는 각 스레드에서 실행된다.

 

13.3 자료 입력 자원과 출력 자원

SRV는 읽기 전용 서술자이다.

출력 자원을 계산 셰이더에 묶으려면

UAV 자원을 ALLOW_UNORDERED_ACCESS 플래그를 지정해서 만들고 D3D12_UNORDERED_ACCESS_VIEW_DESC 구조체를 서술해 UAV 서술자를 만들어야 한다. 

 

출력 자원은 RW 접두사가 붙으며 출력 원소의 형식과 차원을 < > 으로 지정해야한다.

ㄴ 예시 : RWTexture2D<int2> gOutput;

 

 

텍스처 필터링을 거친 표본을 사용할 경우 문제점

1. Sample 메서드를 사용할 수 없어서 SampleLevel 메서드를 사용하여 텍스처 필터링을 통해서 텍스처의 표본을 추출해야 한다.

2. 정수 색인이 아닌 [0, 1] 구간으로 정규화된 텍스처 좌표를 사용해야 한다. [ x / 텍스처 너비, y / 텍스처 높이 ] 를 사용해야 한다.

 

13.3.4 구조적 버퍼 자원

SRV 버퍼 자원은 정점 버퍼 자원을 기본 힙에 만들어 올리는 것과 동일한 방법으로 하면 된다.

UAV 버퍼 자원은 생성할때 자원 상태와 플래그를 UNORDERED_ACCESS로 해줘야 한다

 

struct Data

{

float3 v1;

float2 v2;

}

 

(.hlsl)

StructuredBuffer<Data> gInputA : register(t0);

StructuredBuffer<Data> gInputB : register(t1);

StructuredBuffer<Data> gOutput : register(u0);

 

(응용 프로그램)

slotRootParameter[0].InitAsShaderResourceView(0);

slotRootParameter[1].InitAsShaderResourceView(1);

slotRootParameter[2].InitAsUnorderedAccessView(0);

 

루트 서술자들을 받는 루트 서명은 서술자 힙을 거치지 않고 자원의 가상 주소를 루트 인수로서 직접 전달할 수 있는데 이 방법은 텍스처가 아니라 버퍼 자원에 대한 SRV와 UAV에만 가능하다.

 

13.3.5 계산 셰이더의 결과를 시스템 메모리에 복사

계산 셰이더에서 UAV 버퍼 자원에 결과값을 저장하고 메모리에 복사하기 위해서는 힙 속성이 READBACK인 자원(UAV 버퍼와 자료 형식과 크기가 동일) 을 만든 뒤에 cmdlist->CopyResource 메서드를 이용해 자원을 READBACK 버퍼에 복사한다. 이후 Map 함수로 자료를 매핑하여 CPU에서 읽을 수 있도록 한다.

 

13.4 스레드 식별 시스템 값

그룹 ID (SV_GroupID) : 그룹 ID

그룹 스레드 ID (SV_GroupThreadID) : 그룹 내에서 각 스레드의 ID

배분 스레드 ID : SV_DispatchThreadID

3가지 식별 값 모두 int3의 자료형을 가진다.

 

13.5 추가 버퍼와 소비 버퍼

자료를 계산하는 순서가 중요하지 않은 경우 추가 버퍼와 소비 버퍼라고 불리는 종류의 구조적 버퍼를 사용하는 것이 좋다.

 

ConsumeStructuredBuffer<Particle> gInput;

AppendStructuredBuffer<Particle> gOutput;

 

Particle  p = gInput.Consume();

 

gOutput.Append(p);

 

13.6 공유 메모리와 동기화

groupshared float gCache[256]; // 스레드 그룹마다 하나씩 존재

공유 메모리의 흔한 용도는 텍스처 값을 저장하는 것이다. 동일한 텍스처를 여러 번 추출하는 것은 느리기 때문에 

 

스레드 그룹 내 각 스레드가 gCache (공유 메모리)에 값을 저장을 완료하기 전에 gCache 내용을 사용하려 하면 안되기 때문에, GroupMemoryBarrierWithGroupSync() 함수를 사용해 스레드가 모두 공유 메모리에 값을 기록하기 전까지 대기하도록 만들어야 한다.

 

13.7.2 텍스처 대상 렌더링 기법

원래 후면버퍼를 화면에 출력하는 방법

후면버퍼 = 텍스처

텍스처에 대한 rtv 서술자를 출력 병합기 단계에 묶고(OMSetRenderTargets) Present 함수 호출하여 화면에 후면 버퍼 용 출력

 

또 다른 텍스처를 생성하고 그에 대한 rtv 를 만들어서 똑같이 진행해도 가능함 (화면 밖 텍스처 대상 렌더링 방법)

텍스처 대상 렌더링 기법의 용도

1. 그림자 매핑

2. 화면 공간 주변광 차폐 (SSAO)

3. 입방체 맵을 이용한 동적 반사

 

연습문제

1. 2. 3.

1번 2번 문제는 해결했으나 3번 문제는 해결하지 못했다.

계산 셰이더와 출력 결과값 파일
UAV로 사용할 버퍼 자원을 만들고 입력 자료로 사용할 자료를 임시 업로드 버퍼를 사용하여 복사했다. (정점 버퍼 올리는 방법과 동일)
DX11의 UAV FLAG
DX12의 UAV FLAG

DX11에서는 버퍼에 대한 UAV를 만들 때 D3D11_BUFFER_UAV_FLAG_APPEND를 지정하면 해당 버퍼를 셰이더에서 추가 버퍼 혹은 소비 버퍼로 인식하게 만들 수 있는 거 같은데 책과 인터넷을 찾아봐도 DX12에서 추가 소비 버퍼의 사용 예제가 보이질 않는다. FLAG_NONE을 대체하여 사용해봤는데 계산 셰이더가 제대로 작동하지 않는다. 책에서도 이에 대한 설명이 부실하여 일단은 넘어가도록 해야겠다.

D3D12_BUFFER_UAV_FLAG_APPEND는 없다..?

UAV_FLAG_NONE으로 설정하고 셰이더 파일에서 추가 소비 버퍼를 사용하는 대신에 구조적 버퍼(RWStructuredBuffer)로 바꿔주기만 해도 정상적으로 결과가 나온다.

 

5.

예제가 이미 만들어져 있어서 성능 비교만 했다. 격자 크기 512x512 시뮬레이션 대신에 256x256으로 진행하였다.(512로 하면 CPU 버전에서 파도가 렌더링되지 않음)

CPU에서 파도를 처리하는 경우
계산 셰이더(GPU) 에서 파도를 처리하는 경우

GPU에서 계산을 수행하는 것이 CPU에서 수행하는 것보다 처리가 확연하게 빠름을 알 수 있었다.

CPU를 사용한 프로그램은 괜찮았는데 계산 셰이더를 사용한 프로그램을 돌리니 GPU에서 평소에 듣지 못하던 소음이 발생했다.

 

동작 과정:

Disturb가 발생할 때 mCurrsolUav 서술자가 가리키는 버퍼에 계산 셰이더를 이용해서 파도를 일으키면 (계산 셰이더1)

Update마다 mCurrsolUav와 mPrevSolUav를 이용해 mNextSolUav에 다음 정점 위치를 계산하여 기록한다 (계산 셰이더2)

Update가 끝나면 srv, uav, 자원을 서로바꿔준다. (prev를 next로 curr을 prev로 next를 curr로)

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/13

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

'읽은 책 > DirectX 12를 이용한 3D 게임 프로그래밍 입문' 카테고리의 다른 글

15. 1인칭 카메라 구축과 동적 색인화  (1) 2022.06.29
14. 테셀레이션 단계들  (0) 2022.06.29
12. 기하 셰이더  (0) 2022.06.24
11. 스텐실 적용  (0) 2022.06.22
10. 혼합  (0) 2022.06.22

+ Recent posts