# 变更记录:交换链与渲染表面实现 **提交日期**: 2026-03-31 **提交哈希**: `95d8893` **变更类型**: 功能新增 --- ## 变更概述 本次提交实现了 D3D12 交换链(Swap Chain)和渲染表面管理,完成了渲染输出的基础架构。同时修复了循环包含问题,确保编译正确。 ## 修改文件 ### 新增文件 | 文件 | 说明 | |------|------| | `D3D12Surface.h` | 交换链和渲染目标管理类头文件 | | `D3D12Surface.cpp` | 交换链实现 | ### 修改文件 | 文件 | 变更说明 | |------|----------| | `GraphicsPlatformInterface.h` | 修复循环包含,添加 `surface_id` 定义 | | `Renderer.h` | 移除循环包含,保持类型定义 | | `Renderer.cpp` | 实现表面相关函数 | | `D3D12Core.h/cpp` | 添加描述符堆访问函数 | | `D3D12Interface.cpp` | 更新平台接口 | | `D3D12CommonHeader.h` | 编码修复 | --- ## 技术要点 ### 1. 交换链(Swap Chain)原理 #### 什么是交换链? 交换链是 DXGI 提供的机制,用于管理前后缓冲区的交换: ``` ┌─────────────────────────────────────────────────────────────┐ │ 交换链工作原理 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 后台缓冲 │ │ 后台缓冲 │ │ 后台缓冲 │ │ │ │ 0 │ │ 1 │ │ 2 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ └─────────────┼─────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ Present() │ │ │ └──────┬───────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ 前台缓冲 │ ───► 显示器 │ │ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` #### 三重缓冲优势 | 特性 | 说明 | |------|------| | **减少撕裂** | 前台缓冲独立于后台缓冲,避免部分更新 | | **提高并行性** | CPU 可提前录制多帧命令 | | **平滑帧率** | 缓冲区平滑帧时间波动 | ### 2. d3d12_surface 类设计 #### 核心职责 ```cpp class d3d12_surface { // 交换链管理 IDXGISwapChain4* _swap_chain; // 渲染目标管理(每个后台缓冲区一个) render_target_data _render_target_data[frame_buffer_count]; // 视口和裁剪 D3D12_VIEWPORT _viewport; D3D12_RECT _scissor_rect; }; ``` #### 生命周期 ``` 构造 d3d12_surface(window) │ ▼ create_swap_chain(factory, cmd_queue, format) │ ├─► 创建 IDXGISwapChain4 ├─► 分配 RTV 描述符 └─► 初始化视口/裁剪矩形 │ ▼ 渲染循环 │ ├─► back_buffer() 获取当前缓冲区 ├─► rtv() 获取渲染目标视图 └─► present() 呈现帧 │ ▼ 析构 ~d3d12_surface() │ └─► release() 释放资源 ``` ### 3. 交换链创建流程 ```cpp void d3d12_surface::create_swap_chain(...) { // 1. 配置交换链描述 DXGI_SWAP_CHAIN_DESC1 desc{}; desc.BufferCount = frame_buffer_count; // 3 个后台缓冲区 desc.Format = to_non_srgb(format); // 像素格式 desc.Width = _window.width(); desc.Height = _window.height(); desc.SampleDesc.Count = 1; // 禁用 MSAA // 2. 创建交换链 factory->CreateSwapChainForHwnd(cmd_queue, hwnd, &desc, ...); // 3. 禁用 Alt+Enter 全屏切换(由应用处理) factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER); // 4. 分配 RTV 描述符 for(u32 i = 0; i < frame_buffer_count; ++i) { _render_target_data[i].rtv = core::rtv_heap().allocate(); } // 5. 创建渲染目标视图 finalize(); } ``` ### 4. 渲染目标视图创建 ```cpp void d3d12_surface::finalize() { for(u32 i = 0; i < frame_buffer_count; ++i) { // 获取后台缓冲区资源 _swap_chain->GetBuffer(i, IID_PPV_ARGS(&data.resource)); // 创建渲染目标视图 D3D12_RENDER_TARGET_VIEW_DESC desc{}; desc.Format = core::default_render_target_format(); desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; core::device()->CreateRenderTargetView(data.resource, &desc, data.rtv.cpu); } // 设置视口和裁剪矩形 _viewport = {0, 0, (float)width, (float)height, 0.0f, 1.0f}; _scissor_rect = {0, 0, (s32)width, (s32)height}; } ``` ### 5. 循环包含修复 #### 问题 ``` GraphicsPlatformInterface.h → Renderer.h → GraphicsPlatformInterface.h (循环!) ``` #### 解决方案 将 `surface_id` 定义移到 `Renderer.h`,`GraphicsPlatformInterface.h` 包含 `Renderer.h`: ``` Renderer.h (定义 surface_id) ↓ GraphicsPlatformInterface.h (使用 surface_id) ``` --- ## 视口与裁剪矩形 ### 视口(Viewport) ```cpp D3D12_VIEWPORT _viewport{ .TopLeftX = 0.0f, .TopLeftY = 0.0f, .Width = (float)width, .Height = (float)height, .MinDepth = 0.0f, .MaxDepth = 1.0f }; ``` 定义光栅化阶段的渲染区域和深度范围。 ### 裁剪矩形(Scissor Rect) ```cpp D3D12_RECT _scissor_rect{0, 0, (s32)width, (s32)height}; ``` 定义像素输出的裁剪区域,区域外的像素将被丢弃。 --- ## 后续工作 - [ ] 实现深度模板视图 - [ ] 渲染第一个三角形 - [ ] 实现根签名和管线状态对象 --- ## 相关文档 - [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)