[dx12显示图片] ImGui Learn Data Day 3
调试心得:相对路径陷阱与编译期/运行期的区别,以及如何显示图片

问题背景
最近我遇到了一个非常深刻的教训: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 的行为:
- Visual Studio 将代码编译成
.exe。 - VS 指挥 Windows 运行该程序,并指定项目文件夹(
.vcxproj所在目录,即你的rt/)作为程序的工作目录。
- Visual Studio 将代码编译成
- 为什么不以 main.cpp 为基准?
一个项目可能包含数百个分布在不同文件夹下的.cpp文件。如果以源文件位置为基准,资源路径管理将变得极其混乱。因此,IDE 统一规定:运行时所有相对路径的起点,默认都是项目根目录。
ImGui显示图片
要在 ImGui 中显示一张图片,本质上是一个 “搬运数据” 和 “建立索引” 的过程。我们可以把它分为四个核心步骤:
- CPU 阶段:把图片文件读到内存(RAM)。
- GPU 搬运阶段:把内存里的数据搬运到显存(VRAM)。
- 描述符阶段(最关键):给这张图片办一张“身份证”(SRV)。
- 渲染阶段:把“身份证”交给 ImGui。
要在 ImGui 中显示一张图片,本质上需要涉及计算机图形学的底层知识。但这门学科体系庞大,对于初学者来说,想要为了显示一张图而去系统学习图形学,时间成本过高(之后我会专门开一期文章来探讨这部分理论)。
鉴于 DX12 涉及的概念(如内存管理、描述符堆等)对于新手来说过于晦涩,我选择借助 AI 辅助生成了具体的实现代码。
然而,即便有了代码,跑通它也绝非易事。过程中我遇到了大量环境配置和依赖库的问题。本文将详细记录我遇到的“坑”以及解决方案,并在文末附上完整的可用代码。
第一步:确认系统与显卡支持的 DirectX 版本
首先,你需要确认你的硬件和系统是否支持 DX12。
- 按下
Win + R键,输入dxdiag并回车,打开 DirectX 诊断工具。 - 在“系统”或“显示”选项卡下,查看“DirectX 版本”或“功能级别”。
- 注:Windows 10 及以上系统通常默认同时支持 DX11 和 DX12。
⚠️ 特别注意:DirectX 11 和 DirectX 12 在架构上有本质的区别,它们不仅仅是版本号的不同,底层逻辑完全不同。
第二步:解决头文件缺失问题 (Windows SDK)
ImGui 官方提供的示例通常可以直接运行,但如果你编译时报错提示找不到文件(例如 d3d11.h 或 d3d12.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)。
如果你发现代码中缺少这个文件:
- 你需要手动下载该文件(通常可以在 Microsoft 的 GitHub 仓库
DirectX-Headers中找到)。 - 或者在使用
DirectXTex等库时,它们通常也会包含这个辅助文件。 - 下载后,将其放入你的项目目录,并在解决方案资源管理器中添加现有项将其包含进去。
第四步:链接库文件配置
在 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 来移植代码,这绝对是行不通的。
如果你在编译时遇到大量“未定义标识符”或宏定义不存在的错误,请按以下顺序排查:
- 头文件未包含:检查
.h文件是否不仅存在于文件夹中,还必须在 Visual Studio 的解决方案资源管理器中被添加到了项目里。 - 拼写错误:检查是否拼写错误,或者使用了旧版/新版 API 的名称。
- 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;
}

浙公网安备 33010602011771号