#include "D3D12Core.h" #include "D3D12Resources.h" #include "D3D12Surface.h" using namespace Microsoft::WRL; namespace XEngine::graphics::d3d12::core { namespace { using suface_collection = utl::free_list; /** * @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 设备指针 * @details 指向 Direct3D 12 设备的智能指针,用于创建渲染管线、管理资源与 GPU 命令 */ ID3D12Device8* main_device{ nullptr }; /** * @brief DXGI 工厂指针 * @details 指向 DXGI 工厂的智能指针,用于创建 Direct3D 12 设备 */ 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 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 特本级别 constexpr D3D_FEATURE_LEVEL minumum_feature_level{ D3D_FEATURE_LEVEL_11_0 }; bool failed_init() { shutdown(); return false; } /** * @brief 确定要使用的 GPU * @details 枚举所有可用的 GPU,选择支持最小特征级别的 Direct3D 12 设备 * 注意:该功能可通过以下方式扩展:例如,检查是否有任何 * 输出设备(即显示器)已连接,枚举支持的分辨率,提供 * 一种机制供用户在多适配器环境中选择要使用的适配器等 * @return IDXGIAdapter4* 指向确定的 GPU 的智能指针 */ IDXGIAdapter4* determine_main_adapter() { IDXGIAdapter4* adapter; for (u32 i{ 0 }; dxgi_factory->EnumAdapterByGpuPreference(i, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)) != DXGI_ERROR_NOT_FOUND; ++i) { //获取支持最小特征级别的 Direct3D 12 设备 if (SUCCEEDED(D3D12CreateDevice(adapter, minumum_feature_level, __uuidof(ID3D12Device8), nullptr))) { return adapter; } release(adapter); } return nullptr; } /** * @brief 获取指定适配器支持的最高 Direct3D 特性级别 * @details 检查指定适配器是否支持 Direct3D 12 特性级别 * @param adapter 指向要检查的适配器的智能指针 * @return D3D_FEATURE_LEVEL 支持的最高 Direct3D 特性级别 */ D3D_FEATURE_LEVEL get_max_feature_level(IDXGIAdapter4* adapter) { constexpr D3D_FEATURE_LEVEL feature_levels[4]{ D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_12_1, }; D3D12_FEATURE_DATA_FEATURE_LEVELS feature_level_info{}; feature_level_info.NumFeatureLevels = (_countof(feature_levels)); feature_level_info.pFeatureLevelsRequested = feature_levels; ComPtr device; DXCall(D3D12CreateDevice(adapter, minumum_feature_level, IID_PPV_ARGS(&device))); DXCall(device->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &feature_level_info, sizeof(feature_level_info))); 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& resources{ deferred_releases[frame_index] }; if(!resources.empty()) { for(auto& resource : resources) release(resource); resources.clear(); } } }// 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 initialize() { if (main_device) shutdown(); // 在DEBUG模式下,捕获 DXGI 可能抛出的异常 u32 dxgi_factory_flag{ 0 }; // 开启调试层, 需要graphics tools支持 #ifdef _DEBUG { ComPtr debug_interface; if(SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_interface)))) { debug_interface->EnableDebugLayer(); } else { OutputDebugStringA("warning: d3d12 debug interface is not available\n"); } dxgi_factory_flag |= DXGI_CREATE_FACTORY_DEBUG; } #endif // 创建 DXGI 工厂实例,用于枚举显卡适配器和创建交换链等操作 HRESULT hr{ S_OK }; DXCall(hr = CreateDXGIFactory2(dxgi_factory_flag, IID_PPV_ARGS(&dxgi_factory))); if (FAILED(hr)) return failed_init(); // 确定要使用的 GPU ComPtr main_adapter; main_adapter.Attach(determine_main_adapter()); if (!main_adapter) return failed_init(); // 获取主适配器支持的最高 Direct3D 特性级别,并且不得低于最小要求 D3D_FEATURE_LEVEL max_feature_level{ get_max_feature_level(main_adapter.Get()) }; assert(max_feature_level >= minumum_feature_level); if (max_feature_level < minumum_feature_level) return failed_init(); // 使用最高适配特性级别创建Direct3D 12 设备 DXCall(hr = D3D12CreateDevice(main_adapter.Get(), max_feature_level, IID_PPV_ARGS(&main_device))); if (FAILED(hr)) return failed_init(); #ifdef _DEBUG { ComPtr info_queue; DXCall(main_device->QueryInterface(IID_PPV_ARGS(&info_queue))); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); } #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; } void shutdown() { gfx_command.release(); // 注意,我们需要在所有的依赖资源之前调用延迟释放函数. // 否则会导致依赖当基础前资源(贴图等)的资源(着色器等)不能被释放. for(u32 i{ 0 }; i < frame_buffer_count; ++i) { process_deferred_release(i); } 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 { { // 关闭调试层,确保最后只有一个活动的主设备 ComPtr info_queue; DXCall(main_device->QueryInterface(IID_PPV_ARGS(&info_queue))); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, false); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, false); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, false); } ComPtr debug_device; DXCall(main_device->QueryInterface(IID_PPV_ARGS(&debug_device))); release(main_device); DXCall(debug_device->ReportLiveDeviceObjects( D3D12_RLDO_SUMMARY | D3D12_RLDO_DETAIL | D3D12_RLDO_IGNORE_INTERNAL )); } #endif // _DEBUG 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