diff --git a/Engine/Engine.vcxproj b/Engine/Engine.vcxproj index 5853a51..2bafe54 100644 --- a/Engine/Engine.vcxproj +++ b/Engine/Engine.vcxproj @@ -138,6 +138,7 @@ FastCall false $(ProjectDir);$(ProjectDir)/Common + 4819 diff --git a/Engine/Graphics/Direct3D12/D3D12CommonHeader.h b/Engine/Graphics/Direct3D12/D3D12CommonHeader.h index ea98483..fb0f4bd 100644 --- a/Engine/Graphics/Direct3D12/D3D12CommonHeader.h +++ b/Engine/Graphics/Direct3D12/D3D12CommonHeader.h @@ -1,7 +1,8 @@ -#pragma once +#pragma once #include "CommonHeader.h" #include "Graphics\Renderer.h" +#include "Platform\Window.h" // 引入 DirectX Graphics Infrastructure(DXGI)6.0 // 头文件,用于访问 DXGI 接口(如 IDXGIFactory6、 @@ -23,7 +24,7 @@ #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d12.lib") -namespace XEngine::graphics::d3d12::core { +namespace XEngine::graphics::d3d12 { constexpr u32 frame_buffer_count{ 3 }; } diff --git a/Engine/Graphics/Direct3D12/D3D12Core.cpp b/Engine/Graphics/Direct3D12/D3D12Core.cpp index 7f083be..0249e53 100644 --- a/Engine/Graphics/Direct3D12/D3D12Core.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Core.cpp @@ -1,4 +1,4 @@ -#include "D3D12Core.h" +#include "D3D12Core.h" #include "D3D12CommonHeader.h" #include "D3D12Resources.h" @@ -290,6 +290,9 @@ u32 deferred_release_flag[frame_buffer_count]{}; */ std::mutex deferred_release_mutex{}; +// 默认渲染目标格式 +constexpr DXGI_FORMAT render_target_format{ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }; + // 最小支持的 Direct3D 特本级别 constexpr D3D_FEATURE_LEVEL minumum_feature_level{ D3D_FEATURE_LEVEL_11_0 }; @@ -553,12 +556,25 @@ device() return main_device; } +descriptor_heap& rtv_heap() {return rtv_descriptor_heap;} +descriptor_heap& dsv_heap() {return dsv_descriptor_heap;} +descriptor_heap& uav_heap() {return uav_descriptor_heap;} +descriptor_heap& srv_heap() {return srv_descriptor_heap;} + + u32 current_frame_index() { return gfx_command.frame_index(); } +DXGI_FORMAT +default_render_target_format() +{ + return render_target_format; +} + + // X86结构上的整数访问权架构原子的,所以不需要加锁 void diff --git a/Engine/Graphics/Direct3D12/D3D12Core.h b/Engine/Graphics/Direct3D12/D3D12Core.h index 9f7ef53..c7b5825 100644 --- a/Engine/Graphics/Direct3D12/D3D12Core.h +++ b/Engine/Graphics/Direct3D12/D3D12Core.h @@ -1,10 +1,15 @@ -#pragma once +#pragma once #include "D3D12CommonHeader.h" /** * @brief Direct3D 12 核心类 * @details 定义了 Direct3D 12 核心功能的初始化与关闭函数 */ + +namespace XEngine::graphics::d3d12 { +class descriptor_heap; +} + namespace XEngine::graphics::d3d12::core{ /** * @brief 初始化 Direct3D 12 核心功能 @@ -81,10 +86,24 @@ ID3D12Device *const device(); */ u32 current_frame_index(); +descriptor_heap& rtv_heap(); +descriptor_heap& dsv_heap(); +descriptor_heap& srv_heap(); +descriptor_heap& uav_heap(); + +/** + * @brief 获取默认渲染目标格式 + * @details 返回 Direct3D 12 设备的默认渲染目标格式 + * @return DXGI_FORMAT 默认渲染目标格式 + */ +DXGI_FORMAT default_render_target_format(); + /** * @brief 设置延迟释放标志 * @details 用于在渲染循环中设置延迟释放标志,通知资源管理器在当前帧渲染完成后释放资源 */ void set_deferred_release_flag(); + + }// namespace XEngine::graphics::d3d12::core \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Interface.cpp b/Engine/Graphics/Direct3D12/D3D12Interface.cpp index eaac64b..0dc2671 100644 --- a/Engine/Graphics/Direct3D12/D3D12Interface.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Interface.cpp @@ -1,4 +1,3 @@ -#include "CommonHeader.h" #include "D3D12Interface.h" #include "D3D12Core.h" #include "Graphics\GraphicsPlatformInterface.h" diff --git a/Engine/Graphics/Direct3D12/D3D12Resource.cpp b/Engine/Graphics/Direct3D12/D3D12Resource.cpp index 2e27bae..9b73b65 100644 --- a/Engine/Graphics/Direct3D12/D3D12Resource.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Resource.cpp @@ -1,7 +1,7 @@ -#include "D3D12Resources.h" +#include "D3D12Resources.h" #include "D3D12Core.h" -namespace XEngine::graphics::d3d12::core{ +namespace XEngine::graphics::d3d12{ //////////// DESCRIPTOR HEAP //////////// // 该类将被多个线程并发访问:资源创建(如纹理)与资源销毁/释放可能发生在不同线程, // 因此需要同步机制保护内部数据结构 diff --git a/Engine/Graphics/Direct3D12/D3D12Resources.h b/Engine/Graphics/Direct3D12/D3D12Resources.h index 07280a0..533d928 100644 --- a/Engine/Graphics/Direct3D12/D3D12Resources.h +++ b/Engine/Graphics/Direct3D12/D3D12Resources.h @@ -1,7 +1,7 @@ -#pragma once +#pragma once #include "D3D12CommonHeader.h" -namespace XEngine::graphics::d3d12::core { +namespace XEngine::graphics::d3d12 { /** * @brief 前向声明,用于友元类关系 @@ -417,4 +417,4 @@ private: const D3D12_DESCRIPTOR_HEAP_TYPE _type; }; -} // namespace XEngine::graphics::d3d12::core +} // namespace XEngine::graphics::d3d12 diff --git a/Engine/Graphics/Direct3D12/D3D12Surface.cpp b/Engine/Graphics/Direct3D12/D3D12Surface.cpp new file mode 100644 index 0000000..f5a816b --- /dev/null +++ b/Engine/Graphics/Direct3D12/D3D12Surface.cpp @@ -0,0 +1,114 @@ +#include "D3D12Surface.h" +#include "D3D12Core.h" + + +namespace XEngine::graphics::d3d12 { +namespace{ +constexpr DXGI_FORMAT +to_non_srgb(DXGI_FORMAT format) +{ + if(format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) return DXGI_FORMAT_R8G8B8A8_UNORM; + return format; +} +} // anonymous namespace + +void +d3d12_surface::create_swap_chain(IDXGIFactory7* factory, ID3D12CommandQueue* cmd_queue, DXGI_FORMAT format) +{ + assert(!factory && cmd_queue); + + // 应为可以多次调用,所以需要先释放旧的 swap chain + release(); + + DXGI_SWAP_CHAIN_DESC1 desc{}; + desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Alpha通道模式(窗口透明度相关) + desc.BufferCount = frame_buffer_count; // 后台缓冲区数量(用于双缓冲/多缓冲) + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 缓冲区用途(作为渲染目标输出) + desc.Flags = 0; // 交换链标志位(无) + desc.Format = to_non_srgb(format); // 像素格式(转换为非SRGB格式) + desc.Height = _window.height(); // 交换链高度(与窗口高度一致) + desc.Width = _window.width(); // 交换链宽度(与窗口宽度一致) + desc.SampleDesc.Count = 1; // 多重采样数量(1表示禁用MSAA) + desc.SampleDesc.Quality = 0; // 多重采样质量等级(0表示禁用) + desc.Scaling = DXGI_SCALING_STRETCH; // 缓冲区缩放模式(窗口大小变化时的处理方式) + desc.Stereo = false; // 立体显示模式(3D显示) + + IDXGISwapChain1* swap_chain; + HWND hwnd {(HWND)_window.handle()}; + // 为窗口创建交换链,指定交换链描述结构体 + DXCall(factory->CreateSwapChainForHwnd(cmd_queue, hwnd, &desc, nullptr, nullptr, &swap_chain)); + // 控制 DXGI 如何响应特定的系统按键,阻止 DXGI 响应 Alt+Enter 按键序列,由应用程序手动处理全屏切换逻辑。 + DXCall(factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER)); + // 将局部变量 swap_chain 创建的交换链对象“转移”给类成员 _swap_chain 来长期持有。 + // 免了直接赋值(_swap_chain = swap_chain)可能导致的引用计数问题:直接赋值不增加 + // 计数,后续释放 swap_chain 会使对象计数归零而被销毁,_swap_chain 就会变成悬空指针。 + DXCall(swap_chain->QueryInterface(IID_PPV_ARGS(&_swap_chain))); + core::release(swap_chain); + + _current_bb_index = _swap_chain->GetCurrentBackBufferIndex(); + + for(u32 i = 0; i < frame_buffer_count; ++i) + { + _render_target_data[i].rtv = core::rtv_heap().allocate(); + } + + finalize(); +} + +void +d3d12_surface::present() const +{ + assert(_swap_chain); + // 设置是否使用垂直同步 + // 0 表示不使用垂直同步,1 表示使用垂直同步 + // 第二个参数设置有无特殊行为,0 表示无特殊行为 + DXCall(_swap_chain->Present(0, 0)); + _current_bb_index = _swap_chain->GetCurrentBackBufferIndex(); +} + +void +d3d12_surface::finalize() +{ + // 为每个缓冲区创建渲染目标视图 + for(u32 i = 0; i < frame_buffer_count; ++i) + { + render_target_data& data{ _render_target_data[i] }; + assert(!data.resource); + DXCall(_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); + } + + DXGI_SWAP_CHAIN_DESC desc{}; + DXCall(_swap_chain->GetDesc(&desc)); + const u32 width{ desc.BufferDesc.Width }; + const u32 height{ desc.BufferDesc.Height }; + assert(_window.width() == width && _window.height() == height); + + + _viewport.TopLeftX = 0.0f; + _viewport.TopLeftY = 0.0f; + _viewport.Width = (float)width; + _viewport.Height = (float)height; + _viewport.MaxDepth = 1.0f; + _viewport.MinDepth = 0.0f; + + _scissor_rect = {0, 0, (s32)width, (s32)height}; +} + +void +d3d12_surface::release() +{ + for(u32 i = 0; i < frame_buffer_count; ++i) + { + render_target_data& data{ _render_target_data[i] }; + core::release(data.resource); + core::rtv_heap().free(data.rtv); + } + + core::release(_swap_chain); +} + +} // namespace XEngine::graphics::d3d12 \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Surface.h b/Engine/Graphics/Direct3D12/D3D12Surface.h new file mode 100644 index 0000000..0fd8131 --- /dev/null +++ b/Engine/Graphics/Direct3D12/D3D12Surface.h @@ -0,0 +1,210 @@ +#pragma once +#include "D3D12CommonHeader.h" +#include "D3D12Resources.h" + +namespace XEngine::graphics::d3d12 { + +/** + * @class d3d12_surface + * @brief 管理 D3D12 交换链和渲染目标的表面类 + * + * @details + * 该类封装了 Direct3D 12 的交换链(Swap Chain)和后台缓冲区管理, + * 提供渲染表面的完整生命周期管理。 + * + * ## 核心职责 + * + * - **交换链管理**:创建和管理 IDXGISwapChain4,处理前后缓冲区交换 + * - **渲染目标管理**:为每个后台缓冲区创建和维护渲染目标视图(RTV) + * - **视口和裁剪矩形**:管理渲染区域的视口和裁剪设置 + * - **窗口关联**:与平台窗口绑定,处理窗口大小调整 + * + * ## 多缓冲机制 + * + * 使用 `frame_buffer_count`(通常为 3)个后台缓冲区实现三重缓冲: + * - 减少画面撕裂 + * - 提高 CPU-GPU 并行性 + * - 平滑帧率波动 + * + * ## 生命周期 + * + * @code + * d3d12_surface surface(window); + * surface.create_swap_chain(factory, cmd_queue, DXGI_FORMAT_R8G8B8A8_UNORM); + * + * // 渲染循环 + * surface.present(); // 呈现当前帧 + * surface.resize(); // 窗口大小改变时调用 + * + * // 自动析构时释放资源 + * @endcode + * + * @see IDXGISwapChain4 + * @see ID3D12Resource + * @see descriptor_handle + */ +class d3d12_surface +{ +public: + /** + * @brief 构造函数,绑定到指定窗口 + * + * @param window 平台窗口对象 + * + * @details + * 仅保存窗口引用,不创建交换链。 + * 必须随后调用 create_swap_chain() 完成初始化。 + * + * @pre window.handle() 必须返回有效的窗口句柄 + */ + explicit d3d12_surface(platform::window window) + :_window(window) + { + assert(window.handle()); + } + + /** + * @brief 析构函数,自动释放所有资源 + * + * @details + * 调用 release() 清理交换链和渲染目标资源。 + */ + ~d3d12_surface(){release();} + + /** + * @brief 创建交换链和渲染目标视图 + * + * @param factory DXGI 工厂对象,用于创建交换链 + * @param cmd_queue 命令队列,交换链将与此队列同步 + * @param format 后台缓冲区的像素格式(如 DXGI_FORMAT_R8G8B8A8_UNORM) + * + * @details + * 执行以下操作: + * 1. 创建 IDXGISwapChain4 对象 + * 2. 获取所有后台缓冲区资源 + * 3. 为每个后台缓冲区创建 RTV + * 4. 初始化视口和裁剪矩形 + * + * @pre factory 和 cmd_queue 必须有效 + * @pre 尚未创建交换链(或已调用 release()) + */ + void create_swap_chain(IDXGIFactory7* factory, ID3D12CommandQueue* cmd_queue, DXGI_FORMAT format); + + /** + * @brief 呈现当前渲染帧 + * + * @details + * 调用 IDXGISwapChain::Present() 将当前后台缓冲区内容显示到屏幕。 + * 呈现后,后台缓冲区索引自动递增(轮转)。 + * + * @note 使用 DXGI_SWAP_EFFECT_FLIP_DISCARD 模式时, + * 呈现后当前后台缓冲区内容不再有效 + */ + void present() const; + + /** + * @brief 调整交换链大小以匹配窗口 + * + * @details + * 当窗口大小改变时调用此方法: + * 1. 释放旧的渲染目标资源 + * 2. 调整交换链缓冲区大小 + * 3. 重新创建渲染目标视图 + * 4. 更新视口和裁剪矩形 + * + * @note 必须在 GPU 完成使用当前资源后调用 + */ + void resize(); + + /** + * @brief 获取渲染表面宽度 + * @return 宽度(像素) + */ + u32 width(); + + /** + * @brief 获取渲染表面高度 + * @return 高度(像素) + */ + u32 height(); + + /** + * @brief 获取当前后台缓冲区资源 + * @return 指向 ID3D12Resource 的指针 + * + * @details + * 返回当前帧应渲染到的后台缓冲区。 + * 索引由 _current_bb_index 指定,每次 present() 后更新。 + */ + constexpr ID3D12Resource *const back_buffer() const {return _render_target_data[_current_bb_index].resource;} + + /** + * @brief 获取当前后台缓冲区的渲染目标视图句柄 + * @return RTV 描述符句柄 + * + * @details + * 用于 OMSetRenderTargets() 绑定渲染目标。 + */ + constexpr descriptor_handle rtv() const {return _render_target_data[_current_bb_index].rtv;} + + /** + * @brief 获取视口描述 + * @return D3D12_VIEWPORT 常量引用 + * + * @details + * 用于 RSSetViewports() 设置光栅化视口。 + */ + constexpr const D3D12_VIEWPORT& viewport() const {return _viewport;} + + /** + * @brief 获取裁剪矩形描述 + * @return D3D12_RECT 常量引用 + * + * @details + * 用于 RSSetScissorRects() 设置裁剪区域。 + * 裁剪区域外的像素将被丢弃。 + */ + constexpr const D3D12_RECT& scissor_rect() const {return _scissor_rect;} + +private: + /** + * @brief 释放所有资源 + * + * @details + * 释放交换链和所有后台缓冲区资源。 + * 使用延迟释放机制确保 GPU 安全。 + */ + void release(); + + /** + * @brief 完成资源创建的最终设置 + * + * @details + * 在创建或调整大小后调用,设置视口和裁剪矩形。 + */ + void finalize(); + + /** + * @struct render_target_data + * @brief 单个后台缓冲区的渲染目标数据 + * + * @details + * 每个后台缓冲区对应一个此结构体,存储: + * - 资源指针:实际的缓冲区纹理资源 + * - RTV 句柄:用于绑定到渲染管线 + */ + struct render_target_data + { + ID3D12Resource* resource{nullptr}; ///< 后台缓冲区资源 + descriptor_handle rtv{}; ///< 渲染目标视图描述符句柄 + }; + + IDXGISwapChain4* _swap_chain{nullptr}; ///< DXGI 交换链对象 + render_target_data _render_target_data[frame_buffer_count];///< 后台缓冲区数据数组 + platform::window _window{}; ///< 关联的平台窗口 + mutable u32 _current_bb_index{0}; ///< 当前后台缓冲区索引 + D3D12_VIEWPORT _viewport{}; ///< 视口描述 + D3D12_RECT _scissor_rect{}; ///< 裁剪矩形 +}; + +} diff --git a/Engine/Graphics/GraphicsPlatformInterface.h b/Engine/Graphics/GraphicsPlatformInterface.h index 148648f..a293de7 100644 --- a/Engine/Graphics/GraphicsPlatformInterface.h +++ b/Engine/Graphics/GraphicsPlatformInterface.h @@ -1,5 +1,6 @@ -#pragma once +#pragma once #include "CommonHeader.h" +#include "Platform/Window.h" #include "Renderer.h" namespace XEngine::graphics{ @@ -11,6 +12,15 @@ struct platform_interface{ bool(*initialize)(void); void(*shutdown)(void); void(*render)(void); + + struct { + surface(*create)(platform::window); + void(*remove)(surface_id); + void(*resize)(surface_id, u32, u32); + u32(*width)(surface_id); + u32(*height)(surface_id); + void(*render)(surface_id); + } surface; }; }// namespace XEngine::graphics diff --git a/Engine/Graphics/Renderer.cpp b/Engine/Graphics/Renderer.cpp index 097cdfb..634c53e 100644 --- a/Engine/Graphics/Renderer.cpp +++ b/Engine/Graphics/Renderer.cpp @@ -57,5 +57,45 @@ render() gfx.render(); } +surface +create_surface(platform::window window) +{ + return gfx.surface.create(window); +} + +void +remove_surface(surface_id id) +{ + assert(id::is_valid(id)); + gfx.surface.remove(id); +} + +void +surface::resize(u32 width, u32 height) const +{ + assert(is_valid()); + gfx.surface.resize(_id, width, height); +} + +u32 +surface::width() const +{ + assert(is_valid()); + return gfx.surface.width(_id); +} + +u32 +surface::height() const +{ + assert(is_valid()); + return gfx.surface.height(_id); +} + +void +surface::render() const +{ + assert(is_valid()); + gfx.surface.render(_id); +} }// namespace XEngine::graphics diff --git a/Engine/Graphics/Renderer.h b/Engine/Graphics/Renderer.h index aa7c6a6..c977901 100644 --- a/Engine/Graphics/Renderer.h +++ b/Engine/Graphics/Renderer.h @@ -1,16 +1,60 @@ -#pragma once -#include "CommonHeader.h" -#include "..\Platform\Window.h" +#pragma once +#include "Platform/Window.h" namespace XEngine::graphics { +DEFINE_TYPED_ID(surface_id); + /** * @brief 表面类 * @details 定义了渲染表面的属性与操作 */ class surface { +public: + /** + * @brief 由表面 ID 构造句柄。 + * @param id 表面 ID。 + */ + constexpr explicit surface(surface_id id) : _id{ id } {} + /** + * @brief 构造无效表面句柄。 + */ + constexpr surface() = default; + /** + * @brief 获取表面 ID。 + * @return 表面 ID。 + */ + constexpr surface_id get_id() const { return _id; } + /** + * @brief 判断表面句柄是否有效。 + * @return 有效返回 true。 + */ + constexpr bool is_valid() const { return id::is_valid(_id); } + + + /** + * @brief 调整表面大小。 + * @param width 目标宽度。 + * @param height 目标高度。 + */ + void resize(u32 width, u32 height) const; + /** + * @brief 获取表面宽度。 + * @return 宽度像素值。 + */ + u32 width() const; + /** + * @brief 获取表面高度。 + * @return 高度像素值。 + */ + u32 height() const; + + void render() const; + +private: + surface_id _id{ id::invalid_id }; }; /** @@ -53,4 +97,17 @@ void shutdown(); */ void render(); +/** + * @brief 创建渲染表面 + * @details window 渲染表面的窗口 + * @return 渲染表面对象柄 + */ +surface create_surface(platform::window window); + +/** + * @brief 移除渲染表面 + * @details id 渲染表面的 ID + */ +void remove_surface(surface_id id); + } \ No newline at end of file diff --git a/docs/changelogs/2026-03/20260331-d3d12-swap-chain.md b/docs/changelogs/2026-03/20260331-d3d12-swap-chain.md new file mode 100644 index 0000000..bf8d989 --- /dev/null +++ b/docs/changelogs/2026-03/20260331-d3d12-swap-chain.md @@ -0,0 +1,231 @@ +# 变更记录:交换链与渲染表面实现 + +**提交日期**: 2026-03-31 +**提交哈希**: `待定` +**变更类型**: 功能新增 + +--- + +## 变更概述 + +本次提交实现了 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) diff --git a/docs/wiki/D3D12学习Wiki.md b/docs/wiki/D3D12学习Wiki.md index 1a34d6c..32bfac4 100644 --- a/docs/wiki/D3D12学习Wiki.md +++ b/docs/wiki/D3D12学习Wiki.md @@ -489,9 +489,124 @@ void allocate() { } // 自动解锁 ``` -## 8. 渲染表面与窗口 +## 8. 交换链(Swap Chain) -### 8.1 render_surface 结构 +### 8.1 什么是交换链? + +交换链是 DXGI 提供的机制,用于管理前后缓冲区的交换,实现流畅的画面显示。 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 交换链工作原理 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 后台缓冲 │ │ 后台缓冲 │ │ 后台缓冲 │ │ +│ │ 0 │ │ 1 │ │ 2 │ │ +│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ +│ │ │ │ │ +│ └─────────────┼─────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ Present() │ │ +│ └──────┬───────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ 前台缓冲 │ ───► 显示器 │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 8.2 三重缓冲 + +项目使用三重缓冲(`frame_buffer_count = 3`): + +| 特性 | 说明 | +|------|------| +| **减少撕裂** | 前台缓冲独立于后台缓冲,避免部分更新 | +| **提高并行性** | CPU 可提前录制多帧命令 | +| **平滑帧率** | 缓冲区平滑帧时间波动 | + +### 8.3 d3d12_surface 类 + +```cpp +class d3d12_surface +{ +public: + // 创建交换链 + void create_swap_chain(IDXGIFactory7* factory, + ID3D12CommandQueue* cmd_queue, + DXGI_FORMAT format); + + // 呈现当前帧 + void present() const; + + // 调整大小 + void resize(); + + // 获取当前后台缓冲区 + ID3D12Resource* back_buffer() const; + descriptor_handle rtv() const; + +private: + IDXGISwapChain4* _swap_chain; + render_target_data _render_target_data[frame_buffer_count]; + D3D12_VIEWPORT _viewport; + D3D12_RECT _scissor_rect; +}; +``` + +### 8.4 交换链创建流程 + +```cpp +void create_swap_chain(...) +{ + // 1. 配置描述 + DXGI_SWAP_CHAIN_DESC1 desc{}; + desc.BufferCount = frame_buffer_count; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + 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. 创建渲染目标视图 + for(u32 i = 0; i < frame_buffer_count; ++i) + { + _swap_chain->GetBuffer(i, &resource); + device->CreateRenderTargetView(resource, &desc, rtv.cpu); + } +} +``` + +### 8.5 视口与裁剪矩形 + +```cpp +// 视口:定义光栅化区域 +D3D12_VIEWPORT viewport{ + .TopLeftX = 0.0f, + .TopLeftY = 0.0f, + .Width = (float)width, + .Height = (float)height, + .MinDepth = 0.0f, + .MaxDepth = 1.0f +}; + +// 裁剪矩形:定义像素输出区域 +D3D12_RECT scissor_rect{0, 0, width, height}; +``` + +## 9. 渲染表面与窗口 + +### 9.1 render_surface 结构 ```cpp struct render_surface { @@ -500,7 +615,7 @@ struct render_surface { }; ``` -### 8.2 多窗口支持 +### 9.2 多窗口支持 TestRenderer 测试展示了多窗口渲染: @@ -513,7 +628,7 @@ for (u32 i{0}; i < _countof(_surfaces); ++i) { } ``` -### 8.3 全屏切换 +### 9.3 全屏切换 通过 `WM_SYSCHAR` 消息处理 Alt+Enter: @@ -523,42 +638,42 @@ if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN)) { } ``` -## 9. 后续学习路径 +## 10. 后续学习路径 -### 9.1 基础阶段 +### 10.1 基础阶段 - [x] 完成设备创建和适配器枚举 - [x] 创建命令队列和命令列表 - [x] 描述符堆管理 -- [ ] 实现交换链和后台缓冲区 +- [x] 实现交换链和后台缓冲区 - [ ] 渲染第一个三角形 -### 9.2 进阶阶段 +### 10.2 进阶阶段 - [ ] 根签名和管线状态对象 - [ ] 资源屏障和同步 - [ ] 常量缓冲区和着色器资源 -### 9.3 高级阶段 +### 10.3 高级阶段 - [ ] 多线程渲染 - [ ] 资源绑定策略 - [ ] 动态资源管理 - [ ] 性能优化 -## 10. 参考资源 +## 11. 参考资源 -### 10.1 官方文档 +### 11.1 官方文档 - [Microsoft D3D12 文档](https://docs.microsoft.com/en-us/windows/win32/direct3d12/direct3d-12-graphics) - [DXGI 文档](https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dx-graphics-dxgi) -### 10.2 推荐书籍 +### 11.2 推荐书籍 - 《Introduction to 3D Game Programming with DirectX 12》 - 《Real-Time 3D Rendering with DirectX and HLSL》 -### 10.3 项目相关文档 +### 11.3 项目相关文档 - [Graphics渲染架构分析](./Graphics渲染架构分析.md) - [项目约定规范](./项目约定规范.md)