Compare commits

...

7 Commits

Author SHA1 Message Date
SpecialX
188a8aea2f feat(d3d12): 实现渲染目标纹理和深度缓冲区
核心变更:
- 实现 d3d12_render_texture 类,支持多 Mip 级别 RTV
- 实现 d3d12_depth_buffer 类,支持 DSV 和 SRV 双视图
- 为所有纹理类添加析构函数,确保资源自动释放
- 深度缓冲区格式转换处理(D32_FLOAT -> R32_TYPELESS)

文档完善:
- 新增变更记录文档
- 更新 D3D12 学习 Wiki,添加渲染目标纹理和深度缓冲区章节
2026-04-01 17:01:18 +08:00
SpecialX
4d13d8df89 feat(d3d12): 新增纹理资源类,修复 Surface 重复释放问题
核心变更:
- 新增 d3d12_texture 和 d3d12_render_texture 类
- 新增 d3d12_texture_init_info 结构,支持三种资源创建方式
- 新增 D3D12Helpers.h,提供堆属性辅助结构
- 改用 utl::free_list 管理 surface,解决重复释放问题
- 为 d3d12_surface 添加移动语义,支持撕裂检测

文档完善:
- 为 FreeList.h 和 Vector.h 添加完整 Doxygen 中文注释
- 更新 D3D12 学习 Wiki,添加 SRV、资源创建方式、纹理资源类章节
- 新增变更记录文档
2026-04-01 16:17:42 +08:00
SpecialX
95d8893182 feat(d3d12): 实现交换链与渲染表面管理
- 新增 d3d12_surface 类,管理交换链和渲染目标
- 实现三重缓冲后台缓冲区管理
- 添加视口和裁剪矩形配置
- 修复 GraphicsPlatformInterface.h 循环包含问题
- 添加完整的中文 Doxygen 注释
- 更新 D3D12 学习 Wiki,添加交换链章节
2026-03-31 11:12:11 +08:00
SpecialX
b6c0211d6a feat(d3d12): 完善描述符堆延迟释放机制与FreeList栈式索引管理
- 添加完整的中文Doxygen注释文档
- 实现process_deferred_release()延迟释放处理
- 添加deferred_release模板函数和current_frame_index()
- 实现延迟释放队列和帧索引管理
- 详细说明FreeList栈式索引分配/释放算法
- 更新D3D12学习Wiki,添加延迟释放机制章节
2026-03-30 16:59:27 +08:00
SpecialX
54916b0ac6 feat: implement descriptor heap with thread-safe allocation
D3D12 Resources:
- Add descriptor_handle struct with CPU/GPU handles
- Add descriptor_heap class for descriptor management
- Implement allocate() and free() methods
- Add mutex for thread-safe access
- Support all D3D12 descriptor heap types

D3D12 Core:
- Add device() function to expose main device
- Add release() template function for COM objects

Documentation:
- Add changelog for descriptor heap implementation
- Update D3D12 Wiki with descriptor heap section
- Mark descriptor heap task as completed
2026-03-30 14:04:34 +08:00
SpecialX
f1584ec3c6 feat: implement Fence synchronization for CPU-GPU frame sync
D3D12 Core:
- Add ID3D12Fence1 and fence_value to d3d12_command
- Add fence_event for CPU waiting
- Implement wait() in command_frame for frame sync
- Implement flush() to wait all frames complete
- Add fence_value tracking per frame
- Signal fence at end_frame with incremented value

TestRenderer:
- Call graphics::render() in run()

Documentation:
- Add changelog for Fence sync implementation
- Update D3D12 Wiki with Fence sync section
2026-03-27 18:56:43 +08:00
SpecialX
7da17ccadd feat: implement command queue with multi-frame buffering
D3D12 Core:
- Add d3d12_command class for command queue management
- Support Direct/Compute/Copy command queue types
- Implement multi-frame buffering (frame_buffer_count=3)
- Add begin_frame/end_frame rendering cycle
- Add NAME_D3D12_OBJECT_INDEXED macro

Platform Interface:
- Add render function pointer to platform_interface
- Implement render() in Renderer

Documentation:
- Add changelog for command queue implementation
- Update D3D12 Wiki with multi-frame buffering section
- Mark command queue task as completed
2026-03-27 12:31:12 +08:00
26 changed files with 5203 additions and 155 deletions

View File

@@ -36,6 +36,8 @@
<ClInclude Include="Graphics\Direct3D12\D3D12CommonHeader.h" /> <ClInclude Include="Graphics\Direct3D12\D3D12CommonHeader.h" />
<ClInclude Include="Graphics\Direct3D12\D3D12Core.h" /> <ClInclude Include="Graphics\Direct3D12\D3D12Core.h" />
<ClInclude Include="Graphics\Direct3D12\D3D12Interface.h" /> <ClInclude Include="Graphics\Direct3D12\D3D12Interface.h" />
<ClInclude Include="Graphics\Direct3D12\D3D12Resources.h" />
<ClInclude Include="Graphics\Direct3D12\D3D12Surface.h" />
<ClInclude Include="Graphics\GraphicsPlatformInterface.h" /> <ClInclude Include="Graphics\GraphicsPlatformInterface.h" />
<ClInclude Include="Graphics\Renderer.h" /> <ClInclude Include="Graphics\Renderer.h" />
<ClInclude Include="Platform\IncludeWindowCpp.h" /> <ClInclude Include="Platform\IncludeWindowCpp.h" />
@@ -59,10 +61,15 @@
<ClCompile Include="Core\MainWin32.cpp" /> <ClCompile Include="Core\MainWin32.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Core.cpp" /> <ClCompile Include="Graphics\Direct3D12\D3D12Core.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Interface.cpp" /> <ClCompile Include="Graphics\Direct3D12\D3D12Interface.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Resource.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Surface.cpp" />
<ClCompile Include="Graphics\Renderer.cpp" /> <ClCompile Include="Graphics\Renderer.cpp" />
<ClCompile Include="Platform\PlatformWin32.cpp" /> <ClCompile Include="Platform\PlatformWin32.cpp" />
<ClCompile Include="Platform\Window.cpp" /> <ClCompile Include="Platform\Window.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="Graphics\Direct3D12\D3D12Resources" />
</ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion> <VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword> <Keyword>Win32Proj</Keyword>
@@ -133,6 +140,7 @@
<CallingConvention>FastCall</CallingConvention> <CallingConvention>FastCall</CallingConvention>
<BufferSecurityCheck>false</BufferSecurityCheck> <BufferSecurityCheck>false</BufferSecurityCheck>
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)/Common</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)/Common</AdditionalIncludeDirectories>
<DisableSpecificWarnings>4819</DisableSpecificWarnings>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem> <SubSystem>

View File

