在 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_COMMOND3D12_RESOURCE_STATE_RENDER_TARGETD3D12_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。

    • 资源创建方式选择: 构造函数内部根据 infoheapresource 成员的设置,灵活地选择使用 CreateCommittedResourceCreatePlacedResource 或直接使用已有的 resource 来创建纹理资源。

      • info.resource 非空: 直接使用传入的 resource,不创建新的资源,用于封装已存在的 D3D12 资源。

      • info.heapinfo.desc 都非空: 使用 CreatePlacedResource 在指定的堆 info.heap 中创建资源,并根据 info.allocation_info 指定偏移量。

      • info.desc 非空且 info.heapinfo.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->FormatDXGI_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 (包括修改后的 descsrv_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_HANDLED3D12_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_RESOURCED3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE 状态。

资源状态转换需要使用 资源屏障 (Resource Barrier) (ID3D12GraphicsCommandList::ResourceBarrier) 在命令列表中显式指定。