ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Wave Intrinsics
    Graphics/기본 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;
        }
    }

     

    그림1. Wavefront 내의 RGB 값의 평균 값을 최종 이미지에 출력한 결과
    그림2. Wave Intrinsics 을 사용하지 않은 기본화면 결과

     

    구현 코드

    https://github.com/scahp/jEngine/tree/WaveIntrinsics

     

    레퍼런스

    1. Introduction to compute shaders

    2. More compute shaders

    3. Even more compute shaders

    4. Wave Intrinsics

    5. HLSL Shader Model 6.0

    6. Vulkan Subgroup Tutorial

    7. Direct3D 12 shader model 6 wave intrinsics sample

     

     

    댓글

Designed by Tistory & scahp.