From 54916b0ac6249f497798ef968f24b23bb34aca40 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:03:16 +0800 Subject: [PATCH] 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 --- Engine/Engine.vcxproj | 5 + Engine/Engine.vcxproj.filters | 5 + .../Graphics/Direct3D12/D3D12CommonHeader.h | 3 + Engine/Graphics/Direct3D12/D3D12Core.cpp | 7 + Engine/Graphics/Direct3D12/D3D12Core.h | 14 ++ Engine/Graphics/Direct3D12/D3D12Resource.cpp | 101 +++++++++ Engine/Graphics/Direct3D12/D3D12Resources.h | 67 ++++++ .../2026-03/20260330-d3d12-descriptor-heap.md | 212 ++++++++++++++++++ docs/changelogs/README.md | 1 + docs/wiki/D3D12学习Wiki.md | 93 ++++++-- 10 files changed, 495 insertions(+), 13 deletions(-) create mode 100644 Engine/Graphics/Direct3D12/D3D12Resource.cpp create mode 100644 Engine/Graphics/Direct3D12/D3D12Resources.h create mode 100644 docs/changelogs/2026-03/20260330-d3d12-descriptor-heap.md diff --git a/Engine/Engine.vcxproj b/Engine/Engine.vcxproj index 51c5ef2..5853a51 100644 --- a/Engine/Engine.vcxproj +++ b/Engine/Engine.vcxproj @@ -36,6 +36,7 @@ + @@ -59,10 +60,14 @@ + + + + 17.0 Win32Proj diff --git a/Engine/Engine.vcxproj.filters b/Engine/Engine.vcxproj.filters index 0e34e77..fa395f0 100644 --- a/Engine/Engine.vcxproj.filters +++ b/Engine/Engine.vcxproj.filters @@ -29,6 +29,7 @@ + @@ -44,5 +45,9 @@ + + + + \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12CommonHeader.h b/Engine/Graphics/Direct3D12/D3D12CommonHeader.h index 2d65ace..ea98483 100644 --- a/Engine/Graphics/Direct3D12/D3D12CommonHeader.h +++ b/Engine/Graphics/Direct3D12/D3D12CommonHeader.h @@ -16,6 +16,9 @@ // 用于简化 COM 对象的生命周期管理 #include +// 引入互斥锁头文件,用于保护资源的互斥锁 +#include + #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d12.lib") diff --git a/Engine/Graphics/Direct3D12/D3D12Core.cpp b/Engine/Graphics/Direct3D12/D3D12Core.cpp index f74c5ae..745db36 100644 --- a/Engine/Graphics/Direct3D12/D3D12Core.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Core.cpp @@ -421,4 +421,11 @@ render() // 为下一帧标记并增加围栏值 gfx_command.end_frame(); } + +ID3D12Device *const +device() +{ + return main_device; +} + }// namespace XEngine::graphics::d3d12::core diff --git a/Engine/Graphics/Direct3D12/D3D12Core.h b/Engine/Graphics/Direct3D12/D3D12Core.h index 85cd3f7..c7f5762 100644 --- a/Engine/Graphics/Direct3D12/D3D12Core.h +++ b/Engine/Graphics/Direct3D12/D3D12Core.h @@ -1,4 +1,5 @@ #pragma once +#include "D3D12CommonHeader.h" /** * @brief Direct3D 12 核心类 @@ -22,6 +23,12 @@ void shutdown(); */ void render(); + +/** + * @brief 通用资源释放模板函数 + * @details 用于安全释放 DirectX COM 对象,检查空指针后调用 Release 并置空 + * @tparam T COM 接口类型 + */ template constexpr void release(T*& resource) { @@ -32,4 +39,11 @@ constexpr void release(T*& resource) } } +/** + * @brief 获取 Direct3D 12 设备 + * @details 返回 Direct3D 12 设备的智能指针 + * @return ID3D12Device* Direct3D 12 设备的智能指针 + */ +ID3D12Device *const device(); + }// namespace XEngine::graphics::d3d12 \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Resource.cpp b/Engine/Graphics/Direct3D12/D3D12Resource.cpp new file mode 100644 index 0000000..2159f65 --- /dev/null +++ b/Engine/Graphics/Direct3D12/D3D12Resource.cpp @@ -0,0 +1,101 @@ +#include "D3D12Resources.h" +#include "D3D12Core.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(capacity)); + _capacity = capacity; + _size = 0; + + for(u32 i = 0; i < capacity; ++i) _free_handles[i] = i; + + _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() +{ +} +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); + + handle = {}; +} + +} //XEngine::graphics::d3d12 \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Resources.h b/Engine/Graphics/Direct3D12/D3D12Resources.h new file mode 100644 index 0000000..0441829 --- /dev/null +++ b/Engine/Graphics/Direct3D12/D3D12Resources.h @@ -0,0 +1,67 @@ +#pragma once +#include "D3D12CommonHeaders.h" + +namespace XEngine::graphics::d3d12{ +class descriptor_heap; +struct descriptor_handle +{ + D3D12_CPU_DESCRIPTOR_HANDLE cpu{}; + D3D12_GPU_DESCRIPTOR_HANDLE gpu{}; + + constexpr bool is_valid() const { return cpu.ptr != 0; } + constexpr bool is_shader_visible() const { return gpu.ptr != 0; } +#ifdef _DEBUG +private: + friend class descriptor_heap; + descriptor_heap* container{ nullptr }; + u32 index{ u32_invalid_id }; +#endif +}; // descriptor_handle +class descriptor_heap +{ +public: + explicit descriptor_heap(const D3D12_DESCRIPTOR_HEAP_TYPE type): _type(type){} + DISABLE_COPY_AND_MOVE(descriptor_heap); + ~descriptor_heap(){assert(!_heap);} + + bool initialize(u32 capacity, bool is_shader_visible); + void release(); + + [[nodiscard]] descriptor_handle allocate(); + void free(descriptor_handle handle); + + constexpr D3D12_DESCRIPTOR_HEAP_TYPE type() const { return _type; } + constexpr D3D12_CPU_DESCRIPTOR_HANDLE cpu_start() const { return _cpu_start; } + constexpr D3D12_GPU_DESCRIPTOR_HANDLE gpu_start() const { return _gpu_start; } + constexpr ID3D12DescriptorHeap *const heap() const { return _heap; } + constexpr u32 capacity() const { return _capacity; } + constexpr u32 size() const { return _size; } + constexpr u32 descriptor_size() const { return _descriptor_size; } + constexpr bool is_shader_visible() const { return _gpu_start.ptr != 0; } +private: + // 一个描述符堆是一个内存块,基于他是否着色器可见,分配在系统内存还是显存 + ID3D12DescriptorHeap* _heap; + + // CPU起始句柄(用于CPU端描述符操作) + D3D12_CPU_DESCRIPTOR_HANDLE _cpu_start{}; + // GPU起始句柄(仅当堆为着色器可见时有效) + D3D12_GPU_DESCRIPTOR_HANDLE _gpu_start{}; + + // 空闲的描述符句柄 + std::unique_ptr _free_handles{}; + + // 用于保护资源初始化的互斥锁 + std::mutex _mutex; + + // 描述符堆的容量 + u32 _capacity{0}; + // 已经分配的描述符数量 + u32 _size{0}; + // 不同的描述符堆类型在不同的硬件上有不同的大小,所以需要记录下描述符的大小 + u32 _descriptor_size{0}; + const D3D12_DESCRIPTOR_HEAP_TYPE _type; +}; +} + + + diff --git a/docs/changelogs/2026-03/20260330-d3d12-descriptor-heap.md b/docs/changelogs/2026-03/20260330-d3d12-descriptor-heap.md new file mode 100644 index 0000000..7e3f399 --- /dev/null +++ b/docs/changelogs/2026-03/20260330-d3d12-descriptor-heap.md @@ -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` | 添加 `` 头文件 | + +--- + +## 技术要点 + +### 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 _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 +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) diff --git a/docs/changelogs/README.md b/docs/changelogs/README.md index 0c7aafe..66ab063 100644 --- a/docs/changelogs/README.md +++ b/docs/changelogs/README.md @@ -16,6 +16,7 @@ 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 设备创建与调试层实现 | diff --git a/docs/wiki/D3D12学习Wiki.md b/docs/wiki/D3D12学习Wiki.md index 2834cd2..b44e620 100644 --- a/docs/wiki/D3D12学习Wiki.md +++ b/docs/wiki/D3D12学习Wiki.md @@ -238,9 +238,76 @@ _cmd_queue->Signal(_fence, _fence_value); **围栏值溢出**:64位无符号整数,每秒1000帧需要5.8亿年才回绕,无需担心。 -## 7. 渲染表面与窗口 +## 7. 描述符堆 -### 7.1 render_surface 结构 +### 7.1 什么是描述符堆? + +描述符堆是一块连续内存,用于存储描述符(Descriptor)。描述符是告诉 GPU 如何访问资源的数据结构。 + +### 7.2 描述符堆类型 + +| 类型 | 用途 | 着色器可见 | +|------|------|------------| +| `CBV_SRV_UAV` | 常量缓冲区、着色器资源、无序访问 | 可选 | +| `SAMPLER` | 采样器 | 可选 | +| `RTV` | 渲染目标视图 | 否 | +| `DSV` | 深度模板视图 | 否 | + +### 7.3 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; } +}; +``` + +### 7.4 descriptor_heap 类 + +```cpp +class descriptor_heap +{ + bool initialize(u32 capacity, bool is_shader_visible); + descriptor_handle allocate(); // 分配描述符 + void free(descriptor_handle); // 释放描述符 + +private: + ID3D12DescriptorHeap* _heap; + std::unique_ptr _free_handles{}; // 空闲索引池 + std::mutex _mutex; // 线程安全 +}; +``` + +### 7.5 内存模型 + +``` +描述符堆内存布局: +┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ +│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │...│ │ │ │ +└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ +↑ +_cpu_start / _gpu_start + +_free_handles[] = [0, 1, 2, 3, ...] // 空闲索引池 +``` + +### 7.6 线程安全 + +描述符堆可能被多线程并发访问,使用互斥锁保护: + +```cpp +std::lock_guard lock(_mutex); +``` + +## 8. 渲染表面与窗口 + +### 8.1 render_surface 结构 ```cpp struct render_surface { @@ -249,7 +316,7 @@ struct render_surface { }; ``` -### 7.2 多窗口支持 +### 8.2 多窗口支持 TestRenderer 测试展示了多窗口渲染: @@ -262,7 +329,7 @@ for (u32 i{0}; i < _countof(_surfaces); ++i) { } ``` -### 7.3 全屏切换 +### 8.3 全屏切换 通过 `WM_SYSCHAR` 消息处理 Alt+Enter: @@ -272,42 +339,42 @@ if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN)) { } ``` -## 8. 后续学习路径 +## 9. 后续学习路径 -### 8.1 基础阶段 +### 9.1 基础阶段 - [x] 完成设备创建和适配器枚举 - [x] 创建命令队列和命令列表 +- [x] 描述符堆管理 - [ ] 实现交换链和后台缓冲区 - [ ] 渲染第一个三角形 -### 8.2 进阶阶段 +### 9.2 进阶阶段 -- [ ] 描述符堆管理 - [ ] 根签名和管线状态对象 - [ ] 资源屏障和同步 - [ ] 常量缓冲区和着色器资源 -### 8.3 高级阶段 +### 9.3 高级阶段 - [ ] 多线程渲染 - [ ] 资源绑定策略 - [ ] 动态资源管理 - [ ] 性能优化 -## 9. 参考资源 +## 10. 参考资源 -### 9.1 官方文档 +### 10.1 官方文档 - [Microsoft D3D12 文档](https://docs.microsoft.com/en-us/windows/win32/direct3d12/direct3d-12-graphics) - [DXGI 文档](https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dx-graphics-dxgi) -### 9.2 推荐书籍 +### 10.2 推荐书籍 - 《Introduction to 3D Game Programming with DirectX 12》 - 《Real-Time 3D Rendering with DirectX and HLSL》 -### 9.3 项目相关文档 +### 10.3 项目相关文档 - [Graphics渲染架构分析](./Graphics渲染架构分析.md) - [项目约定规范](./项目约定规范.md)