본문 바로가기

개발/Rust

Rust에서 DirectX12 개발하기(4) - DirectX 초기화하기(RenderTargetView 생성)

이전 편에서는 CommandAllocator와 CommandQueue 그리고 SwapChain을 구성하는 방법에 대해서 알아보았습니다. 

 

2021.06.09 - [개발/Rust] - Rust에서 DirectX12 개발하기(3) - DirectX 초기화하기(CommandQueue, CommandAllocator, SwapChain 초기화)

 

Rust에서 DirectX12 개발하기(3) - DirectX 초기화하기(CommandQueue, CommandAllocator, SwapChain 초기화)

이전 편에서는 Rust에서 DirectX12를 사용하기 위해서, 디바이스를 초기화하는 방법을 살펴보았습니다. 2021.06.07 - [개발/Rust] - Rust에서 DirectX12 개발하기(2) - DirectX 초기화하기(디바이스 초기화) Rust..

honey-balm.tistory.com

 

이번 편에서는 RenderTargetView를 생성하는 방법에 대해서 알아보겠습니다.

 

 


 

 

RenderTargetView(RTV)란 간단하게 말해서 렌더링 파이프라인의 출력(한 프레임의 이미지 등)을 특정한 리소스와 바인딩할 때 사용됩니다. DirectX12에서는 어떠한 자원이 렌더링 파이프라인에 직접적으로 연결되지 않습니다. 연결된다는 것은 파이프라인이 사용할 자원에 대한 정보를 얻게 되는 과정으로 이해하시면 될 것 같습니다. 

 

즉, 이전 단계에서 SwapChain을 생성했고, 그 때 buffer_index로 지정한 숫자만큼의 버퍼를 생성했습니다. 하지만 이 버퍼를 렌더링 파이프라인에 직접적으로 연결할 수 없다는 뜻입니다. 위에서도 설명했듯이, 렌더링 파이프라인에 연결하기 위해서는 RenderTargetView를 사용해야하는 것입니다. 

 

사실 DirectX12에서는 RenderTargetView만이 아닌 다른 많은 리소스들이 파이프라인에 직접적으로 연결되지 않습니다. 그렇기 때문에 Descriptor라고 불리는 설명자를 생성해서, 이 Descriptor를 파이프라인에 연결해야합니다[각주:1]. RTV도 이 Descriptor의 일종입니다. 

 

RTV를 생성하기 전에, RTV를 담고 있을 Descriptor Heap[각주:2]을 생성해야 합니다. Descriptor Heap이란 여러 Descriptor를 가지고 있는 일종의 Descriptor 배열입니다. 

 

RTV를 생성하는 코드는 아래와 같습니다.

 

fn create_rtv_heap(
    &self,
    buffer_count: u32,
    device: &ID3D12Device,
) -> Result<(ID3D12DescriptorHeap, u32)> {
    let rtv_heap: ID3D12DescriptorHeap = unsafe {
        device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC {
            NumDescriptors: buffer_count,
            Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
            ..Default::default()
        })
    }?;

    let rtv_desc_size =
        unsafe { device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) };

    Ok((rtv_heap, rtv_desc_size))
}

 

 

ID3D12Device::CreateDescriptorHeap[각주:3] 함수를 통해서 쉽게 DescriptorHeap을 생성할 수 있습니다. 매개변수로는 D3D12_DESCRIPTOR_HEAP_DESC[각주:4]가 전달됩니다. D3D12_DESCRIPTOR_HEAP_DESC의 구조체는 아래와 같습니다.

 

typedef struct D3D12_DESCRIPTOR_HEAP_DESC {
  D3D12_DESCRIPTOR_HEAP_TYPE  Type;
  UINT                        NumDescriptors;
  D3D12_DESCRIPTOR_HEAP_FLAGS Flags;
  UINT                        NodeMask;
} D3D12_DESCRIPTOR_HEAP_DESC;

 

이 중에서 Type은 Descriptor의 타입을 설정하는 곳이고, NumDescripotr는 Descriptor의 수, Flags는 해당 힙이 GPU에서 사용될 수 있는지 지정합니다. NodeMask는 하나의 GPU만 사용할 경우에는 0으로 설정하면 됩니다. 

 

