feat(d3d12): 实现交换链与渲染表面管理

- 新增 d3d12_surface 类,管理交换链和渲染目标
- 实现三重缓冲后台缓冲区管理
- 添加视口和裁剪矩形配置
- 修复 GraphicsPlatformInterface.h 循环包含问题
- 添加完整的中文 Doxygen 注释
- 更新 D3D12 学习 Wiki,添加交换链章节
This commit is contained in:
SpecialX
2026-03-31 11:12:11 +08:00
parent b6c0211d6a
commit 95d8893182
14 changed files with 840 additions and 27 deletions

View File

@@ -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