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
This commit is contained in:
@@ -20,6 +20,9 @@
|
||||
#pragma comment(lib, "dxgi.lib")
|
||||
#pragma comment(lib, "d3d12.lib")
|
||||
|
||||
namespace XEngine::graphics::d3d12::core {
|
||||
constexpr u32 frame_buffer_count{ 3 };
|
||||
}
|
||||
|
||||
// 定义 DirectX 调试宏 DXCall,用于在调试模式下检查 DirectX API 调用返回值
|
||||
// 如果调用失败(FAILED),则输出错误信息(文件名、行号、调用语句)并触发断点
|
||||
@@ -46,9 +49,24 @@ if(FAILED(x)){ \
|
||||
#endif // !DXCall
|
||||
#endif // _DEBUG
|
||||
|
||||
// 定义 DirectX 对象命名宏,用于在调试模式下为 Direct3D 12 对象设置名称
|
||||
// 定义 DirectX 对象命名宏,用于在调试模式下为 Direct3D 12 对象设置调试名称
|
||||
// 这些宏仅在 _DEBUG 模式下生效,可帮助开发者在 PIX、RenderDoc 等图形调试工具中
|
||||
// 识别和追踪 D3D12 对象(如缓冲区、纹理、管线状态等)
|
||||
// NAME_D3D12_OBJECT: 为单个对象设置名称
|
||||
// NAME_D3D12_OBJECT_INDEXED: 为数组中的对象设置带索引的名称(如资源数组)
|
||||
#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
|
||||
#define NAME_D3D12_OBJECT(obj, name)
|
||||
#define NAME_D3D12_OBJECT(obj,name)
|
||||
#define NAME_D3D12_OBJECT_INDEXED(obj,n,name)
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,164 @@
|
||||
using namespace Microsoft::WRL;
|
||||
namespace XEngine::graphics::d3d12::core {
|
||||
namespace {
|
||||
/**
|
||||
* @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
|
||||
{
|
||||
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");
|
||||
|
||||
|
||||
_error:
|
||||
release();
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
}
|
||||
|
||||
void begin_frame()
|
||||
{
|
||||
command_frame& frame{_cmd_frames[_frame_index]};
|
||||
frame.wait();
|
||||
// 重置命令分配器将释放之前帧分配的命令内存,使其可重新用于录制新帧的命令
|
||||
// 重置命令列表将命令列表重置为可录制状态,准备录制录制命令
|
||||
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]);
|
||||
_frame_index = (_frame_index + 1) % frame_buffer_count;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
struct command_frame
|
||||
{
|
||||
ID3D12CommandAllocator* cmd_allocator{ nullptr };
|
||||
|
||||
void wait()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
core::release(cmd_allocator);
|
||||
}
|
||||
};
|
||||
|
||||
ID3D12CommandQueue* _cmd_queue{ nullptr };
|
||||
ID3D12GraphicsCommandList6* _cmd_list{ nullptr };
|
||||
command_frame _cmd_frames[frame_buffer_count]{};
|
||||
u32 _frame_index{ 0 };
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 主 Direct3D 12 设备指针
|
||||
@@ -137,15 +295,16 @@ initialize()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
shutdown()
|
||||
{
|
||||
release(dxgi_factory);
|
||||
|
||||
// 关闭调试层,确保最后只有一个活动的主设备
|
||||
#ifdef _DEBUG
|
||||
#ifdef _DEBUG
|
||||
{
|
||||
{
|
||||
{
|
||||
// 关闭调试层,确保最后只有一个活动的主设备
|
||||
ComPtr<ID3D12InfoQueue> info_queue;
|
||||
DXCall(main_device->QueryInterface(IID_PPV_ARGS(&info_queue)));
|
||||
|
||||
@@ -166,4 +325,12 @@ shutdown()
|
||||
|
||||
release(main_device);
|
||||
}
|
||||
|
||||
void
|
||||
render()
|
||||
{
|
||||
begin_frame();
|
||||
|
||||
end_frame();
|
||||
}
|
||||
}// namespace XEngine::graphics::d3d12::core
|
||||
|
||||
@@ -16,6 +16,11 @@ bool initialize();
|
||||
* @details 调用 Direct3D 12 设备的关闭函数,释放所有资源
|
||||
*/
|
||||
void shutdown();
|
||||
/**
|
||||
* @brief 渲染 Direct3D 12 核心功能
|
||||
* @details 调用 Direct3D 12 设备的渲染函数,渲染当前渲染表面
|
||||
*/
|
||||
void render();
|
||||
|
||||
template<typename T>
|
||||
constexpr void release(T*& resource)
|
||||
|
||||
@@ -8,6 +8,7 @@ void get_platform_interface(platform_interface& pi)
|
||||
{
|
||||
pi.initialize = core::initialize;
|
||||
pi.shutdown = core::shutdown;
|
||||
pi.render = core::render;
|
||||
}
|
||||
}// namespace XEngine::graphics::d3d12
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace XEngine::graphics{
|
||||
struct platform_interface{
|
||||
bool(*initialize)(void);
|
||||
void(*shutdown)(void);
|
||||
void(*render)(void);
|
||||
};
|
||||
}// namespace XEngine::graphics
|
||||
|
||||
|
||||
@@ -39,13 +39,23 @@ set_platform_interface(graphics_platform platform)
|
||||
* @param platform 图形渲染平台类型
|
||||
* @return true 如果初始化成功,否则返回 false
|
||||
*/
|
||||
bool initialize(graphics_platform platform)
|
||||
bool
|
||||
initialize(graphics_platform platform)
|
||||
{
|
||||
return set_platform_interface(platform) && gfx.initialize();
|
||||
}
|
||||
|
||||
void shutdown()
|
||||
void
|
||||
shutdown()
|
||||
{
|
||||
gfx.shutdown();
|
||||
}
|
||||
|
||||
void
|
||||
render()
|
||||
{
|
||||
gfx.render();
|
||||
}
|
||||
|
||||
|
||||
}// namespace XEngine::graphics
|
||||
|
||||
@@ -47,4 +47,10 @@ bool initialize(graphics_platform platform);
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 渲染调用接口
|
||||
* @details 调用渲染函数指针,渲染当前渲染表面
|
||||
*/
|
||||
void render();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user