위 코드에서는 RTV를 저장할 힙을 생성하므로, Type 필드를 D3D12_DESCRIPTOR_HEAP_TYPE_RTV로 설정했고, NumDescriptors 필드는 buffer_count로 설정합니다. 나머지 값은 기본 값으로 초기화하면 됩니다. 

 

각 힙에 담길 Descriptor의 크기를 Device::GetDescriptorHandleIncrementSize[각주:5] 함수를 통해서 얻습니다. 이 크기는 힙 내부에서 Descriptor를 조회할 때 사용됩니다. 

 

RTV를 담는 힙을 생성했으니, 이제는 실제 RTV를 생성하면 됩니다. 코드는 아래와 같습니다. 

 

fn create_render_target(
    &self,
    buffer_count: u32,
    rtv_desc_size: u32,
    device: &ID3D12Device,
    swap_chain: &IDXGISwapChain,
    rtv_heap: &ID3D12DescriptorHeap,
) -> Result<Vec<ID3D12Resource>> {
    let rtv_handle = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() };

    let mut render_targets = Vec::new();

    for i in 0..buffer_count {
        let render_target: ID3D12Resource = unsafe { swap_chain.GetBuffer(i) }?;

        unsafe {
            device.CreateRenderTargetView(
                &render_target,
                std::ptr::null(),
                &D3D12_CPU_DESCRIPTOR_HANDLE {
                    ptr: rtv_handle.ptr + (rtv_desc_size * i) as usize,
                },
            )
        }

        render_targets.push(render_target);
    }

    Ok(render_targets)
}

 

위 코드에서는 D3D12_CPU_DESCRIPTOR_HANDLE이란 구조체를 ID3D12DescriptorHeap::GetCPUDescriptorHandleForHeapStart[각주:6] 함수를 통해서 얻습니다. 

DirectX12에서 사용되는 리소스들은 종류에 따라서 가시성의 범위가 달라집니다. 기본적으로 모든 리소스는 CPU에서 사용될 수 있지만, GPU에서는 특정 리소스들만이 사용될 수 있습니다. RTV의 경우 CPU에서만 사용되는 리소스입니다. 그렇기 때문에 CPU Handle을 얻습니다.

 

그 다음 buffer_count만큼 RTV를 생성해주고, 생성한 RTV를 render_targets 벡터에 push 해주면 됩니다. RTV를 생성할 때는 ID3D12Device::CreateRenderTargetView[각주:7]라는 함수를 사용합니다. 

 

SwapChain에서 버퍼를 얻어온 다음, 해당 버퍼를 CreateRendeTargetView의 pSource로 전달해줍니다. pDesc는 NullDescriptor를 전달해주고, 마지막으로 D3D12_CPU_DESCRIPTOR_HANDLE 구조체를 전달해줍니다.

 

D3D12_CPU_DESCRIPTOR_HANDLE의 유일한 필드는 ptr이며, 생성할 RTV의 주소를 지정해주면 됩니다. 이전에 얻은 rtv_handle.ptr을 통해 힙의 시작 지점 포인터를 얻은 후 create_rtv_heap 함수를 통해서 얻은 rtv_desc_size의 크기를 통해서 각 rtv의 주소값을 구해줍니다.

 

 

각 생성 함수를 호출하는 코드는 아래와 같습니다.

 

let (rtv_heap, rtv_desc_size) = ret.create_rtv_heap(2, &device)?;
let render_targets =
    ret.create_render_target(2, rtv_desc_size, &device, &swap_chain, &rtv_heap);

 

 


 

 

이번 편에서는 DescriptorHeap을 생성하는 방법과 RenderTargetView를 생성하는 방법에 대해서 알아봤습니다.

 

샘플 프로젝트 코드는 github에 올렸습니다.

 

seonhwi07jp/rust-d3d12 (github.com)

 

seonhwi07jp/rust-d3d12

Contribute to seonhwi07jp/rust-d3d12 development by creating an account on GitHub.

github.com

 

해당 코드는 ver5 브랜치로 이동하면 볼 수 있습니다.