DescriptorHeap
- 前言
- 1.DescriptorAllocator
-
- [1.1 源码展示](#1.1 源码展示)
- [1.2 源码分析](#1.2 源码分析)
- [1.3 类使用分析](#1.3 类使用分析)
- [1.4 类改进](#1.4 类改进)
- 2.DescriptorAllocator
-
- [2.1 源码展示](#2.1 源码展示)
- [2.2 源码分析](#2.2 源码分析)
- 3.DescriptorAllocator
-
- [3.1 源码展示](#3.1 源码展示)
- [3.2 源码分析](#3.2 源码分析)
- [3.3 类使用分析](#3.3 类使用分析)
- [3.4 类改进](#3.4 类改进)
- 4.总结
前言
- 书接上回CommandListManager,本篇文章分析DescriptorHeap.h,其包含三个类DescriptorAllocator、DescriptorHandle、DescriptorHeap。
1.DescriptorAllocator
1.1 源码展示
类头文件如下:
cpp
#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;
};
类源文件如下:
cpp
#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 源码分析
类成员变量如下:
cpp
// 每个堆中包含的描述符大小,平衡内存性能和分配频率 (太大内存可能浪费,太小可能多次分配)
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;
类方法如下:
cpp
// 构造函数,传入类型,初始化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实现如下:
cpp
// 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 源码分析
分析如下:
cpp
// 此句柄指向着色器可见的描述符或描述符表(连续描述符)
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头文件如下:
cpp
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源文件如下:
cpp
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;
#else
m_Heap->SetName(Name.c_str());
#endif
m_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 源码分析
类成员变量分析如下:
cpp
// 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;
类实现分析如下:
cpp
// 析构时调用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;
#else
m_Heap->SetName(Name.c_str());
#endif
// 查询描述符大小
m_DescriptorSize = g_Device->GetDescriptorHandleIncrementSize(m_HeapDesc.Type);
// 初始化可用描述符数为堆大小
m_NumFreeDescriptors = m_HeapDesc.NumDescriptors;
// 用堆CPU和GPU起始描述符句柄初始化m_FirstHandle
m_FirstHandle = DescriptorHandle(
m_Heap->GetCPUDescriptorHandleForHeapStart(),
m_Heap->GetGPUDescriptorHandleForHeapStart());
// 下一个可使用描述符句柄为m_FirstHandle
m_NextFreeHandle = m_FirstHandle;
}
// 申请Count个描述符,返回描述符句柄
DescriptorHandle DescriptorHeap::Alloc( uint32_t Count )
{
// 断言空间必须足够
ASSERT(HasAvailableSpace(Count), "Descriptor Heap out of space. Increase heap size.");
// 返回下一个可用描述符句柄m_NextFreeHandle
DescriptorHandle ret = m_NextFreeHandle;
// 递增m_NextFreeHandle,递减m_NumFreeDescriptors
m_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_DescriptorSize
if (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改为独立组件,头文件如下:
cpp
#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;
};
源文件如下:
cpp
#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改为独立组件,头文件如下:
cpp
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;
};
源文件如下:
cpp
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;
#else
m_Heap->SetName(Name.c_str());
#endif
m_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,可独立使用。