@@ -29,6 +29,8 @@
<ClInclude Include="Graphics\Direct3D12\D3D12Core.h" /> <ClInclude Include="Graphics\Direct3D12\D3D12Core.h" />
<ClInclude Include="Graphics\Direct3D12\D3D12Interface.h" /> <ClInclude Include="Graphics\Direct3D12\D3D12Interface.h" />
<ClInclude Include="Graphics\GraphicsPlatformInterface.h" /> <ClInclude Include="Graphics\GraphicsPlatformInterface.h" />
<ClInclude Include="Graphics\Direct3D12\D3D12Resources.h" />
<ClInclude Include="Graphics\Direct3D12\D3D12Surface.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="Components\Entity.cpp" /> <ClCompile Include="Components\Entity.cpp" />
@@ -44,5 +46,10 @@
<ClCompile Include="Graphics\Renderer.cpp" /> <ClCompile Include="Graphics\Renderer.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Core.cpp" /> <ClCompile Include="Graphics\Direct3D12\D3D12Core.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Interface.cpp" /> <ClCompile Include="Graphics\Direct3D12\D3D12Interface.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Resource.cpp" />
<ClCompile Include="Graphics\Direct3D12\D3D12Surface.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="Graphics\Direct3D12\D3D12Resources" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
#include "CommonHeader.h" #include "CommonHeader.h"
#include "Graphics\Renderer.h" #include "Graphics\Renderer.h"
#include "Platform\Window.h"
// 引入 DirectX Graphics InfrastructureDXGI6.0 // 引入 DirectX Graphics InfrastructureDXGI6.0
// 头文件,用于访问 DXGI 接口(如 IDXGIFactory6、 // 头文件,用于访问 DXGI 接口(如 IDXGIFactory6、
@@ -16,10 +17,16 @@
// 用于简化 COM 对象的生命周期管理 // 用于简化 COM 对象的生命周期管理
#include <wrl.h> #include <wrl.h>
// 引入互斥锁头文件,用于保护资源的互斥锁
#include <mutex>
#pragma comment(lib, "dxgi.lib") #pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d12.lib") #pragma comment(lib, "d3d12.lib")
namespace XEngine::graphics::d3d12 {
constexpr u32 frame_buffer_count{ 3 };
}
// 定义 DirectX 调试宏 DXCall用于在调试模式下检查 DirectX API 调用返回值 // 定义 DirectX 调试宏 DXCall用于在调试模式下检查 DirectX API 调用返回值
// 如果调用失败FAILED则输出错误信息文件名、行号、调用语句并触发断点 // 如果调用失败FAILED则输出错误信息文件名、行号、调用语句并触发断点
@@ -46,9 +53,24 @@ if(FAILED(x)){ \
#endif // !DXCall #endif // !DXCall
#endif // _DEBUG #endif // _DEBUG
// 定义 DirectX 对象命名宏,用于在调试模式下为 Direct3D 12 对象设置名称 // 定义 DirectX 对象命名宏,用于在调试模式下为 Direct3D 12 对象设置调试名称
// 这些宏仅在 _DEBUG 模式下生效,可帮助开发者在 PIX、RenderDoc 等图形调试工具中
// 识别和追踪 D3D12 对象(如缓冲区、纹理、管线状态等)
// NAME_D3D12_OBJECT: 为单个对象设置名称
// NAME_D3D12_OBJECT_INDEXED: 为数组中的对象设置带索引的名称(如资源数组)
#ifdef _DEBUG #ifdef _DEBUG
#define NAME_D3D12_OBJECT(obj,name) obj->SetName(name); OutputDebugString(L"::D3D12 Object Created: "); OutputDebugString(name); OutputDebugString(L"\n"); #define NAME_D3D12_OBJECT(obj,name) obj->SetName(name); OutputDebugString(L"::D3D12 Object Created: "); OutputDebugString(name); OutputDebugString(L"\n");
#define NAME_D3D12_OBJECT_INDEXED(obj,n,name) \
{ \
wchar_t full_name[128]; \
if(swprintf_s(full_name, L"%s[%llu]", name, (u64)n) >0 ){ \
obj->SetName(full_name); \
OutputDebugString(L"::D3D12 Object Created: "); \
OutputDebugString(full_name); \
OutputDebugString(L"\n"); \
}}
#else #else
#define NAME_D3D12_OBJECT(obj,name) #define NAME_D3D12_OBJECT(obj,name)
#define NAME_D3D12_OBJECT_INDEXED(obj,n,name)
#endif #endif

View File

@@ -1,9 +1,237 @@
#include "D3D12Core.h" #include "D3D12Core.h"
#include "D3D12CommonHeader.h" #include "D3D12Resources.h"
#include "D3D12Surface.h"
using namespace Microsoft::WRL; using namespace Microsoft::WRL;
namespace XEngine::graphics::d3d12::core { namespace XEngine::graphics::d3d12::core {
namespace { namespace {
using suface_collection = utl::free_list<d3d12_surface>;
/**
* @brief D3D12命令管理类设计说明
* @details 本类采用RAII设计模式封装Direct3D 12的命令队列和命令列表提供类型安全的GPU命令提交机制
*
* ## 整体设计思路
*
* 1. **类型安全封装**: 通过模板或构造函数参数区分Direct/Compute/Copy命令类型
* 避免运行时类型错误,编译期即可确定队列类型
*
* 2. **资源生命周期管理**:
* - 构造函数负责创建命令队列和命令列表
* - release()方法统一释放资源,支持异常安全(构造函数失败时调用)
* - 析构函数自动调用release(),防止资源泄漏
*
* 3. **帧同步机制**: 内部command_frame结构体管理每帧的命令分配器
* 支持CPU-GPU帧同步避免命令分配器重置冲突
*
* 4. **命名调试支持**: 使用NAME_D3D12_OBJECT宏为D3D对象设置调试名称
* 便于GPU调试工具PIX/RenderDoc识别
*
* ## 核心优势
*
* - **异常安全**: 构造函数采用goto错误处理模式任何步骤失败都会自动清理已分配资源
* - **多队列支持**: 单一类支持D3D12三种命令队列类型Direct/Compute/Copy代码复用率高
* - **现代D3D12 API**: 使用ID3D12GraphicsCommandList6接口支持最新渲染特性如Mesh Shader
* - **零开销抽象**: 轻量级封装不引入额外运行时开销直接操作底层D3D12对象
* - **可扩展性**: 预留frame管理接口可轻松扩展为多缓冲帧循环、命令列表录制状态追踪等功能
*/
//
// ## 多帧渲染架构设计原理
//
// 现代GPU渲染采用"生产者-消费者"模型CPU作为命令生产者录制渲染命令
// GPU作为消费者异步执行。为避免CPU等待GPU完成需要引入帧缓冲机制。
//
// ### 为什么需要多帧缓冲?
//
// 1. **CPU-GPU并行性**: 单缓冲模式下CPU必须等待GPU完成当前帧才能录制下一帧
// 导致CPU空闲等待。多帧缓冲允许CPU提前录制N帧GPU异步执行最大化硬件利用率
//
// 2. **命令分配器冲突解决**: D3D12中ID3D12CommandAllocator在GPU执行期间
// 不能被重置。每帧使用独立的分配器当前帧提交GPU后CPU可立即重置
// (frame_buffer_count-1)帧之前的分配器,实现无等待循环
//
// 3. **帧时序稳定性**: 缓冲N帧可平滑帧率波动避免单帧卡顿影响整体流畅度
//
// ### 帧索引轮转机制
//
// 使用环形缓冲区(ring buffer)管理帧资源:
// - 当前帧索引: current_frame_index % frame_buffer_count
// - 每帧提交后递增索引到达frame_buffer_count时归零
// - 确保CPU不会超前GPU超过frame_buffer_count帧防止资源冲突
//
// ### 命令列表创建策略
//
// 创建时使用_cmd_frames[0].cmd_allocator作为初始分配器原因
// - 命令列表创建时必须绑定一个分配器即使后续通过Reset()切换
// - 选择索引0确保初始化阶段有确定的资源状态
// - 实际录制前会调用Reset()绑定当前帧对应的分配器
//
// ### 立即关闭命令列表的设计考量
//
// 创建后立即调用Close(),因为:
// - 新创建的命令列表处于"录制打开"状态
// - 但实际渲染前需要重新Reset()绑定正确的帧分配器
// - 先Close()使列表进入可提交状态,避免状态不一致
// - 这是一种防御性编程,确保对象始终处于有效状态
class d3d12_command
{
public:
d3d12_command() = default;
DISABLE_COPY_AND_MOVE(d3d12_command)
explicit d3d12_command(ID3D12Device8 *const device, D3D12_COMMAND_LIST_TYPE type)
{
HRESULT hr{ S_OK };
// 创建命令队列
D3D12_COMMAND_QUEUE_DESC desc{};
desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; // 无特殊标志
desc.NodeMask = 0; // 单GPU节点
desc.Type = type; // 命令队列类型Direct/Compute/Copy
desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; // 普通优先级
DXCall(hr = device->CreateCommandQueue(&desc, IID_PPV_ARGS(&_cmd_queue)));
if(FAILED(hr)) goto _error;
NAME_D3D12_OBJECT(_cmd_queue, type == D3D12_COMMAND_LIST_TYPE_DIRECT ?
L"Direct Command Queue" :
type == D3D12_COMMAND_LIST_TYPE_COMPUTE ?
L"Compute Command Queue" : L" Command Queue");
// 为所有帧创建命令分配器
for(u32 i{ 0 }; i < frame_buffer_count; ++i)
{
command_frame& frame{_cmd_frames[i]};
DXCall(hr = device->CreateCommandAllocator(type, IID_PPV_ARGS(&frame.cmd_allocator)));
if(FAILED(hr)) goto _error;
NAME_D3D12_OBJECT_INDEXED(frame.cmd_allocator, i, type == D3D12_COMMAND_LIST_TYPE_DIRECT ?
L"Direct Command Allocator" :
type == D3D12_COMMAND_LIST_TYPE_COMPUTE ?
L"Compute Command Allocator" : L" Command Allocator");
}
// 创建命令列表 - 采用多帧缓冲设计实现CPU-GPU并行渲染,传入第一帧的分配器作为初始分配器,并立即关闭命令列表
DXCall(hr = device->CreateCommandList(0,type,_cmd_frames[0].cmd_allocator,nullptr,IID_PPV_ARGS(&_cmd_list)));
if(FAILED(hr)) goto _error;
DXCall(_cmd_list->Close());
NAME_D3D12_OBJECT(_cmd_list, type == D3D12_COMMAND_LIST_TYPE_DIRECT ?
L"Direct Command List" :
type == D3D12_COMMAND_LIST_TYPE_COMPUTE ?
L"Compute Command List" : L" Command List");
DXCall(hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence)));
if(FAILED(hr)) goto _error;
NAME_D3D12_OBJECT(_fence, L"D3D12 Fence");
_fence_event = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);
assert(_fence_event);
return;
_error:
release();
}
~d3d12_command()
{
assert(!_cmd_queue && !_cmd_list && !_fence);
}
// 等待当前帧被标记为完成信号,并重置命令分配器和命令列表
void begin_frame()
{
command_frame& frame{_cmd_frames[_frame_index]};
frame.wait(_fence_event, _fence);
// 重置命令分配器将释放之前帧分配的命令内存,使其可重新用于录制新帧的命令
// 重置命令列表将命令列表重置为可录制状态,准备录制录制命令
DXCall(frame.cmd_allocator->Reset());
DXCall(_cmd_list->Reset(frame.cmd_allocator, nullptr));
}
// 使用新的围栏值来标记这个围栏
void end_frame()
{
//在提交命令列表前,先关闭命令列表,确保命令列表进入可提交状态
DXCall(_cmd_list->Close());
// 将命令列表转为数组形式提交给命令队列执行
// 虽然目前只有单个命令列表且为单线程工作模式,但仍采用数组方式以保持代码的扩展性
ID3D12CommandList *const cmd_lists[]{_cmd_list};
_cmd_queue->ExecuteCommandLists(_countof(cmd_lists), &cmd_lists[0]);
u64& fence_value{_fence_value};
++fence_value;
command_frame& frame{_cmd_frames[_frame_index]};
frame.fence_value = fence_value;
_cmd_queue->Signal(_fence, fence_value);
_frame_index = (_frame_index + 1) % frame_buffer_count;
}
/**
* @brief 等待所有帧的命令列表执行完成
* @details 确保所有帧的命令列表执行完成,避免资源冲突
*/
void flush()
{
for(u32 i{ 0 }; i < frame_buffer_count; ++i)
{
_cmd_frames[i].wait(_fence_event, _fence);
}
_frame_index = 0;
}
void release()
{
flush();
core::release(_fence);
_fence_value = 0;
CloseHandle(_fence_event);
_fence_event = nullptr;
core::release(_cmd_queue);
core::release(_cmd_list);
for(u32 i{ 0 }; i < frame_buffer_count; ++i)
{
_cmd_frames[i].release();
}
}
constexpr ID3D12CommandQueue *const command_queue() const {return _cmd_queue;}
constexpr ID3D12GraphicsCommandList6 *const command_list() const {return _cmd_list;}
constexpr u32 frame_index() const {return _frame_index;}
private:
struct command_frame
{
ID3D12CommandAllocator* cmd_allocator{ nullptr };
u64 fence_value{ 0 };
void wait(HANDLE fence_event, ID3D12Fence1* fence)
{
assert(fence && fence_event);
// 如果当前的Fence值小于目标值说明GPU还没有执行完成当前的命令列表
if(fence->GetCompletedValue() < fence_value)
{
// 我们需要等待GPU执行当前的命令列表设置事件并等待事件触发
DXCall(fence->SetEventOnCompletion(fence_value, fence_event));
WaitForSingleObject(fence_event, INFINITE);
}
}
void release()
{
core::release(cmd_allocator);
fence_value = 0;
}
};
ID3D12CommandQueue* _cmd_queue{ nullptr };
ID3D12GraphicsCommandList6* _cmd_list{ nullptr };
ID3D12Fence1* _fence{ nullptr };
// 对于围栏值来说他是64位无符号整型,有2^64-1个值,即便每秒1000帧,也需要5.8亿年才能回绕,所以不需要担心一直递增导致溢出的问题
u64 _fence_value{ 0 };
command_frame _cmd_frames[frame_buffer_count]{};
HANDLE _fence_event{ nullptr };
u32 _frame_index{ 0 };
};
/** /**
* @brief 主 Direct3D 12 设备指针 * @brief 主 Direct3D 12 设备指针
@@ -17,6 +245,59 @@ ID3D12Device8* main_device{ nullptr };
*/ */
IDXGIFactory7* dxgi_factory{ nullptr }; IDXGIFactory7* dxgi_factory{ nullptr };
/**
* @brief 命令管理类实例
* @details 用于管理 Direct3D 12 命令队列和命令列表,提供类型安全的 GPU命令提交机制
*/
d3d12_command gfx_command;
suface_collection surfaces;
/**
* @brief RTV 描述符堆
* @details 用于存储渲染目标视图的描述符,用于渲染管线中的输出
*/
descriptor_heap rtv_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_RTV};
/**
* @brief DSV 描述符堆
* @details 用于存储深度模板视图的描述符,用于渲染管线中的深度模板测试
*/
descriptor_heap dsv_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_DSV};
/**
* @brief SRV 描述符堆
* @details 用于存储着色器资源视图的描述符,用于渲染管线中的着色器资源访问
*/
descriptor_heap srv_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
/**
* @brief UAV 描述符堆
* @details 用于存储无序访问视图的描述符,用于渲染管线中的无序访问
*/
descriptor_heap uav_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
/**
* @brief 延迟释放队列
* @details 每个帧缓冲区对应一个待释放资源的队列,用于在渲染完成后释放资源
*/
utl::vector<IUnknown*> deferred_releases[frame_buffer_count]{};
/**
* @brief 延迟释放标志数组
* @details 每个帧缓冲区对应一个标志位用于记录是否需要延迟释放资源
*/
u32 deferred_release_flag[frame_buffer_count]{};
/**
* @brief 延迟释放互斥锁
* @details 用于保护延迟释放标志数组的并发访问,确保线程安全
*/
std::mutex deferred_release_mutex{};
// 默认渲染目标格式
constexpr DXGI_FORMAT render_target_format{ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB };
// 最小支持的 Direct3D 特本级别 // 最小支持的 Direct3D 特本级别
constexpr D3D_FEATURE_LEVEL minumum_feature_level{ D3D_FEATURE_LEVEL_11_0 }; constexpr D3D_FEATURE_LEVEL minumum_feature_level{ D3D_FEATURE_LEVEL_11_0 };
@@ -81,8 +362,48 @@ get_max_feature_level(IDXGIAdapter4* adapter)
return feature_level_info.MaxSupportedFeatureLevel; return feature_level_info.MaxSupportedFeatureLevel;
} }
/**
* @brief 处理延迟释放资源
* @details 遍历指定帧索引的延迟释放资源索引数组,释放每个资源
* @param frame_index 要处理的帧索引
* @note 使用 __declspec(noinline) 防止编译器内联此函数,确保在调试时能够准确断点
*/
void __declspec(noinline)
process_deferred_release(u32 frame_index)
{
std::lock_guard lock{ deferred_release_mutex };
// 我们在开始的的时候清楚这个帧的标志位,如果我们在结尾的时候清除,
// 他可能被其他线程重写.
deferred_release_flag[frame_index] = 0;
rtv_descriptor_heap.process_deferred_release(frame_index);
dsv_descriptor_heap.process_deferred_release(frame_index);
srv_descriptor_heap.process_deferred_release(frame_index);
uav_descriptor_heap.process_deferred_release(frame_index);
utl::vector<IUnknown*>& resources{ deferred_releases[frame_index] };
if(!resources.empty())
{
for(auto& resource : resources) release(resource);
resources.clear();
}
}
}// anonymous namespace }// anonymous namespace
namespace detail{
void
deferred_release(IUnknown* resource)
{
const u32 frmae_idx {current_frame_index()};
std::lock_guard lock{ deferred_release_mutex };
deferred_releases[frmae_idx].push_back(resource);
set_deferred_release_flag();
}
} // detail namespace
bool bool
initialize() initialize()
@@ -96,8 +417,15 @@ initialize()
#ifdef _DEBUG #ifdef _DEBUG
{ {
ComPtr<ID3D12Debug3> debug_interface; ComPtr<ID3D12Debug3> debug_interface;
DXCall(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_interface))); if(SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_interface))))
{
debug_interface->EnableDebugLayer(); debug_interface->EnableDebugLayer();
}
else
{
OutputDebugStringA("warning: d3d12 debug interface is not available\n");
}
dxgi_factory_flag |= DXGI_CREATE_FACTORY_DEBUG; dxgi_factory_flag |= DXGI_CREATE_FACTORY_DEBUG;
} }
#endif #endif
@@ -121,9 +449,6 @@ initialize()
DXCall(hr = D3D12CreateDevice(main_adapter.Get(), max_feature_level, IID_PPV_ARGS(&main_device))); DXCall(hr = D3D12CreateDevice(main_adapter.Get(), max_feature_level, IID_PPV_ARGS(&main_device)));
if (FAILED(hr)) return failed_init(); if (FAILED(hr)) return failed_init();
// 为 Direct3D 12 设备设置名称
NAME_D3D12_OBJECT(main_device, L"Main Device");
#ifdef _DEBUG #ifdef _DEBUG
{ {
ComPtr<ID3D12InfoQueue> info_queue; ComPtr<ID3D12InfoQueue> info_queue;
@@ -135,17 +460,61 @@ initialize()
} }
#endif // _DEBUG #endif // _DEBUG
// 使用 placement new 在已分配的内存上构造对象
// new (&gfx_command) 表示在 gfx_command 的地址处调用构造函数
// 这种用法允许我们在不分配新内存的情况下,在指定内存位置构造对象
// 常用于需要在特定内存地址构造对象,或重新初始化已存在的对象
// 这里 gfx_command 是一个类成员变量,我们直接在其内存位置上构造 d3d12_command 对象
// 避免了额外的内存分配同时可以传递构造参数main_device 和命令队列类型)
new (&gfx_command) d3d12_command(main_device, D3D12_COMMAND_LIST_TYPE_DIRECT);
if(!gfx_command.command_queue()) return failed_init();
bool result{true};
result &= rtv_descriptor_heap.initialize(512, false);
result &= dsv_descriptor_heap.initialize(512, false);
result &= srv_descriptor_heap.initialize(4096, false);
result &= uav_descriptor_heap.initialize(512, false);
if(!result) return failed_init();
// 为 Direct3D 12 设备设置名称
NAME_D3D12_OBJECT(main_device, L"Main Device");
NAME_D3D12_OBJECT(rtv_descriptor_heap.heap(), L"RTV Descriptor Heap");
NAME_D3D12_OBJECT(dsv_descriptor_heap.heap(), L"DSV Descriptor Heap");
NAME_D3D12_OBJECT(srv_descriptor_heap.heap(), L"SRV Descriptor Heap");
NAME_D3D12_OBJECT(uav_descriptor_heap.heap(), L"UAV Descriptor Heap");
return true; return true;
} }
void void
shutdown() shutdown()
{ {
gfx_command.release();
// 注意,我们需要在所有的依赖资源之前调用延迟释放函数.
// 否则会导致依赖当基础前资源(贴图等)的资源(着色器等)不能被释放.
for(u32 i{ 0 }; i < frame_buffer_count; ++i)
{
process_deferred_release(i);
}
release(dxgi_factory); release(dxgi_factory);
// 关闭调试层,确保最后只有一个活动的主设备 rtv_descriptor_heap.release();
dsv_descriptor_heap.release();
srv_descriptor_heap.release();
uav_descriptor_heap.release();
// 某些类型仅在 shutdown/reset/clear 时使用延迟释放机制来释放资源,
// 为了确保这些资源被正确释放,需要在 shutdown 最后额外调用一次延迟释放函数。
process_deferred_release(0);
#ifdef _DEBUG #ifdef _DEBUG
{ {
{ {
// 关闭调试层,确保最后只有一个活动的主设备
ComPtr<ID3D12InfoQueue> info_queue; ComPtr<ID3D12InfoQueue> info_queue;
DXCall(main_device->QueryInterface(IID_PPV_ARGS(&info_queue))); DXCall(main_device->QueryInterface(IID_PPV_ARGS(&info_queue)));
@@ -166,4 +535,99 @@ shutdown()
release(main_device); release(main_device);
} }
ID3D12Device *const
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
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 }// namespace XEngine::graphics::d3d12::core

View File

@@ -1,9 +1,15 @@
#pragma once #pragma once
#include "D3D12CommonHeader.h"
/** /**
* @brief Direct3D 12 核心类 * @brief Direct3D 12 核心类
* @details 定义了 Direct3D 12 核心功能的初始化与关闭函数 * @details 定义了 Direct3D 12 核心功能的初始化与关闭函数
*/ */
namespace XEngine::graphics::d3d12 {
class descriptor_heap;
}
namespace XEngine::graphics::d3d12::core{ namespace XEngine::graphics::d3d12::core{
/** /**
* @brief 初始化 Direct3D 12 核心功能 * @brief 初始化 Direct3D 12 核心功能
@@ -17,6 +23,13 @@ bool initialize();
*/ */
void shutdown(); void shutdown();
/**
* @brief 立即释放 DirectX COM 对象并将指针置空
* @details 安全释放资源:若指针非空则调用其 Release() 方法,随后将原指针置为 nullptr。
* 适用于所有继承自 IUnknown 的 COM 接口类型。
* @tparam T COM 接口类型(如 ID3D12Resource、ID3D11Buffer 等)
* @param resource 待释放的 COM 接口指针(引用传递,释放后自动置空)
*/
template<typename T> template<typename T>
constexpr void release(T*& resource) constexpr void release(T*& resource)
{ {
@@ -27,4 +40,70 @@ constexpr void release(T*& resource)
} }
} }
}// namespace XEngine::graphics::d3d12 namespace detail {
/**
* @brief 延迟释放 COM 对象(内部实现)
* @details 将资源加入延迟释放队列,不立即调用 Release(),用于异步或帧末统一回收。
* @param resource COM 对象指针(按值传递,仅用于记录,不修改外部指针)
*/
void deferred_release(IUnknown* resource);
} // namespace detail
/**
* @brief 安全包装延迟释放,并将外部指针置空
* @details 调用 detail::deferred_release 将资源加入延迟释放队列,同时将传入的指针置为 nullptr
* 防止调用者误用悬空指针。适用于需要延迟释放但希望立即清空原指针的场景。
* @tparam T COM 接口类型
* @param resource 待延迟释放的 COM 接口指针(引用传递,释放后自动置空)
*/
template<typename T>
constexpr void deferred_release(T*& resource)
{
if(resource)
{
detail::deferred_release(resource);
resource = nullptr;
}
}
/**
* @brief 获取 Direct3D 12 设备
* @details 返回 Direct3D 12 设备的智能指针
* @return ID3D12Device* Direct3D 12 设备的智能指针
*/
ID3D12Device *const device();
/**
* @brief 获取当前帧索引
* @details 返回当前渲染的帧索引
* @return u32 当前帧索引
*/
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();
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

@@ -0,0 +1,14 @@
#pragma once
#include "D3D12CommonHeader.h"
namespace XEngine::graphics::d3d12::d3dx
{
constexpr struct{
D3D12_HEAP_PROPERTIES default_heap{
D3D12_HEAP_TYPE_DEFAULT, // 堆类型默认堆GPU可读写CPU不可直接访问。选项D3D12_HEAP_TYPE_DEFAULT默认堆、D3D12_HEAP_TYPE_UPLOAD上传堆CPU写入GPU读取、D3D12_HEAP_TYPE_READBACK回读堆GPU写入CPU读取、D3D12_HEAP_TYPE_CUSTOM自定义堆
D3D12_CPU_PAGE_PROPERTY_UNKNOWN, // CPU页属性未知使用默认设置。选项D3D12_CPU_PAGE_PROPERTY_UNKNOWN未知、D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE不可用、D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE写入合并、D3D12_CPU_PAGE_PROPERTY_WRITE_BACK写回
D3D12_MEMORY_POOL_UNKNOWN, // 内存池未知由驱动自动选择。选项D3D12_MEMORY_POOL_UNKNOWN未知、D3D12_MEMORY_POOL_L0系统内存池、D3D12_MEMORY_POOL_L1显存池
0, // 节点掩码单GPU系统设为0多GPU系统中指定使用哪个GPU节点
0, // 保留字段必须为0为将来扩展预留
};
}heap_properties;
}

View File

@@ -1,4 +1,3 @@
#include "CommonHeader.h"
#include "D3D12Interface.h" #include "D3D12Interface.h"
#include "D3D12Core.h" #include "D3D12Core.h"
#include "Graphics\GraphicsPlatformInterface.h" #include "Graphics\GraphicsPlatformInterface.h"
@@ -8,6 +7,14 @@ void get_platform_interface(platform_interface& pi)
{ {
pi.initialize = core::initialize; pi.initialize = core::initialize;
pi.shutdown = core::shutdown; pi.shutdown = core::shutdown;
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 }// namespace XEngine::graphics::d3d12

View File

@@ -0,0 +1,276 @@
#include "D3D12Resources.h"
#include "D3D12Core.h"
#include "D3D12Helpers.h"
namespace XEngine::graphics::d3d12{
//////////// DESCRIPTOR HEAP ////////////
// 该类将被多个线程并发访问:资源创建(如纹理)与资源销毁/释放可能发生在不同线程,
// 因此需要同步机制保护内部数据结构
bool
descriptor_heap::initialize(u32 capacity, bool is_shader_visible)
{
std::lock_guard lock(_mutex);
// 检查容量有效性必须大于0且不超过最大限制
assert(capacity && capacity <= D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2);
// 对于采样器描述符堆,需要额外检查容量是否超过最大采样器堆大小限制
assert(!(_type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER &&
capacity > D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE));
// 对于DSV和RTV他们在描述符堆的内存模型中本身就是GPU不可见的
if(_type == D3D12_DESCRIPTOR_HEAP_TYPE_DSV ||
_type == D3D12_DESCRIPTOR_HEAP_TYPE_RTV)
{
is_shader_visible = false;
}
// 应为这个功能将被调用多次,所以需要先释放之前的资源
release();
ID3D12Device *const device {core::device()};
assert(device);
D3D12_DESCRIPTOR_HEAP_DESC desc{};
desc.Flags = is_shader_visible
? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
: D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
desc.NumDescriptors = capacity;
desc.Type = _type;
desc.NodeMask = 0;
HRESULT hr {S_OK};
DXCall(hr = device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_heap)));
if(FAILED(hr)) return false;
_free_handles = std::move(std::make_unique<u32[]>(capacity));
_capacity = capacity;
_size = 0;
for(u32 i = 0; i < capacity; ++i) _free_handles[i] = i;
DEBUG_OP(for(u32 i{0}; i<frame_buffer_count;++i) assert(_deferred_free_indices[i].empty()));
_descriptor_size = device->GetDescriptorHandleIncrementSize(_type);
_cpu_start = _heap->GetCPUDescriptorHandleForHeapStart();
_gpu_start = is_shader_visible
? _heap->GetGPUDescriptorHandleForHeapStart()
: D3D12_GPU_DESCRIPTOR_HANDLE{0};
return true;
}
void
descriptor_heap::release()
{
assert(!_size);
core::deferred_release(_heap);
}
// 处理延迟释放
// 注意这里的free_handles对应的是一整块连续的内存,而不是对应的描述符句柄的索引index
// 描述符自生是记录了自己的地址(通过简单的计算可以得出偏移和索引)
// 所以释放后,只需要指向释放出来的索引index即可,这样下次新增时即可以
// 放在对应的空闲区域.
// 这一切都是应为freelist和内存是一对一对应的关系
// 延迟释放机制确保GPU完成使用后才回收描述符避免GPU仍在访问时重用
// 使用双缓冲/多帧缓冲机制,当前帧释放的描述符会在若干帧后安全回收
// _free_handles作为空闲索引栈_size指向栈顶回收的索引压入栈中供后续分配使用
void
descriptor_heap::process_deferred_release(u32 frame_index)
{
std::lock_guard lock(_mutex);
assert(frame_index < frame_buffer_count);
utl::vector<u32>& indices { _deferred_free_indices[frame_index] };
if(!indices.empty())
{
for(auto index : indices)
{
--_size;
_free_handles[_size] = index;
}
indices.clear();
}
}
descriptor_handle
descriptor_heap::allocate()
{
std::lock_guard lock(_mutex);
assert(_heap);
assert(_size < _capacity);
const u32 index { _free_handles[_size] };
const u32 offset { index * _descriptor_size };
++_size;
descriptor_handle handle{};
handle.cpu.ptr = _cpu_start.ptr + offset;
if(is_shader_visible())
{
handle.gpu.ptr = _gpu_start.ptr + offset;
}
DEBUG_OP(handle.container = this);
DEBUG_OP(handle.index = index);
return handle;
}
void
descriptor_heap::free(descriptor_handle handle)
{
if(!handle.is_valid()) return;
std::lock_guard lock(_mutex);
assert(_heap && _size);
assert(handle.container == this);
assert(handle.cpu.ptr >= _cpu_start.ptr);
assert((handle.cpu.ptr - _cpu_start.ptr) % _descriptor_size == 0);
assert(handle.index < _capacity);
const u32 index{ (u32)(handle.cpu.ptr - _cpu_start.ptr) / _descriptor_size };
assert(handle.index == index);
const u32 frame_index{ core::current_frame_index() };
_deferred_free_indices[frame_index].push_back(index);
core::set_deferred_release_flag();
handle = {};
}
//////////// D3D12 TEXTURE ////////////
d3d12_texture::d3d12_texture( d3d12_texture_init_info info)
{
auto *const device {core::device()};
assert(device);
D3D12_CLEAR_VALUE *const clear_value{
(info.desc &&
(info.desc->Flags &D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET ||
info.desc && info.desc->Flags &D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
? &info.clear_value : nullptr
};
if(info.resource)
{
_resource = info.resource;
}
else if(info.heap && info.desc)
{
assert(!info.resource);
DXCall(device->CreatePlacedResource(
info.heap,
info.allocation_info.Offset,
info.desc,
info.initial_state,
clear_value,
IID_PPV_ARGS(&_resource)
));
}
else if (info.desc)
{
assert(!info.srv_desc);
DXCall(device->CreateCommittedResource(
&d3dx::heap_properties.default_heap,
D3D12_HEAP_FLAG_NONE,
info.desc,
info.initial_state,
clear_value,
IID_PPV_ARGS(&_resource)
));
}
assert(_resource);
_srv = core::srv_heap().allocate();
device->CreateShaderResourceView(_resource, info.srv_desc, _srv.cpu);
}
void
d3d12_texture::release()
{
core::srv_heap().free(_srv);
core::deferred_release(_resource);
}
//////////// D3D12 RENDER TEXTURE ////////////
d3d12_render_texture::d3d12_render_texture(d3d12_texture_init_info info)
: _texture(info)
{
assert(info.desc);
_mip_count = resource()->GetDesc().MipLevels;
assert(_mip_count && _mip_count <= d3d12_texture::max_mips);
descriptor_heap& rtv_heap {core::rtv_heap()};
D3D12_RENDER_TARGET_VIEW_DESC desc{};
desc.Format = info.desc->Format;
desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
desc.Texture2D.MipSlice = 0;
auto *const device {core::device()};
assert(device);
for(u32 i{0}; i<_mip_count;++i)
{
_rtv[i] = rtv_heap.allocate();
device->CreateRenderTargetView(resource(), &desc, _rtv[i].cpu);
++desc.Texture2D.MipSlice;
}
}
void
d3d12_render_texture::release()
{
for(u32 i{0}; i<_mip_count;++i) core::srv_heap().free(_rtv[i]);
_texture.release();
_mip_count = 0;
}
//////////// D3D12 DEPTH TEXTURE ////////////
d3d12_depth_buffer::d3d12_depth_buffer(d3d12_texture_init_info info)
: _texture(info)
{
assert(info.desc);
const DXGI_FORMAT dsv_format {info.desc->Format};
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc{};
if(info.desc->Format == DXGI_FORMAT_D32_FLOAT)
{
info.desc->Format = DXGI_FORMAT_R32_TYPELESS;
srv_desc.Format = DXGI_FORMAT_R32_FLOAT;
}
srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = 1;
srv_desc.Texture2D.MostDetailedMip = 0;
srv_desc.Texture2D.PlaneSlice = 0;
srv_desc.Texture2D.ResourceMinLODClamp = 0.f;
assert(!info.srv_desc && !info.resource);
info.srv_desc = &srv_desc;
_texture = d3d12_texture(info);
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc{};
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsv_desc.Flags = D3D12_DSV_FLAG_NONE;
dsv_desc.Format = dsv_format;
dsv_desc.Texture2D.MipSlice = 0;
_dsv = core::dsv_heap().allocate();
auto *const device {core::device()};
assert(device);
device->CreateDepthStencilView(resource(), &dsv_desc, _dsv.cpu);
}
void
d3d12_depth_buffer::release()
{
core::dsv_heap().free(_dsv);
_texture.release();
_dsv = {};
}
} //XEngine::graphics::d3d12

View File

@@ -0,0 +1,561 @@
#pragma once
#include "D3D12CommonHeader.h"
namespace XEngine::graphics::d3d12 {
/**
* @brief 前向声明,用于友元类关系
*/
class descriptor_heap;
/**
* @struct descriptor_handle
* @brief 封装 CPU 和 GPU 描述符句柄的包装结构体
*
* @details
* 该结构体为 D3D12 中的描述符句柄管理提供统一接口。
* 描述符句柄表示描述符堆中特定描述符的指针。
*
* - **CPU 句柄**CPU 用于创建和修改描述符视图SRV、UAV、CBV、RTV、DSV
* - **GPU 句柄**GPU 在着色器执行期间访问描述符(仅着色器可见)
*
* @note
* - CPU 句柄在分配后始终有效
* - GPU 句柄仅在从着色器可见的描述符堆分配时有效
* - DEBUG 模式下存储额外的跟踪信息用于验证和调试
*
* @see descriptor_heap
* @see D3D12_CPU_DESCRIPTOR_HANDLE
* @see D3D12_GPU_DESCRIPTOR_HANDLE
*
* @example
* @code
* descriptor_handle handle = heap.allocate();
* if (handle.is_valid()) {
* device->CreateShaderResourceView(texture, &srvDesc, handle.cpu);
* if (handle.is_shader_visible()) {
* cmdList->SetGraphicsRootDescriptorTable(0, handle.gpu);
* }
* }
* @endcode
*/
struct descriptor_handle
{
/**
* @brief CPU 端描述符句柄
*
* @details
* 用于 CPU 端操作如创建视图CreateShaderResourceView、
* CreateUnorderedAccessView、CreateRenderTargetView 等)。
* 从任何类型的描述符堆成功分配后,此句柄始终有效。
*/
D3D12_CPU_DESCRIPTOR_HANDLE cpu{};
/**
* @brief GPU 端描述符句柄
*
* @details
* 用于通过根描述符表将描述符绑定到图形管线。
* 仅当从着色器可见的描述符堆分配时有效
* CBV_SRV_UAV 或 SAMPLER 类型,带有 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
*
* @note 值为 0 表示此句柄不是着色器可见的
*/
D3D12_GPU_DESCRIPTOR_HANDLE gpu{};
/**
* @brief 检查此句柄是否指向有效的描述符
*
* @return 如果 CPU 句柄非空则返回 true否则返回 false
*
* @note 有效句柄不保证底层描述符已正确初始化
*/
constexpr bool is_valid() const { return cpu.ptr != 0; }
/**
* @brief 检查此句柄是否可从 GPU 着色器访问
*
* @return 如果 GPU 句柄非空(着色器可见)则返回 true否则返回 false
*
* @details
* 着色器可见句柄可通过 SetGraphicsRootDescriptorTable 或
* SetComputeRootDescriptorTable 绑定到管线。非着色器可见句柄RTV、DSV
* 只能通过 OMSetRenderTargets 或类似方法绑定。
*/
constexpr bool is_shader_visible() const { return gpu.ptr != 0; }
#ifdef _DEBUG
private:
/**
* @brief 指向所属描述符堆的指针(仅 DEBUG 模式)
*
* @details
* 用于验证以确保句柄被释放到正确的堆。
* 此指针在分配时设置,在释放操作时验证。
*/
friend class descriptor_heap;
descriptor_heap* container{nullptr};
/**
* @brief 描述符堆内的索引(仅 DEBUG 模式)
*
* @details
* 存储此描述符在其所属堆中的逻辑索引。
* 用于调试和验证目的,跟踪描述符分配情况。
*/
u32 index{u32_invalid_id};
#endif
};
/**
* @class descriptor_heap
* @brief 管理 D3D12 描述符堆的分配、释放和生命周期
*
* @details
* 该类为 D3D12 描述符堆提供高级抽象,实现:
*
* - **池化分配**:基于空闲列表管理,实现 O(1) 分配/释放
* - **线程安全**:互斥锁保护操作,支持多线程资源创建
* - **延迟释放**:帧感知的资源生命周期管理,防止 GPU 危险
* - **类型安全**:通过构造函数参数实现编译时类型绑定
*
* ## 描述符堆类型
*
* | 类型 | 着色器可见 | 典型用途 |
* |------|------------|----------|
* | CBV_SRV_UAV | 可选 | 常量缓冲区、纹理、UAV |
* | SAMPLER | 可选 | 纹理采样器 |
* | RTV | 否 | 渲染目标视图 |
* | DSV | 否 | 深度模板视图 |
*
* ## 内存模型
*
* - **着色器可见堆**:分配在 GPU 可见内存(显存或映射到 GPU 的系统内存)
* - **非着色器可见堆**仅分配在系统内存CPU 可访问)
*
* ## 线程安全
*
* 所有公有方法都是线程安全的。多个线程可以同时从同一个堆分配和释放描述符。
* 内部互斥锁确保操作的原子性。
*
* ## 延迟释放机制
*
* 当描述符被释放时,不会立即返回到空闲池。而是放入帧索引的延迟释放队列。
* 当对应帧在 GPU 上完成后,调用 process_deferred_release() 时,描述符才会
* 返回到空闲池,确保不会发生 GPU-GPU 或 CPU-GPU 危险。
*
* @see descriptor_handle
* @see D3D12_DESCRIPTOR_HEAP_TYPE
* @see ID3D12DescriptorHeap
*
* @example
* @code
* // 创建一个包含 1024 个描述符的着色器可见 CBV/SRV/UAV 堆
* descriptor_heap cbv_srv_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
* cbv_srv_heap.initialize(1024, true);
*
* // 分配描述符
* descriptor_handle tex_handle = cbv_srv_heap.allocate();
* device->CreateShaderResourceView(texture, &srvDesc, tex_handle.cpu);
*
* // 绑定到管线
* ID3D12DescriptorHeap* heaps[] = {cbv_srv_heap.heap()};
* cmdList->SetDescriptorHeaps(1, heaps);
* cmdList->SetGraphicsRootDescriptorTable(0, tex_handle.gpu);
*
* // 不再需要时释放(延迟到帧完成)
* cbv_srv_heap.free(tex_handle);
* @endcode
*/
class descriptor_heap
{
public:
/**
* @brief 构造指定类型的描述符堆
*
* @param type D3D12 描述符堆类型CBV_SRV_UAV、SAMPLER、RTV 或 DSV
*
* @note 此构造函数不分配任何资源。调用 initialize() 创建堆
*/
explicit descriptor_heap(const D3D12_DESCRIPTOR_HEAP_TYPE type) : _type(type) {}
/**
* @brief 禁用拷贝构造以防止资源重复
*/
DISABLE_COPY_AND_MOVE(descriptor_heap);
/**
* @brief 析构函数,断言所有资源已释放
*
* @note 堆必须在析构前通过 release() 显式释放
*/
~descriptor_heap() { assert(!_heap); }
/**
* @brief 初始化具有指定容量的描述符堆
*
* @param capacity 此堆可容纳的最大描述符数量
* @param is_shader_visible 堆是否应着色器可见
*
* @return 初始化成功返回 true否则返回 false
*
* @details
* - 对于 RTV/DSV 堆is_shader_visible 强制为 false设计上 GPU 不可访问)
* - 容量不得超过 D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2
* - 对于采样器堆,容量不得超过 D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE
*
* @pre 堆必须未初始化(如需重新初始化,先调用 release()
* @post 堆已准备好进行 allocate() 调用
*/
bool initialize(u32 capacity, bool is_shader_visible);
/**
* @brief 处理特定帧的延迟释放
*
* @param frame_index 应处理其延迟释放的帧索引
*
* @details
* 此方法应在新帧开始时调用,确认 GPU 已完成对应帧的处理。
* 它将该帧期间释放的所有描述符返回到空闲池。
*
* @pre GPU 必须已完成指定帧(通过 Fence 同步确认)
*/
void process_deferred_release(u32 frame_index);
/**
* @brief 释放此堆持有的所有资源
*
* @details
* - 通过 flush() 等待所有帧完成
* - 释放底层 ID3D12DescriptorHeap
* - 清除所有内部跟踪结构
*
* @post 堆处于未初始化状态,可重新初始化
*/
void release();
/**
* @brief 从堆中分配描述符
*
* @return 指向已分配描述符的 descriptor_handle
*
* @throws 如果堆已满或未初始化则触发断言失败
*
* @details
* - 线程安全:使用互斥锁保护内部状态
* - O(1) 复杂度:从空闲列表弹出
* - 返回的句柄包含 CPU 和 GPU如果着色器可见地址
*
* @pre 堆必须已初始化且有可用容量
* @note 标记 [[nodiscard]] 以防止意外丢弃句柄
*/
[[nodiscard]] descriptor_handle allocate();
/**
* @brief 释放描述符,将其放入延迟释放队列
*
* @param handle 要释放的描述符句柄
*
* @details
* - 线程安全:使用互斥锁保护内部状态
* - 描述符不会立即可重用
* - 在 GPU 完成当前帧后调用 process_deferred_release() 时,
* 描述符才会返回到空闲池
* - 无效句柄会被安全忽略
*
* @note DEBUG 模式下验证句柄属于此堆
*/
void free(descriptor_handle handle);
/**
* @brief 返回 D3D12 描述符堆类型
* @return 此堆的类型CBV_SRV_UAV、SAMPLER、RTV 或 DSV
*/
constexpr D3D12_DESCRIPTOR_HEAP_TYPE type() const { return _type; }
/**
* @brief 返回堆中第一个描述符的 CPU 句柄
* @return 起始 CPU 描述符句柄
*/
constexpr D3D12_CPU_DESCRIPTOR_HANDLE cpu_start() const { return _cpu_start; }
/**
* @brief 返回堆中第一个描述符的 GPU 句柄
* @return 起始 GPU 描述符句柄,如果非着色器可见则为 {0}
*/
constexpr D3D12_GPU_DESCRIPTOR_HANDLE gpu_start() const { return _gpu_start; }
/**
* @brief 返回底层 D3D12 描述符堆接口
* @return 指向 ID3D12DescriptorHeap 的指针,如果未初始化则为 nullptr
*/
constexpr ID3D12DescriptorHeap* const heap() const { return _heap; }
/**
* @brief 返回此堆可容纳的最大描述符数量
* @return 堆的总容量
*/
constexpr u32 capacity() const { return _capacity; }
/**
* @brief 返回当前已分配的描述符数量
* @return 已分配描述符计数(不包括延迟释放的)
*/
constexpr u32 size() const { return _size; }
/**
* @brief 返回单个描述符的字节大小
* @return 描述符大小,因堆类型和硬件而异
*/
constexpr u32 descriptor_size() const { return _descriptor_size; }
/**
* @brief 检查此堆是否着色器可见
* @return 如果 GPU 句柄有效则返回 true否则返回 false
*/
constexpr bool is_shader_visible() const { return _gpu_start.ptr != 0; }
private:
/**
* @brief 底层 D3D12 描述符堆接口
*
* @details
* 内存位置取决于着色器可见性:
* - 着色器可见:分配在 GPU 可访问内存(显存或映射到 GPU 的系统内存)
* - 非着色器可见:仅分配在系统内存
*/
ID3D12DescriptorHeap* _heap{nullptr};
/**
* @brief 堆中第一个描述符的 CPU 句柄
*
* @details
* 用作计算单个描述符 CPU 句柄的基地址。
* initialize() 成功调用后立即有效。
*/
D3D12_CPU_DESCRIPTOR_HANDLE _cpu_start{};
/**
* @brief 堆中第一个描述符的 GPU 句柄
*
* @details
* 仅对着色器可见堆有效。用作计算可通过根描述符表绑定到管线的
* GPU 句柄的基地址。值为 {0} 表示非着色器可见堆。
*/
D3D12_GPU_DESCRIPTOR_HANDLE _gpu_start{};
/**
* @brief 存储可用描述符索引的空闲列表
*
* @details
* 实现为预分配数组,实现 O(1) 分配/释放。
* 初始填充所有索引 [0, capacity-1]。
* 分配描述符时,从前面消耗索引。
* 释放描述符后(延迟处理完成),索引返回。
*/
std::unique_ptr<u32[]> _free_handles{};
/**
* @brief 每帧的延迟释放队列
*
* @details
* 当调用 free() 时,描述符索引放入当前帧的队列。
* 只有在该帧 GPU 完成后调用 process_deferred_release() 时,
* 索引才会返回到 _free_handles。
*
* 这防止了 GPU 危险:当描述符可能仍被 GPU 使用时被新资源重用。
*
* 数组大小为 frame_buffer_count通常为 3三重缓冲
*/
utl::vector<u32> _deferred_free_indices[frame_buffer_count]{};
/**
* @brief 用于线程安全访问内部状态的互斥锁
*
* @details
* 保护所有可变状态_size、_free_handles 和 _deferred_free_indices。
* 必需,因为资源创建/销毁可能发生在多个线程上。
*/
std::mutex _mutex{};
/**
* @brief 此堆可容纳的最大描述符数量
*
* @details
* 在 initialize() 时设置,此后不可变。
* 用于边界检查和空闲列表初始化。
*/
u32 _capacity{0};
/**
* @brief 当前已分配的描述符数量
*
* @details
* 表示从 _free_handles 消耗的索引数量。
* 不包括延迟释放队列中的描述符。
*/
u32 _size{0};
/**
* @brief 单个描述符的字节大小
*
* @details
* 从 ID3D12Device::GetDescriptorHandleIncrementSize() 获取。
* 因堆类型和硬件而异。用于句柄偏移计算。
*/
u32 _descriptor_size{0};
/**
* @brief D3D12 描述符堆类型
*
* @details
* 构造时设置,不可变。决定:
* - 可创建的视图类型SRV、UAV、RTV 等)
* - 是否支持着色器可见性
* - 允许的最大容量
*/
const D3D12_DESCRIPTOR_HEAP_TYPE _type;
};
struct d3d12_texture_init_info
{
ID3D12Heap1* heap{nullptr};
ID3D12Resource* resource{nullptr};
D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr};
D3D12_RESOURCE_DESC* desc{nullptr};
D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{};
D3D12_RESOURCE_STATES initial_state{};
D3D12_CLEAR_VALUE clear_value{};
};
class d3d12_texture
{
public:
constexpr static u32 max_mips{ 14 };
d3d12_texture() = default;
explicit d3d12_texture(d3d12_texture_init_info info);
~d3d12_texture() { release(); }
DISABLE_COPY(d3d12_texture);
constexpr d3d12_texture(d3d12_texture&& o)
: _resource(o._resource), _srv(o._srv) //这些值只是指针和句柄不需要move
{
o.reset();
}
constexpr d3d12_texture& operator=(d3d12_texture&& o)
{
assert(this != &o);
if(this != &o)
{
release();
move(o);
}
return *this;
}
void release();
constexpr ID3D12Resource *const resource() const { return _resource; }
constexpr descriptor_handle srv() const { return _srv; }
private:
constexpr void move(d3d12_texture& o)
{
_resource = o._resource;
_srv = o._srv;
o.reset();
}
constexpr void reset()
{
_resource = nullptr;
_srv = {};
}
ID3D12Resource* _resource{nullptr};
descriptor_handle _srv;
};
class d3d12_render_texture
{
public:
d3d12_render_texture() = default;
explicit d3d12_render_texture(d3d12_texture_init_info info);
DISABLE_COPY(d3d12_render_texture);
~d3d12_render_texture() { release(); }
constexpr d3d12_render_texture(d3d12_render_texture&& o)
: _texture{std::move(o._texture)}, _mip_count{o._mip_count}
{
for(u32 i = 0; i < o._mip_count; ++i) _rtv[i] = o._rtv[i];
o.reset();
}
constexpr d3d12_render_texture& operator=(d3d12_render_texture&& o)
{
assert(this != &o);
if(this != &o)
{
reset();
move(o);
}
return *this;
}
void release();
constexpr u32 mip_count() const { return _mip_count; }
constexpr D3D12_CPU_DESCRIPTOR_HANDLE rtv(u32 mip) const { assert(mip < _mip_count); return _rtv[mip].cpu; }
constexpr descriptor_handle srv() const { return _texture.srv(); }
constexpr ID3D12Resource *const resource() const { return _texture.resource(); }
private:
constexpr void move(d3d12_render_texture& o)
{
_texture = std::move(o._texture);
for(u32 i = 0; i < o._mip_count; ++i) _rtv[i] = o._rtv[i];
o.reset();
}
constexpr void reset()
{
for(u32 i = 0; i < _mip_count; ++i) _rtv[i] = {};
_mip_count = 0;
}
d3d12_texture _texture{};
descriptor_handle _rtv[d3d12_texture::max_mips]{};
u32 _mip_count{0};
};
class d3d12_depth_buffer
{
public :
d3d12_depth_buffer() = default;
explicit d3d12_depth_buffer(d3d12_texture_init_info info);
DISABLE_COPY(d3d12_depth_buffer);
~d3d12_depth_buffer() { release(); }
constexpr d3d12_depth_buffer(d3d12_depth_buffer&& o)
: _texture{std::move(o._texture)}, _dsv(o._dsv)
{
o._dsv = {};
}
constexpr d3d12_depth_buffer& operator=(d3d12_depth_buffer&& o)
{
assert(this != &o);
if(this != &o)
{
_texture = std::move(o._texture);
_dsv = o._dsv;
o._dsv = {};
}
return *this;
}
void release();
constexpr D3D12_CPU_DESCRIPTOR_HANDLE dsv() const { return _dsv.cpu; }
constexpr descriptor_handle srv() const { return _texture.srv(); }
constexpr ID3D12Resource *const resource() const { return _texture.resource(); }
private:
d3d12_texture _texture{};
descriptor_handle _dsv{};
};
} // namespace XEngine::graphics::d3d12

View File

@@ -0,0 +1,138 @@
#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();
if (SUCCEEDED(factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &_allow_tearing, sizeof(u32))) && _allow_tearing)
{
_present_flags = DXGI_PRESENT_ALLOW_TEARING;
}
DXGI_SWAP_CHAIN_DESC1 desc{};
desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Alpha通道模式窗口透明度相关
desc.BufferCount = 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显示
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 < 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, _present_flags));
_current_bb_index = _swap_chain->GetCurrentBackBufferIndex();
}
void
d3d12_surface::finalize()
{
// 为每个缓冲区创建渲染目标视图
for (u32 i = 0; i < 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 < 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);
}
void
d3d12_surface::resize()
{
}
u32
d3d12_surface::width()
{
return _window.width();
}
u32
d3d12_surface::height()
{
return _window.height();
}
} // namespace XEngine::graphics::d3d12

