[dx12显示图片] ImGui Learn Data Day 3

调试心得:相对路径陷阱与编译期/运行期的区别,以及如何显示图片
image-20251214224437304

问题背景

最近我遇到了一个非常深刻的教训:imgui代码始终无法加载 graph/Alice.png,这让我一度怀疑是 DirectX 12 环境配置的问题。

就在刚刚,我终于发现了原因。
我的文件结构如下:

  • 项目根目录rt/
  • 源代码位置rt/imguiest/main.cpp
  • 资源位置rt/imguiest/graph/

然而,我的 Visual Studio 项目配置的工作目录(Working Directory) 是以 rt 为根目录开始的。问题正出在这里。

代码分析

我在代码中尝试这样加载图片:

 // 调用修复后的加载函数
 bool ret = LoadTextureFromFile("graph/Alice.png", &my_texture_resource, &my_image_width, &my_image_height);

这段代码试图以相对路径寻找 graph 文件夹,但始终失败。
原因在于:程序运行时,相对路径是从工作目录 rt 开始计算的。程序试图寻找 rt/graph/Alice.png,但实际上文件位于 rt/imguiest/graph/Alice.png,因此无法找到。

核心疑问

这里产生了一个困惑:我的头文件也放在 rt/imguiest 中,为什么编译器能找到头文件,程序运行时却找不到图片资源?


深度解析:编译时 vs 运行时

这个问题的本质在于编程中两个完全不同的阶段:编译时(Compile-time)运行时(Runtime)

1. 找头文件 (#include) —— 编译器在工作

  • 阶段:编译时。
  • 原理:当你编写 #include "my_header.h" 时,这是给预处理器看的。C++ 标准规定,使用双引号 "" 引用头文件时,优先在当前源文件所在的目录查找
  • 结论:编译器知道你的 main.cpp 位于 rt/imguiest/,所以它会默认从这个文件夹开始寻找旁边的头文件。这是为了方便开发者引用同目录下的模块。

2. 找图片 (LoadTexture) —— 程序在工作

  • 阶段:运行时。
  • 原理:当你运行编译好的程序(.exe)时,它已经脱离了源码文件,只认“当前工作目录 (Current Working Directory)”
  • IDE 的行为
    1. Visual Studio 将代码编译成 .exe
    2. VS 指挥 Windows 运行该程序,并指定项目文件夹.vcxproj 所在目录,即你的 rt/)作为程序的工作目录
  • 为什么不以 main.cpp 为基准?
    一个项目可能包含数百个分布在不同文件夹下的 .cpp 文件。如果以源文件位置为基准,资源路径管理将变得极其混乱。因此,IDE 统一规定:运行时所有相对路径的起点,默认都是项目根目录。

ImGui显示图片

要在 ImGui 中显示一张图片,本质上是一个 “搬运数据”“建立索引” 的过程。我们可以把它分为四个核心步骤:

  1. CPU 阶段:把图片文件读到内存(RAM)。
  2. GPU 搬运阶段:把内存里的数据搬运到显存(VRAM)。
  3. 描述符阶段(最关键):给这张图片办一张“身份证”(SRV)。
  4. 渲染阶段:把“身份证”交给 ImGui。

要在 ImGui 中显示一张图片,本质上需要涉及计算机图形学的底层知识。但这门学科体系庞大,对于初学者来说,想要为了显示一张图而去系统学习图形学,时间成本过高(之后我会专门开一期文章来探讨这部分理论)。

鉴于 DX12 涉及的概念(如内存管理、描述符堆等)对于新手来说过于晦涩,我选择借助 AI 辅助生成了具体的实现代码。

然而,即便有了代码,跑通它也绝非易事。过程中我遇到了大量环境配置和依赖库的问题。本文将详细记录我遇到的“坑”以及解决方案,并在文末附上完整的可用代码。

第一步:确认系统与显卡支持的 DirectX 版本

首先,你需要确认你的硬件和系统是否支持 DX12。

  1. 按下 Win + R 键,输入 dxdiag 并回车,打开 DirectX 诊断工具。
  2. 在“系统”或“显示”选项卡下,查看“DirectX 版本”或“功能级别”。
    • 注:Windows 10 及以上系统通常默认同时支持 DX11 和 DX12。

