MiniEngine学习笔记 : DescriptorHeap
DescriptorHeap
- 前言
- 1.DescriptorAllocator
- 1.1 源码展示
- 1.2 源码分析
- 1.3 类使用分析
- 1.4 类改进
- 2.DescriptorAllocator
- 2.1 源码展示
- 2.2 源码分析
- 3.DescriptorAllocator
- 3.1 源码展示
- 3.2 源码分析
- 3.3 类使用分析
- 3.4 类改进
- 4.总结
前言
- 书接上回CommandListManager,本篇文章分析DescriptorHeap.h,其包含三个类DescriptorAllocator、DescriptorHandle、DescriptorHeap。
1.DescriptorAllocator
1.1 源码展示
类头文件如下:
#pragma once#include <mutex>
#include <vector>
#include <queue>
#include <string>// This is an unbounded resource descriptor allocator. It is intended to provide space for CPU-visible
// resource descriptors as resources are created. For those that need to be made shader-visible, they
// will need to be copied to a DescriptorHeap or a DynamicDescriptorHeap.
class DescriptorAllocator
{
public:DescriptorAllocator(D3D12_DESCRIPTOR_HEAP_TYPE Type) : m_Type(Type), m_CurrentHeap(nullptr), m_DescriptorSize(0){m_CurrentHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;}D3D12_CPU_DESCRIPTOR_HANDLE Allocate( uint32_t Count );static void DestroyAll(void);protected:static const uint32_t sm_NumDescriptorsPerHeap = 256;static std::mutex sm_AllocationMutex;static std::vector<Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>> sm_DescriptorHeapPool;static ID3D12DescriptorHeap* RequestNewHeap( D3D12_DESCRIPTOR_HEAP_TYPE Type );D3D12_DESCRIPTOR_HEAP_TYPE m_Type;ID3D12DescriptorHeap* m_CurrentHeap;D3D12_CPU_DESCRIPTOR_HANDLE m_CurrentHandle;uint32_t m_DescriptorSize;uint32_t m_RemainingFreeHandles;
};
类源文件如下:
#include "pch.h"
#include "DescriptorHeap.h"
#include "GraphicsCore.h"
#include "CommandListManager.h"using namespace Graphics;//
// DescriptorAllocator implementation
//
std::mutex DescriptorAllocator::sm_AllocationMutex;
std::vector<Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>> DescriptorAllocator::sm_DescriptorHeapPool;void DescriptorAllocator::DestroyAll(void)
{sm_DescriptorHeapPool.clear();
}ID3D12DescriptorHeap* DescriptorAllocator::RequestNewHeap(D3D12_DESCRIPTOR_HEAP_TYPE Type)
{std::lock_guard<std::mutex> LockGuard(sm_AllocationMutex);D3D12_DESCRIPTOR_HEAP_DESC Desc;Desc.Type = Type;Desc.NumDescriptors = sm_NumDescriptorsPerHeap;Desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;Desc.NodeMask = 1;Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> pHeap;ASSERT_SUCCEEDED(Graphics::g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap)));sm_DescriptorHeapPool.emplace_back(pHeap);return pHeap.Get();
}D3D12_CPU_DESCRIPTOR_HANDLE DescriptorAllocator::Allocate( uint32_t Count )
{if (m_CurrentHeap == nullptr || m_RemainingFreeHandles < Count){m_CurrentHeap = RequestNewHeap(m_Type);m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();m_RemainingFreeHandles = sm_NumDescriptorsPerHeap;if (m_DescriptorSize == 0)m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type);}D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle;m_CurrentHandle.ptr += Count * m_DescriptorSize;m_RemainingFreeHandles -= Count;return ret;
}
1.2 源码分析
类成员变量如下:
// 每个堆中包含的描述符大小,平衡内存性能和分配频率 (太大内存可能浪费,太小可能多次分配)
static const uint32_t sm_NumDescriptorsPerHeap = 256; // 访问该类静态变量的锁
static std::mutex sm_AllocationMutex;// 类静态变量-创建的所有描述符堆的数组
static std::vector<Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>> sm_DescriptorHeapPool;// 此描述符分配器的类型
D3D12_DESCRIPTOR_HEAP_TYPE m_Type;// 关联的描述符堆
ID3D12DescriptorHeap* m_CurrentHeap;// 关联的描述符堆中,第一个可用的描述符句柄
D3D12_CPU_DESCRIPTOR_HANDLE m_CurrentHandle;// 描述符类型的大小
uint32_t m_DescriptorSize;// 剩余的描述符句柄数量
uint32_t m_RemainingFreeHandles;
类方法如下:
// 构造函数,传入类型,初始化m_Type、m_CurrentHeap
DescriptorAllocator::DescriptorAllocator(D3D12_DESCRIPTOR_HEAP_TYPE Type): m_Type(Type), m_CurrentHeap(nullptr), m_DescriptorSize(0) {// 设置当前描述符的指针为无效CPU地址,此堆用来存储CPU描述符,// CPU和GPU描述符的无效值都为宏:D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN。m_CurrentHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;
}// 静态方法-销毁所有描述符堆 (非线程安全)
void DescriptorAllocator::DestroyAll(void)
{// 清空静态变量描述符数组 (ComPtr智能指针将保证对象正常析构)sm_DescriptorHeapPool.clear();
}// 静态方法-申请一个新描述符堆 (线程安全)
ID3D12DescriptorHeap* DescriptorAllocator::RequestNewHeap(D3D12_DESCRIPTOR_HEAP_TYPE Type)
{// 加锁std::lock_guard<std::mutex> LockGuard(sm_AllocationMutex);// 描述符堆描述D3D12_DESCRIPTOR_HEAP_DESC Desc;Desc.Type = Type; // 类型Desc.NumDescriptors = sm_NumDescriptorsPerHeap; // 堆内描述符数量Desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; // 仅CPU可见堆Desc.NodeMask = 1;// 创建堆并压入sm_DescriptorHeapPool,然后返回堆 (其生命周期使用DestroyAll管理)Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> pHeap;ASSERT_SUCCEEDED(Graphics::g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap)));sm_DescriptorHeapPool.emplace_back(pHeap);return pHeap.Get();
}// 非静态方法,分配Count个描述符
D3D12_CPU_DESCRIPTOR_HANDLE DescriptorAllocator::Allocate( uint32_t Count )
{// 初始化后m_CurrentHeap为nullptr,第一次必进入此判断if (m_CurrentHeap == nullptr || m_RemainingFreeHandles < Count){// 调用静态方法申请一个对象,然后保存到自身m_CurrentHeap = RequestNewHeap(m_Type);// 使用堆CPU描述符起点初始化当前描述符句柄m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();// 剩余可用描述符句柄数量等于堆大小m_RemainingFreeHandles = sm_NumDescriptorsPerHeap;// 若未初始化描述符大小,则通过设备查询// 此方法第一次调用和后续容量不够时,都会进入此分支,但仅第一次查询if (m_DescriptorSize == 0)m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type);}// 返回当前描述符句柄D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle;// 递增当前描述符句柄 += 分配描述符数 * 单描述符大小m_CurrentHandle.ptr += Count * m_DescriptorSize;// 减去分配掉的描述符数量m_RemainingFreeHandles -= Count;return ret;
}
1.3 类使用分析
- DescriptorAllocator是一个无界资源描述符分配器,它的目的是在创建资源时为cpu可见的资源描述符提供空间,对于那些需要被着色器可见的,它们需要被复制到DescriptorHeap或DynamicDescriptorHeap。
- 类运行机制:可以看到DescriptorAllocator类的RequestNewHeap方法是保护的,即外界无法使用,仅供此类对象自身使用。该类的使用方法是创建DescriptorAllocator类对象,构造时设置了描述符类型,然后使用Allocate(int)自由的分配CPU可见描述符,当堆内空间不够时,对象会自动调用RequestNewHeap关联到新的堆,达到无界描述符分配的效果。当需要销毁时,需要调用静态方法DestroyAll,统一销毁所有描述符堆。
- 类优点:
– 动态扩容: 采用“用尽即申请”的策略,当当前堆空间不足时,会自动调用RequestNewHeap申请一个新的固定大小(256个描述符)的堆,实现了无界分配。
– 高效分配:在同一个堆内进行连续分配,只需移动指针并减少计数器,开销极小。
1.4 类改进
- 有两个地方使用了全局gDevice,一个是RequestNewHeap创建描述符堆时,一个是Allocate获取描述符类型大小时。若要改为独立组件,只需在构造函数中传入ID3D12Device并保存到对象成员变量m_Device,调用Allocate使用直接使用,调用RequestNewHeap时传入即可。
- 想了一下MiniEngine中使用全局变量确实方便,但是这是对于其开发DirectX示例而言。在其他游戏引擎或图形项目中,总不能要求每个人都这样写,项目需要考虑封装限制访问,保证访问的安全性。虽然每个对象保存Device指针多了8个字节,但是DescriptorAllocator对象是无界描述符,实际使用时最多每种类型创建一个就行了,最多多出几十个字节,没有什么影响。
- 修改后代码在文章最后统一给出。
2.DescriptorAllocator
2.1 源码展示
DescriptorAllocator实现如下:
// This handle refers to a descriptor or a descriptor table (contiguous descriptors) that is shader visible.
class DescriptorHandle
{
public:DescriptorHandle(){m_CpuHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;m_GpuHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;}/*// Should we allow constructing handles that might not be shader visible?DescriptorHandle( D3D12_CPU_DESCRIPTOR_HANDLE CpuHandle ): m_CpuHandle(CpuHandle){m_GpuHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;}*/DescriptorHandle( D3D12_CPU_DESCRIPTOR_HANDLE CpuHandle, D3D12_GPU_DESCRIPTOR_HANDLE GpuHandle ): m_CpuHandle(CpuHandle), m_GpuHandle(GpuHandle){}DescriptorHandle operator+ ( INT OffsetScaledByDescriptorSize ) const{DescriptorHandle ret = *this;ret += OffsetScaledByDescriptorSize;return ret;}void operator += ( INT OffsetScaledByDescriptorSize ){if (m_CpuHandle.ptr != D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN)m_CpuHandle.ptr += OffsetScaledByDescriptorSize;if (m_GpuHandle.ptr != D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN)m_GpuHandle.ptr += OffsetScaledByDescriptorSize;}const D3D12_CPU_DESCRIPTOR_HANDLE* operator&() const { return &m_CpuHandle; }operator D3D12_CPU_DESCRIPTOR_HANDLE() const { return m_CpuHandle; }operator D3D12_GPU_DESCRIPTOR_HANDLE() const { return m_GpuHandle; }size_t GetCpuPtr() const { return m_CpuHandle.ptr; }uint64_t GetGpuPtr() const { return m_GpuHandle.ptr; }bool IsNull() const { return m_CpuHandle.ptr == D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN; }bool IsShaderVisible() const { return m_GpuHandle.ptr != D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN; }private:D3D12_CPU_DESCRIPTOR_HANDLE m_CpuHandle;D3D12_GPU_DESCRIPTOR_HANDLE m_GpuHandle;
};
2.2 源码分析
分析如下:
// 此句柄指向着色器可见的描述符或描述符表(连续描述符)
class DescriptorHandle
{
public:// 默认构造函数,将CPU和GPU描述符句柄都设为无效值DescriptorHandle(){m_CpuHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;m_GpuHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;}/*// Should we allow constructing handles that might not be shader visible?DescriptorHandle( D3D12_CPU_DESCRIPTOR_HANDLE CpuHandle ): m_CpuHandle(CpuHandle){m_GpuHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;}*/// 有参构造函数,传入CPU和GPU描述符句柄DescriptorHandle( D3D12_CPU_DESCRIPTOR_HANDLE CpuHandle, D3D12_GPU_DESCRIPTOR_HANDLE GpuHandle ): m_CpuHandle(CpuHandle), m_GpuHandle(GpuHandle){}// 偏移描述符句柄运算DescriptorHandle operator+ ( INT OffsetScaledByDescriptorSize ) const{DescriptorHandle ret = *this;ret += OffsetScaledByDescriptorSize;return ret;}// 描述符句柄偏移运算,CPU和GPU都偏移对应大小void operator += ( INT OffsetScaledByDescriptorSize ){if (m_CpuHandle.ptr != D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN)m_CpuHandle.ptr += OffsetScaledByDescriptorSize;if (m_GpuHandle.ptr != D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN)m_GpuHandle.ptr += OffsetScaledByDescriptorSize;}// 使用&运算符时,返回CPU描述符句柄成员的地址const D3D12_CPU_DESCRIPTOR_HANDLE* operator&() const { return &m_CpuHandle; }operator D3D12_CPU_DESCRIPTOR_HANDLE() const { return m_CpuHandle; }operator D3D12_GPU_DESCRIPTOR_HANDLE() const { return m_GpuHandle; }size_t GetCpuPtr() const { return m_CpuHandle.ptr; }uint64_t GetGpuPtr() const { return m_GpuHandle.ptr; }// 判断是否为空,返回CPU描述符句柄是否无效bool IsNull() const { return m_CpuHandle.ptr == D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN; }// 判断句柄是否GPU可见,返回GPU描述符句柄是否无效bool IsShaderVisible() const { return m_GpuHandle.ptr != D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN; }private:// CPU和GPU描述符句柄D3D12_CPU_DESCRIPTOR_HANDLE m_CpuHandle;D3D12_GPU_DESCRIPTOR_HANDLE m_GpuHandle;
};
DescriptorHandle较为简单,不做过多赘述。
3.DescriptorAllocator
3.1 源码展示
DescriptorAllocator头文件如下:
class DescriptorHeap
{
public:DescriptorHeap(void) {}~DescriptorHeap(void) { Destroy(); }void Create( const std::wstring& DebugHeapName, D3D12_DESCRIPTOR_HEAP_TYPE Type, uint32_t MaxCount );void Destroy(void) { m_Heap = nullptr; }bool HasAvailableSpace( uint32_t Count ) const { return Count <= m_NumFreeDescriptors; }DescriptorHandle Alloc( uint32_t Count = 1 );DescriptorHandle operator[] (uint32_t arrayIdx) const { return m_FirstHandle + arrayIdx * m_DescriptorSize; }uint32_t GetOffsetOfHandle(const DescriptorHandle& DHandle ) {return (uint32_t)(DHandle.GetCpuPtr() - m_FirstHandle.GetCpuPtr()) / m_DescriptorSize; }bool ValidateHandle( const DescriptorHandle& DHandle ) const;ID3D12DescriptorHeap* GetHeapPointer() const { return m_Heap.Get(); }uint32_t GetDescriptorSize(void) const { return m_DescriptorSize; }private:Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_Heap;D3D12_DESCRIPTOR_HEAP_DESC m_HeapDesc;uint32_t m_DescriptorSize;uint32_t m_NumFreeDescriptors;DescriptorHandle m_FirstHandle;DescriptorHandle m_NextFreeHandle;
};
DescriptorAllocator源文件如下:
void DescriptorHeap::Create( const std::wstring& Name, D3D12_DESCRIPTOR_HEAP_TYPE Type, uint32_t MaxCount )
{m_HeapDesc.Type = Type;m_HeapDesc.NumDescriptors = MaxCount;m_HeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;m_HeapDesc.NodeMask = 1;ASSERT_SUCCEEDED(g_Device->CreateDescriptorHeap(&m_HeapDesc, MY_IID_PPV_ARGS(m_Heap.ReleaseAndGetAddressOf())));#ifdef RELEASE(void)Name;
#elsem_Heap->SetName(Name.c_str());
#endifm_DescriptorSize = g_Device->GetDescriptorHandleIncrementSize(m_HeapDesc.Type);m_NumFreeDescriptors = m_HeapDesc.NumDescriptors;m_FirstHandle = DescriptorHandle(m_Heap->GetCPUDescriptorHandleForHeapStart(),m_Heap->GetGPUDescriptorHandleForHeapStart());m_NextFreeHandle = m_FirstHandle;
}DescriptorHandle DescriptorHeap::Alloc( uint32_t Count )
{ASSERT(HasAvailableSpace(Count), "Descriptor Heap out of space. Increase heap size.");DescriptorHandle ret = m_NextFreeHandle;m_NextFreeHandle += Count * m_DescriptorSize;m_NumFreeDescriptors -= Count;return ret;
}bool DescriptorHeap::ValidateHandle( const DescriptorHandle& DHandle ) const
{if (DHandle.GetCpuPtr() < m_FirstHandle.GetCpuPtr() ||DHandle.GetCpuPtr() >= m_FirstHandle.GetCpuPtr() + m_HeapDesc.NumDescriptors * m_DescriptorSize)return false;if (DHandle.GetGpuPtr() - m_FirstHandle.GetGpuPtr() !=DHandle.GetCpuPtr() - m_FirstHandle.GetCpuPtr())return false;return true;
}
3.2 源码分析
类成员变量分析如下:
// D3D12描述符堆COM指针
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_Heap;// 堆的描述信息
D3D12_DESCRIPTOR_HEAP_DESC m_HeapDesc;// 堆中描述符的大小和剩余可用描述符数目
uint32_t m_DescriptorSize;
uint32_t m_NumFreeDescriptors;// 堆中首个描述符的句柄
DescriptorHandle m_FirstHandle;// 堆中下一个可用描述符的句柄
DescriptorHandle m_NextFreeHandle;
类实现分析如下:
// 析构时调用Destroy
DescriptorHeap::DescriptorHeap(void) {}
DescriptorHeap::~DescriptorHeap(void) { Destroy(); }// 销毁时将COM指针置为空,将自动调用其析构函数
void DescriptorHeap::Destroy(void) { m_Heap = nullptr; }// 检查是否还能容纳Count个描述符
bool DescriptorHeap::HasAvailableSpace(uint32_t Count) const {return Count <= m_NumFreeDescriptors;
}// 获取描述符句柄相对偏移个数
uint32_t DescriptorHeap::GetOffsetOfHandle(const DescriptorHandle& DHandle) {// 使用 (CPU句柄-堆CPU句柄起点) / 描述符大小return (uint32_t)(DHandle.GetCpuPtr() - m_FirstHandle.GetCpuPtr()) /m_DescriptorSize;
}// 获取D3D12描述符堆对象
ID3D12DescriptorHeap* DescriptorHeap::GetHeapPointer() const {return m_Heap.Get();
}// 获取描述符大小
uint32_t DescriptorHeap::GetDescriptorSize(void) const {return m_DescriptorSize;
}// 创建描述符堆
void DescriptorHeap::Create( const std::wstring& Name, D3D12_DESCRIPTOR_HEAP_TYPE Type, uint32_t MaxCount )
{// 设置描述符类型Type,描述符个数MaxCount,指定描述符GPU可见m_HeapDesc.Type = Type;m_HeapDesc.NumDescriptors = MaxCount;m_HeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;m_HeapDesc.NodeMask = 1;// 使用全局g_Device创建描述符堆对象,记录到m_Heap (若之前已有对象会释放)ASSERT_SUCCEEDED(g_Device->CreateDescriptorHeap(&m_HeapDesc, MY_IID_PPV_ARGS(m_Heap.ReleaseAndGetAddressOf())));#ifdef RELEASE(void)Name;
#elsem_Heap->SetName(Name.c_str());
#endif// 查询描述符大小m_DescriptorSize = g_Device->GetDescriptorHandleIncrementSize(m_HeapDesc.Type);// 初始化可用描述符数为堆大小m_NumFreeDescriptors = m_HeapDesc.NumDescriptors;// 用堆CPU和GPU起始描述符句柄初始化m_FirstHandlem_FirstHandle = DescriptorHandle(m_Heap->GetCPUDescriptorHandleForHeapStart(),m_Heap->GetGPUDescriptorHandleForHeapStart());// 下一个可使用描述符句柄为m_FirstHandlem_NextFreeHandle = m_FirstHandle;
}// 申请Count个描述符,返回描述符句柄
DescriptorHandle DescriptorHeap::Alloc( uint32_t Count )
{// 断言空间必须足够ASSERT(HasAvailableSpace(Count), "Descriptor Heap out of space. Increase heap size.");// 返回下一个可用描述符句柄m_NextFreeHandleDescriptorHandle ret = m_NextFreeHandle;// 递增m_NextFreeHandle,递减m_NumFreeDescriptorsm_NextFreeHandle += Count * m_DescriptorSize;m_NumFreeDescriptors -= Count;return ret;
}// 检查描述符句柄是否有效
bool DescriptorHeap::ValidateHandle( const DescriptorHandle& DHandle ) const
{// 描述符的CPU句柄必须位于 m_FirstHandle.GetCpuPtr()// 到 m_FirstHandle.GetCpuPtr() + m_HeapDesc.NumDescriptors * m_DescriptorSizeif (DHandle.GetCpuPtr() < m_FirstHandle.GetCpuPtr() ||DHandle.GetCpuPtr() >= m_FirstHandle.GetCpuPtr() + m_HeapDesc.NumDescriptors * m_DescriptorSize)return false;// 描述符CPU句柄到堆内起始描述符CPU句柄的偏移// 必须等于GPU偏移if (DHandle.GetGpuPtr() - m_FirstHandle.GetGpuPtr() !=DHandle.GetCpuPtr() - m_FirstHandle.GetCpuPtr())return false;return true;
}
3.3 类使用分析
- DescriptorHeap用于分配GPU可见的描述符,描述符的本质:它是一个"资源访问凭证"或"资源地址索引",其中包含了GPU如何查找和解释资源数据的关键信息(如资源在GPU内存中的地址、格式、维度等)。
- 不懂就问DeepSeek,下面是其分析回答,其中DescriptorAllocator是CPU端堆,DescriptorHeap是GPU可见堆。