View File

@@ -0,0 +1,303 @@
#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 }; ///< 呈现标志位
};
}

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "CommonHeader.h" #include "CommonHeader.h"
#include "Platform/Window.h"
#include "Renderer.h" #include "Renderer.h"
namespace XEngine::graphics{ namespace XEngine::graphics{
@@ -10,6 +11,15 @@ namespace XEngine::graphics{
struct platform_interface{ struct platform_interface{
bool(*initialize)(void); bool(*initialize)(void);
void(*shutdown)(void); void(*shutdown)(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 }// namespace XEngine::graphics

View File

@@ -39,13 +39,57 @@ set_platform_interface(graphics_platform platform)
* @param platform 图形渲染平台类型 * @param platform 图形渲染平台类型
* @return true 如果初始化成功,否则返回 false * @return true 如果初始化成功,否则返回 false
*/ */
bool initialize(graphics_platform platform) bool
initialize(graphics_platform platform)
{ {
return set_platform_interface(platform) && gfx.initialize(); return set_platform_interface(platform) && gfx.initialize();
} }
void shutdown() void
shutdown()
{ {
gfx.shutdown(); gfx.shutdown();
} }
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 }// namespace XEngine::graphics

View File

@@ -1,16 +1,60 @@
#pragma once #pragma once
#include "CommonHeader.h" #include "Platform/Window.h"
#include "..\Platform\Window.h"
namespace XEngine::graphics { namespace XEngine::graphics {
DEFINE_TYPED_ID(surface_id);
/** /**
* @brief 表面类 * @brief 表面类
* @details 定义了渲染表面的属性与操作 * @details 定义了渲染表面的属性与操作
*/ */
class surface 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 };
}; };
/** /**
@@ -47,4 +91,17 @@ bool initialize(graphics_platform platform);
*/ */
void shutdown(); void shutdown();
/**
* @brief 创建渲染表面
* @details window 渲染表面的窗口
* @return 渲染表面对象柄
*/
surface create_surface(platform::window window);
/**
* @brief 移除渲染表面
* @details id 渲染表面的 ID
*/
void remove_surface(surface_id id);
} }

View File

@@ -1,11 +1,97 @@
/** /**
* @file FreeList.h * @file FreeList.h
* @brief 带复用槽位的稀疏对象容器模板 * @brief 带槽位复用机制的稀疏对象容器模板
*
* @details * @details
* free_list 维护已使用槽位 + 空闲链结构,支持: * free_list 是一种特殊的容器,维护"已使用槽位 + 空闲链表"结构,
* - O(1) 近似开销的新增与删除; * 特别适合需要频繁增删元素且使用索引作为外部句柄的场景。
* - 删除后槽位复用,降低频繁分配释放成本; *
* - 以索引作为外部句柄,与 id 系统协同使用。 * ## 核心特性
*
* - **O(1) 增删操作**:新增和删除操作近似常数时间复杂度
* - **槽位复用**:删除后的槽位会被回收并重新利用,避免频繁内存分配
* - **索引句柄**:使用 u32 索引作为外部句柄,与 id 系统完美配合
* - **内存紧凑**:底层使用连续内存,缓存友好
*
* ## 数据结构原理
*
* ```
* 初始状态:
* _array: [ 空 | 空 | 空 | 空 ]
* _next_free_index = invalid_id
* _size = 0
*
* 添加元素 A、B、C 后:
* _array: [ A | B | C | 空 ]
* _next_free_index = invalid_id
* _size = 3
*
* 删除元素 B 后(槽位 1 被标记为空闲):
* _array: [ A | ->2 | C | 空 ] // 槽位 1 存储下一个空闲索引 2
* _next_free_index = 1 // 指向第一个空闲槽位
* _size = 2
*
* 再删除元素 A 后:
* _array: [ ->1 | ->2 | C | 空 ] // 槽位 0 存储下一个空闲索引 1
* _next_free_index = 0
* _size = 1
*
* 添加新元素 D 时(复用槽位 0:
* _array: [ D | ->2 | C | 空 ]
* _next_free_index = 1 // 指向下一个空闲槽位
* _size = 2
* ```
*
* ## 空闲链表机制
*
* 删除元素时,该槽位被加入空闲链表:
* 1. 调用元素的析构函数
* 2. 将当前 `_next_free_index` 值写入该槽位(作为链表下一个节点)
* 3. 更新 `_next_free_index` 指向该槽位
*
* 添加元素时,优先从空闲链表获取槽位:
* 1. 如果 `_next_free_index` 有效,复用该槽位
* 2. 从槽位中读取下一个空闲索引,更新 `_next_free_index`
* 3. 否则,在数组末尾追加新元素
*
* ## 类型要求
*
* 由于空闲槽位需要存储 u32 索引,要求:
* - `sizeof(T) >= sizeof(u32)`(至少 4 字节)
*
* ## 使用场景
*
* - 游戏对象管理(实体 ID 系统)
* - 资源句柄管理(纹理、网格等)
* - 渲染表面管理(多窗口场景)
* - 任何需要稳定索引句柄的系统
*
* ## 注意事项
*
* - 删除后的索引可能被新元素复用,外部系统需要处理这种情况
* - 不保证元素在内存中的顺序
* - 与 std::vector 配合使用时可能触发重复构造(见 USE_STL_VECTOR 宏)
*
* @example
* @code
* utl::free_list<game_object> objects;
*
* // 添加对象
* u32 id1 = objects.add(player_data);
* u32 id2 = objects.add(enemy_data);
*
* // 访问对象
* objects[id1].update();
*
* // 删除对象(槽位被回收)
* objects.remove(id1);
*
* // 新对象可能复用 id1 的槽位
* u32 id3 = objects.add(new_data); // id3 可能等于 id1
* @endcode
*
* @see utl::vector
* @see DEFINE_TYPED_ID
*/ */
#pragma once #pragma once
#include "CommonHeader.h" #include "CommonHeader.h"
@@ -13,23 +99,42 @@
namespace XEngine::utl { namespace XEngine::utl {
#if USE_STL_VECTOR #if USE_STL_VECTOR
#pragma message("WARNING: using utl::free_list with std::vector result in duplicate calls to class constructor!"); #pragma message("WARNING: using utl::free_list with std::vector result in duplicate calls to class constructor!")
#endif #endif
/**
* @class free_list
* @brief 带槽位复用机制的稀疏对象容器
*
* @tparam T 元素类型,必须满足 `sizeof(T) >= sizeof(u32)`
*
* @details
* 该容器使用空闲链表管理已删除的槽位,实现 O(1) 的增删操作。
* 特别适合需要稳定索引句柄的场景,如游戏对象管理、资源句柄等。
*/
template<typename T> template<typename T>
class free_list class free_list
{ {
static_assert(sizeof(T) >= sizeof(u32)); static_assert(sizeof(T) >= sizeof(u32), "Type T must be at least 4 bytes to store free list indices");
public: public:
/** /**
* @brief 构造空容器 * @brief 默认构造函数,创建空容器
*
* @details
* 创建一个空的 free_list不预分配任何内存。
* 首次添加元素时会触发内存分配。
*/ */
free_list() = default; free_list() = default;
/** /**
* @brief 预留底层存储容量。 * @brief 预留容量的构造函数
* @param count 预留元素数量。 *
* @param count 预留的元素槽位数量
*
* @details
* 预分配指定数量的槽位,避免后续添加元素时的多次扩容。
* 适用于已知大概元素数量的场景。
*/ */
explicit free_list(u32 count) explicit free_list(u32 count)
{ {
@@ -37,7 +142,11 @@ public:
} }
/** /**
* @brief 销毁容器并校验无存活元素。 * @brief 析构函数,销毁容器
*
* @details
* 断言检查容器为空_size == 0确保所有元素已被正确移除。
* 在 DEBUG 模式下使用 USE_STL_VECTOR 时,会清空内存以便检测悬空引用。
*/ */
~free_list() ~free_list()
{ {
@@ -49,10 +158,23 @@ public:
} }
/** /**
* @brief 新增一个元素并返回槽位 ID * @brief 添加新元素并返回槽位 ID
* @tparam params 构造参数类型。 *
* @param p 构造参数 * @tparam params 构造参数类型包
* @return 新元素 ID。 * @param p 传递给元素构造函数的参数
* @return 新分配的槽位 IDu32 索引)
*
* @details
* 添加元素的策略:
* 1. **有空闲槽位**:从空闲链表头部取出一个槽位复用
* 2. **无空闲槽位**:在数组末尾追加新元素
*
* 复用槽位时:
* - 从该槽位读取下一个空闲索引,更新 `_next_free_index`
* - 使用 placement new 在该槽位构造新元素
*
* @note 返回的 ID 在元素被删除前保持有效
* @note 删除后该 ID 可能被新元素复用
*/ */
template<class ... params> template<class ... params>
constexpr u32 add(params&&... p) constexpr u32 add(params&&... p)
@@ -76,8 +198,19 @@ public:
} }
/** /**
* @brief 除指定 ID 的元素并回收到空闲链。 * @brief 除指定 ID 的元素并回收其槽位
* @param id 元素 ID。 *
* @param id 要移除元素的槽位 ID
*
* @details
* 移除元素的步骤:
* 1. 调用元素的析构函数
* 2. DEBUG 模式下填充 0xcc 标记已删除
* 3. 将当前 `_next_free_index` 写入该槽位(链表链接)
* 4. 更新 `_next_free_index` 指向该槽位
*
* @pre id 必须是有效的已分配槽位
* @pre 该槽位未被删除(不能重复删除)
*/ */
constexpr void remove(u32 id) constexpr void remove(u32 id)
{ {
@@ -91,28 +224,35 @@ public:
} }
/** /**
* @brief 获取当前有效元素数量 * @brief 获取当前有效元素数量
* @return 元素数量。 *
* @return 容器中有效元素的个数
*
* @note 这与 capacity() 不同size() 返回实际存储的元素数
*/ */
constexpr u32 size() const constexpr u32 size() const
{ {
return _size; return _size;
} }
/** /**
* @brief 获取当前已分配槽位总数 * @brief 获取已分配槽位总数
* @return 容量值。 *
* @return 底层数组的大小(包括空闲槽位)
*
* @details
* 返回值 >= size(),因为可能存在已删除但未释放的槽位。
* 这些槽位会在后续添加元素时被复用。
*/ */
constexpr u32 capacity() const constexpr u32 capacity() const
{ {
return _array.size(); return _array.size();
} }
/** /**
* @brief 判断容器是否为空 * @brief 检查容器是否为空
* @return 空返回 true。 *
* @return 如果容器中没有有效元素则返回 true
*/ */
constexpr bool empty() const constexpr bool empty() const
{ {
@@ -120,9 +260,12 @@ public:
} }
/** /**
* @brief ID 访问元素 * @brief 通过 ID 访问元素(可修改)
* @param id 元素 ID。 *
* @return 元素引用。 * @param id 元素的槽位 ID
* @return 元素的引用
*
* @pre id 必须有效且指向未删除的元素
*/ */
[[nodiscard]] constexpr T& operator[](u32 id) [[nodiscard]] constexpr T& operator[](u32 id)
{ {
@@ -131,9 +274,12 @@ public:
} }
/** /**
* @brief ID 访问常量元素。 * @brief 通过 ID 访问元素(只读)
* @param id 元素 ID。 *
* @return 常量元素引用。 * @param id 元素的槽位 ID
* @return 元素的常量引用
*
* @pre id 必须有效且指向未删除的元素
*/ */
[[nodiscard]] constexpr const T& operator[](u32 id) const [[nodiscard]] constexpr const T& operator[](u32 id) const
{ {
@@ -142,6 +288,18 @@ public:
} }
private: private:
/**
* @brief 检查指定槽位是否已被删除
*
* @param id 要检查的槽位 ID
* @return 如果槽位已被删除则返回 true
*
* @details
* 通过检查槽位内容是否被 0xcc 填充来判断是否已删除。
* 仅在 sizeof(T) > sizeof(u32) 时有效,否则直接返回 true。
*
* @note 仅用于断言检查,确保不会访问已删除的元素
*/
constexpr bool already_removed(u32 id) const constexpr bool already_removed(u32 id) const
{ {
if constexpr (sizeof(T) > sizeof(u32)) if constexpr (sizeof(T) > sizeof(u32))

View File

@@ -1,11 +1,66 @@
/** /**
* @file Vector.h * @file Vector.h
* @brief 自定义动态数组容器实现 * @brief 自定义动态数组容器实现
*
* @details * @details
* 该容器提供接近 std::vector 的常用能力,并支持可选析构策略 * 该容器提供 std::vector 相似的功能,同时支持可选析构策略
* - 动态扩容、插入、删除与无序删除; * 设计目标是在性能敏感场景下提供更精细的内存管理控制。
* - 连续内存访问与迭代器接口; *
* - 在性能敏感场景下复用底层内存分配行为。 * ## 核心特性
*
* - **动态扩容**:自动管理内存,支持预留容量避免频繁分配
* - **连续内存**:元素在内存中连续存储,缓存友好
* - **可选析构**:通过模板参数控制是否在删除时调用元素析构函数
* - **迭代器支持**:提供 begin()/end() 支持范围 for 循环
*
* ## 与 std::vector 的区别
*
* | 特性 | utl::vector | std::vector |
* |------|-------------|-------------|
* | 析构控制 | 可选模板参数 | 始终析构 |
* | 内存分配 | realloc | allocator |
* | 无序删除 | erase_unordered | 无 |
*
* ## 析构策略
*
* 模板参数 `destruct` 控制元素析构行为:
* - `destruct = true`(默认):删除元素时调用析构函数
* - `destruct = false`:跳过析构,适用于 POD 类型或外部管理生命周期
*
* ## 扩容策略
*
* 当容量不足时,按 `(capacity + 1) * 1.5` 扩容:
* - 初始容量 0 → 1
* - 容量 4 → 6
* - 容量 10 → 15
*
* ## 无序删除优化
*
* `erase_unordered()` 通过将末尾元素移动到删除位置实现 O(1) 删除,
* 适用于元素顺序不重要的场景。
*
* @example
* @code
* // 基本使用
* utl::vector<int> nums;
* nums.push_back(1);
* nums.push_back(2);
* nums.emplace_back(3);
*
* // 遍历
* for (int& n : nums) {
* n *= 2;
* }
*
* // 无序删除(高效但不保证顺序)
* nums.erase_unordered(0); // 用末尾元素替换位置 0
*
* // POD 类型优化(跳过析构)
* utl::vector<float, false> vertices;
* vertices.resize(1000); // 不调用析构函数
* @endcode
*
* @see utl::free_list
*/ */
#pragma once #pragma once
#include "CommonHeader.h" #include "CommonHeader.h"
@@ -13,24 +68,40 @@
namespace XEngine::utl { namespace XEngine::utl {
/** /**
* @brief 自定义连续内存动态数组。 * @class vector
* @tparam T 元素类型。 * @brief 自定义连续内存动态数组
* @tparam destruct 是否在删除时调用析构。 *
* @tparam T 元素类型
* @tparam destruct 是否在删除时调用析构函数,默认为 true
*
* @details
* 提供动态数组的完整功能,包括动态扩容、插入、删除等操作。
* 通过模板参数控制析构行为,适用于不同性能需求场景。
*/ */
template<typename T, bool destruct = true> template<typename T, bool destruct = true>
class vector class vector
{ {
public: public:
// Default constructor. Doesn~t allocate memory.
/** /**
* @brief 构造空容器 * @brief 默认构造函数,创建空容器
*
* @details
* 创建一个空的 vector不分配任何内存。
* 首次添加元素时会触发内存分配。
*/ */
vector() = default; vector() = default;
/** /**
* @brief 构造并调整到指定元素数量 * @brief 构造并调整到指定元素数量
* @param count 目标元素数量。 *
* @param count 目标元素数量
*
* @details
* 创建包含 count 个默认构造元素的容器。
* 要求类型 T 必须可默认构造。
*
* @pre T 必须满足 is_default_constructible
*/ */
constexpr explicit vector(u64 count) constexpr explicit vector(u64 count)
{ {
@@ -38,20 +109,45 @@ public:
} }
/** /**
* @brief 构造并填充指定数量元素 * @brief 构造并填充指定数量元素
* @param count 目标元素数量。 *
* @param value 填充值。 * @param count 目标元素数量
* @param value 用于填充的值
*
* @details
* 创建包含 count 个元素副本的容器。
* 要求类型 T 必须可拷贝构造。
*
* @pre T 必须满足 is_copy_constructible
*/ */
constexpr explicit vector(u64 count, const T& value) constexpr explicit vector(u64 count, const T& value)
{ {
resize(count, value); resize(count, value);
} }
/**
* @brief 拷贝构造函数
*
* @param o 要拷贝的源容器
*
* @details
* 创建源容器的深拷贝,包括所有元素。
* 新容器具有独立的内存空间。
*/
constexpr vector(const vector& o) constexpr vector(const vector& o)
{ {
*this = o; *this = o;
} }
/**
* @brief 移动构造函数
*
* @param o 要移动的源容器
*
* @details
* 转移源容器的所有权,不进行元素拷贝。
* 移动后源容器处于空状态。
*/
constexpr vector(vector&& o) constexpr vector(vector&& o)
:_capacity{o._capacity}, :_capacity{o._capacity},
_size{o._size}, _size{o._size},
@@ -60,7 +156,15 @@ public:
o.reset(); o.reset();
} }
/**
* @brief 拷贝赋值运算符
*
* @param o 要拷贝的源容器
* @return 当前容器的引用
*
* @details
* 清空当前容器,然后深拷贝源容器的所有元素。
*/
constexpr vector& operator=(const vector& o) constexpr vector& operator=(const vector& o)
{ {
assert(this != std::addressof(o)); assert(this != std::addressof(o));
@@ -78,6 +182,15 @@ public:
return *this; return *this;
} }
/**
* @brief 移动赋值运算符
*
* @param o 要移动的源容器
* @return 当前容器的引用
*
* @details
* 销毁当前容器的资源,然后转移源容器的所有权。
*/
constexpr vector& operator=(vector&& o) constexpr vector& operator=(vector&& o)
{ {
assert(this != std::addressof(o)); assert(this != std::addressof(o));
@@ -90,11 +203,19 @@ public:
return *this; return *this;
} }
/**
* @brief 析构函数,释放所有资源
*/
~vector() { destroy(); } ~vector() { destroy(); }
/** /**
* @brief 追加拷贝元素。 * @brief 在末尾追加元素的拷贝
* @param value 元素值。 *
* @param value 要追加的元素值
*
* @details
* 如果容量不足,会自动扩容。
* 内部调用 emplace_back 实现。
*/ */
constexpr void push_back(const T& value) constexpr void push_back(const T& value)
{ {
@@ -102,8 +223,12 @@ public:
} }
/** /**
* @brief 追加右值元素。 * @brief 在末尾追加元素的右值引用
* @param value 元素值。 *
* @param value 要追加的元素值(右值)
*
* @details
* 使用移动语义追加元素,避免不必要的拷贝。
*/ */
constexpr void push_back(const T&& value) constexpr void push_back(const T&& value)
{ {
@@ -111,17 +236,24 @@ public:
} }
/** /**
* @brief 原位构造并追加元素 * @brief 原位构造并追加元素
* @tparam params 构造参数类型。 *
* @param p 构造参数 * @tparam params 构造参数类型包
* @return 新元素引用。 * @param p 传递给元素构造函数的参数
* @return 新构造元素的引用
*
* @details
* 在容器末尾原位构造新元素,避免额外的拷贝或移动操作。
* 如果容量不足,按 1.5 倍扩容。
*
* @note 扩容公式:`new_capacity = (old_capacity + 1) * 3 / 2`
*/ */
template<typename... params> template<typename... params>
constexpr decltype(auto) emplace_back(params&&... p) constexpr decltype(auto) emplace_back(params&&... p)
{ {
if (_size == _capacity) if (_size == _capacity)
{ {
reserve(((_capacity + 1) * 3) >> 1); // reserve 50% more reserve(((_capacity + 1) * 3) >> 1);
} }
assert(_size < _capacity); assert(_size < _capacity);
@@ -131,8 +263,15 @@ public:
} }
/** /**
* @brief 调整元素数量,新增元素默认构造 * @brief 调整元素数量,新增元素默认构造
* @param new_size 新大小。 *
* @param new_size 目标元素数量
*
* @details
* - 如果 new_size > 当前大小,在末尾默认构造新元素
* - 如果 new_size < 当前大小,删除末尾多余元素
*
* @pre T 必须满足 is_default_constructible
*/ */
constexpr void resize(u64 new_size) constexpr void resize(u64 new_size)
{ {
@@ -161,11 +300,17 @@ public:
assert(new_size == _size); assert(new_size == _size);
} }
/** /**
* @brief 调整元素数量,新增元素使用给定值填充 * @brief 调整元素数量,新增元素使用给定值填充
* @param new_size 新大小。 *
* @param value 填充值。 * @param new_size 目标元素数量
* @param value 用于填充新元素的值
*
* @details
* - 如果 new_size > 当前大小,在末尾拷贝构造新元素
* - 如果 new_size < 当前大小,删除末尾多余元素
*
* @pre T 必须满足 is_copy_constructible
*/ */
constexpr void resize(u64 new_size, const T& value) constexpr void resize(u64 new_size, const T& value)
{ {
@@ -194,17 +339,21 @@ public:
assert(new_size == _size); assert(new_size == _size);
} }
/** /**
* @brief 预留最小容量 * @brief 预留最小容量
* @param new_capacity 目标容量。 *
* @param new_capacity 目标容量
*
* @details
* 如果 new_capacity > 当前容量,重新分配更大的内存块。
* 使用 realloc 实现,会自动复制原有数据。
*
* @note 此函数只会增加容量,不会减少
*/ */
constexpr void reserve(u64 new_capacity) constexpr void reserve(u64 new_capacity)
{ {
if (new_capacity > _capacity) if (new_capacity > _capacity)
{ {
// NOTE: realoc() will automatically copy the data in the buffer
// if a new region of memory iss allocated.
void* new_buffer{ realloc(_data, new_capacity * sizeof(T)) }; void* new_buffer{ realloc(_data, new_capacity * sizeof(T)) };
assert(new_buffer); assert(new_buffer);
if (new_buffer) if (new_buffer)
@@ -216,9 +365,16 @@ public:
} }
/** /**
* @brief 删除指定下标元素并保持顺序 * @brief 删除指定下标元素并保持顺序
* @param index 删除下标。 *
* @return 指向删除位置的指针。 * @param index 要删除元素的下标
* @return 指向删除位置后一个元素的指针
*
* @details
* 删除元素后,将后续所有元素向前移动一位。
* 时间复杂度 O(n)。
*
* @pre index 必须小于当前元素数量
*/ */
constexpr T *const erase(u64 index) constexpr T *const erase(u64 index)
{ {
@@ -227,9 +383,16 @@ public:
} }
/** /**
* @brief 删除指定位置元素并保持顺序 * @brief 删除指定位置元素并保持顺序
* @param item 待删除元素指针。 *
* @return 指向删除位置的指针 * @param item 指向删除元素的指针
* @return 指向删除位置后一个元素的指针
*
* @details
* 删除元素后,将后续所有元素向前移动一位。
* 时间复杂度 O(n)。
*
* @pre item 必须指向容器内的有效元素
*/ */
constexpr T *const erase(T *const item) constexpr T *const erase(T *const item)
{ {
@@ -247,9 +410,18 @@ public:
} }
/** /**
* @brief 无序删除指定下标元素 * @brief 无序删除指定下标元素
* @param index 删除下标。 *
* @return 指向删除位置的指针。 * @param index 要删除元素的下标
* @return 指向删除位置的指针
*
* @details
* 用末尾元素替换被删除元素,然后减少大小。
* 时间复杂度 O(1),但不保证元素顺序。
*
* @pre index 必须小于当前元素数量
*
* @note 适用于元素顺序不重要的场景
*/ */
constexpr T *const erase_unordered(u64 index) constexpr T *const erase_unordered(u64 index)
{ {
@@ -258,9 +430,18 @@ public:
} }
/** /**
* @brief 无序删除指定位置元素 * @brief 无序删除指定位置元素
* @param item 待删除元素指针。 *
* @return 指向删除位置的指针 * @param item 指向删除元素的指针
* @return 指向删除位置的指针
*
* @details
* 用末尾元素替换被删除元素,然后减少大小。
* 时间复杂度 O(1),但不保证元素顺序。
*
* @pre item 必须指向容器内的有效元素
*
* @note 适用于元素顺序不重要的场景
*/ */
constexpr T *const erase_unordered(T *const item) constexpr T *const erase_unordered(T *const item)
{ {
@@ -279,7 +460,11 @@ public:
} }
/** /**
* @brief 清空容器中的有元素 * @brief 清空容器中的有元素
*
* @details
* 调用所有元素的析构函数(如果 destruct = true
* 然后将大小设为 0。不释放底层内存。
*/ */
constexpr void clear() constexpr void clear()
{ {
@@ -290,10 +475,14 @@ public:
_size = 0; _size = 0;
} }
/** /**
* @brief 与另一个容器交换内容 * @brief 与另一个容器交换内容
* @param o 目标容器。 *
* @param o 目标容器
*
* @details
* 交换两个容器的所有权,不进行元素拷贝。
* 时间复杂度 O(1)。
*/ */
constexpr void swap(vector& o) constexpr void swap(vector& o)
{ {
@@ -305,20 +494,20 @@ public:
} }
} }
/** /**
* @brief 返回底层数据指针 * @brief 返回底层数据指针
* @return 数据首地址。 *
* @return 指向第一个元素的指针
*/ */
[[nodiscard]] constexpr T* data() [[nodiscard]] constexpr T* data()
{ {
return _data; return _data;
} }
/** /**
* @brief 返回只读底层数据指针 * @brief 返回只读底层数据指针
* @return 数据首地址。 *
* @return 指向第一个元素的常量指针
*/ */
[[nodiscard]] constexpr T *const data() const [[nodiscard]] constexpr T *const data() const
{ {
@@ -326,8 +515,9 @@ public:
} }
/** /**
* @brief 判断容器是否为空 * @brief 判断容器是否为空
* @return 空返回 true。 *
* @return 如果容器中没有元素则返回 true
*/ */
[[nodiscard]] constexpr bool empty() const [[nodiscard]] constexpr bool empty() const
{ {
@@ -335,87 +525,160 @@ public:
} }
/** /**
* @brief 获取元素个数 * @brief 获取元素个数
* @return 元素数量。 *
* @return 容器中当前存储的元素数量
*/ */
[[nodiscard]] constexpr u64 size() const [[nodiscard]] constexpr u64 size() const
{ {
return _size; return _size;
} }
/** /**
* @brief 获取当前容量 * @brief 获取当前容量
* @return 容量值。 *
* @return 容器当前分配的存储空间可容纳的元素数量
*/ */
[[nodiscard]] constexpr u64 capacity() const [[nodiscard]] constexpr u64 capacity() const
{ {
return _capacity; return _capacity;
} }
/**
* @brief 下标访问运算符(可修改)
*
* @param index 元素下标
* @return 元素的引用
*
* @pre index 必须小于当前元素数量
*/
[[nodiscard]] constexpr T& operator [](u64 index) [[nodiscard]] constexpr T& operator [](u64 index)
{ {
assert(_data && index < _size); assert(_data && index < _size);
return _data[index]; return _data[index];
} }
/**
* @brief 下标访问运算符(只读)
*
* @param index 元素下标
* @return 元素的常量引用
*
* @pre index 必须小于当前元素数量
*/
[[nodiscard]] constexpr const T& operator [](u64 index) const [[nodiscard]] constexpr const T& operator [](u64 index) const
{ {
assert(_data && index < _size); assert(_data && index < _size);
return _data[index]; return _data[index];
} }
/**
* @brief 访问第一个元素(可修改)
*
* @return 第一个元素的引用
*
* @pre 容器必须非空
*/
[[nodiscard]] constexpr T& front() [[nodiscard]] constexpr T& front()
{ {
assert(_data && _size); assert(_data && _size);
return _data[0]; return _data[0];
} }
/**
* @brief 访问第一个元素(只读)
*
* @return 第一个元素的常量引用
*
* @pre 容器必须非空
*/
[[nodiscard]] constexpr const T& front() const [[nodiscard]] constexpr const T& front() const
{ {
assert(_data && _size); assert(_data && _size);
return _data[0]; return _data[0];
} }
/**
* @brief 访问最后一个元素(可修改)
*
* @return 最后一个元素的引用
*
* @pre 容器必须非空
*/
[[nodiscard]] constexpr T& back() [[nodiscard]] constexpr T& back()
{ {
assert(_data && _size); assert(_data && _size);
return _data[_size -1]; return _data[_size -1];
} }
/**
* @brief 访问最后一个元素(只读)
*
* @return 最后一个元素的常量引用
*
* @pre 容器必须非空
*/
[[nodiscard]] constexpr const T& back() const [[nodiscard]] constexpr const T& back() const
{ {
assert(_data && _size); assert(_data && _size);
return _data[_size - 1]; return _data[_size - 1];
} }
/**
* @brief 返回指向首元素的迭代器
*
* @return 指向第一个元素的指针
*
* @details 支持范围 for 循环
*/
[[nodiscard]] constexpr T* begin() [[nodiscard]] constexpr T* begin()
{ {
return std::addressof(_data[0]); return std::addressof(_data[0]);
} }
/**
* @brief 返回指向首元素的常量迭代器
*
* @return 指向第一个元素的常量指针
*/
[[nodiscard]] constexpr const T* begin() const [[nodiscard]] constexpr const T* begin() const
{ {
return std::addressof(_data[0]); return std::addressof(_data[0]);
} }
/**
* @brief 返回指向尾后位置的迭代器
*
* @return 指向最后一个元素之后位置的指针
*
* @details 支持范围 for 循环
*/
[[nodiscard]] constexpr T* end() [[nodiscard]] constexpr T* end()
{ {
assert(!(_data == nullptr && _size > 0)); assert(!(_data == nullptr && _size > 0));
return std::addressof(_data[_size]); return std::addressof(_data[_size]);
} }
/**
* @brief 返回指向尾后位置的常量迭代器
*
* @return 指向最后一个元素之后位置的常量指针
*/
[[nodiscard]] constexpr const T* end() const [[nodiscard]] constexpr const T* end() const
{ {
assert(!(_data == nullptr && _size > 0)); assert(!(_data == nullptr && _size > 0));
return std::addressof(_data[_size]); return std::addressof(_data[_size]);
} }
private:
private:
/**
* @brief 从另一个容器转移所有权
*
* @param o 源容器
*
* @details
* 复制源容器的成员变量,然后将源容器重置为空状态。
*/
constexpr void move(vector& o) constexpr void move(vector& o)
{ {
_capacity = o._capacity; _capacity = o._capacity;
@@ -424,6 +687,12 @@ private:
o.reset(); o.reset();
} }
/**
* @brief 重置容器为空状态
*
* @details
* 将所有成员变量设为初始值,不释放内存。
*/
constexpr void reset() constexpr void reset()
{ {
_capacity = 0; _capacity = 0;
@@ -431,6 +700,16 @@ private:
_data = nullptr; _data = nullptr;
} }
/**
* @brief 析构指定范围内的元素
*
* @param first 起始索引
* @param last 结束索引(不包含)
*
* @details
* 对 [first, last) 范围内的每个元素调用析构函数。
* 仅在 destruct = true 时有效。
*/
constexpr void destruct_range(u64 first, u64 last) constexpr void destruct_range(u64 first, u64 last)
{ {
assert(destruct); assert(destruct);
@@ -444,6 +723,12 @@ private:
} }
} }
/**
* @brief 销毁容器,释放所有资源
*
* @details
* 清空所有元素,释放底层内存,重置容量为 0。
*/
constexpr void destroy() constexpr void destroy()
{ {
assert([&] {return _capacity ? _data != nullptr : _data == nullptr; }()); assert([&] {return _capacity ? _data != nullptr : _data == nullptr; }());
@@ -458,6 +743,4 @@ private:
T* _data{ nullptr }; T* _data{ nullptr };
}; };
} }

View File

@@ -2,7 +2,7 @@
* @file TestRenderer.cpp * @file TestRenderer.cpp
* @brief 渲染功能综合测试实现。 * @brief 渲染功能综合测试实现。
*/ */
#include "Test.h"
#include "TestRenderer.h" #include "TestRenderer.h"
#include "Graphics/Renderer.h" #include "Graphics/Renderer.h"
#include "Platform/Platform.h" #include "Platform/Platform.h"
@@ -11,11 +11,11 @@
#include <fstream> #include <fstream>
#if TEST_RENDERER #ifdef TEST_RENDERER
using namespace XEngine; using namespace XEngine;
time_it timer{};
graphics::render_surface _surfaces[4]; graphics::render_surface _surfaces[4];
void destroy_render_surface(graphics::render_surface& surface);
LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{ {
bool toggle_fullscreen{ false }; bool toggle_fullscreen{ false };
@@ -26,11 +26,17 @@ LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
bool all_closed{ true }; bool all_closed{ true };
for (u32 i{ 0 }; i < _countof(_surfaces); ++i) for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
{ {
if(!_surfaces[i].window.is_closed()) if(_surfaces[i].window.is_valid()){
if(_surfaces[i].window.is_closed())
{
destroy_render_surface(_surfaces[i]);
}
else
{ {
all_closed = false; all_closed = false;
} }
} }
}
if (all_closed) if (all_closed)
{ {
PostQuitMessage(0); PostQuitMessage(0);
@@ -49,6 +55,12 @@ LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
} }
break; break;
} }
case WM_KEYDOWN:
if(wparam == VK_ESCAPE)
{
PostMessage(hwnd,WM_CLOSE,0,0);
return 0;
}
} }
@@ -59,12 +71,16 @@ void
create_render_surface(graphics::render_surface& surface, const platform::window_init_info info) create_render_surface(graphics::render_surface& surface, const platform::window_init_info info)
{ {
surface.window = platform::create_window(&info); surface.window = platform::create_window(&info);
surface.surface = graphics::create_surface(surface.window);
} }
void void
_destroy_render_surface(graphics::render_surface& surface) destroy_render_surface(graphics::render_surface& surface)
{ {
platform::remove_window(surface.window.get_id()); graphics::render_surface temp{surface};
surface = {};
if(temp.surface.is_valid())graphics::remove_surface(temp.surface.get_id());
if(temp.window.is_valid())platform::remove_window(temp.window.get_id());
} }
bool bool
@@ -93,7 +109,16 @@ engine_test::initialize()
void void
engine_test::run() engine_test::run()
{ {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); timer.begin();
//std::this_thread::sleep_for(std::chrono::milliseconds(10));
for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
{
if(_surfaces[i].surface.is_valid())
{
_surfaces[i].surface.render();
}
}
timer.end();
} }
bool bool
@@ -101,11 +126,11 @@ engine_test::shutdown()
{ {
for (u32 i{ 0 }; i < _countof(_surfaces); ++i) for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
{ {
_destroy_render_surface(_surfaces[i]); destroy_render_surface(_surfaces[i]);
} }
graphics::shutdown(); graphics::shutdown();
return true; return true;
} }
#endif #endif // TEST_RENDERER