⚠️ 特别注意:DirectX 11 和 DirectX 12 在架构上有本质的区别,它们不仅仅是版本号的不同,底层逻辑完全不同。

第二步:解决头文件缺失问题 (Windows SDK)

ImGui 官方提供的示例通常可以直接运行,但如果你编译时报错提示找不到文件(例如 d3d11.hd3d12.h),这通常不是代码问题,而是开发环境缺失

  • 错误原因:你的 Visual Studio 安装时未勾选相应的 Windows SDK
  • 错误做法:不要去网上随便下载一个 .h 文件放到项目里,这会导致更多兼容性问题。
  • 正确做法:打开 Visual Studio Installer,修改安装配置,确保勾选了 "Windows 10 SDK" (或 Win11 SDK) 以及 "C++ 桌面开发" 工作负载。

第三步:处理 DX12 特有的 d3dx12.h

⚠️ 特别注意:DX12 开发中经常用到一个辅助头文件 d3dx12.h
这个文件不包含在标准的 Windows SDK 中,它是一个微软官方提供的辅助库(Helper Library)。

如果你发现代码中缺少这个文件:

  1. 你需要手动下载该文件(通常可以在 Microsoft 的 GitHub 仓库 DirectX-Headers 中找到)。
  2. 或者在使用 DirectXTex 等库时,它们通常也会包含这个辅助文件。
  3. 下载后,将其放入你的项目目录,并在解决方案资源管理器中添加现有项将其包含进去。

第四步:链接库文件配置

在 C++ 中使用 DirectX,除了头文件,还需要链接对应的 .lib 库文件。为了省去在项目属性页面手动配置链接器的麻烦,我们可以直接在代码中使用 #pragma comment 指令:

// 自动链接库文件,省去手动配置库依赖的繁琐步骤
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
// 如果使用了 GUID 相关功能,可能还需要链接 dxguid
#pragma comment(lib, "dxguid.lib") 

第五步:常见报错与排查思路

由于 DX12 和 DX11 接口差异巨大,千万不要尝试通过简单地将变量名中的 11 改为 12 来移植代码,这绝对是行不通的。

如果你在编译时遇到大量“未定义标识符”或宏定义不存在的错误,请按以下顺序排查:

  1. 头文件未包含:检查 .h 文件是否不仅存在于文件夹中,还必须在 Visual Studio 的解决方案资源管理器中被添加到了项目里。
  2. 拼写错误:检查是否拼写错误,或者使用了旧版/新版 API 的名称。
  3. SDK 版本不匹配:如果提示 SDK 版本不存在或不兼容:
    • 右键点击项目解决方案 -> 选择 “重定解决方案目标” (Retarget Solution)
    • 在弹出的窗口中选择你当前已安装的 Windows SDK 版本,点击确定。

我的终于能跑的代码:

#include "imgui/imgui.h"
#include "imgui/imgui_impl_win32.h"
#include "imgui/imgui_impl_dx12.h"
#include <d3d12.h>
#include <dxgi1_4.h>
#include <tchar.h>
#include <iostream>
#include <fstream>

// 引入 d3dx12.h (确保文件在你的工程目录下)
#include "imgui/d3dx12.h" 

// 自动链接库文件,省去配置项目属性的麻烦
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "dxguid.lib")

#define STB_IMAGE_IMPLEMENTATION
#include "imgui/stb_image.h"

// 全局变量
static ID3D12Device* g_pd3dDevice = nullptr;
static ID3D12DescriptorHeap* g_pd3dRtvHeap = nullptr;
static ID3D12DescriptorHeap* g_pd3dSrvHeap = nullptr; // SRV 堆
static ID3D12CommandQueue* g_pd3dCommandQueue = nullptr;
static ID3D12GraphicsCommandList* g_pd3dCommandList = nullptr;
static ID3D12CommandAllocator* g_pd3dCommandAllocator = nullptr; // 必须有分配器
static IDXGISwapChain3* g_pSwapChain = nullptr;
static ID3D12Resource* g_mainRenderTargetResource[2] = {}; // 双缓冲
static ID3D12Fence* g_fence = nullptr;
static HANDLE g_fenceEvent = nullptr;
static UINT64 g_fenceLastSignaledValue = 0;
static int const NUM_BACK_BUFFERS = 2;

