fix(d3d12): 修复 Surface 重复释放问题,完善容器文档

- 改用 utl::free_list 管理 surface,避免 vector 扩容导致的资源重复释放
- 为 d3d12_surface 添加移动语义,禁用拷贝构造
- 添加撕裂检测支持(DXGI_PRESENT_ALLOW_TEARING)
- 为 FreeList.h 和 Vector.h 添加完整的 Doxygen 中文注释
- 更新 D3D12 学习 Wiki,添加 free_list 章节
This commit is contained in:
SpecialX
2026-03-31 16:48:33 +08:00
parent 95d8893182
commit 80cb696a3c
16 changed files with 1141 additions and 228 deletions

View File

@@ -1,10 +1,12 @@
#include "D3D12Core.h"
#include "D3D12CommonHeader.h"
#include "D3D12Resources.h"
#include "D3D12Surface.h"
using namespace Microsoft::WRL;
namespace XEngine::graphics::d3d12::core {
namespace {
using suface_collection = utl::free_list<d3d12_surface>;
/**
* @brief D3D12命令管理类设计说明
* @details 本类采用RAII设计模式封装Direct3D 12的命令队列和命令列表提供类型安全的GPU命令提交机制
@@ -249,6 +251,9 @@ IDXGIFactory7* dxgi_factory{ nullptr };
*/
d3d12_command gfx_command;
suface_collection surfaces;
/**
@@ -531,25 +536,6 @@ shutdown()
release(main_device);
}
void
render()
{
// 等待GPU完成命令列表,并重置命令分配器和命令列表
gfx_command.begin_frame();
ID3D12GraphicsCommandList6* cmd_list{ gfx_command.command_list() };
const u32 frame_index{ current_frame_index() };
if(deferred_release_flag[frame_index])
{
process_deferred_release(frame_index);
}
// 记录命令
//
// 完成命令记录,立即提交命令列表到命令队列执行
// 为下一帧标记并增加围栏值
gfx_command.end_frame();
}
ID3D12Device *const
device()
{
@@ -583,5 +569,65 @@ set_deferred_release_flag()
deferred_release_flag[current_frame_index()] = 1;
}
#pragma region surface
surface
create_surface(platform::window window)
{
surface_id id{ surfaces.add(window) };
surfaces[id].create_swap_chain(dxgi_factory,gfx_command.command_queue(),render_target_format);
return surface{id};
}
void
remove_surface(surface_id id)
{
gfx_command.flush();
surfaces.remove(id);
}
void
resize_surface(surface_id id, u32 width, u32 height)
{
gfx_command.flush();
surfaces[id].resize();
}
u32
surface_width(surface_id id)
{
return surfaces[id].width();
}
u32
surface_height(surface_id id)
{
return surfaces[id].height();
}
void
render_surface(surface_id id)
{
// 等待GPU完成命令列表,并重置命令分配器和命令列表
gfx_command.begin_frame();
ID3D12GraphicsCommandList6* cmd_list{ gfx_command.command_list() };
const u32 frame_index{ current_frame_index() };
if(deferred_release_flag[frame_index])
{
process_deferred_release(frame_index);
}
// 呈现交换链
surfaces[id].present();
// 记录命令
//
// 完成命令记录,立即提交命令列表到命令队列执行
// 为下一帧标记并增加围栏值
gfx_command.end_frame();
}
#pragma endregion
}// namespace XEngine::graphics::d3d12::core

View File

@@ -22,12 +22,6 @@ bool initialize();
* @details 调用 Direct3D 12 设备的关闭函数,释放所有资源
*/
void shutdown();
/**
* @brief 渲染 Direct3D 12 核心功能
* @details 调用 Direct3D 12 设备的渲染函数,渲染当前渲染表面
*/
void render();
/**
* @brief 立即释放 DirectX COM 对象并将指针置空
@@ -104,6 +98,12 @@ DXGI_FORMAT default_render_target_format();
*/
void set_deferred_release_flag();
surface create_surface(platform::window window);
void remove_surface(surface_id id);
void resize_surface(surface_id id, u32 width, u32 height);
u32 surface_width(surface_id id);
u32 surface_height(surface_id id);
void render_surface(surface_id id);
}// namespace XEngine::graphics::d3d12::core

View File

@@ -7,7 +7,14 @@ void get_platform_interface(platform_interface& pi)
{
pi.initialize = core::initialize;
pi.shutdown = core::shutdown;
pi.render = core::render;
pi.surface.create = core::create_surface;
pi.surface.remove = core::remove_surface;
pi.surface.resize = core::resize_surface;
pi.surface.width = core::surface_width;
pi.surface.height = core::surface_height;
pi.surface.render = core::render_surface;
}
}// namespace XEngine::graphics::d3d12