View File

@@ -0,0 +1,135 @@
# 变更记录:命令队列与多帧缓冲
**提交日期**: 2026-03-26
**提交哈希**: `26e18bd`
**变更类型**: 功能实现
---
## 变更概述
本次提交实现了 D3D12 命令队列管理类 `d3d12_command`,支持多帧缓冲渲染架构,实现了 CPU-GPU 并行渲染的基础设施。
## 修改文件
### Engine/Graphics/Direct3D12/
| 文件 | 变更说明 |
|------|----------|
| `D3D12Core.cpp` | 添加 `d3d12_command` 类,实现命令队列和命令列表管理 |
| `D3D12Core.h` | 添加 `render()` 函数声明 |
| `D3D12CommonHeader.h` | 添加 `frame_buffer_count` 常量和 `NAME_D3D12_OBJECT_INDEXED` 宏 |
### Engine/Graphics/
| 文件 | 变更说明 |
|------|----------|
| `GraphicsPlatformInterface.h` | 添加 `render` 函数指针 |
| `Renderer.h` | 添加 `render()` 函数声明 |
| `Renderer.cpp` | 实现 `render()` 函数 |
---
## 技术要点
### 1. 多帧缓冲架构
```cpp
constexpr u32 frame_buffer_count{ 3 };
```
采用三重缓冲设计,允许 CPU 提前录制命令GPU 异步执行,最大化硬件利用率。
### 2. d3d12_command 类
```cpp
class d3d12_command
{
// 创建命令队列(支持 Direct/Compute/Copy 三种类型)
// 为每帧创建独立的命令分配器
// 创建命令列表
void begin_frame(); // 等待帧完成,重置分配器和命令列表
void end_frame(); // 关闭命令列表,提交执行
};
```
### 3. 帧索引轮转机制
```cpp
_frame_index = (_frame_index + 1) % frame_buffer_count;
```
环形缓冲区管理帧资源,确保 CPU 不会超前 GPU 超过 3 帧。
### 4. command_frame 结构
```cpp
struct command_frame
{
ID3D12CommandAllocator* cmd_allocator{ nullptr };
void wait(); // 等待 GPU 完成该帧
void release(); // 释放资源
};
```
每帧独立的命令分配器,避免 GPU 执行期间重置冲突。
### 5. 命名调试宏
```cpp
#define NAME_D3D12_OBJECT_INDEXED(obj, n, name) \
obj->SetName(full_name); ...
```
支持为多个同类对象设置带索引的调试名称。
---
## 渲染流程
```
render()
├─► begin_frame()
│ ├─► 等待当前帧 GPU 完成
│ ├─► 重置命令分配器
│ └─► 重置命令列表
└─► end_frame()
├─► 关闭命令列表
├─► 提交命令列表执行
└─► 递增帧索引
```
---
## 设计原理
### 为什么需要多帧缓冲?
1. **CPU-GPU 并行性**: 单缓冲模式下 CPU 必须等待 GPU 完成,多帧缓冲允许 CPU 提前录制 N 帧
2. **命令分配器冲突解决**: D3D12 中命令分配器在 GPU 执行期间不能被重置,每帧独立分配器解决此问题
3. **帧时序稳定性**: 缓冲 N 帧可平滑帧率波动
### 命令列表创建后立即关闭
```cpp
DXCall(_cmd_list->Close());
```
新创建的命令列表处于"录制打开"状态,立即关闭使其进入可提交状态,这是一种防御性编程。
---
## 后续工作
- [ ] 实现 Fence 同步机制
- [ ] 实现交换链
- [ ] 实现描述符堆
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -0,0 +1,154 @@
# 变更记录Fence 同步机制实现
**提交日期**: 2026-03-27
**提交哈希**: `b00a906`
**变更类型**: 功能实现
---
## 变更概述
本次提交实现了 D3D12 Fence围栏同步机制完成 CPU-GPU 帧同步,确保命令列表执行顺序正确,避免资源冲突。
## 修改文件
### Engine/Graphics/Direct3D12/
| 文件 | 变更说明 |
|------|----------|
| `D3D12Core.cpp` | 添加 Fence 同步机制,实现 `wait()``flush()` 方法 |
### EngineTest/
| 文件 | 变更说明 |
|------|----------|
| `TestRenderer.cpp` | 在 `run()` 中调用 `graphics::render()` |
---
## 技术要点
### 1. Fence 对象
```cpp
ID3D12Fence1* _fence{ nullptr };
u64 _fence_value{ 0 };
HANDLE _fence_event{ nullptr };
```
- **Fence**: GPU 可设置的计数器,用于同步
- **Fence Value**: 64位无符号整数每帧递增
- **Fence Event**: Windows 事件对象,用于 CPU 等待
### 2. command_frame 结构
```cpp
struct command_frame
{
ID3D12CommandAllocator* cmd_allocator{ nullptr };
u64 fence_value{ 0 }; // 该帧的围栏值
void wait(HANDLE fence_event, ID3D12Fence1* fence);
};
```
每帧记录其围栏值,用于判断 GPU 是否完成该帧。
### 3. 帧同步等待
```cpp
void wait(HANDLE fence_event, ID3D12Fence1* fence)
{
if(fence->GetCompletedValue() < fence_value)
{
fence->SetEventOnCompletion(fence_value, fence_event);
WaitForSingleObject(fence_event, INFINITE);
}
}
```
- 检查 GPU 是否完成到目标围栏值
- 未完成则设置事件并等待
### 4. 帧结束信号
```cpp
void end_frame()
{
// ... 提交命令列表 ...
++_fence_value;
_cmd_frames[_frame_index].fence_value = _fence_value;
_cmd_queue->Signal(_fence, _fence_value);
_frame_index = (_frame_index + 1) % frame_buffer_count;
}
```
- 递增围栏值
- 记录当前帧的围栏值
- 向 GPU 发送信号
### 5. flush 方法
```cpp
void flush()
{
for(u32 i{ 0 }; i < frame_buffer_count; ++i)
{
_cmd_frames[i].wait(_fence_event, _fence);
}
_frame_index = 0;
}
```
等待所有帧完成,用于资源释放前确保 GPU 完成。
---
## 同步流程
```
begin_frame()
├─► 检查当前帧的 fence_value
└─► 如果 GPU 未完成CPU 等待
└─► 重置分配器和命令列表
end_frame()
├─► 提交命令列表
├─► ++fence_value
├─► 记录当前帧的 fence_value
├─► Signal(fence, fence_value)
└─► 递增帧索引
```
---
## 围栏值溢出问题
```cpp
// 64位无符号整数即便每秒1000帧也需要5.8亿年才能回绕
u64 _fence_value{ 0 };
```
无需担心溢出问题。
---
## 后续工作
- [ ] 交换链实现
- [ ] 描述符堆
- [ ] 渲染目标视图
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -0,0 +1,250 @@
# 变更记录:延迟释放机制与描述符堆完善
**提交日期**: 2026-03-30
**提交哈希**: `6e0df60`
**变更类型**: 功能完善
---
## 变更概述
本次提交完善了描述符堆的延迟释放机制,实现了完整的 FreeList 栈式索引管理,并添加了详细的中文注释文档。
## 修改文件
### Engine/Graphics/Direct3D12/
| 文件 | 变更说明 |
|------|----------|
| `D3D12Resources.h` | 添加完整的中文 Doxygen 注释 |
| `D3D12Resource.cpp` | 实现 `process_deferred_release()` 延迟释放处理 |
| `D3D12Core.h` | 添加 `deferred_release` 模板函数和 `current_frame_index()` |
| `D3D12Core.cpp` | 实现延迟释放队列和帧索引管理 |
---
## 技术要点
### 1. FreeList 栈式索引管理
#### 数据结构
```cpp
// 空闲索引栈
std::unique_ptr<u32[]> _free_handles{};
// 栈顶指针(同时也是已分配数量)
u32 _size{0};
// 总容量
u32 _capacity{0};
```
#### 核心原理
`_free_handles` 是一个**预分配的数组**,同时充当**栈**的角色:
- `_size` 既是已分配数量,也是栈顶指针
- 分配时:从栈顶弹出索引
- 释放时:将索引压入栈顶
```
初始化状态capacity = 5
┌─────────────────────────────────────┐
│ _free_handles = [0, 1, 2, 3, 4] │
│ _size = 0 (栈顶指向位置 0) │
└─────────────────────────────────────┘
分配 2 个描述符后:
┌─────────────────────────────────────┐
│ _free_handles = [0, 1, 2, 3, 4] │
│ _size = 2 (栈顶指向位置 2) │
│ 已分配索引: 0, 1 │
└─────────────────────────────────────┘
释放索引 0 后(延迟处理完成):
┌─────────────────────────────────────┐
│ _free_handles = [0, 0, 2, 3, 4] │
│ _size = 1 (栈顶指向位置 1) │
│ 已分配索引: 1 │
│ 索引 0 已回收到空闲栈 │
└─────────────────────────────────────┘
```
#### 分配算法
```cpp
descriptor_handle allocate()
{
std::lock_guard lock(_mutex);
// 从栈顶取出索引
const u32 index = _free_handles[_size];
++_size; // 栈顶指针上移
// 计算句柄地址
const u32 offset = index * _descriptor_size;
handle.cpu.ptr = _cpu_start.ptr + offset;
return handle;
}
```
**时间复杂度**: O(1)
#### 释放算法
```cpp
void free(descriptor_handle handle)
{
// 不立即释放,放入延迟队列
const u32 frame_index = current_frame_index();
_deferred_free_indices[frame_index].push_back(handle.index);
}
void process_deferred_release(u32 frame_index)
{
for(auto index : _deferred_free_indices[frame_index])
{
--_size; // 栈顶指针下移
_free_handles[_size] = index; // 索引压入栈顶
}
}
```
**时间复杂度**: O(n)n 为该帧释放的描述符数量
---
### 2. 延迟释放机制
#### 为什么需要延迟释放?
```
问题场景:
帧 0: CPU 分配描述符索引 0 → 绑定纹理 A
帧 1: CPU 释放描述符索引 0 → 立即重用 → 绑定纹理 B
帧 0: GPU 还在执行,访问描述符索引 0 → 读到纹理 B 的数据!
GPU 危险!数据竞争!
```
#### 解决方案
```
帧 0: CPU 分配描述符索引 0 → 绑定纹理 A
帧 1: CPU 释放描述符索引 0 → 放入延迟队列(帧 1
帧 2: CPU 继续工作...
帧 0: GPU 完成帧 0 的执行
帧 3: CPU 处理帧 0 的延迟释放 → 索引 0 回到空闲池
安全GPU 已完成使用
```
#### 数据结构
```cpp
// 每帧一个延迟释放队列
utl::vector<u32> _deferred_free_indices[frame_buffer_count]{};
// 全局延迟释放资源队列
utl::vector<IUnknown*> deferred_releases[frame_buffer_count]{};
// 延迟释放标志
u32 deferred_release_flag[frame_buffer_count]{};
```
---
### 3. 完整流程图
```
┌─────────────────────────────────────────────────────────────────┐
│ 初始化 │
│ _free_handles = [0, 1, 2, 3, 4] │
│ _size = 0 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ allocate() │
│ index = _free_handles[_size] // 取索引 0 │
│ _size++ // _size = 1 │
│ 返回描述符句柄(索引 0
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ allocate() │
│ index = _free_handles[_size] // 取索引 1 │
│ _size++ // _size = 2 │
│ 返回描述符句柄(索引 1
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ free(索引 0) │
│ 放入延迟队列: _deferred_free_indices[当前帧].push_back(0) │
│ _size 不变_free_handles 不变 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ GPU 完成帧Fence 同步) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ process_deferred_release(frame_index) │
│ --_size // _size = 1 │
│ _free_handles[_size] = 0 // 索引 0 压入栈顶 │
│ _free_handles = [0, 0, 2, 3, 4] │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ allocate() │
│ index = _free_handles[_size] // 取索引 0重用
│ _size++ // _size = 2 │
│ 返回描述符句柄(索引 0
└─────────────────────────────────────────────────────────────────┘
```
---
### 4. 线程安全
```cpp
// 所有操作都使用互斥锁保护
std::mutex _mutex{};
void allocate() {
std::lock_guard lock(_mutex); // 自动加锁
// ... 操作
} // 自动解锁
```
---
## 设计优势
| 特性 | 说明 |
|------|------|
| **O(1) 分配** | 栈顶弹出,无需遍历查找 |
| **O(1) 释放** | 栈顶压入,无需查找位置 |
| **内存高效** | 预分配数组,无动态分配开销 |
| **线程安全** | 互斥锁保护所有操作 |
| **GPU 安全** | 延迟释放防止数据竞争 |
---
## 后续工作
- [ ] 实现交换链
- [ ] 实现渲染目标视图
- [ ] 渲染第一个三角形
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -0,0 +1,212 @@
# 变更记录:描述符堆实现
**提交日期**: 2026-03-30
**提交哈希**: `a03c544`
**变更类型**: 功能实现
---
## 变更概述
本次提交实现了 D3D12 描述符堆Descriptor Heap管理类提供描述符的分配和释放功能支持多线程安全访问。
## 新增文件
### Engine/Graphics/Direct3D12/
| 文件 | 说明 |
|------|------|
| `D3D12Resources.h` | 描述符句柄和描述符堆类定义 |
| `D3D12Resource.cpp` | 描述符堆实现 |
## 修改文件
| 文件 | 变更说明 |
|------|----------|
| `D3D12Core.h` | 添加 `device()` 函数声明和 `release` 模板函数 |
| `D3D12Core.cpp` | 添加 `device()` 函数实现 |
| `D3D12CommonHeader.h` | 添加 `<mutex>` 头文件 |
---
## 技术要点
### 1. descriptor_handle 结构
```cpp
struct descriptor_handle
{
D3D12_CPU_DESCRIPTOR_HANDLE cpu{}; // CPU 句柄
D3D12_GPU_DESCRIPTOR_HANDLE gpu{}; // GPU 句柄(着色器可见时有效)
constexpr bool is_valid() const { return cpu.ptr != 0; }
constexpr bool is_shader_visible() const { return gpu.ptr != 0; }
};
```
**设计要点**
- 封装 CPU 和 GPU 双句柄
- 提供有效性检查方法
- DEBUG 模式下记录容器和索引用于调试
### 2. descriptor_heap 类
```cpp
class descriptor_heap
{
public:
explicit descriptor_heap(const D3D12_DESCRIPTOR_HEAP_TYPE type);
bool initialize(u32 capacity, bool is_shader_visible);
void release();
descriptor_handle allocate(); // 分配描述符
void free(descriptor_handle handle); // 释放描述符
// 访问器
constexpr ID3D12DescriptorHeap* heap() const;
constexpr D3D12_CPU_DESCRIPTOR_HANDLE cpu_start() const;
constexpr D3D12_GPU_DESCRIPTOR_HANDLE gpu_start() const;
constexpr bool is_shader_visible() const;
private:
ID3D12DescriptorHeap* _heap;
D3D12_CPU_DESCRIPTOR_HANDLE _cpu_start{};
D3D12_GPU_DESCRIPTOR_HANDLE _gpu_start{};
std::unique_ptr<u32[]> _free_handles{}; // 空闲句柄池
std::mutex _mutex; // 线程安全
u32 _capacity{0};
u32 _size{0};
u32 _descriptor_size{0};
const D3D12_DESCRIPTOR_HEAP_TYPE _type;
};
```
### 3. 描述符堆类型
| 类型 | 用途 | 着色器可见 |
|------|------|------------|
| `D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV` | 常量缓冲区、着色器资源、无序访问 | 可选 |
| `D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER` | 采样器 | 可选 |
| `D3D12_DESCRIPTOR_HEAP_TYPE_RTV` | 渲染目标视图 | 否 |
| `D3D12_DESCRIPTOR_HEAP_TYPE_DSV` | 深度模板视图 | 否 |
### 4. 内存模型
```
┌─────────────────────────────────────────────────────┐
│ Shader-Visible 描述符堆 (GPU 显存) │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │...│ │ │ │ │
│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │
│ ↑ │
│ _cpu_start / _gpu_start │
└─────────────────────────────────────────────────────┘
_free_handles[] = [0, 1, 2, 3, 4, 5, ...] // 空闲索引池
_size = 0 // 已分配数量
```
### 5. 分配算法
```cpp
descriptor_handle allocate()
{
std::lock_guard lock(_mutex); // 线程安全
const u32 index = _free_handles[_size]; // 取空闲索引
const u32 offset = index * _descriptor_size; // 计算偏移
++_size; // 递增已分配数量
descriptor_handle handle{};
handle.cpu.ptr = _cpu_start.ptr + offset; // CPU 句柄
if(is_shader_visible())
handle.gpu.ptr = _gpu_start.ptr + offset; // GPU 句柄
return handle;
}
```
### 6. 线程安全
```cpp
std::mutex _mutex; // 保护并发访问
// 使用 lock_guard 保护
std::lock_guard lock(_mutex);
```
描述符堆可能被多个线程并发访问(资源创建/销毁),需要互斥锁保护。
---
## D3D12Core 扩展
### 新增 device() 函数
```cpp
// D3D12Core.h
ID3D12Device *const device();
// D3D12Core.cpp
ID3D12Device *const device()
{
return main_device;
}
```
提供对 D3D12 设备的访问,用于创建描述符堆等资源。
### 新增 release 模板函数
```cpp
template<typename T>
constexpr void release(T*& resource)
{
if(resource)
{
resource->Release();
resource = nullptr;
}
}
```
通用的 COM 对象释放模板,避免代码重复。
---
## 使用示例
```cpp
// 创建描述符堆
descriptor_heap cbv_srv_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
cbv_srv_heap.initialize(100, true); // 100个描述符着色器可见
// 分配描述符
descriptor_handle handle = cbv_srv_heap.allocate();
// 创建视图
device->CreateShaderResourceView(texture, &srvDesc, handle.cpu);
// 绑定到管线
ID3D12DescriptorHeap* heaps[] = { cbv_srv_heap.heap() };
cmdList->SetDescriptorHeaps(1, heaps);
cmdList->SetGraphicsRootDescriptorTable(0, handle.gpu);
// 释放描述符
cbv_srv_heap.free(handle);
```
---
## 后续工作
- [ ] 实现资源类Texture, Buffer
- [ ] 实现交换链
- [ ] 实现渲染目标视图
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -0,0 +1,231 @@
# 变更记录:交换链与渲染表面实现
**提交日期**: 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)