// 辅助函数声明
bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
void WaitForLastSubmittedFrame();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// --- 修复后的纹理加载函数 ---
// 这里的 ID3D12Resource** out_resource 是用来存纹理内存的
bool LoadTextureFromFile(const char* filename, ID3D12Resource** out_resource, int* out_width, int* out_height)
{
    // 1. 读取图片
    int image_width = 0;
    int image_height = 0;
    unsigned char* image_data = stbi_load(filename, &image_width, &image_height, NULL, 4);
    if (image_data == NULL) return false;

    // 2. 创建纹理资源描述
    D3D12_RESOURCE_DESC textureDesc = {};
    textureDesc.MipLevels = 1;
    textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    textureDesc.Width = image_width;
    textureDesc.Height = image_height;
    textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
    textureDesc.DepthOrArraySize = 1;
    textureDesc.SampleDesc.Count = 1;
    textureDesc.SampleDesc.Quality = 0;
    textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;

    // 3. 创建纹理资源 (默认堆)
    CD3DX12_HEAP_PROPERTIES defaultHeapProps(D3D12_HEAP_TYPE_DEFAULT);
    g_pd3dDevice->CreateCommittedResource(
        &defaultHeapProps,
        D3D12_HEAP_FLAG_NONE,
        &textureDesc,
        D3D12_RESOURCE_STATE_COPY_DEST,
        nullptr,
        IID_PPV_ARGS(out_resource));

    // 4. 创建上传堆 (临时用于传数据)
    UINT64 uploadBufferSize = GetRequiredIntermediateSize(*out_resource, 0, 1);
    ID3D12Resource* uploadHeap = nullptr;
    CD3DX12_HEAP_PROPERTIES uploadHeapProps(D3D12_HEAP_TYPE_UPLOAD);
    CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);

    g_pd3dDevice->CreateCommittedResource(
        &uploadHeapProps,
        D3D12_HEAP_FLAG_NONE,
        &bufferDesc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(&uploadHeap));

    // 5. 录制上传命令
    D3D12_SUBRESOURCE_DATA textureData = {};
    textureData.pData = image_data;
    textureData.RowPitch = image_width * 4;
    textureData.SlicePitch = textureData.RowPitch * image_height;

    // 重置命令列表以录制上传操作
    g_pd3dCommandAllocator->Reset();
    g_pd3dCommandList->Reset(g_pd3dCommandAllocator, nullptr);

    UpdateSubresources(g_pd3dCommandList, *out_resource, uploadHeap, 0, 0, 1, &textureData);

    // 资源屏障:把状态从 复制目标 改为 像素着色器资源
    CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(*out_resource, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
    g_pd3dCommandList->ResourceBarrier(1, &barrier);

    // 执行并等待完成 (简单粗暴的方式,防止 uploadHeap 被提前释放)
    g_pd3dCommandList->Close();
    ID3D12CommandList* ppCommandLists[] = { g_pd3dCommandList };
    g_pd3dCommandQueue->ExecuteCommandLists(1, ppCommandLists);
    WaitForLastSubmittedFrame(); // 等待 GPU 搞定

    // 清理临时内存
    uploadHeap->Release();
    stbi_image_free(image_data);

    *out_width = image_width;
    *out_height = image_height;

    return true;
}

bool CreateDeviceD3D(HWND hWnd)
{
    // 1. 创建 DXGI 工厂
    IDXGIFactory4* dxgiFactory = nullptr;
    if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) return false;

    // 2. 创建设备
    if (D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_pd3dDevice)) != S_OK) return false;

    // 3. 创建命令队列
    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    if (g_pd3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&g_pd3dCommandQueue)) != S_OK) return false;

    // 4. 创建交换链
    DXGI_SWAP_CHAIN_DESC1 sd = {};
    sd.BufferCount = NUM_BACK_BUFFERS;
    sd.Width = 0; sd.Height = 0;
    sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    sd.SampleDesc.Count = 1;

    IDXGISwapChain1* swapChain1 = nullptr;
    dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1);
    swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain));
    swapChain1->Release();
    dxgiFactory->Release();

    // 5. 创建 RTV 描述符堆 (Render Target)
    D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
    rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    rtvDesc.NumDescriptors = NUM_BACK_BUFFERS;
    g_pd3dDevice->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&g_pd3dRtvHeap));

    // 6. 创建 SRV 描述符堆 (Shader Resource - 存图片的)
    D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {};
    srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    srvDesc.NumDescriptors = 64; // 预留 64 个位置
    srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
    g_pd3dDevice->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&g_pd3dSrvHeap));

    // 7. 创建命令分配器和命令列表
    g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_pd3dCommandAllocator));
    g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_pd3dCommandAllocator, nullptr, IID_PPV_ARGS(&g_pd3dCommandList));
    g_pd3dCommandList->Close(); // 初始化后先关闭

    // 8. 创建同步围栏
    g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_fence));
    g_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);

    CreateRenderTarget();
    return true;
}