- DescriptorAllocator对比DescriptorHeap。

3.4 类改进
- DescriptorAllocator::Allocate函数使用全局变量gDevice,因此在DescriptorAllocator构造函数中传入ID3D12Device,并保存在类中使用即可。然后就是DescriptorAllocator::RequestNewHeap也需要用设备创建堆,改为使用参数,对象将自身设备传入即可。
- DescriptorAllocator改为独立组件,头文件如下:
#pragma once#pragma once#include <mutex>
#include <vector>
#include <queue>
#include <string>// This is an unbounded resource descriptor allocator. It is intended to provide space for CPU-visible
// resource descriptors as resources are created. For those that need to be made shader-visible, they
// will need to be copied to a DescriptorHeap or a DynamicDescriptorHeap.
class DescriptorAllocator
{
public:DescriptorAllocator(ID3D12Device* Device, D3D12_DESCRIPTOR_HEAP_TYPE Type) :m_Device(Device), m_Type(Type), m_CurrentHeap(nullptr), m_DescriptorSize(0){m_CurrentHandle.ptr = D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN;}D3D12_CPU_DESCRIPTOR_HANDLE Allocate(uint32_t Count);static void DestroyAll(void);protected:static const uint32_t sm_NumDescriptorsPerHeap = 256;static std::mutex sm_AllocationMutex;static std::vector<Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>> sm_DescriptorHeapPool;static ID3D12DescriptorHeap* RequestNewHeap(ID3D12Device* Device, D3D12_DESCRIPTOR_HEAP_TYPE Type);ID3D12Device* m_Device;D3D12_DESCRIPTOR_HEAP_TYPE m_Type;ID3D12DescriptorHeap* m_CurrentHeap;D3D12_CPU_DESCRIPTOR_HANDLE m_CurrentHandle;uint32_t m_DescriptorSize;uint32_t m_RemainingFreeHandles;
};
源文件如下:
#include "pch.h"
#include "DescriptorHeap.h"
#include "CommandListManager.h"std::mutex DescriptorAllocator::sm_AllocationMutex;
std::vector<Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>> DescriptorAllocator::sm_DescriptorHeapPool;void DescriptorAllocator::DestroyAll(void)
{sm_DescriptorHeapPool.clear();
}ID3D12DescriptorHeap* DescriptorAllocator::RequestNewHeap(ID3D12Device* Device, D3D12_DESCRIPTOR_HEAP_TYPE Type)
{std::lock_guard<std::mutex> LockGuard(sm_AllocationMutex);D3D12_DESCRIPTOR_HEAP_DESC Desc;Desc.Type = Type;Desc.NumDescriptors = sm_NumDescriptorsPerHeap;Desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;Desc.NodeMask = 1;Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> pHeap;ASSERT_SUCCEEDED(Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap)));sm_DescriptorHeapPool.emplace_back(pHeap);return pHeap.Get();
}D3D12_CPU_DESCRIPTOR_HANDLE DescriptorAllocator::Allocate(uint32_t Count)
{if (m_CurrentHeap == nullptr || m_RemainingFreeHandles < Count){m_CurrentHeap = RequestNewHeap(m_Device, m_Type);m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();m_RemainingFreeHandles = sm_NumDescriptorsPerHeap;if (m_DescriptorSize == 0)m_DescriptorSize = m_Device->GetDescriptorHandleIncrementSize(m_Type);}D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle;m_CurrentHandle.ptr += Count * m_DescriptorSize;m_RemainingFreeHandles -= Count;return ret;
}
DescriptorHeap::Create使用了全局g_Device,添加设备参数即可。
DescriptorHeap改为独立组件,头文件如下:
class DescriptorHeap
{
public:DescriptorHeap(void) {}~DescriptorHeap(void) { Destroy(); }void Create(ID3D12Device* Device, const std::wstring& DebugHeapName, D3D12_DESCRIPTOR_HEAP_TYPE Type, uint32_t MaxCount);void Destroy(void) { m_Heap = nullptr; }bool HasAvailableSpace(uint32_t Count) const { return Count <= m_NumFreeDescriptors; }DescriptorHandle Alloc(uint32_t Count = 1);DescriptorHandle operator[] (uint32_t arrayIdx) const { return m_FirstHandle + arrayIdx * m_DescriptorSize; }uint32_t GetOffsetOfHandle(const DescriptorHandle& DHandle) {return (uint32_t)(DHandle.GetCpuPtr() - m_FirstHandle.GetCpuPtr()) / m_DescriptorSize;}bool ValidateHandle(const DescriptorHandle& DHandle) const;ID3D12DescriptorHeap* GetHeapPointer() const { return m_Heap.Get(); }uint32_t GetDescriptorSize(void) const { return m_DescriptorSize; }private:Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_Heap;D3D12_DESCRIPTOR_HEAP_DESC m_HeapDesc;uint32_t m_DescriptorSize;uint32_t m_NumFreeDescriptors;DescriptorHandle m_FirstHandle;DescriptorHandle m_NextFreeHandle;
};
源文件如下:
void DescriptorHeap::Create(ID3D12Device* Device, const std::wstring& Name, D3D12_DESCRIPTOR_HEAP_TYPE Type, uint32_t MaxCount)
{m_HeapDesc.Type = Type;m_HeapDesc.NumDescriptors = MaxCount;m_HeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;m_HeapDesc.NodeMask = 1;ASSERT_SUCCEEDED(Device->CreateDescriptorHeap(&m_HeapDesc, MY_IID_PPV_ARGS(m_Heap.ReleaseAndGetAddressOf())));#ifdef RELEASE(void)Name;
#elsem_Heap->SetName(Name.c_str());
#endifm_DescriptorSize = Device->GetDescriptorHandleIncrementSize(m_HeapDesc.Type);m_NumFreeDescriptors = m_HeapDesc.NumDescriptors;m_FirstHandle = DescriptorHandle(m_Heap->GetCPUDescriptorHandleForHeapStart(),m_Heap->GetGPUDescriptorHandleForHeapStart());m_NextFreeHandle = m_FirstHandle;
}DescriptorHandle DescriptorHeap::Alloc(uint32_t Count)
{ASSERT(HasAvailableSpace(Count), "Descriptor Heap out of space. Increase heap size.");DescriptorHandle ret = m_NextFreeHandle;m_NextFreeHandle += Count * m_DescriptorSize;m_NumFreeDescriptors -= Count;return ret;
}bool DescriptorHeap::ValidateHandle(const DescriptorHandle& DHandle) const
{if (DHandle.GetCpuPtr() < m_FirstHandle.GetCpuPtr() ||DHandle.GetCpuPtr() >= m_FirstHandle.GetCpuPtr() + m_HeapDesc.NumDescriptors * m_DescriptorSize)return false;if (DHandle.GetGpuPtr() - m_FirstHandle.GetGpuPtr() !=DHandle.GetCpuPtr() - m_FirstHandle.GetCpuPtr())return false;return true;
}
4.总结
-
本篇文章分析了DescriptorAllocator、DescriptorHandle、DescriptorHeap等类,并且使用注释进行了逐行分析,最后给出了独立组件实现。
-
实现代码位于DescriptorHeap,可独立使用。
