#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(); } constexpr static u32 buffer_count{ 3 }; #if USE_STL_VECTOR DISABLE_COPY(d3d12_surface) /** * @brief 移动构造函数 * * @param o 要移动的源对象 * * @details * 转移所有资源所有权,将源对象置为空状态。 * 用于 vector 扩容时安全转移资源。 */ constexpr d3d12_surface(d3d12_surface&& o) noexcept : _swap_chain{o._swap_chain}, _window{o._window}, _current_bb_index{o._current_bb_index} , _viewport{o._viewport}, _scissor_rect{o._scissor_rect} , _allow_tearing{o._allow_tearing}, _present_flags{o._present_flags} { for (u32 i = 0; i < frame_buffer_count; ++i) { _render_target_data[i].resource = o._render_target_data[i].resource; _render_target_data[i].rtv = o._render_target_data[i].rtv; } o.reset(); } /** * @brief 移动赋值运算符 * * @param o 要移动的源对象 * @return 当前对象引用 * * @details * 先释放当前资源,再转移所有权。 */ constexpr d3d12_surface& operator=(d3d12_surface&& o) noexcept { assert(this != &o); if (this != &o) { release(); move(o); } return *this; } #else DISABLE_COPY_AND_MOVE(d3d12_surface); #endif /** * @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: #if USE_STL_VECTOR constexpr void move(d3d12_surface& o) { _swap_chain = o._swap_chain; for (u32 i = 0; i < frame_buffer_count; ++i) { _render_target_data[i] = o._render_target_data[i]; } _window = o._window; _current_bb_index = o._current_bb_index; _viewport = o._viewport; _scissor_rect = o._scissor_rect; _allow_tearing = o._allow_tearing; _present_flags = o._present_flags; o.reset(); } constexpr void reset() { _swap_chain = nullptr; for (u32 i = 0; i < frame_buffer_count; ++i) { _render_target_data[i] = {}; } _window = {}; _current_bb_index = 0; _viewport = {}; _scissor_rect = {}; _allow_tearing = 0; _present_flags = 0; } #endif /** * @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[buffer_count]; ///< 后台缓冲区数据数组 platform::window _window{}; ///< 关联的平台窗口 mutable u32 _current_bb_index{0}; ///< 当前后台缓冲区索引 D3D12_VIEWPORT _viewport{}; ///< 视口描述 D3D12_RECT _scissor_rect{}; ///< 裁剪矩形 u32 _allow_tearing{ 0 }; ///< 是否允许撕裂 u32 _present_flags{ 0 }; ///< 呈现标志位 }; }