ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • DX12 Shader Visible Descriptor Heap
    Graphics/기본 2023. 8. 5. 00:37

    DX12 Shader Visible Descriptor Heap


    최초 작성 : 2023-08-05
    마지막 수정 : 2023-08-05
    최재호

     

    목표

    Shader visible descriptor heap(Online descriptor heap), CPU only visible descriptor heap(Offline descriptor heap) 에 대해서 알아보고 UE5.2 에서의 구현도 확인해 봅시다.
     

    내용

    DX12 리소스 중 버퍼 또는 텍스처와 같이 Shader 에 전달하여 사용할 수 있는 리소스들이 있습니다. 이런 리소스를 Shader 에서 사용할 수 있게 하려면 Descriptor 를 만들어야 합니다. Descriptor 의 종류는 CBV, SRV, UAV, RTV, DSV, Sampler 등이 있습니다.
     
    아래의 코드는 SRV 를 생성하는 예제입니다.

    void CreateShaderResourceView(
      [in, optional] ID3D12Resource                        *pResource,
      [in, optional] const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc,
      [in]           D3D12_CPU_DESCRIPTOR_HANDLE           DestDescriptor
    );
    출처 : 레퍼런스7

     
    Descriptor 는 ID3D12DescriptorHeap 에서 생성할 수 있습니다. 그리고 이 Descriptor 의 바로 전에 본 CreateShaderResourceView 의 3번째 파라메터에 전달하면 됩니다. 아래는 DescriptorHeap 의 타입을 보여줍니다.

    enum D3D12_DESCRIPTOR_HEAP_TYPE
    {
        D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV	= 0,
        D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER	= ( D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV + 1 ) ,
        D3D12_DESCRIPTOR_HEAP_TYPE_RTV	= ( D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER + 1 ) ,
        D3D12_DESCRIPTOR_HEAP_TYPE_DSV	= ( D3D12_DESCRIPTOR_HEAP_TYPE_RTV + 1 ) ,
        D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES	= ( D3D12_DESCRIPTOR_HEAP_TYPE_DSV + 1 ) 
    } D3D12_DESCRIPTOR_HEAP_TYPE;

     
    실제로 렌더링 커맨드를 호출하여 렌더링 하기 전에 반드시 필요한 작업이 있는데 바로 SetDescriptorHeaps 함수를 호출하여 Descriptor 를 생성한 Heap 을 바인딩해 주는 것입니다. 실제 코드 호출은 아래와 같이 될 것입니다. SetDescriptorHeaps 는 다음 두 타입의 Heap (D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER)만 바인딩 가능합니다. 그리고 각 Heap 타입은 중복될 수 없습니다. 즉, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 1개, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER 1개만 가능하다는 것입니다.

    ID3D12DescriptorHeap* ppHeaps[] = { SRVDescriptorHeap.Heap.Get() };								// SamplerState test
    GraphicsCommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

    이렇게 SetDescriptorHeaps 에 전달되는 Descriptor heap 은 Shader visible descriptor heap 옵션으로 생성된 것만 전달해야 합니다. Shader visible descriptor heap 은 생성할 수 있는 Descriptor 의 개수에 제한이 있습니다. 레퍼런스4 에 의하면 이 개수는 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 의 경우 1,000,000 정도 D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER  는 2048 라고 합니다. 레퍼런스5 도 참고해 주세요.
     
    하지만 실제로 게임에서 생성할 수 있는 리소스는 Shader visible descriptor heap 이 가질 수 있는 최대 크기보다 더 클 수 있을 것입니다. 그래서 DX12 는 CPU only visible descriptor heap 따로 관리합니다. 리소스 생성 시 Descriptor 를 CPU only visible descriptor heap 에서 생성하고 렌더링에 사용할 Descriptor 만 모아서 CopyDescriptors, CopyDescriptorSimple 을 사용하여 Shader visible descriptor heap 으로 복사합니다. CPU only visible descriptor heap 은 생성 개수에 제한이 없습니다. Descriptor handle 은 모든 Descriptor heap 에 대해서 unique 함이 보장되는 점도 중요합니다.
     
    레퍼런스1 에서는 Shader descriptor heap 의 타입별 Descriptor 관리 방식에 대한 전략을 설명해 줍니다. 어떤 내용인지 한번 확인해 봅시다.
     
    리소스 수 제한 없이 Descriptor 을 할당할 수 있도록 하기 위해서 Non shader visible descriptor heap 을 사용합니다. Heap 이 다 차면 새로운 Heap 을 만들어주기도 하고, 기존 사용 완료된 Heap 을 재사용하기도 하면서 Heap 을 활용하게 됩니다. 그리고 Heap 으로 부터 생성된 Descriptor 의 Handle 은 Heap 전체에 대해서 unique 함이 보장된다고 합니다(레퍼런스 3). 그림1는 non shader visible descriptor heap 을 보여줍니다.

    그림1. 자유롭게 확장하여 생성하는 Non shader visible descriptor (출처 : 레퍼런스1)

     
    또 다른 Descriptor 타입은 Shader visible descriptor heap 입니다. 이 타입의 경우 Heap 은 단 1개만 생성됩니다. Heap 이 가득 찬다고 해서 크기를 조정하거나 하지 않습니다. 이렇게 하는 이유는 asynchronous compute 와 graphics workloads 들이 병렬로 실행될 수 있도록 하기 위해서 Nvidia 에서 권고하고 있다고 합니다. (레퍼런스2)

    그림2. 1개만 존재하는 Shader visible descriptor (출처 : 레퍼런스1)

    Shader visible descriptor heap 에는 Static 과 Dynamic 영역이 나뉘어서 사용할 수 있습니다. Static 의 경우 여러 프레임에서 재사용할 수 있는 Decriptor 들을 담아두고, Dynamic 영역에는 재사용하지 않고 자주 바뀌는 Descriptor 를 사용하여 필요한만큼 사용하고 Reset 하여 다시 사용하도록 합니다. Dynamic 영역에 실제로 Descriptor 를 매번 생성하는 것은 아니며 Non shader visible descriptor heap 으로부터 이미 생성된 Descriptor 정보를 ID3D12Device::CopyDescriptors() 함수를 사용하여 복사합니다.
     
    UE5.2 에서는 Shader visible descriptor 를 Online descriptor 라고 부르고, Non shader visible descriptor 를 Offline descriptor 라고 부릅니다. 그리고 각각의 Descriptor 는 FD3D12OnlineDescriptorManager, FD3D12OfflineDescriptorManager 에서 할당받을 수 있습니다. 아래 그림3 과 같이 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE 플래그의 여부에 따라서 Online/Offline descriptor heap 이 결정됩니다.

    그림3

     
    Online descriptor heap 의 전역에 1개의 Heap 만 생성하고 유지합니다. 이때 생성한 Heap 은 생성 후 일정크기의 블록단위로 나눠서 사용합니다. 이렇게 하는 이유는 위에서 본 Nvidia 권고와 같이 병렬 작업들의 퍼포먼스를 위해서입니다. 그림4 는 Online descriptor heap 을 블록단위로 나누고, 블록단위로 Heap 을 할당하는 함수입니다.

    그림4.

     
    레퍼런스6 에 의하면 DX12 에서 병렬로 Commandlist 를 녹화하는 경우 Descriptor Heap 생성에 동기화 비용을 줄이기 위해서는 Commandlist 당 DescriptorHeap 을 할당하는 것이 좋다고 합니다. UE5.2도 실제 그렇게 구현합니다.
    아래 그림5 를 보면 새로운 Commandlist 할당 후 DescriptorCache->OpenCommandList 를 호출하는 것을 볼 수 있습니다. 다. 바로 여기서 실제 사용할 Online Descriptor Heap 을 할당받습니다.

    그림5.

    계속해서 코드를 따라가 봅시다. CurrentViewHeap 의 OpenCommandList 호출 전 CurrentViewHeap 이 무엇인지 확인해 봅시다.
     
    그림6을 보면 CurrentViewHeap 에는 SubAllocatedViewHeap 이 할당되어 있습니다. 이 객체는 FD3D12SubAllocatedOnlineHeap 입니다. 그리고 FD3D12SubAllocatedOnlineHeap 의 OpenCommnadList 는 그림4 에서 본 Online Descriptor Heap Block 의 할당임을 확인할 수 있습니다.

    그림6.

     
    Online Heap 이 매 Commandlist 가 할당될 때마다 준비되어 Commandlist 간에 별도의 동기화가 필요 없을 것이라는 점을 확인했습니다. 
     
    이제 Offline heap descriptor 에 대해서 알아봅시다. Offline heap descriptor 는 아래와 같은 형태로 생성됩니다. 가장 많이 쓰일 것 같은 ShaderResourceView 를 기준으로 코드를 봅시다. 아래 그림7을 보면 OfflineDescriptor 에서 생성한 Descriptor 는 FD3D12ViewDescriptorHandle 에 담아서 FD3DShaderResourceView 가 소유하도록 해주는 것을 볼 수 있습니다.

    그림7.

     
    이제 Offline descriptor heap 이 생성되는 것을 봤으니 마지막으로 CPU only visible heap 에서 Shader visible heap 으로의 복사 코드를 봅시다. 아래의 콜스택은 ComputeShader 가 호출되고 있는 콜스택입니다. 콜스택을 보면 SetSRVs 함수를 통해서 SRV Descriptor 를 준비합니다.

    >UnrealEditor-D3D12RHI.dll!FD3D12DescriptorCache::SetSRVs(EShaderFrequency ShaderStage, const FD3D12RootSignature * RootSignature, FD3D12ShaderResourceViewCache & Cache, const unsigned __int64 & SlotsNeededMask, unsigned int SlotsNeeded, unsigned int & HeapSlot) Line 412	C++
    UnrealEditor-D3D12RHI.dll!FD3D12StateCache::ApplyResources(const FD3D12RootSignature * const pRootSignature, unsigned int StartStage, unsigned int EndStage) Line 698	C++
    UnrealEditor-D3D12RHI.dll!FD3D12StateCache::ApplyState(ED3D12PipelineType PipelineType) Line 559	C++
    UnrealEditor-D3D12RHI.dll!FD3D12CommandContext::RHIDispatchComputeShader(unsigned int ThreadGroupCountX, unsigned int ThreadGroupCountY, unsigned int ThreadGroupCountZ) Line 150	C++

    아래 SetSRVs 내부를 봅시다.
    1. OnlineDescriptorHeap 을 얻어와 복사할 Dest 위치를 얻어옵니다.
    2. 실제 사용할 SRV 리소스가 있는지 확인하고 있는 경우 해당 리소스의 SRV Descriptor, 없는 경우 NullSRV 의 Descriptor 를 준비합니다.
    3. CopyDescriptors 를 사용하여 Descriptor 를 CPU only visible 에서 GPU visible 인 Descriptor heap 으로 복사합니다.

     
    이제 Online descriptor heap 인 CurrentViewHeap 에 필요한 SRV 리소스들이 모두 복사되었을 것입니다.
     
    마지막으로 Descriptor heap 을 바인딩하는 SetDescriptorHeaps 함수가 어디서 불리는지 확인해 봅시다. SetDescriptorHeaps 는 이미 OpenCommandList 가 호출되는 시점에서 호출되는 것을 아래의 콜스택으로 확인할 수 있습니다.

    >UnrealEditor-D3D12RHI.dll!FD3D12DescriptorCache::SetDescriptorHeaps(bool bForceHeapChanged) Line 97	C++
    UnrealEditor-D3D12RHI.dll!FD3D12DescriptorCache::SwitchToGlobalSamplerHeap() Line 655	C++
    UnrealEditor-D3D12RHI.dll!FD3D12DescriptorCache::OpenCommandList() Line 122	C++
    UnrealEditor-D3D12RHI.dll!FD3D12CommandContext::OpenCommandList() Line 385	C++

    FD3D12DescriptorCache::SetDescriptorHeaps 함수의 내부는 아래와 같습니다.
    1. 오버라이딩 된 경우가 아니면 CurrentViewHeap 을 사용할 것입니다.
    2. SetDescriptorHeap 을 호출하여 이번 사용할 Online descriptor heap 을 바인딩합니다.

     
     

    레퍼런스

    1. http://simonstechblog.blogspot.com/2019/06/d3d12-descriptor-heap-management.html
    2. https://developer.nvidia.com/dx12-dos-and-donts
    3. https://learn.microsoft.com/en-us/windows/win32/direct3d12/descriptors-overview
    4. http://diligentgraphics.com/diligent-engine/architecture/d3d12/managing-descriptor-heaps
    5. https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-support?redirectedfrom=MSDN
    6. https://github.com/Microsoft/DirectX-Graphics-Samples/issues/114
    7. https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/nf-d3d12-id3d12device-createshaderresourceview

     
     

    'Graphics > 기본' 카테고리의 다른 글

    AsyncCompute - DX12, Vulkan  (0) 2024.02.13
    Bindless Resource - DX12, Vulkan  (0) 2024.01.17
    BCn Texture Compression Formats 정리  (0) 2023.04.21
    Wave Intrinsics  (0) 2022.09.27
    Variable Shading Rate(VRS)  (2) 2022.09.27

    댓글

Designed by Tistory & scahp.