ABOUT ME

🕹️게임 개발자가 되기를
✨꿈꾸는 사람

  • [019] 서술자 힙 관리 클래스
    Programming/Dev 2025. 5. 1. 16:30

    Dev

    원래는 씬 변경 구현을 하려 했으나..

    일단 이게 더 급하고 중요한 것 같아서 먼저 하게 됐다.

    그래픽스 코드 구조를 이쁘게 만든 뒤에 인게임 로직 코드를 작성하는게 목표다.

     

     

     

     

    DescriptorManager::DescriptorManager() :
        m_srvHeap{ D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1024, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 0 },
        m_rtvHeap{ D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 512, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 0 },
        m_dsvHeap{ D3D12_DESCRIPTOR_HEAP_TYPE_DSV, 512, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 0 }
    {
        if (s_srvDescriptorSize == 0)
            s_srvDescriptorSize = g_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
        if (s_rtvDescriptorSize == 0)
            s_rtvDescriptorSize = g_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
        if (s_dsvDescriptorSize == 0)
            s_dsvDescriptorSize = g_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
    }

    `DescriptorManager`는 싱글톤으로 크기가 큰 SRV, RTV, DSV 힙을 관리하도록 했다.

    처음에는 가변 크기 힙을 구현하려고 했으나 어려운 부분이 많고 이렇게 크기를 크게 잡아둬도 큰 문제가 없다고 판단했다.

     

     

     

     

    DescriptorManager::Heap::Heap(D3D12_DESCRIPTOR_HEAP_TYPE type, UINT numDescriptors, D3D12_DESCRIPTOR_HEAP_FLAGS flags, UINT nodeMask) :
        m_desc{}
    {
        m_desc.Type = type;
        m_desc.NumDescriptors = numDescriptors;
        m_desc.Flags = flags;
        m_desc.NodeMask = nodeMask;
        g_d3dDevice->CreateDescriptorHeap(&m_desc, IID_PPV_ARGS(&m_heap));
    
        m_descriptors.resize(numDescriptors);
        m_freeList.reserve(numDescriptors);
        for (int i : std::views::iota(0)
                   | std::views::take(numDescriptors)
                   | std::views::reverse)
        {
            m_freeList.push_back(i);
        }
    }

    `m_descriptors` 는 CPU, GPU 핸들을 갖고 있고,
    `m_freeList` 에는 `m_descriptors` 컨테이너에서 사용 가능한 인덱스를 담고 있다.

     

     

     

     

    Descriptor* DescriptorManager::Heap::Allocate()
    {
        assert(!m_freeList.empty() && "NO AVAILABLE HEAP SPACE");
    
        INT index{ m_freeList.back() };
        m_freeList.pop_back();
    
        UINT descriptorSize{ GetDescriptorSize() };
        CD3DX12_CPU_DESCRIPTOR_HANDLE cpuHandle{ m_heap->GetCPUDescriptorHandleForHeapStart(), index, descriptorSize };
        CD3DX12_GPU_DESCRIPTOR_HANDLE gpuHandle{ D3D12_DEFAULT };
        if (m_desc.Flags == D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)
            gpuHandle.InitOffsetted(m_heap->GetGPUDescriptorHandleForHeapStart(), index, descriptorSize);
        m_descriptors[index] = Descriptor{ cpuHandle, gpuHandle };
        return &m_descriptors[index];
    }
    
    void DescriptorManager::Heap::Deallocate(Descriptor* descriptor)
    {
        auto it{ std::ranges::find_if(m_descriptors, [descriptor](const auto& desc) { return &desc == descriptor; }) };
        if (it == m_descriptors.end())
            return;
    
        *it = Descriptor{};
    
        INT index{ static_cast<INT>(std::distance(m_descriptors.begin(), it)) };
        m_freeList.push_back(index);
    }

    서술자를 할당, 해제 하는 함수이다.

    할당할 때 `m_freeList` 에서 인덱스 하나 꺼내서 그 인덱스에 있는 원소에 핸들을 세팅해준다.

    해제할 때는 해당 인덱스에 있는 원소를 기본값(=유효하지 않은 값)으로 설정하고 `m_freeList` 에 넣어 사용 가능하다고 표시한다.

     

     

     

     

    void SwapChain::CreateRenderTargetView()
    {
        auto dm{ DescriptorManager::GetInstance() };
        if (!dm)
            return;
    
        for (size_t i{ 0 }; i < FRAME_COUNT; ++i)
        {
            if (FAILED(m_swapChain->GetBuffer(static_cast<UINT>(i), IID_PPV_ARGS(&m_frameResources[i].backBuffer))))
            {
                assert(false);
                return;
            }
    
            if (m_frameResources[i].rtvDesc)
            {
                dm->Deallocate(D3D12_DESCRIPTOR_HEAP_TYPE_RTV, m_frameResources[i].rtvDesc);
                m_frameResources[i].rtvDesc = nullptr;
            }
    
            m_frameResources[i].rtvDesc = dm->Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
            m_frameResources[i].rtvDesc->CreateRenderTargetView(m_frameResources[i].backBuffer, nullptr);
        }
    }

    사용할 때는 `Allocate` 함수 호출 할 때 할당 받을 힙 타입을 인자로 넘겨주면 된다.

    위 코드는 RTV를 만드는 코드이다.

     

     

     

     

    void SwapChain::Begin3D()
    {
        if (FAILED(m_frameResources[m_frameIndex].commandAllocator->Reset()))
        {
            assert(false);
            return;
        }
    
        if (FAILED(g_commandList->Reset(m_frameResources[m_frameIndex].commandAllocator.Get(), nullptr)))
        {
            assert(false);
            return;
        }
    
        g_commandList->SetGraphicsRootSignature(g_rootSignature.Get());
        g_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_frameResources[m_frameIndex].backBuffer.Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
    
        auto rtvHandle{ m_frameResources[m_frameIndex].rtvDesc->GetCpuHandle() };
        auto dsvHandle{ m_dsvDesc->GetCpuHandle() };
        g_commandList->OMSetRenderTargets(1, &rtvHandle, TRUE, &dsvHandle);
        g_commandList->RSSetViewports(1, &g_viewport);
        g_commandList->RSSetScissorRects(1, &g_scissorRect);
    
        constexpr std::array clearColor{ 0.15625f, 0.171875f, 0.203125f, 1.0f };
        g_commandList->ClearRenderTargetView(rtvHandle, clearColor.data(), 0, nullptr);
        g_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
    
        if (auto dm{ D3D::DescriptorManager::GetInstance() })
            dm->SetDescriptorHeaps();
    }

    렌더링 시작할 때 `SetDescriptorHeaps` 함수를 호출해줘서 SRV 서술자 힙을 세팅해준다.

     

     

     

     

    void Viewport::Render()
    {
        static auto renderTarget{ Graphics::D3D::CreateRenderTarget(1920, 1080) };
        static std::shared_ptr<Graphics::D3D::Camera> camera;
        if (!camera)
        {
            camera = Graphics::D3D::CreateCamera();
        }
    
        static std::shared_ptr<Resource::Model> robot;
        if (!robot)
        {
            auto prop{ Resource::Get(L"Data/Test.dat/Robot") };
            robot = prop->GetModel();
        }
    
        if (ImGui::Begin(WINDOW_NAME, nullptr, ImGuiWindowFlags_HorizontalScrollbar))
        {
            Graphics::D3D::PushRenderTarget(renderTarget);
            Graphics::D3D::SetCamera(camera);
            Graphics::D3D::Render(robot);
            Graphics::D3D::PopRenderTarget();
            Graphics::ImGui::Image(renderTarget, ImVec2{ 1920, 1080 });
        }
        ImGui::End();
    }

    위 코드는 ImGui에서 텍스쳐를 렌더링하는 코드다.

    `class RenderTarget` 을 만들 때 SRV를 하나 할당해서 ImGui에서도 쓸 수 있게 만들었다.

     

     

     

     

    ImGui에 잘 들어간 모습

    렌더타겟을 만들어서 그 곳에 모델을 하나 그리고, 그것을 ImGui로 그리게 해보았다.

    사실 이거 하려고 서술자 힙 관리 클래스를 만든 것 같다...

    댓글