2026/4/21 19:26:59
网站建设
项目流程
电商网站建设的核心是什么,重庆在线课程平台,网站模板能上传图片,网站网格设计#xff1a;相对路径陷阱与编译期/运行期的区别#xff0c;以及如何显示图片image-20251214224437304问题背景最近我遇到了一个非常深刻的教训#xff1a;imgui代码始终无法加载 graph/Alice.png#xff0c;这让我一度怀疑是 DirectX 12 环境配置的问题。就在刚刚#xff…相对路径陷阱与编译期/运行期的区别以及如何显示图片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 的行为Visual Studio 将代码编译成 .exe。VS 指挥 Windows 运行该程序并指定项目文件夹.vcxproj 所在目录即你的 rt/作为程序的工作目录。为什么不以 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 nul