-
Wave IntrinsicsGraphics/기본 2022. 9. 27. 01:05
개요
레퍼런스1, 2, 3을 통해 GPU 가 실행하는 각각의 Work Item 이 Wavefront 라는 그룹단위로 실행되는 것을 알 수 있었습니다. Shading Model 6(SM6) 이상 부터는 Wavefront 관련 Intrinsics 가 추가되었고 이 기능을 활용할 수 있습니다. 이것에 대해서 알아봅시다.
내용
GPU 작업은 여러 카테고리로 분류됩니다. 전체 작업을 나타내는 Domain. 그리고 그 보다 작은 단위의 WorkGroup 이 있습니다. WorkGroup 은 동일한 Compute Unit 에 실행되며 WorkGroup 단위로 내부 작업을 동기화 하기에 좋습니다. 마지막으로 WorkGroup 은 각각의 WorkItem 들이 모여서 구성됩니다.
Work Gorup 과 Work Item 사이에 단위가 하나 더 있는데 그것이 바로 Wavefront (AMD 인 경우 Wavefront, Nvidia 는 Warp) 입니다. Wavefront 는 Compute Unit 에서 LockStep 으로 동시에 실행되는 Work Item의 묶음 입니다. (AMD Wavefront는 64개 Warp는 32개)
Wavefront 내의 WorkItem 들이 LockStep 으로 실행되기 때문에 이 장점을 활용할 수 있을 것입니다. 예를들면 Wavefront 내에서 가장 작은 값을 구한다거나 또는 Wavefront 내에 있는 RGB 값의 평균값을 구하는 것이 가능할 것입니다. 기존에 존재하던 DDX, DDY 도 Wavefront 내의 인접한 픽셀을 정보를 교환하는 방식으로 구현합니다. 이 내용 또한 레퍼런스2 에서 확인 했었습니다. SM6 부터는 명시적으로 Wavefront 내의 정보들을 활용할 수 있는 기능이 추가되었습니다. 레퍼런스5 는 SM6 에서 사용가능 한 Wave Intrinsics 함수를 명세합니다. Wave Intrinsics 를 사용하면 DDX, DDY 와 같은 함수도 직접 구현 할 수 있습니다.
레퍼런스1, 2, 3이나 이전의 다른 글을 통해서 Wave Intrinsics 를 간접적으로 접해왔는데, 오늘은 해당 내용을 직접 구현해보는 시간을 가져봅시다. 그리고 레퍼런스7 은 마이크로 소프트에서 제공하는 샘플로, DirectX12 를 사용하여 Wave Intrinsics 의 샘플을 보여줍니다. 오늘은 이 예제를 사용하여 Wave Intrinsics 를 구현하려고 합니다.
구현은 Vulkan API 와 HLSL language 를 사용하여 구현하였습니다. 레퍼런스6 과 같이 GLSL 은 Subgroup 이라는 이름으로 Wave Intrinsics 를 다룰 수 있습니다만 여기서는 HLSL 로 작성하고 ShaderConductor 를 통해 Spirv 로 변환하는 형태로 구현하였습니다.
오늘 구현해볼 것은 Wavefront 내의 RGB 값의 평균값을 계산하여 Wavefront 단위로 같은 값을 사용하도록 할 것입니다. 이것을 사용하여 Wavefront 크기로 확대된 픽셀을 렌더링하는 예제를 만들 것입니다. 오늘 구현을 위해 필요한 함수는 총 3가지 입니다. 이 세가지를 먼저 알아봅시다.
WaveActiveBallot(true) : uint4(총 64비트) 를 반환하며, Active lane 는 1, Helper lane 는 0을 설정하여 리턴
countbits(uint x) : uint 를 넘겨주는 1로 설정된 비트의 개수를 리턴
WaveActiveSum(T) : 모든 Active lane 의 T 값을 합산하여 돌려줍니다. 그리고 모든 Active lane 은 이 합산한 값을 가짐
아래는 입력 이미지를 Wave operation 을 적용하여 출력 이미지에 기록하는 Compute shader 입니다.
// 이미지를 복사하는 Compute shader [numthreads(16, 16, 1)] void main(uint3 GlobalInvocationID : SV_DispatchThreadID) { if (GlobalInvocationID.x >= ComputeCommon.Width || GlobalInvocationID.y >= ComputeCommon.Height) return; // 입력 이미지에서 컬러를 가져옵니다. float3 rgb = inputImage[uint2(GlobalInvocationID.xy)].rgb; // Wave operation test if (ComputeCommon.UseWaveIntrinsics <= 0) { // Wave operation 을 사용하지 않는 경우 바로 출력 이미지에 rgb 기록 resultImage[int2(GlobalInvocationID.xy)] = float4(rgb, 1.0); } else { // Wave operation 을 사용하여 Wavefront 내의 rgb 값의 평균을 사용하도록 함 // Active lane 의 비트를 얻고, 총 Active lane 을 수를 얻습니다. uint4 activeLaneMask = WaveActiveBallot(true); uint numActiveLanes = countbits(activeLaneMask.x) + countbits(activeLaneMask.y) + countbits(activeLaneMask.z) + countbits(activeLaneMask.w); // Active lane 의 rgb 값의 평균 값을 얻습니다. float4 avgColor = float4(WaveActiveSum(rgb) / float(numActiveLanes), 1.0); // 최종 결과를 출력 이미지에 기록합니다. resultImage[int2(GlobalInvocationID.xy)] = avgColor; } }
구현 코드
https://github.com/scahp/jEngine/tree/WaveIntrinsics
레퍼런스
1. Introduction to compute shaders
7. Direct3D 12 shader model 6 wave intrinsics sample
'Graphics > 기본' 카테고리의 다른 글
DX12 Shader Visible Descriptor Heap (0) 2023.08.05 BCn Texture Compression Formats 정리 (0) 2023.04.21 Variable Shading Rate(VRS) (2) 2022.09.27 Halfspace Fog 공식 유도 정리 - 흡수와 산란 응용 (2) 2022.03.22 SphereMap TwoMirrorBall - 360도 방향 커버 됨 (0) 2020.12.11