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、资源创建方式、纹理资源类章节 - 新增变更记录文档
This commit is contained in:
@@ -1,11 +1,97 @@
|
||||
/**
|
||||
* @file FreeList.h
|
||||
* @brief 带复用槽位的稀疏对象容器模板。
|
||||
* @brief 带槽位复用机制的稀疏对象容器模板
|
||||
*
|
||||
* @details
|
||||
* free_list 维护“已使用槽位 + 空闲链”结构,支持:
|
||||
* - O(1) 近似开销的新增与删除;
|
||||
* - 删除后槽位复用,降低频繁分配释放成本;
|
||||
* - 以索引作为外部句柄,与 id 系统协同使用。
|
||||
* free_list 是一种特殊的容器,维护"已使用槽位 + 空闲链表"结构,
|
||||
* 特别适合需要频繁增删元素且使用索引作为外部句柄的场景。
|
||||
*
|
||||
* ## 核心特性
|
||||
*
|
||||
* - **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
|
||||
#include "CommonHeader.h"
|
||||
@@ -13,23 +99,42 @@
|
||||
namespace XEngine::utl {
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* @class free_list
|
||||
* @brief 带槽位复用机制的稀疏对象容器
|
||||
*
|
||||
* @tparam T 元素类型,必须满足 `sizeof(T) >= sizeof(u32)`
|
||||
*
|
||||
* @details
|
||||
* 该容器使用空闲链表管理已删除的槽位,实现 O(1) 的增删操作。
|
||||
* 特别适合需要稳定索引句柄的场景,如游戏对象管理、资源句柄等。
|
||||
*/
|
||||
template<typename T>
|
||||
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:
|
||||
/**
|
||||
* @brief 构造空容器。
|
||||
* @brief 默认构造函数,创建空容器
|
||||
*
|
||||
* @details
|
||||
* 创建一个空的 free_list,不预分配任何内存。
|
||||
* 首次添加元素时会触发内存分配。
|
||||
*/
|
||||
free_list() = default;
|
||||
|
||||
/**
|
||||
* @brief 预留底层存储容量。
|
||||
* @param count 预留元素数量。
|
||||
* @brief 预留容量的构造函数
|
||||
*
|
||||
* @param count 预留的元素槽位数量
|
||||
*
|
||||
* @details
|
||||
* 预分配指定数量的槽位,避免后续添加元素时的多次扩容。
|
||||
* 适用于已知大概元素数量的场景。
|
||||
*/
|
||||
explicit free_list(u32 count)
|
||||
{
|
||||
@@ -37,7 +142,11 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 销毁容器并校验无存活元素。
|
||||
* @brief 析构函数,销毁容器
|
||||
*
|
||||
* @details
|
||||
* 断言检查容器为空(_size == 0),确保所有元素已被正确移除。
|
||||
* 在 DEBUG 模式下使用 USE_STL_VECTOR 时,会清空内存以便检测悬空引用。
|
||||
*/
|
||||
~free_list()
|
||||
{
|
||||
@@ -49,10 +158,23 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 新增一个元素并返回槽位 ID。
|
||||
* @tparam params 构造参数类型。
|
||||
* @param p 构造参数。
|
||||
* @return 新元素 ID。
|
||||
* @brief 添加新元素并返回其槽位 ID
|
||||
*
|
||||
* @tparam params 构造参数类型包
|
||||
* @param p 传递给元素构造函数的参数
|
||||
* @return 新分配的槽位 ID(u32 索引)
|
||||
*
|
||||
* @details
|
||||
* 添加元素的策略:
|
||||
* 1. **有空闲槽位**:从空闲链表头部取出一个槽位复用
|
||||
* 2. **无空闲槽位**:在数组末尾追加新元素
|
||||
*
|
||||
* 复用槽位时:
|
||||
* - 从该槽位读取下一个空闲索引,更新 `_next_free_index`
|
||||
* - 使用 placement new 在该槽位构造新元素
|
||||
*
|
||||
* @note 返回的 ID 在元素被删除前保持有效
|
||||
* @note 删除后该 ID 可能被新元素复用
|
||||
*/
|
||||
template<class ... params>
|
||||
constexpr u32 add(params&&... p)
|
||||
@@ -76,8 +198,19 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 删除指定 ID 的元素并回收到空闲链。
|
||||
* @param id 元素 ID。
|
||||
* @brief 移除指定 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)
|
||||
{
|
||||
@@ -91,28 +224,35 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前有效元素数量。
|
||||
* @return 元素数量。
|
||||
* @brief 获取当前有效元素数量
|
||||
*
|
||||
* @return 容器中有效元素的个数
|
||||
*
|
||||
* @note 这与 capacity() 不同,size() 返回实际存储的元素数
|
||||
*/
|
||||
constexpr u32 size() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 获取当前已分配槽位总数。
|
||||
* @return 容量值。
|
||||
* @brief 获取已分配槽位总数
|
||||
*
|
||||
* @return 底层数组的大小(包括空闲槽位)
|
||||
*
|
||||
* @details
|
||||
* 返回值 >= size(),因为可能存在已删除但未释放的槽位。
|
||||
* 这些槽位会在后续添加元素时被复用。
|
||||
*/
|
||||
constexpr u32 capacity() const
|
||||
{
|
||||
return _array.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 判断容器是否为空。
|
||||
* @return 空返回 true。
|
||||
* @brief 检查容器是否为空
|
||||
*
|
||||
* @return 如果容器中没有有效元素则返回 true
|
||||
*/
|
||||
constexpr bool empty() const
|
||||
{
|
||||
@@ -120,9 +260,12 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 ID 访问元素。
|
||||
* @param id 元素 ID。
|
||||
* @return 元素引用。
|
||||
* @brief 通过 ID 访问元素(可修改)
|
||||
*
|
||||
* @param id 元素的槽位 ID
|
||||
* @return 元素的引用
|
||||
*
|
||||
* @pre id 必须有效且指向未删除的元素
|
||||
*/
|
||||
[[nodiscard]] constexpr T& operator[](u32 id)
|
||||
{
|
||||
@@ -131,9 +274,12 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 ID 访问常量元素。
|
||||
* @param id 元素 ID。
|
||||
* @return 常量元素引用。
|
||||
* @brief 通过 ID 访问元素(只读)
|
||||
*
|
||||
* @param id 元素的槽位 ID
|
||||
* @return 元素的常量引用
|
||||
*
|
||||
* @pre id 必须有效且指向未删除的元素
|
||||
*/
|
||||
[[nodiscard]] constexpr const T& operator[](u32 id) const
|
||||
{
|
||||
@@ -142,6 +288,18 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 检查指定槽位是否已被删除
|
||||
*
|
||||
* @param id 要检查的槽位 ID
|
||||
* @return 如果槽位已被删除则返回 true
|
||||
*
|
||||
* @details
|
||||
* 通过检查槽位内容是否被 0xcc 填充来判断是否已删除。
|
||||
* 仅在 sizeof(T) > sizeof(u32) 时有效,否则直接返回 true。
|
||||
*
|
||||
* @note 仅用于断言检查,确保不会访问已删除的元素
|
||||
*/
|
||||
constexpr bool already_removed(u32 id) const
|
||||
{
|
||||
if constexpr (sizeof(T) > sizeof(u32))
|
||||
|
||||
Reference in New Issue
Block a user