-
DX12 Shader Visible Descriptor HeapGraphics/기본 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 을 보여줍니다.
또 다른 Descriptor 타입은 Shader visible descriptor heap 입니다. 이 타입의 경우 Heap 은 단 1개만 생성됩니다. Heap 이 가득 찬다고 해서 크기를 조정하거나 하지 않습니다. 이렇게 하는 이유는 asynchronous compute 와 graphics workloads 들이 병렬로 실행될 수 있도록 하기 위해서 Nvidia 에서 권고하고 있다고 합니다. (레퍼런스2)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 이 결정됩니다.
Online descriptor heap 의 전역에 1개의 Heap 만 생성하고 유지합니다. 이때 생성한 Heap 은 생성 후 일정크기의 블록단위로 나눠서 사용합니다. 이렇게 하는 이유는 위에서 본 Nvidia 권고와 같이 병렬 작업들의 퍼포먼스를 위해서입니다. 그림4 는 Online descriptor heap 을 블록단위로 나누고, 블록단위로 Heap 을 할당하는 함수입니다.
레퍼런스6 에 의하면 DX12 에서 병렬로 Commandlist 를 녹화하는 경우 Descriptor Heap 생성에 동기화 비용을 줄이기 위해서는 Commandlist 당 DescriptorHeap 을 할당하는 것이 좋다고 합니다. UE5.2도 실제 그렇게 구현합니다.
아래 그림5 를 보면 새로운 Commandlist 할당 후 DescriptorCache->OpenCommandList 를 호출하는 것을 볼 수 있습니다. 다. 바로 여기서 실제 사용할 Online Descriptor Heap 을 할당받습니다.계속해서 코드를 따라가 봅시다. CurrentViewHeap 의 OpenCommandList 호출 전 CurrentViewHeap 이 무엇인지 확인해 봅시다.
그림6을 보면 CurrentViewHeap 에는 SubAllocatedViewHeap 이 할당되어 있습니다. 이 객체는 FD3D12SubAllocatedOnlineHeap 입니다. 그리고 FD3D12SubAllocatedOnlineHeap 의 OpenCommnadList 는 그림4 에서 본 Online Descriptor Heap Block 의 할당임을 확인할 수 있습니다.
Online Heap 이 매 Commandlist 가 할당될 때마다 준비되어 Commandlist 간에 별도의 동기화가 필요 없을 것이라는 점을 확인했습니다.
이제 Offline heap descriptor 에 대해서 알아봅시다. Offline heap descriptor 는 아래와 같은 형태로 생성됩니다. 가장 많이 쓰일 것 같은 ShaderResourceView 를 기준으로 코드를 봅시다. 아래 그림7을 보면 OfflineDescriptor 에서 생성한 Descriptor 는 FD3D12ViewDescriptorHandle 에 담아서 FD3DShaderResourceView 가 소유하도록 해주는 것을 볼 수 있습니다.
이제 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