- 新增 d3d12_surface 类,管理交换链和渲染目标 - 实现三重缓冲后台缓冲区管理 - 添加视口和裁剪矩形配置 - 修复 GraphicsPlatformInterface.h 循环包含问题 - 添加完整的中文 Doxygen 注释 - 更新 D3D12 学习 Wiki,添加交换链章节
588 lines
20 KiB
C++
588 lines
20 KiB
C++
#include "D3D12Core.h"
|
||
#include "D3D12CommonHeader.h"
|
||
#include "D3D12Resources.h"
|
||
|
||
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
|
||
{
|
||
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;
|
||
|
||
|
||
|
||
/**
|
||
* @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 特本级别
|
||
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<ID3D12Device> 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<IUnknown*>& 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<ID3D12Debug3> 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<IDXGIAdapter4> 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<ID3D12InfoQueue> 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<ID3D12InfoQueue> 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<ID3D12DebugDevice2> 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);
|
||
}
|
||
|
||
void
|
||
render()
|
||
{
|
||
// 等待GPU完成命令列表,并重置命令分配器和命令列表
|
||
gfx_command.begin_frame();
|
||
ID3D12GraphicsCommandList6* cmd_list{ gfx_command.command_list() };
|
||
|
||
const u32 frame_index{ current_frame_index() };
|
||
if(deferred_release_flag[frame_index])
|
||
{
|
||
process_deferred_release(frame_index);
|
||
}
|
||
// 记录命令
|
||
//
|
||
// 完成命令记录,立即提交命令列表到命令队列执行
|
||
// 为下一帧标记并增加围栏值
|
||
gfx_command.end_frame();
|
||
}
|
||
|
||
ID3D12Device *const
|
||
device()
|
||
{
|
||
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;
|
||
}
|
||
|
||
|
||
}// namespace XEngine::graphics::d3d12::core
|