View File

@@ -0,0 +1,404 @@
# 变更记录Surface 管理重构、纹理资源与文档完善
**提交日期**: 2026-04-01
**提交哈希**: `b72fcf4`
**变更类型**: Bug修复 + 功能新增 + 文档完善
---
## 变更概述
本次提交修复了 surface 管理中的资源重复释放问题,新增纹理资源类,并为容器和 D3D12 资源添加了完整的文档。
## 修改文件
### 核心修复
| 文件 | 变更说明 |
|------|----------|
| `D3D12Core.cpp` | 改用 `utl::free_list<d3d12_surface>` 管理 surface |
| `D3D12Surface.h` | 添加移动语义,禁用拷贝,支持撕裂检测 |
| `D3D12Surface.cpp` | 完善交换链创建和资源释放逻辑 |
### 新增功能
| 文件 | 变更说明 |
|------|----------|
| `D3D12Helpers.h` | 新增堆属性辅助结构 `d3dx::heap_properties` |
| `D3D12Resources.h` | 新增 `d3d12_texture_init_info``d3d12_texture``d3d12_render_texture` 类 |
| `D3D12Resource.cpp` | 实现纹理资源创建和 SRV 绑定 |
### 文档完善
| 文件 | 变更说明 |
|------|----------|
| `FreeList.h` | 添加完整 Doxygen 中文注释 |
| `Vector.h` | 添加完整 Doxygen 中文注释 |
---
## Bug 修复详情
### 问题Surface 重复释放
**现象**:关闭多个窗口时,`release()` 被调用超过预期次数,导致空指针崩溃。
**根本原因**
1. **手动调用析构函数**
```cpp
// 错误做法
surfaces[id].~d3d12_surface(); // 对象仍在 vector 中
// vector 析构时会再次调用析构函数
```
2. **Vector 扩容浅拷贝**
```
vector 扩容时:
1. 分配新内存
2. 移动元素(默认移动是浅拷贝)
3. 析构旧元素 → 释放资源
4. 新元素持有悬空指针 → 崩溃
```
3. **描述符句柄未重置**
```cpp
core::rtv_heap().free(data.rtv);
// free() 只修改局部变量data.rtv 仍指向已释放的描述符
```
### 解决方案
#### 1. 使用 `utl::free_list` 替代 `utl::vector`
```cpp
// D3D12Core.cpp
using surface_collection = utl::free_list<d3d12_surface>;
surface_collection surfaces;
// 创建 surface
surface create_surface(platform::window window)
{
surfaces.emplace_back(window);
surface_id id{ (u32)surfaces.size() - 1 };
surfaces[id].create_swap_chain(...);
return surface{id};
}
// 删除 surface
void remove_surface(surface_id id)
{
gfx_command.flush();
surfaces.remove(id); // free_list 的 remove 正确处理
}
```
#### 2. 实现移动语义
```cpp
class d3d12_surface
{
public:
// 移动构造函数
d3d12_surface(d3d12_surface&& other) noexcept
: _swap_chain{other._swap_chain}
, _window{other._window}
// ... 转移所有资源
{
other._swap_chain = nullptr; // 源对象置空
}
// 禁用拷贝
d3d12_surface(const d3d12_surface&) = delete;
d3d12_surface& operator=(const d3d12_surface&) = delete;
};
```
#### 3. 重置描述符句柄
```cpp
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);
data.rtv = {}; // 重置句柄,防止重复释放
}
core::release(_swap_chain);
}
```
---
## FreeList 容器详解
### 数据结构原理
`utl::free_list` 使用空闲链表管理已删除的槽位:
```
初始状态:
_array: [ 空 | 空 | 空 | 空 ]
_next_free_index = invalid_id
_size = 0
添加元素 A、B、C 后:
_array: [ A | B | C | 空 ]
_next_free_index = invalid_id
_size = 3
删除元素 B 后(槽位 1 被标记为空闲):
_array: [ A | ->2 | C | 空 ] // 槽位 1 存储下一个空闲索引
_next_free_index = 1
_size = 2
添加新元素 D 时(复用槽位 0:
_array: [ D | ->2 | C | 空 ]
_next_free_index = 1
_size = 2
```
### 核心优势
| 特性 | 说明 |
|------|------|
| **O(1) 增删** | 新增和删除操作近似常数时间 |
| **槽位复用** | 删除后的槽位被回收,避免频繁内存分配 |
| **稳定索引** | 使用 u32 索引作为外部句柄,与 ID 系统配合 |
| **内存紧凑** | 底层使用连续内存,缓存友好 |
### 与 Vector 的区别
| 特性 | free_list | vector |
|------|-----------|--------|
| 删除操作 | O(1),槽位复用 | O(n) 需要移动元素 |
| 索引稳定性 | 删除后索引可能被复用 | 删除后索引失效 |
| 内存碎片 | 无碎片,槽位复用 | 删除后内存不释放 |
| 适用场景 | 游戏对象、资源句柄 | 顺序容器、临时数据 |
---
## Vector 容器详解
### 析构策略
模板参数 `destruct` 控制元素析构行为:
```cpp
// 默认:删除时调用析构函数
utl::vector<std::string> strings; // destruct = true
// POD 类型优化:跳过析构
utl::vector<float, false> vertices; // destruct = false
```
### 扩容策略
```
扩容公式new_capacity = (old_capacity + 1) * 3 / 2
初始容量 0 → 1
容量 4 → 6
容量 10 → 15
```
### 无序删除优化
```cpp
// 普通删除O(n),保持顺序
vec.erase(index);
// 无序删除O(1),用末尾元素替换
vec.erase_unordered(index); // 适用于顺序不重要的场景
```
---
## 交换链详解
### 核心职责
交换链连接窗口与渲染管线,管理一组后台缓冲区,并通过 `Present()` 实现缓冲区翻转,将绘制内容显示到窗口。
**交换链只负责**
- 缓冲区的分配与翻转
**开发者需显式完成**
- 绑定渲染目标(`OMSetRenderTargets`
- GPU 同步Fence
- 状态转换(资源屏障)
### 标准使用流程
```
1. 获取当前后台缓冲区GetBuffer
2. 将其绑定为渲染目标OMSetRenderTargets
3. 执行绘制命令(写入该缓冲区)
4. 提交命令并同步Fence
5. 调用 Present翻转缓冲区显示图像
```
### 职责边界
| 操作 | 负责方 | 说明 |
|------|--------|------|
| 缓冲区分配 | 交换链 | 创建指定数量的后台缓冲区 |
| 缓冲区翻转 | 交换链 | `Present()` 切换前后缓冲区 |
| 渲染目标绑定 | 开发者 | `OMSetRenderTargets()` 绑定 RTV |
| GPU 同步 | 开发者 | 使用 Fence 确保 GPU 完成渲染 |
| 状态转换 | 开发者 | 资源屏障管理缓冲区状态 |
| 窗口大小调整 | 开发者 | 调用 `ResizeBuffers()` 重新分配 |
---
## SRV 创建与资源创建方式
### CreateShaderResourceView 参数说明
```cpp
void CreateShaderResourceView(
ID3D12Resource *pResource,
const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc,
D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor
);
```
| 参数 | 说明 |
|------|------|
| `pResource` | 要创建 SRV 的 GPU 资源指针(必须是有效资源,不能为 nullptr |
| `pDesc` | 视图描述符指定格式、维度、Mip 级别范围等。为 `nullptr` 时使用资源默认属性 |
| `DestDescriptor` | SRV 描述符堆中 CPU 描述符句柄的位置 |
**关键点**
- `pDesc` 为 `nullptr` 时,视图描述符默认使用资源本身的格式和全部子资源(空描述符初始化)
- 同一个资源可以创建多个不同的 SRV不同格式、不同 Mip 切片)
### D3D12 资源创建函数对比
| 函数 | 堆类型 | 说明 |
|------|--------|------|
| `CreateCommittedResource` | 隐式堆 | D3D12 自动分配堆,资源直接映射。适用于大多数常规资源 |
| `CreatePlacedResource` | 显式堆 | 资源放置在用户创建的堆的特定偏移位置。用于精确控制内存布局 |
| `CreateReservedResource` | 预留资源 | 仅预留虚拟地址,不提交物理内存。用于稀疏资源,支持流式加载 |
**选择建议**
- **Committed**:最常用,堆由系统隐式管理
- **Placed**:需要显式堆,资源放置于堆的指定偏移
- **Reserved**:仅预留虚拟地址,用于稀疏资源,实现内存的按需提交
---
## 纹理资源类
### d3d12_texture_init_info 结构
纹理初始化信息结构,支持三种资源创建方式:
```cpp
struct d3d12_texture_init_info
{
ID3D12Heap1* heap{nullptr}; // 显式堆Placed Resource
ID3D12Resource* resource{nullptr}; // 已有资源(直接使用)
D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr}; // SRV 描述nullptr 使用默认)
D3D12_RESOURCE_DESC* desc{nullptr}; // 资源描述
D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; // 分配信息(偏移量)
D3D12_RESOURCE_STATES initial_state{}; // 初始状态
D3D12_CLEAR_VALUE clear_value{}; // 清除值RTV/DSV
};
```
### d3d12_texture 类
基础纹理类,封装资源创建和 SRV 绑定:
```cpp
class d3d12_texture
{
public:
constexpr static u32 max_mips{ 14 };
explicit d3d12_texture(d3d12_texture_init_info info);
// 移动语义
d3d12_texture(d3d12_texture&& o);
d3d12_texture& operator=(d3d12_texture&& o);
// 禁用拷贝
DISABLE_COPY(d3d12_texture);
void release();
ID3D12Resource* resource() const;
descriptor_handle srv() const;
private:
ID3D12Resource* _resource{nullptr};
descriptor_handle _srv;
};
```
### 资源创建逻辑
```cpp
d3d12_texture::d3d12_texture(d3d12_texture_init_info info)
{
// 优先级 1使用已有资源
if (info.resource) {
_resource = info.resource;
}
// 优先级 2Placed Resource显式堆
else if (info.heap && info.desc) {
device->CreatePlacedResource(
info.heap,
info.allocation_info.Offset,
info.desc, ...);
}
// 优先级 3Committed Resource隐式堆
else if (info.desc) {
device->CreateCommittedResource(
&d3dx::heap_properties.default_heap,
D3D12_HEAP_FLAG_NONE,
info.desc, ...);
}
// 创建 SRV
_srv = core::srv_heap().allocate();
device->CreateShaderResourceView(_resource, info.srv_desc, _srv.cpu);
}
```
### d3dx::heap_properties 辅助结构
```cpp
namespace XEngine::graphics::d3d12::d3dx
{
constexpr struct {
D3D12_HEAP_PROPERTIES default_heap{
D3D12_HEAP_TYPE_DEFAULT, // GPU 可读写CPU 不可访问
D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
D3D12_MEMORY_POOL_UNKNOWN,
0, // 单 GPU 系统
0
};
} heap_properties;
}
```
---
## 后续工作
- [ ] 实现深度模板视图
- [ ] 渲染第一个三角形
- [ ] 实现根签名和管线状态对象
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -0,0 +1,212 @@
# 变更记录:渲染目标纹理与深度缓冲区实现
**提交日期**: 2026-04-01
**提交哈希**: `57afd12`
**变更类型**: 功能新增
---
## 变更概述
本次提交实现了 `d3d12_render_texture`(渲染目标纹理)和 `d3d12_depth_buffer`(深度缓冲区)类,为纹理资源类添加了析构函数。
## 修改文件
| 文件 | 变更说明 |
|------|----------|
| `D3D12Resources.h` | 为纹理类添加析构函数,完善 `d3d12_render_texture``d3d12_depth_buffer` 类定义 |
| `D3D12Resource.cpp` | 实现 `d3d12_render_texture``d3d12_depth_buffer` 构造与释放逻辑 |
---
## d3d12_render_texture 类
### 功能说明
渲染目标纹理类,支持多 Mip 级别的渲染目标视图RTV
```cpp
class d3d12_render_texture
{
public:
d3d12_render_texture() = default;
explicit d3d12_render_texture(d3d12_texture_init_info info);
~d3d12_render_texture() { release(); }
// 移动语义
d3d12_render_texture(d3d12_render_texture&& o);
d3d12_render_texture& operator=(d3d12_render_texture&& o);
// 禁用拷贝
DISABLE_COPY(d3d12_render_texture);
void release();
u32 mip_count() const;
D3D12_CPU_DESCRIPTOR_HANDLE rtv(u32 mip) const;
descriptor_handle srv() const;
ID3D12Resource* resource() const;
private:
d3d12_texture _texture{};
descriptor_handle _rtv[d3d12_texture::max_mips]{}; // 每个 Mip 一个 RTV
u32 _mip_count{0};
};
```
### 构造流程
```cpp
d3d12_render_texture::d3d12_render_texture(d3d12_texture_init_info info)
: _texture(info) // 先创建基础纹理
{
// 获取 Mip 级别数
_mip_count = resource()->GetDesc().MipLevels;
// 为每个 Mip 级别创建 RTV
D3D12_RENDER_TARGET_VIEW_DESC desc{};
desc.Format = info.desc->Format;
desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
for(u32 i = 0; i < _mip_count; ++i)
{
_rtv[i] = rtv_heap.allocate();
device->CreateRenderTargetView(resource(), &desc, _rtv[i].cpu);
++desc.Texture2D.MipSlice; // 下一个 Mip 切片
}
}
```
### 使用场景
- 离屏渲染Render-to-Texture
- 多级渐远纹理生成
- 后处理效果
---
## d3d12_depth_buffer 类
### 功能说明
深度缓冲区类封装深度模板视图DSV和着色器资源视图SRV
```cpp
class d3d12_depth_buffer
{
public:
d3d12_depth_buffer() = default;
explicit d3d12_depth_buffer(d3d12_texture_init_info info);
~d3d12_depth_buffer() { release(); }
// 移动语义
d3d12_depth_buffer(d3d12_depth_buffer&& o);
d3d12_depth_buffer& operator=(d3d12_depth_buffer&& o);
// 禁用拷贝
DISABLE_COPY(d3d12_depth_buffer);
void release();
D3D12_CPU_DESCRIPTOR_HANDLE dsv() const;
descriptor_handle srv() const;
ID3D12Resource* resource() const;
private:
d3d12_texture _texture{};
descriptor_handle _dsv{};
};
```
### 构造流程
```cpp
d3d12_depth_buffer::d3d12_depth_buffer(d3d12_texture_init_info info)
{
// 深度缓冲区需要特殊处理格式
// DSV 使用 D32_FLOATSRV 使用 R32_FLOAT
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc{};
if(info.desc->Format == DXGI_FORMAT_D32_FLOAT)
{
info.desc->Format = DXGI_FORMAT_R32_TYPELESS; // 资源使用无类型格式
srv_desc.Format = DXGI_FORMAT_R32_FLOAT; // SRV 使用浮点格式
}
// 创建纹理和 SRV
info.srv_desc = &srv_desc;
_texture = d3d12_texture(info);
// 创建 DSV
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc{};
dsv_desc.Format = DXGI_FORMAT_D32_FLOAT; // DSV 使用深度格式
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
_dsv = dsv_heap.allocate();
device->CreateDepthStencilView(resource(), &dsv_desc, _dsv.cpu);
}
```
### 格式转换说明
```
┌─────────────────────────────────────────────────────────────┐
│ 深度缓冲区格式处理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户指定格式DXGI_FORMAT_D32_FLOAT │
│ │ │
│ ▼ │
│ 资源格式DXGI_FORMAT_R32_TYPELESS无类型
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ DSV 格式D32_FLOAT SRV 格式R32_FLOAT │
│ (深度测试用) (着色器采样用) │
│ │
└─────────────────────────────────────────────────────────────┘
```
**为什么要这样处理?**
- 深度缓冲区需要作为着色器资源被采样如阴影映射、SSAO
- DSV 格式D32_FLOAT不能直接用于 SRV
- 使用 TYPELESS 格式允许同一资源创建不同格式的视图
---
## 析构函数添加
为所有纹理类添加了析构函数,确保资源自动释放:
```cpp
class d3d12_texture {
public:
~d3d12_texture() { release(); }
// ...
};
class d3d12_render_texture {
public:
~d3d12_render_texture() { release(); }
// ...
};
class d3d12_depth_buffer {
public:
~d3d12_depth_buffer() { release(); }
// ...
};
```
---
## 后续工作
- [ ] 实现根签名和管线状态对象
- [ ] 渲染第一个三角形
- [ ] 实现常量缓冲区
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -16,6 +16,9 @@ changelogs/
| 日期 | 提交 | 变更内容 | | 日期 | 提交 | 变更内容 |
|------|------|----------| |------|------|----------|
| 2026-03-30 | [描述符堆实现](./2026-03/20260330-d3d12-descriptor-heap.md) | D3D12 描述符堆管理和线程安全分配 |
| 2026-03-27 | [Fence同步机制](./2026-03/20260327-d3d12-fence-sync.md) | D3D12 Fence CPU-GPU 帧同步实现 |
| 2026-03-26 | [命令队列与多帧缓冲](./2026-03/20260326-d3d12-command-queue.md) | D3D12 命令队列和多帧渲染架构 |
| 2026-03-26 | [D3D12设备初始化](./2026-03/20260326-d3d12-device-init.md) | D3D12 设备创建与调试层实现 | | 2026-03-26 | [D3D12设备初始化](./2026-03/20260326-d3d12-device-init.md) | D3D12 设备创建与调试层实现 |
| 2026-03-26 | [Graphics模块](./2026-03/20260326-d3d12-foundation.md) | Graphics 模块与 D3D12 后端框架 | | 2026-03-26 | [Graphics模块](./2026-03/20260326-d3d12-foundation.md) | Graphics 模块与 D3D12 后端框架 |
| 2026-03-19 | [DX12初始框架](./2026-03/20260326-dx12-initial.md) | 初始 DX12 基础框架 | | 2026-03-19 | [DX12初始框架](./2026-03/20260326-dx12-initial.md) | 初始 DX12 基础框架 |

File diff suppressed because it is too large Load Diff