void CleanupDeviceD3D()
{
    CleanupRenderTarget();
    if (g_pSwapChain) { g_pSwapChain->SetFullscreenState(false, nullptr); g_pSwapChain->Release(); g_pSwapChain = nullptr; }
    if (g_pd3dCommandQueue) { g_pd3dCommandQueue->Release(); g_pd3dCommandQueue = nullptr; }
    if (g_pd3dCommandList) { g_pd3dCommandList->Release(); g_pd3dCommandList = nullptr; }
    if (g_pd3dCommandAllocator) { g_pd3dCommandAllocator->Release(); g_pd3dCommandAllocator = nullptr; }
    if (g_pd3dRtvHeap) { g_pd3dRtvHeap->Release(); g_pd3dRtvHeap = nullptr; }
    if (g_pd3dSrvHeap) { g_pd3dSrvHeap->Release(); g_pd3dSrvHeap = nullptr; }
    if (g_fence) { g_fence->Release(); g_fence = nullptr; }
    if (g_fenceEvent) { CloseHandle(g_fenceEvent); g_fenceEvent = nullptr; }
    if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; }
}

void CreateRenderTarget()
{
    UINT rtvDescriptorSize = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_pd3dRtvHeap->GetCPUDescriptorHandleForHeapStart();

    for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) {
        g_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&g_mainRenderTargetResource[i]));
        g_pd3dDevice->CreateRenderTargetView(g_mainRenderTargetResource[i], nullptr, rtvHandle);
        rtvHandle.ptr += rtvDescriptorSize;
    }
}

void CleanupRenderTarget()
{
    WaitForLastSubmittedFrame();
    for (UINT i = 0; i < NUM_BACK_BUFFERS; i++)
        if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = nullptr; }
}

void WaitForLastSubmittedFrame()
{
    UINT64 fenceValue = g_fenceLastSignaledValue + 1;
    g_pd3dCommandQueue->Signal(g_fence, fenceValue);
    g_fenceLastSignaledValue = fenceValue;

    if (g_fence->GetCompletedValue() < fenceValue) {
        g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent);
        WaitForSingleObject(g_fenceEvent, INFINITE);
    }
}

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) return true;
    switch (msg) {
    case WM_SIZE:
        if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) {
            CleanupRenderTarget();
            g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);
            CreateRenderTarget();
        }
        return 0;
    case WM_DESTROY:
        ::PostQuitMessage(0);
        return 0;
    }
    return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}

struct UI
{
    bool ShowImage = 0;
}ui;

