ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UE5] D3D12 ResourceAllocation 리뷰 (1/2)
    UE4 & UE5/Rendering 2023. 8. 23. 00:26

    [UE5] D3D12 ResourceAllocation 리뷰 (1/2)


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

    목차

    1. 목표
    2. 내용
      2.1. Non-TransientHeap 방식
        2.1.1. 주요 클래스 파악
          2.1.1.1. FD3D12Buffer, FD3D12Texture
          2.1.1.2. FD3D12UniformBuffer

        2.1.2. 리소스 해제 방식 정리
      2.2. TransientHeap 방식 (RDG 에서 사용)
        2.2.1. 주요 클래스 파악
      2.3. Non-TransientHeap 방식의 전체 UML
      2.4. TransientHeap 방식의 전체 UML
    3. 레퍼런스
     

    1. 목표

    UE5 의 D3D12 Buffer, Texture 의 생성/해제 방식에 대해서 알아봅니다. 최신 그래픽스 API 에서는 리소스 생성/해제 전략을 응용 프로그래머가 좀 더 세부적으로 다룰 수 있습니다. 이런 구현 중에서도 GPU 가 사용 중인 리소스를 적절한 시점에 해제하는 것은 특히 더 중요한 부분입니다. UE5 는 이런 점들을 해결하기 위해서 어떤 리소스 할당/해제 전략을 가지고 있는지 확인해 봅시다.
     
    이 글은 총 2개로 구성됩니다.
    '[UE5] D3D12 ResourceAllocation 리뷰 (1/2)' 는 리소스 생성에 필요한 클래스들의 관계 확인합니다.
    '[UE5] D3D12 ResourceAllocation 리뷰 (2/2)' 는 실제 리소스 할당 코드에서 리소스 할당/해제 방식을 확인합니다.
     
    전체 UML 을 먼저 확인하고 싶으면 이 글의 맨 아래의 2.3과 2.4 를 확인해 주세요.
     
    이 글을 UE5.2 (407acc04a93f09ecb42c07c98b74fd00cc967100) 코드를 기반으로 작성되었습니다.
     
    사전지식
    DirectX12 의 ID3D12Resource 에 관해
    UE4/5 RDG(RenderDependencyGraph)


    개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.

     

    2. 내용

    시작하기 전에 먼저 알아두면 좋은 것들을 둘러봅시다.
     
    먼저 D3D12 리소스의 기본 타입 ID3D12Resource 입니다.
    D3D12 의 Texture 와 Buffer 는 모두 ID3D12Resource 타입으로 만들어집니다. 리소스를 생성하는 방식은 크게 2가지가 있습니다. CreateCommittedResource 와 CreatePlacedResource 입니다. CreateCommittedResource 는 암시적으로 1개의 Heap 을 생성하고 거기에 1개의 버퍼나 텍스처를 생성하는 Standalone방식입니다. CreatePlacedResource 는 명시적으로 별도의 ID3D12Heap 을 만들고, 해당 Heap 의 일부분에 대한 ID3D12Resource 를 만드는 방식(SubAllocation)입니다. 리소스 생성 방식에 대한 예제는 레퍼런스2를 참고해 주세요.
     
    계속해서 UE5 에서 사용하는 리소스 할당 방식 2가지를 알아봅시다.
    먼저 RenderDependecyGraph 에서 사용되는 FD3D12TransientResourceHeapAllocator 입니다. (RDG 에 대한 자세한 내용은 레퍼런스3, 4 를 참고해 주세요). FD3D12TransientResourceHeapAllocator 는 전역 변수로 만들어진 단일 객체입니다. 또한 RDG 에서 생성된 Buffer, Texture 들은 RDG 가 실행되고 나서는 더 이상 사용되지 않는다고 가정합니다. 이런 리소스들은 생성/해제가 빈번하게 반복될 것이라 별도의 전용 캐시 전략을 가집니다. ‘2.2. TransientHeap 방식 (RDG 에서 사용)’ 에서 자세히 알아봅시다.
     
    다음으로 RDG 를 사용하지 않는 경우를 봅시다. 이 경우 주로 사용되는 2개의 Allocator 가 있습니다(이 외의 것들도 있는데 여기서는 이 두 가지만 다룸). FD3D12PoolAllocator 와 FD3D12MultiBuddyAllocator 입니다. 주로 RHICreateStructuredBuffer 나 RHICreateVertexBuffer 와 같이 RHICreateBuffer 를 호출하여 버퍼를 생성하는 경우나 RHICreateTexture2D 와 같이 RHICreateTexture 를 호출하여 텍스처를 생성하는 경우에 해당합니다. 특이하게 RHICreateUniformBuffer 는 생성 시 버퍼의 생명 주기를 지정할 수 있습니다. SingleDraw, SingleFrame or MultiFrame 타입이 있습니다. Single 의 경우 사용할 리소스를 할당한 뒤 매 프레임 해제한 후 재활용합니다. 자세한 것은 ‘2.1. Non-TransientHeap 방식’ 에서 차근차근 알아봅시다.
     
    이 글에서는 RDG 에서 사용하는 방식을 TransientHeap, 그렇지 않은 경우는 Non-TransientHeap 이라고 부르겠습니다.
     

    2.1. Non-TransientHeap 방식

    2.1.1. 주요 클래스 파악

    2.1.1.1. FD3D12Buffer, FD3D12Texture

    Non-TransientHeap 으로 생성되는 리소스는 UE5 의 가장 기본 되는 리소스 생성 방식입니다. 여기서는 FD3D12PoolAllocator, FD3D12MultiBuddyAllocator 를 기반으로 한 여러 Allocator(FD3D12UploadHeapAllocator, FD3D12DefaultBufferAllocator, FD3D12TextureAllocatorPool, FD3d12FastConstantAllocator) 를 생성하여 사용합니다.
    다른 Allocator 도 있지만 이 글에서는 이 네 가지 Allocator 를 통해 FD3D12Buffer, FD3D12Texture, FD3D12UniformBuffer 가 기본적으로 어떻게 생성되는지 확인하는 것을 목표로 합니다.
     
    FD3D12Buffer, FD3D12Texture, FD3D12UniformBuffer 가 ID3D12Resource 를 어떻게 소유하는지 알아봅시다. 그림1 을 보면 FD3D12ResourceLocation 가 실제로 할당된 ID3D12Resource 를 갖고 있는 것을 볼 수 있습니다(FD3D12Resource 에 있음). Buffer, Texture 의 경우는 FD3D12BaseShaderResource 를 상속하는 형태로 FD3D12ResourceLocation 를 가집니다. 그리고 FD3D12UniformBuffer 의 경우는 직접 FD3D12ResourceLocation 을 소유합니다. FD3D12ResourceLocation 은 자신이 ‘어떤 Allocator(Buddy or Pool) 에서 할당되었는지?’ 에 대한 정보도 가지고 있습니다. 이 정보를 기반으로 메모리를 해제 방식을 결정합니다.
     

    그림1. FD3D12ResourceLocation 과 리소스들의 관계

     
    다음으로 FD3D12ResourceLocation 를 만들어 주는 Allocator 에 대해서 알아봅시다. 총 3가지 Allocator 가 있으며 이 Allocator 는 FD3D12PoolAllocator 와 FD3D12MultiBuddyAllocator 를 사용하여 만들어졌습니다.

    • FD3D12UploadHeapAllocator : CPU 에서 기록할 수 있는 Buffer 를 생성하는데 사용합니다. 이 타입의 버퍼는 생성 후 Buffer 의 Pointer 를 받아와서 CPU 에서 기록할 수 있는 UniformBuffer 와 같은 버퍼에 사용됩니다. 이 Allocator 는 용도에 따라서 FD3D12MultiBuddyAllocator 와 FD3D12PoolAllocator 둘 다 사용합니다.
      • UniformBuffer Multi frame 방식의 경우 아래 두 가지 타입의 Allcoator 를 사용합니다. 어떤 타입을 사용할지는 메모리 요청 크기에 따라 결정됩니다.
        • FD3D12MultiBuddyAllcator SmallBlockAllocator
        • FD3D12PoolAllocator BigBlockAllocator
          • FD3D12MultiBuddyAllcator or FD3D12PoolAllocator 를 사용하여 메모리를 할당하는 경우 FD3D12ResourceLocation::ResourceLocationType 이 eSubAllocation 으로 할당됩니다. 만약 BigBlockAllocator 에서 허용하는 크기보다 더 큰 메모리를 할당하게 되면 eStandAlone 으로 할당되는데 이 경우 암묵적으로 전용 Heap 을 생성하여 사용하는 CreateCommittedResource 를 사용하여 리소스를 할당합니다. FD3D12ResourceLocation::ResourceLocationType 타입은 추후 리소스 해제 시에 해제 방식을 결정하는데 사용됩니다.
          • FD3D12ResourceLocation::EAllocatorType 은 BigBlockAllocator 를 사용한 경우 AT_Pool 이고 SmallBlockAllocator 를 사용한 경우 AT_Default 가 됩니다.
      • UniformBuffer Single frame, Single draw 방식의 경우 한 가지 타입의 Allocator 를 사용합니다.
        • FD3D12MultiBuddyAllocator FastConstantPageAllocator
        • 이 곳이 사용되는 곳은 딱 한 군대인데, FD3D12FastConstantAllocator 입니다. 이 리소스의 생성 방식은 조금 특이하기 때문에 ‘2.1.1.2. FD3D12UniformBuffer’ 에서 확인해봅시다.
        • 이 경우 FD3D12ResourceLocation::ResourceLocationType 이 eFastAllocation 이 됩니다. 이 것은 리소스 해제 방식 결정에 사용됩니다.
        • FD3D12ResourceLocation::EAllocatorType 은 AT_Default 가 됩니다.
    • FD3D12DefaultBufferAllocator : Upload Buffer 이외의 버퍼들을 할당합니다. 여기 서는 FD3D12PoolAllocator 를 사용합니다. 이 Allocator 는 여러개의 FD3D12PoolAllocator 를 관리할 수 있습니다. 각각의 FD3D12PoolAllocator 는 서로 다른 타입의 Buffer 생성을 지원 합니다. 만약 지원하는 FD3D12PoolAllocator 가 없다면 새로 생성합니다.
      • FD3D12PoolAllocator 를 사용하여 버퍼를 만들 메모리를 할당 받는 경우 FD3D12ResourceLocation::ResourceLocationType 은 eSubAllocation 이 됩니다. 만약 FD3D12PoolAllocator 에서 허용하는 크기보다 더 큰 메모리를 할당하게 되면 eStandAlone 으로 할당되는데 이 경우 암묵적으로 전용 Heap 을 생성하여 사용하는 CreateCommittedResource 를 사용하여 리소스를 할당합니다.
      • FD3D12ResourceLocation::EAllocatorType 은 AT_Pool 이 됩니다.
    • FD3D12TextureAllocatorPool : ReadOnly4K, ReadOnly, RenderTarget, UAV 타입의 FD3D12PoolAllocator 를 사용하여 텍스처를 할당합니다.
      • 이것 또한 FD3D12PoolAllocator 를 사용하여 버퍼를 만들 메모리를 할당 받는 경우 FD3D12ResourceLocation::ResourceLocationType 은 eSubAllocation 이 됩니다. 만약 FD3D12PoolAllocator 에서 허용하는 크기보다 더 큰 메모리를 할당하게 되면 eStandAlone 으로 할당되는데 이 경우 암묵적으로 전용 Heap 을 생성하여 사용하는 CreateCommittedResource 를 사용하여 리소스를 할당합니다.
      • FD3D12ResourceLocation::EAllocatorType 은 AT_Pool 이 됩니다.

    그림2. FD3D12ResourceLocation 을 할당해주는 3가지 Allocator

     
     
    계속해서 위의 3가지 Allocator 를 구성하는 기반 Allocator 인 FD3D12PoolAllocator 와 FD3D12MultiBuddyAllocator 를 알아봅시다. 그림3 를 봐주세요. FD3D12PoolAllocator 와 FD3D12MultiBuddyAllocator 는 각각 FD3D12MemoryPool, FD3D12BuddyAllocator 를 사용합니다. 이 둘은 서로 메모리를 할당 해제 하는 전략만 다릅니다. 실제 하는 일은 필요한 만큼의 메모리를 할당하고 해제하는 리소스풀입니다.

    • FD3D12MemoryPool 는 메모리 블록 단위로 할당 해제하는 전략을 가지며, FD3D12BuddyAllocator 는 Buddy allocation (레퍼런스5 참고) 전략을 가지는 것으로 보입니다. 메모리 할당 알고리즘은 이 글에서 다루는 범위를 벗어나기 때문에 여기서는 다루지 않을 예정입니다.

     

    1. 실제로 ID3D12Heap 를 가지는 것은 FD3D12MemoryPool 입니다. FD3D12PoolAllocator 는 여러 개의 FRHIMemoryPool 을 필요에 따라 생성/해제시킵니다.
    2. FD3D12MemoryPool 은 2가지 리소스 생성 전략을 가집니다. kPlacedResource 와 kManualSubAllocation 입니다.
      • kPlacedResource 는 ID3D12Heap 을 만들고 그 Heap 메모리를 사용해 여러 개의 ID3D12Resource 로 SubAllocation 할 수 있습니다. kPlacedResource 는 FD3D12MemoryPool 의 BackingHeap 을 사용하여 메모리를 할당합니다.
      • kManualSubAllocation 는 한 개의 ID3D12Resource 를 할당한 다음 Offset 과 Size 를 기반으로 SubAllocation 하는 방식입니다. kManualSubAllocation 는 FD3D12MemoryPool 의 BackingResource 를 사용하여 메모리를 할당합니다.
    3. FD3D12MultiBuddyAllocator 는 FD3D12BuddyAllocator 를 관리해줍니다. 필요에 따라서 FD3D12BudyAllocator 를 생성/해제 시킵니다. 그리고 FD3D12BuddyAllocator 또한 FD3D12MemoryPool 과 동일한 2가지 kPlacedResource, kManualSubAllocation 메모리 할당 전략을 가집니다.
    4. FD3D12FastConstantAllocator 는 UniformBuffer SingleFrame, SingleDraw 에서 사용하는 Allocator 입니다. 자세한 것은 '2.1.1.2 FD3D12UniformBuffer' 에서 보겠습니다.

    그림3. 3가지 타입의 Allocator 를 구성하는데 사용되는 FD3D12MultiBuddyAllocator 와 FD3D12PoolAllocator, 그리고 FD3D12MultiBuddyAllocator 를 통해 생성되는 FD3D12FastConstantAllocator

     

    2.1.1.2. FD3D12UniformBuffer

    FD3D12UniformBuffer 는 FD3D12Buffer, FD3D12Texture 와 다르게 생성 시에 Lifetime(Single or Multi frame) 을 지정할 수 있기 때문에 다른 리소스와는 생성 방식이 조금 다릅니다. 
     
    UniformBuffer Single draw, Single frame 에 사용하는 FD3D12FastConstantAllocator 를 봅시다. 그림3 를 좌측 하단의 FD3D12FastConstantAllocator 를 보면 이 Allocator 는 그냥 FD3D12MultiBuddyAllocator 에서 할당한 GD3D12FastConstantAllocatorPageSize(65536) 크기의 ID3D12Resource 입니다. FD3D12FastConstantAllocator 는 65536 크기의 Buffer 를 하나 할당받아서(이 버퍼를 Page 라 부름) 거기서 필요한 메모리 영역을 잘라서 Signle lifetime 의 UniformBuffer 를 생성하는 데 사용합니다. 그리고 할당받은 65536 의 ID3D12Resource 를 다 소모하게 되면, 그냥 ID3D12Resource 를 새로 할당합니다. 그럼 기존에 사용하던 ID3D12Resource 는 어떻게 될까요? 해제됩니다. 하지만 즉시 해제 되는 것이 아니라 FD3D12BuddyAllocator 의 DeferredDeletionQueue 에 담아뒀다가 해제 가능한 시점에서 해제 시킵니다. 해제 가능 여부는 매 프레임 마다 1씩 증가하는 FrameFence 를 사용합니다. ID3D12Resource 를 DeferredDeletionQueue 에 담을 때 이 FrameFence 를 같이 담아 줍니다. 그래서 현재 프레임의 실행이 완료 시점까지 리소스를 삭제하지 않고 기다리도록 해줍니다.
     

    2.1.2. 리소스 해제 방식 정리

    FD3D12Buffer, FD3D12Texture, FD3D12UniformBuffer 와 같은 리소스들은 FD3D12ResourceLocation 에 실제 ID3D12Resource 두고 있다고 했습니다. 그래서 실제 리소스 해제는 FD3D12ResourceLocation 에서 일어납니다. 리소스 해제 함수는 FD3D12ResourceLocation::ReleaseResource 입니다. 그림2 에서, FD3D12ResourceLocation::ResourceLocationType 에 따라서 서로 다른 리소스 해제 전략을 사용한다고 했습니다. FD3D12ResourceLocation::ResourceLocationType 를 기반으로 리소스 해제 전략을 확인해 봅시다.
     
    1. eStandAlone 의 경우 CreateCommittedResource 로 생성하여 자신만의 Heap 을 가진 리소스입니다. 이 경우 특별한 경우가 아니면 DeferDelete() 를 호출하여 리소스를 해제합니다. DeferDelete 로 해제하는 경우 FD3D12DynamicRHI.ObjectsToDelete 에 등록해 줍니다. ObjectsToDelete 는 다음 함수들을 차례로 호출하여 FlushPendingDeletes → RHIPerFrameRHIFlushComplete 최종 적으로 EnqueueEndOfPipeTask 에 전달됩니다. EnqueueEndOfPipeTask 는 현재 제출된 모든 GPU command 실행 완료 될 때 ObjectsToDelete 의 리소스들을 해제할 수 있게 해줍니다. 자세한 코드는 두 번째 글에서 실제 코드 진행을 보면서 확인해 봅시다.
    2. eSubAllocation 의 경우 FD3D12PoolAllocator 또는 FD3D12BuddyAllocator 를 통해서 할당된 경우입니다. 이 경우 해당 메모리 AllocatorType 이 AT_Pool 인 경우가 FD3D12PoolAllocator, AT_Default 인 경우가 FD3D12BuddyAllocator 인 경우입니다.
    2.1. FD3D12PoolAllocator 는 DeallocateResource 호출 시 FD3D12PoolAllocator 의 멤버인 FrameFencedOperations 에 등록하여 해제를 예약합니다 FrameFencedOperations 는 추후 FD3D12PoolAllocator::CleanUpAllocations 가 호출될 때 FrameFence 를 사용하여 현재 리소스가 사용이 완료되고 나서 리소스가 해제될 수 있도록 합니다.
    2.2. FD3D12BuddyAllocator 는 Deallocate 호출 시 FD3D12BuddyAllocator 의 멤버인 DeferredDeletionQueue 에 등록하여 해제를 예약합니다. FD3D12BuddyAllocator 는 추후 FD3D12BuddyAllocator::CleanUpAllocations 가 호출될 때 FrameFence 를 사용하여 현재 리소스가 사용이 완료되고 나서 리소스가 해제될 수 있도록 합니다.
    3. eHeapAliased 의 경우는 TransientHeap 에서 할당한 리소스들의 경우 이쪽으로 들어옵니다. eStandAlone 과 비슷한 방식으로 리소스를 해제합니다.
    4. eFastAllocation 의 경우 아무 처리를 하지 않고 끝냅니다. 이유는 아래와 같습니다.

    • eFastAllocation 은 Single lifetime UniformBuffer 에 사용되며 FD3D12FastConstantAllocator 를 통해 생성됩니다. eFastAllocation 타입의 경우 해제 시점은 정확히 특정할 수는 없고, 아래와 같은 방식으로 해제됩니다.
    • FD3D12FastConstantAllocator 는 FD3D12UploadHeapAllocator 의 FastConstantPageAllocator(FD3D12MultiBuddyAllocator) 를 사용하여 Page 단위로 리소스를 할당하는 것을 기억해 둡시다.
    • FD3D12FastConstantAllocator 는 FD3D12BuddyAllocator 로 부터 65536 크기의 Page 를 할당해서 이 Page 를 모두 사용하는 경우 새로운 Page 를 할당하여 계속 사용합니다. 새로 할당하는 경우 기존의 할당된 Page 는 바로 해제 처리를 시도를 합니다. 그런데 이 Page 는 한 개의 리소스를 대표하는 것이 아니고, 여러 리소스들이 1 개의 Page 로부터 자신의 영역을 할당받아서 사용하는 방식입니다. 그래서 그냥 바로 해제해버리면 현재 사용 중이거나 사용 예정인 리소스는 어떻게 되는 걸까요? 즉시 해제가 아니라 해제 예약을 하기 때문에 괜찮습니다.
    • 이렇게 해도 괜찮은 이유는 FD3D12FastConstantAllocator 에서 생성한 리소스는 이번 프레임에만 사용할 리소스이기 때문입니다. FD3D12BuddyAllocator 의 구현에 따르면 해제 요청을 받은 리소스는 DeferredDeletionQueue 에 들어가게 됩니다. 그리고 해제 시점은 EndFrame 시점에서 CleanUpAllocations 호출 까지로 지연될 것입니다. 또한 FD3D12BuddyAllocator 는 DeferredDeletionQueue 에 있는 리소스를 해제시킬 때, FrameFence 를 사용하여 리소스가 사용 중에는 해제되지 않을 것임을 보장해 줍니다. 이런 방식을 통해 이미 다 차버린 Page 의 경우 신경 쓰지 않고 바로 해제 처리해서 잊어버리고 새 Page 를 계속해서 할당하여 사용할 수 있을 것입니다.

     

    2.2. RDG 방식의 TransientHeap

    2.2.1. 주요 클래스 파악

    RDG 는 필요한 렌더패스를 등록하고, 그 렌더패스에 필요한 리소스 또한 할당할 수 있습니다. 여기서 할당되는 리소스는 이번 렌더패스에서만 사용되는 임시 리소스일 것입니다(RDG 가 할당하지 않는 리소스도 RDG 에서 사용할 수 있으며 이 경우 External 리소스로 보게 됨). 오늘 우리가 관심이 있는 것은 바로 이 RDG 의 임시 리소스입니다.
    FRDGBuilder 는 FRDGBuffer, FRDGTexture, FRDGUniformBuffer 와 같은 리소스를 생성할 수 있습니다. 이 것 외에도 RenderTarget 과 같은 객체도 생성할 수 있지만 위의 3가지 타입의 리소스를 사용하여 만들거나 생성 과정에서 크게 다르지 않기 때문에 다루지 않을 것입니다.
    FRDGTexture 와 FRDGBuffer 는 각각 FRHITransientTexture, FRHITransientBuffer 를 사용하여 생성됩니다. 이 둘은 FRHITransientResource 클래스를 상속하며, FRHIResource 가 실제 FD3D12Texture, FD3D12Buffer 등등의 타입들로 대응됩니다. 즉, 실제 리소스는 FRDGBuffer, FRDGTexture 가 소유하고 있는 FRHITransientTexture, FRHITransientBuffer 가 갖고 있는 것을 기억하면 됩니다. 그림4 를 보면 FRDGBuilder 로부터 생성된 Buffer, Texture 가 어떤 관계를 갖고 있는지 보여줍니다.

    그림4. FRHITransientTexture, FRHITransientBuffer 와 FRDGTexture, FRDGBuffer 사이의 관계

     
    FRDGUniformBuffer 의 경우는 FRHITransientBuffer 와 FRHITransientTexture 는 다른 방식으로 할당됩니다. UniformBuffer 는 Non-TransientHeap 방식의 생성에서 이미 Single lifetime 방식으로 생성하는 능력을 갖고 있기 때문에 그것을 그대로 사용합니다. 그림5 를 참고해 주세요. 자세한 내용은 ‘2.1.1.2. FD3D12UniformBuffer’ 에서 FD3D12FastConstantAllocator 를 사용하여 FD3D12UniformBuffer 생성하는 부분을 확인하면 됩니다.
     

    그림5. UploadHeap 의 FD3D12FastConstantAllocator 를 통해 FD3D12UniformBuffer 를 만들어 FRDGUniformBuffer 를 생성

    계속해서 FRDGTexture 와 FRDGBuffer 의 실제 리소스인 FRHITransientTexture, FRHITransientBuffer 를 생성하는 방식을 알아봅시다.
     
    그림6 을 보면 FRDGBuilder 는 FRDGTransientResourceAllocator 를 갖고 있습니다. 이 객체는 전역변수로 선언된 단일 객체입니다. FRDGTransientResourceAllocator 이 소유하고 있는 FD3D12TransientResourceHeapAllocator 가 FRHITransientTexture, FRHITransientBuffer 를 생성할 수 있습니다. FRDGTransientResourceAllocator 는 여기에서 FRHITransientRenderTarget 의 생성/해제 풀링을 하는 기능을 더 한 것입니다.
     

    그림6. FRHITransientTexture, FRHITransientBuffer 를 생성할 수 있는 FD3D12TransientResourceHeapAllocator 그리고 거기에 추가로 FRDGTransientRenderTarget 을 생성할 수 있는 FRDGTransientResourceAllocator

     
    그림6 를 통해서 FD3D12TransientResourceHeapAllocator 가 FRHITransientTexture, FRHITransientBuffer 를 생성하는 최전선에 있는 클래스인 것을 확인했습니다(FRDGBuilder 기준). 그렇다면 이 두 리소스를 생성하기 위해서 Heap 을 관리해주는 클래스가 있을 것입니다. 어떤 전략을 사용하여 Heap 을 사용하는지 계속해서 확인해 봅시다.
     
    그림7 를 보면 실제 FD3D12Heap 을 관리하는 객체는 바로 FRHITransientHeap 입니다. FRHITransientHeap 는 Texture, Buffer 를 새로 생성할 수 있는 함수도 소유하고 있으며, 리소스를 생성해야 하는 경우 멤버 변수인 FRHITransientHeapAllocator 클래스를 통해서 할당할 수 있는 메모리 영역을 얻어옵니다. 이 메모리 영역을 사용하여 실제 FD3D12TransientHeap 의 FD3D12Heap 에서 메모리를 할당합니다.
    FRHITransientHeap 은 실제 자신이 생성했던 FRHITransientTexture, FRHITransientBuffer 를 캐시하고 있으며, 필요한 경우 다시 재활용합니다. 그러기 위해서 사용하는 클래스가 TRHITransientResourceCache 입니다. 이 클래스는 다음 조건이 만족하지 않는 한 생성해 둔 리소스를 계속해서 캐시 해둡니다.

    • 자신의 Capacity 를 초과하며, 마지막으로 리소스를 사용한 뒤 GarbageCollectLatency 만큼 지나게 되면 리소스를 제거합니다. 자세한 해제 부분은 다음글에서 알아봅시다.

    만약 리소스를 할당할 수 있는 FD3D12TransientHeap 을 모두 다 사용하면 추가 Heap 을 할당해야겠죠? 그것을 위한 클래스가 바로 FD3D12TranseintHeapCache 입니다. 이 클래스는 FRHITransientHeap 을 필요한 리소스 양에 따라서 관리합니다. LiveList, FreeList 를 별로도 관리하여 FRHITransientHeap 을 풀링 하는 것도 확인할 수 있습니다.
    마지막으로 볼 클래스는 FRDGBuilder 기준 FRHITransientTexture, FRHITransientBuffer 생성하는 최전선에 있었던 클래스 FD3D12TransientResourceHeapAllocator 입니다. 이 클래스는 위에서 본 FD3D12TranseintHeapCache 클래스를 사용해서 Heap 이 더 필요하면 할당받습니다. 그리고 Heap 이 할당 요청받은 리소스가 이미 캐시 되어 있다면 그것을 사용하고 없으면 새로 만드는 역할을 담당합니다.

    그림7. FD3D12TransientResourceHeapAllocator, FD3D12TransientHeapCache, FD3D12TransientHeap

    지금까지 FRHITransientTexture, FRHITransientBuffer, FD3D12UniformBuffer 클래스 관계를 확인했습니다. FRDGTexture, FRDGBuffer, FRDGUniformBuffer 은 위에서 생성한 리소스들을 소유하고 있는 수준이라 실제 코드를 보면서 생성 과정을 확인해도 충분할 것 같습니다.
     

    2.3. Non-TransientHeap 방식의 전체 UML

    그림8.Non-TransientHeap 방식의 전체 UML

    2.4. TransientHeap 방식의 전체 UML

    그림9. TransientHeap 방식의 전체 UML

     

     

    3. 레퍼런스

    1. https://github.com/EpicGames/UnrealEngine/tree/5.2.0-release
    2. https://github.com/microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D12SmallResources
    3. https://docs.unrealengine.com/5.0/ko/render-dependency-graph-in-unreal-engine/
    4. https://epicgames.ent.box.com/s/ul1h44ozs0t2850ug0hrohlzm53kxwrz
    5. https://en.wikipedia.org/wiki/Buddy_memory_allocation

     

    'UE4 & UE5 > Rendering' 카테고리의 다른 글

    [UE5] Nanite (1/5)  (1) 2024.03.14
    [UE5] D3D12 ResourceAllocation 리뷰(2/2)  (0) 2023.08.24
    [UE5] Auto Exposure (2/2) - Histogram  (0) 2022.12.15
    [UE5] Auto Exposure (1/2)  (1) 2022.11.29
    [UE5] Occlusion Culling  (0) 2022.10.08

    댓글

Designed by Tistory & scahp.