View File

@@ -2,113 +2,137 @@
#include "D3D12Core.h"
namespace XEngine::graphics::d3d12 {
namespace{
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;
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);
assert(factory && cmd_queue);
// 应为可以多次调用,所以需要先释放旧的 swap chain
release();
// 应为可以多次调用,所以需要先释放旧的 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显示
if (SUCCEEDED(factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &_allow_tearing, sizeof(u32))) && _allow_tearing)
{
_present_flags = DXGI_PRESENT_ALLOW_TEARING;
}
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);
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 = _allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 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.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // 交换链效果(翻转并丢弃旧缓冲区)
desc.Stereo = false; // 立体显示模式3D显示
_current_bb_index = _swap_chain->GetCurrentBackBufferIndex();
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);
for(u32 i = 0; i < frame_buffer_count; ++i)
{
_render_target_data[i].rtv = core::rtv_heap().allocate();
}
_current_bb_index = _swap_chain->GetCurrentBackBufferIndex();
finalize();
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();
assert(_swap_chain);
// 设置是否使用垂直同步
// 0 表示不使用垂直同步1 表示使用垂直同步
// 第二个参数设置有无特殊行为0 表示无特殊行为
DXCall(_swap_chain->Present(0, _present_flags));
_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);
}
// 为每个缓冲区创建渲染目标视图
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);
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;
_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};
_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);
}
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);
}
core::release(_swap_chain);
}
void
d3d12_surface::resize()
{
}
u32
d3d12_surface::width()
{
return _window.width();
}
u32
d3d12_surface::height()
{
return _window.height();
}
} // namespace XEngine::graphics::d3d12

View File

@@ -69,7 +69,58 @@ public:
* @details
* 调用 release() 清理交换链和渲染目标资源。
*/
~d3d12_surface(){release();}
~d3d12_surface(){ release(); }
#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 创建交换链和渲染目标视图
@@ -167,6 +218,43 @@ public:
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 释放所有资源
*
@@ -205,6 +293,8 @@ private:
mutable u32 _current_bb_index{0}; ///< 当前后台缓冲区索引
D3D12_VIEWPORT _viewport{}; ///< 视口描述
D3D12_RECT _scissor_rect{}; ///< 裁剪矩形
u32 _allow_tearing{ 0 }; ///< 是否允许撕裂
u32 _present_flags{ 0 }; ///< 呈现标志位
};
}

View File

@@ -11,7 +11,6 @@ namespace XEngine::graphics{
struct platform_interface{
bool(*initialize)(void);
void(*shutdown)(void);
void(*render)(void);
struct {
surface(*create)(platform::window);

View File

@@ -51,12 +51,6 @@ shutdown()
gfx.shutdown();
}
void
render()
{
gfx.render();
}
surface
create_surface(platform::window window)
{

View File

@@ -91,12 +91,6 @@ bool initialize(graphics_platform platform);
*/
void shutdown();
/**
* @brief 渲染调用接口
* @details 调用渲染函数指针,渲染当前渲染表面
*/
void render();
/**
* @brief 创建渲染表面
* @details window 渲染表面的窗口