D3D12 资源
在 Direct3D 12 (D3D12) 中,资源是图形渲染管线中处理数据的核心对象。 纹理(Textures)、缓冲区(Buffers)等都属于资源。 有效管理这些资源对于构建高性能的 D3D12 应用程序至关重要。
您的代码示例通过 C++ 类封装了 D3D12 的纹理资源,让我们以此为入口,深入了解 D3D12 资源管理的关键概念。
1. d3d12_texture_init_info
结构体:资源初始化信息
首先,我们来看 d3d12_texture_init_info
结构体,它定义了创建纹理资源所需的各种信息:
C++
struct d3d12_texture_init_info
{
ID3D12Heap1* heap{ nullptr }; // 堆对象,用于显式指定资源堆
ID3D12Resource* resource; // D3D12 资源对象指针
D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{ nullptr }; // 着色器资源视图描述
D3D12_RESOURCE_DESC* desc{ nullptr }; // 资源描述
D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; // 资源分配信息,用于PlacedResource
D3D12_RESOURCE_STATES initial_state{}; // 资源的初始状态
D3D12_CLEAR_VALUE clear_value{}; // 清除优化的值
};
ID3D12Heap1* heap
: 堆 (Heap) 对象指针。在 D3D12 中,资源内存需要从堆中分配。heap
允许你显式指定资源将要放置在哪个堆中。 如果为nullptr
,则通常使用默认堆或由CreateCommittedResource
创建隐式堆。ID3D12Resource* resource
: 资源 (Resource) 对象指针。 这是 D3D12 资源的核心接口,代表了实际的纹理或缓冲区对象。 初始化时,可以直接传入一个已存在的ID3D12Resource
对象,用于封装已创建的资源。D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc
: 着色器资源视图描述 (Shader Resource View Description)。 用于创建着色器资源视图 (SRV),让着色器可以读取纹理数据。如果需要纹理被着色器访问,就需要提供此描述。D3D12_RESOURCE_DESC* desc
: 资源描述 (Resource Description)。D3D12_RESOURCE_DESC
结构体定义了资源的各种属性,例如尺寸、格式、用途等。 这是创建资源时必须提供的核心信息。D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info
: 资源分配信息 (Resource Allocation Info)。 主要用于CreatePlacedResource
,指定资源在堆中的偏移量等分配信息。D3D12_RESOURCE_STATES initial_state
: 初始资源状态 (Initial Resource State)。 D3D12 资源需要处于特定的状态才能被 GPU 正确访问。initial_state
定义了资源被创建时的初始状态,例如D3D12_RESOURCE_STATE_COMMON
、D3D12_RESOURCE_STATE_RENDER_TARGET
、D3D12_RESOURCE_STATE_DEPTH_WRITE
等。D3D12_CLEAR_VALUE clear_value
: 清除优化值 (Clear Value)。 用于优化某些资源的清除操作,例如渲染目标和深度缓冲区。 如果资源需要清除操作,可以提供此值以提升性能。
2. d3d12_texture
类:基础纹理封装
d3d12_texture
类是对 D3D12 纹理资源的最基础封装,它负责纹理资源的创建、管理和释放,并提供着色器资源视图 (SRV) 的访问接口。
C++
class d3d12_texture
{
public:
constexpr static u32 max_mips{ 14 }; // 最大 Mipmap 级别
d3d12_texture() = default; // 默认构造函数
explicit d3d12_texture(d3d12_texture_init_info info); // 初始化构造函数
DISABLE_COPY(d3d12_texture); // 禁用拷贝构造和赋值
constexpr d3d12_texture(d3d12_texture&& o) // 移动构造函数
: _resource{ o._resource }, _srv{ o._srv }
{
o.reset();
}
d3d12_texture& operator=(d3d12_texture&& o) // 移动赋值运算符
{ /* ... */ }
~d3d12_texture() { release(); } // 析构函数,释放资源
void release(); // 释放资源函数
constexpr ID3D12Resource *const resource() const { return _resource; } // 获取资源对象指针
constexpr descriptor_handle srv() const { return _srv; } // 获取 SRV 描述符句柄
private:
constexpr void move(d3d12_texture& o) { /* ... */ } // 移动函数
constexpr void reset() { /* ... */ } // 重置函数
ID3D12Resource* _resource{ nullptr }; // 内部 D3D12 资源对象指针
descriptor_handle _srv; // SRV 描述符句柄
};
构造函数
d3d12_texture(d3d12_texture_init_info info)
: 这是d3d12_texture
类的核心构造函数,它根据d3d12_texture_init_info
结构体中的信息创建 D3D12 纹理资源和 SRV。资源创建方式选择: 构造函数内部根据
info
中heap
和resource
成员的设置,灵活地选择使用CreateCommittedResource
或CreatePlacedResource
或直接使用已有的resource
来创建纹理资源。info.resource
非空: 直接使用传入的resource
,不创建新的资源,用于封装已存在的 D3D12 资源。info.heap
和info.desc
都非空: 使用CreatePlacedResource
在指定的堆info.heap
中创建资源,并根据info.allocation_info
指定偏移量。info.desc
非空且info.heap
和info.resource
为空: 使用CreateCommittedResource
创建资源,D3D12 会隐式创建和管理堆。
SRV 创建: 创建
ID3D12Resource
后,会调用device->CreateShaderResourceView
创建 SRV,并将其描述符句柄存储在_srv
成员中。
release()
: 释放纹理资源和 SRV。 注意代码中使用了core::deferred_release(_resource)
,这可能是一种延迟释放机制,用于在合适的时机(例如帧末)才真正释放 GPU 资源,以避免同步问题。core::srv_heap().free(_srv)
释放 SRV 描述符句柄到描述符堆管理器。resource()
和srv()
: 提供访问内部ID3D12Resource
指针和 SRV 描述符句柄的接口。移动语义: 提供了移动构造函数和移动赋值运算符,用于高效地转移资源所有权,避免不必要的资源复制和内存分配。
禁用拷贝: 通过
DISABLE_COPY
宏禁用了拷贝构造函数和拷贝赋值运算符,防止错误的资源拷贝行为,因为 D3D12 资源通常不应该被简单拷贝。析构函数
~d3d12_texture()
: 在对象销毁时自动调用release()
释放资源。
3. d3d12_render_texture
类:渲染目标纹理封装
d3d12_render_texture
类继承了 d3d12_texture
的功能,并扩展了渲染目标 (Render Target) 的特性。 渲染目标纹理可以作为渲染管线的输出目标,用于存储渲染的颜色信息。
C++
class d3d12_render_texture
{
public:
d3d12_render_texture() = default;
explicit d3d12_render_texture(d3d12_texture_init_info info); // 初始化构造函数
DISABLE_COPY(d3d12_render_texture);
constexpr d3d12_render_texture(d3d12_render_texture&& o) // 移动构造函数
:_texture{ std::move(o._texture) }, _mip_count{ o._mip_count }
{ /* ... */ }
constexpr d3d12_render_texture& operator=(d3d12_render_texture&& o) // 移动赋值运算符
{ /* ... */ }
~d3d12_render_texture() { release(); } // 析构函数
void release(); // 释放资源函数
constexpr u32 mip_count() const { return _mip_count; } // 获取 Mipmap 级别数
constexpr D3D12_CPU_DESCRIPTOR_HANDLE rtv(u32 mip_index) const { /* ... */ } // 获取 RTV 描述符句柄
constexpr descriptor_handle srv() const { return _texture.srv(); } // 获取 SRV 描述符句柄 (继承自 d3d12_texture)
constexpr ID3D12Resource *const resource() const { return _texture.resource(); } // 获取资源对象指针 (继承自 d3d12_texture)
private:
void move(d3d12_render_texture& o) { /* ... */ } // 移动函数
constexpr void reset() { /* ... */ } // 重置函数
d3d12_texture _texture{}; // 内部 d3d12_texture 对象,用于管理纹理资源
descriptor_handle _rtv[d3d12_texture::max_mips]{}; // RTV 描述符句柄数组,支持 Mipmap
u32 _mip_count{ 0 }; // Mipmap 级别数
};
构造函数
d3d12_render_texture(d3d12_texture_init_info info)
:复用
d3d12_texture
构造: 首先调用d3d12_texture
的构造函数_texture{ info }
创建基础纹理资源,这部分逻辑与d3d12_texture
相同。RTV 创建: 根据纹理的 Mipmap 级别数 (
_mip_count
),循环创建多个渲染目标视图 (RTV)。 每个 Mipmap 级别对应一个 RTV。RTV 描述:
D3D12_RENDER_TARGET_VIEW_DESC
结构体被配置为D3D12_RTV_DIMENSION_TEXTURE2D
,并循环递增Texture2D.MipSlice
成员,为每个 Mipmap 级别创建 RTV。RTV 描述符分配: 使用
core::rtv_heap().allocate()
从 RTV 描述符堆中分配描述符,并将 CPU 句柄存储在_rtv
数组中。CreateRenderTargetView
调用: 调用device->CreateRenderTargetView
创建 RTV,关联纹理资源、RTV 描述和描述符句柄。
release()
: 释放 RTV 描述符和内部d3d12_texture
对象 (_texture.release()
)。rtv(u32 mip_index)
: 根据 Mipmap 索引返回对应的 RTV 描述符句柄,用于在渲染时绑定渲染目标。srv()
和resource()
: 直接调用内部_texture
对象的srv()
和resource()
方法,复用基础纹理的 SRV 和资源对象访问接口。
4. d3d12_depth_buffer
类:深度缓冲区封装
d3d12_depth_buffer
类用于封装深度缓冲区资源。 深度缓冲区用于存储深度信息,在渲染管线中用于深度测试,决定像素的前后遮挡关系。
C++
class d3d12_depth_buffer
{
public:
d3d12_depth_buffer() = default;
explicit d3d12_depth_buffer(d3d12_texture_init_info info); // 初始化构造函数
DISABLE_COPY(d3d12_depth_buffer);
constexpr d3d12_depth_buffer(d3d12_depth_buffer&& o) // 移动构造函数
:_texture{ std::move(o._texture) }, _dsv{ o._dsv }
{
o._dsv = {};
}
constexpr d3d12_depth_buffer& operator=(d3d12_depth_buffer&& o) // 移动赋值运算符
{ /* ... */ }
~d3d12_depth_buffer() { release(); } // 析构函数
void release(); // 释放资源函数
constexpr D3D12_CPU_DESCRIPTOR_HANDLE dsv() const { return _dsv.cpu; } // 获取 DSV 描述符句柄
constexpr descriptor_handle srv() const { return _texture.srv(); } // 获取 SRV 描述符句柄 (继承自 d3d12_texture)
constexpr ID3D12Resource *const resource() const { return _texture.resource(); } // 获取资源对象指针 (继承自 d3d12_texture)
private:
d3d12_texture _texture{}; // 内部 d3d12_texture 对象,用于管理纹理资源
descriptor_handle _dsv{}; // DSV 描述符句柄
};
构造函数
d3d12_depth_buffer(d3d12_texture_init_info info)
:深度格式处理: 构造函数中对深度格式进行了特殊处理。 如果传入的
info.desc->Format
是DXGI_FORMAT_D32_FLOAT
(常用的深度格式),则会将其修改为DXGI_FORMAT_R32_TYPELESS
,并在 SRV 描述中指定DXGI_FORMAT_R32_FLOAT
。 这是因为深度格式通常是无类型的,需要通过视图指定具体的格式来访问。SRV 描述: 创建了一个
D3D12_SHADER_RESOURCE_VIEW_DESC
结构体,并配置为深度纹理的 SRV。 注意srv_desc.Format
被设置为DXGI_FORMAT_R32_FLOAT
,与修改后的info.desc->Format
对应。复用
d3d12_texture
构造: 使用配置好的info
(包括修改后的desc
和srv_desc
) 调用d3d12_texture
的构造函数_texture = d3d12_texture(info)
创建基础纹理资源和 SRV。DSV 创建: 创建深度模版视图 (DSV)。
DSV 描述:
D3D12_DEPTH_STENCIL_VIEW_DESC
结构体被配置为D3D12_DSV_DIMENSION_TEXTURE2D
,并使用传入的原始深度格式dsv_format
。DSV 描述符分配: 使用
core::dsv_heap().allocate()
从 DSV 描述符堆中分配描述符,并将 CPU 句柄存储在_dsv
成员中。CreateDepthStencilView
调用: 调用device->CreateDepthStencilView
创建 DSV,关联纹理资源、DSV 描述和描述符句柄。
release()
: 释放 DSV 描述符和内部d3d12_texture
对象 (_texture.release()
)。dsv()
: 返回 DSV 描述符句柄,用于在渲染时绑定深度缓冲区。srv()
和resource()
: 同样复用内部_texture
对象的srv()
和resource()
方法。
5. 核心 D3D12 API 函数总结
从代码中可以看出,这些资源类封装了以下 D3D12 核心 API 函数:
ID3D12Device::CreateCommittedResource
: 创建并提交资源,同时隐式创建和管理堆。ID3D12Device::CreatePlacedResource
: 在用户指定的堆中创建资源,需要用户预先创建和管理堆。ID3D12Device::CreateShaderResourceView
: 创建着色器资源视图 (SRV),使纹理可以被着色器读取。ID3D12Device::CreateRenderTargetView
: 创建渲染目标视图 (RTV),使纹理可以作为渲染目标。ID3D12Device::CreateDepthStencilView
: 创建深度模版视图 (DSV),使纹理可以作为深度缓冲区。
6. 描述符堆和描述符句柄
代码中使用了 core::srv_heap().allocate()
、core::rtv_heap().allocate()
和 core::dsv_heap().allocate()
来分配描述符句柄。 这暗示了在 core
对象中存在描述符堆管理器,用于分配和管理 SRV、RTV 和 DSV 描述符。
描述符堆是 D3D12 中管理描述符(例如 SRV、RTV、CBV、UAV)的关键机制。 描述符堆是一块 GPU 内存,用于存储描述符。 应用程序需要显式创建描述符堆,并在堆中分配描述符。
描述符句柄 (D3D12_CPU_DESCRIPTOR_HANDLE
和 D3D12_GPU_DESCRIPTOR_HANDLE
) 用于访问描述符堆中的描述符。 CPU 描述符句柄用于 CPU 端操作(例如创建描述符),GPU 描述符句柄用于在命令列表中绑定描述符,供 GPU 访问。
7. 资源状态管理 (Resource State Management)
虽然代码中没有显式展示资源状态转换,但 d3d12_texture_init_info
结构体中包含了 D3D12_RESOURCE_STATES initial_state
成员,这表明资源状态管理是 D3D12 资源管理中不可或缺的一部分。
D3D12 严格管理资源状态,资源在不同的使用场景下需要处于不同的状态。 例如,纹理作为渲染目标时需要处于 D3D12_RESOURCE_STATE_RENDER_TARGET
状态,作为着色器资源被读取时需要处于 D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
或 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
状态。
资源状态转换需要使用 资源屏障 (Resource Barrier) (ID3D12GraphicsCommandList::ResourceBarrier
) 在命令列表中显式指定。