// 主函数 (Entry Point)
// 换成这个 Windows 专用入口
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    // 创建窗口类
    WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGuiDX12", nullptr };
    ::RegisterClassExW(&wc);
    HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"ImGui + DX12", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 720, nullptr, nullptr, wc.hInstance, nullptr);

    // 初始化 DX12
    if (!CreateDeviceD3D(hwnd)) {
        CleanupDeviceD3D();
        ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
        return 1;
    }

    ::ShowWindow(hwnd, SW_SHOWDEFAULT);
    ::UpdateWindow(hwnd);

    // 初始化 ImGui
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    ImGui::StyleColorsDark();
    ImGui_ImplWin32_Init(hwnd);

    // 【重要】初始化 DX12 后端,传入 SRV 堆
    ImGui_ImplDX12_Init(g_pd3dDevice, NUM_BACK_BUFFERS, DXGI_FORMAT_R8G8B8A8_UNORM,
        g_pd3dSrvHeap,
        g_pd3dSrvHeap->GetCPUDescriptorHandleForHeapStart(),
        g_pd3dSrvHeap->GetGPUDescriptorHandleForHeapStart()
    );

    // --- 加载图片 ---
    ID3D12Resource* my_texture_resource = nullptr; // 存放图片的显存资源
    int my_image_width = 0;
    int my_image_height = 0;

    // 调用修复后的加载函数
    bool ret = LoadTextureFromFile("graph/Alice.png", &my_texture_resource, &my_image_width, &my_image_height);
    if (!ret) {
        printf("Failed to load texture!\n");
    }

    // --- 为图片创建 SRV (描述符) ---
    // 1. 计算偏移:ImGui 用了第0个,我们用第1个
    UINT handle_increment = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = g_pd3dSrvHeap->GetCPUDescriptorHandleForHeapStart();
    cpuHandle.ptr += handle_increment; // 偏移 1
    D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = g_pd3dSrvHeap->GetGPUDescriptorHandleForHeapStart();
    gpuHandle.ptr += handle_increment; // 偏移 1

    if (my_texture_resource != nullptr) {
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MipLevels = 1;
        g_pd3dDevice->CreateShaderResourceView(my_texture_resource, &srvDesc, cpuHandle);
    }

    // 主循环
    bool done = false;
    while (!done) {
        MSG msg;
        while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
            if (msg.message == WM_QUIT) done = true;
        }
        if (done) break;

        ImGui_ImplDX12_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();

        // 渲染 UI
        if (ImGui::Begin(u8"IMGUI", nullptr, ImGuiWindowFlags_HorizontalScrollbar)) {
            ImGui::Checkbox("show image?", &ui.ShowImage);
            if (ui.ShowImage)
            {
                if (my_texture_resource) {
                    // 传入 GPU 句柄进行显示
                    ImGui::Image((ImTextureID)gpuHandle.ptr, ImVec2((float)my_image_width, (float)my_image_height));
                }
                else {
                    ImGui::Text("Texture not loaded");
                }
            }
        }
        ImGui::End();

        ImGui::Render();

        // DX12 渲染流程
        UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex();

        // 1. 重置分配器和命令列表
        g_pd3dCommandAllocator->Reset();
        g_pd3dCommandList->Reset(g_pd3dCommandAllocator, nullptr);

        // 2. 资源屏障:Present -> RenderTarget
        D3D12_RESOURCE_BARRIER barrier = {};
        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
        barrier.Transition.pResource = g_mainRenderTargetResource[backBufferIdx];
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
        g_pd3dCommandList->ResourceBarrier(1, &barrier);

        // 3. 设置 SRV 堆 (重要!否则 ImGui 没法画图)
        ID3D12DescriptorHeap* ppHeaps[] = { g_pd3dSrvHeap };
        g_pd3dCommandList->SetDescriptorHeaps(1, ppHeaps);

        // 4. 清屏和设置渲染目标
        D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_pd3dRtvHeap->GetCPUDescriptorHandleForHeapStart();
        rtvHandle.ptr += backBufferIdx * g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
        g_pd3dCommandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

        const float clear_color[4] = { 0.1f, 0.1f, 0.1f, 1.0f };
        g_pd3dCommandList->ClearRenderTargetView(rtvHandle, clear_color, 0, nullptr);

        // 5. 绘制 ImGui
        ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_pd3dCommandList);

        // 6. 资源屏障:RenderTarget -> Present
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
        g_pd3dCommandList->ResourceBarrier(1, &barrier);

        // 7. 关闭并执行
        g_pd3dCommandList->Close();
        ID3D12CommandList* ppCommandLists[] = { g_pd3dCommandList };
        g_pd3dCommandQueue->ExecuteCommandLists(1, ppCommandLists);

        // 8. 呈现
        g_pSwapChain->Present(1, 0);
        WaitForLastSubmittedFrame(); // 简单同步
    }

    WaitForLastSubmittedFrame();

    // 清理
    ImGui_ImplDX12_Shutdown();
    ImGui_ImplWin32_Shutdown();
    ImGui::DestroyContext();
    CleanupDeviceD3D();
    ::DestroyWindow(hwnd);
    ::UnregisterClassW(wc.lpszClassName, wc.hInstance);

    return 0;
}
posted @ 2025-12-14 23:28  粉紫系超人气月兔铃仙  阅读(1)  评论(0)    收藏  举报