Compare commits

..

2 Commits

Author SHA1 Message Date
SpecialX
188a8aea2f feat(d3d12): 实现渲染目标纹理和深度缓冲区
核心变更:
- 实现 d3d12_render_texture 类,支持多 Mip 级别 RTV
- 实现 d3d12_depth_buffer 类,支持 DSV 和 SRV 双视图
- 为所有纹理类添加析构函数,确保资源自动释放
- 深度缓冲区格式转换处理(D32_FLOAT -> R32_TYPELESS)

文档完善:
- 新增变更记录文档
- 更新 D3D12 学习 Wiki,添加渲染目标纹理和深度缓冲区章节
2026-04-01 17:01:18 +08:00
SpecialX
4d13d8df89 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、资源创建方式、纹理资源类章节
- 新增变更记录文档
2026-04-01 16:17:42 +08:00
8 changed files with 1207 additions and 25 deletions

View File

@@ -0,0 +1,14 @@
#pragma once
#include "D3D12CommonHeader.h"
namespace XEngine::graphics::d3d12::d3dx
{
constexpr struct{
D3D12_HEAP_PROPERTIES default_heap{
D3D12_HEAP_TYPE_DEFAULT, // 堆类型默认堆GPU可读写CPU不可直接访问。选项D3D12_HEAP_TYPE_DEFAULT默认堆、D3D12_HEAP_TYPE_UPLOAD上传堆CPU写入GPU读取、D3D12_HEAP_TYPE_READBACK回读堆GPU写入CPU读取、D3D12_HEAP_TYPE_CUSTOM自定义堆
D3D12_CPU_PAGE_PROPERTY_UNKNOWN, // CPU页属性未知使用默认设置。选项D3D12_CPU_PAGE_PROPERTY_UNKNOWN未知、D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE不可用、D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE写入合并、D3D12_CPU_PAGE_PROPERTY_WRITE_BACK写回
D3D12_MEMORY_POOL_UNKNOWN, // 内存池未知由驱动自动选择。选项D3D12_MEMORY_POOL_UNKNOWN未知、D3D12_MEMORY_POOL_L0系统内存池、D3D12_MEMORY_POOL_L1显存池
0, // 节点掩码单GPU系统设为0多GPU系统中指定使用哪个GPU节点
0, // 保留字段必须为0为将来扩展预留
};
}heap_properties;
}

View File

@@ -1,8 +1,11 @@
#include "D3D12Resources.h" #include "D3D12Resources.h"
#include "D3D12Core.h" #include "D3D12Core.h"
#include "D3D12Helpers.h"
namespace XEngine::graphics::d3d12{ namespace XEngine::graphics::d3d12{
//////////// DESCRIPTOR HEAP //////////// //////////// DESCRIPTOR HEAP ////////////
// 该类将被多个线程并发访问:资源创建(如纹理)与资源销毁/释放可能发生在不同线程, // 该类将被多个线程并发访问:资源创建(如纹理)与资源销毁/释放可能发生在不同线程,
// 因此需要同步机制保护内部数据结构 // 因此需要同步机制保护内部数据结构
bool bool
@@ -134,4 +137,140 @@ descriptor_heap::free(descriptor_handle handle)
handle = {}; handle = {};
} }
//////////// D3D12 TEXTURE ////////////
d3d12_texture::d3d12_texture( d3d12_texture_init_info info)
{
auto *const device {core::device()};
assert(device);
D3D12_CLEAR_VALUE *const clear_value{
(info.desc &&
(info.desc->Flags &D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET ||
info.desc && info.desc->Flags &D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
? &info.clear_value : nullptr
};
if(info.resource)
{
_resource = info.resource;
}
else if(info.heap && info.desc)
{
assert(!info.resource);
DXCall(device->CreatePlacedResource(
info.heap,
info.allocation_info.Offset,
info.desc,
info.initial_state,
clear_value,
IID_PPV_ARGS(&_resource)
));
}
else if (info.desc)
{
assert(!info.srv_desc);
DXCall(device->CreateCommittedResource(
&d3dx::heap_properties.default_heap,
D3D12_HEAP_FLAG_NONE,
info.desc,
info.initial_state,
clear_value,
IID_PPV_ARGS(&_resource)
));
}
assert(_resource);
_srv = core::srv_heap().allocate();
device->CreateShaderResourceView(_resource, info.srv_desc, _srv.cpu);
}
void
d3d12_texture::release()
{
core::srv_heap().free(_srv);
core::deferred_release(_resource);
}
//////////// D3D12 RENDER TEXTURE ////////////
d3d12_render_texture::d3d12_render_texture(d3d12_texture_init_info info)
: _texture(info)
{
assert(info.desc);
_mip_count = resource()->GetDesc().MipLevels;
assert(_mip_count && _mip_count <= d3d12_texture::max_mips);
descriptor_heap& rtv_heap {core::rtv_heap()};
D3D12_RENDER_TARGET_VIEW_DESC desc{};
desc.Format = info.desc->Format;
desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
desc.Texture2D.MipSlice = 0;
auto *const device {core::device()};
assert(device);
for(u32 i{0}; i<_mip_count;++i)
{
_rtv[i] = rtv_heap.allocate();
device->CreateRenderTargetView(resource(), &desc, _rtv[i].cpu);
++desc.Texture2D.MipSlice;
}
}
void
d3d12_render_texture::release()
{
for(u32 i{0}; i<_mip_count;++i) core::srv_heap().free(_rtv[i]);
_texture.release();
_mip_count = 0;
}
//////////// D3D12 DEPTH TEXTURE ////////////
d3d12_depth_buffer::d3d12_depth_buffer(d3d12_texture_init_info info)
: _texture(info)
{
assert(info.desc);
const DXGI_FORMAT dsv_format {info.desc->Format};
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc{};
if(info.desc->Format == DXGI_FORMAT_D32_FLOAT)
{
info.desc->Format = DXGI_FORMAT_R32_TYPELESS;
srv_desc.Format = DXGI_FORMAT_R32_FLOAT;
}
srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = 1;
srv_desc.Texture2D.MostDetailedMip = 0;
srv_desc.Texture2D.PlaneSlice = 0;
srv_desc.Texture2D.ResourceMinLODClamp = 0.f;
assert(!info.srv_desc && !info.resource);
info.srv_desc = &srv_desc;
_texture = d3d12_texture(info);
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc{};
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsv_desc.Flags = D3D12_DSV_FLAG_NONE;
dsv_desc.Format = dsv_format;
dsv_desc.Texture2D.MipSlice = 0;
_dsv = core::dsv_heap().allocate();
auto *const device {core::device()};
assert(device);
device->CreateDepthStencilView(resource(), &dsv_desc, _dsv.cpu);
}
void
d3d12_depth_buffer::release()
{
core::dsv_heap().free(_dsv);
_texture.release();
_dsv = {};
}
} //XEngine::graphics::d3d12 } //XEngine::graphics::d3d12

View File

@@ -417,4 +417,145 @@ private:
const D3D12_DESCRIPTOR_HEAP_TYPE _type; const D3D12_DESCRIPTOR_HEAP_TYPE _type;
}; };
struct d3d12_texture_init_info
{
ID3D12Heap1* heap{nullptr};
ID3D12Resource* resource{nullptr};
D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr};
D3D12_RESOURCE_DESC* desc{nullptr};
D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{};
D3D12_RESOURCE_STATES initial_state{};
D3D12_CLEAR_VALUE clear_value{};
};
class d3d12_texture
{
public:
constexpr static u32 max_mips{ 14 };
d3d12_texture() = default;
explicit d3d12_texture(d3d12_texture_init_info info);
~d3d12_texture() { release(); }
DISABLE_COPY(d3d12_texture);
constexpr d3d12_texture(d3d12_texture&& o)
: _resource(o._resource), _srv(o._srv) //这些值只是指针和句柄不需要move
{
o.reset();
}
constexpr d3d12_texture& operator=(d3d12_texture&& o)
{
assert(this != &o);
if(this != &o)
{
release();
move(o);
}
return *this;
}
void release();
constexpr ID3D12Resource *const resource() const { return _resource; }
constexpr descriptor_handle srv() const { return _srv; }
private:
constexpr void move(d3d12_texture& o)
{
_resource = o._resource;
_srv = o._srv;
o.reset();
}
constexpr void reset()
{
_resource = nullptr;
_srv = {};
}
ID3D12Resource* _resource{nullptr};
descriptor_handle _srv;
};
class d3d12_render_texture
{
public:
d3d12_render_texture() = default;
explicit d3d12_render_texture(d3d12_texture_init_info info);
DISABLE_COPY(d3d12_render_texture);
~d3d12_render_texture() { release(); }
constexpr d3d12_render_texture(d3d12_render_texture&& o)
: _texture{std::move(o._texture)}, _mip_count{o._mip_count}
{
for(u32 i = 0; i < o._mip_count; ++i) _rtv[i] = o._rtv[i];
o.reset();
}
constexpr d3d12_render_texture& operator=(d3d12_render_texture&& o)
{
assert(this != &o);
if(this != &o)
{
reset();
move(o);
}
return *this;
}
void release();
constexpr u32 mip_count() const { return _mip_count; }
constexpr D3D12_CPU_DESCRIPTOR_HANDLE rtv(u32 mip) const { assert(mip < _mip_count); return _rtv[mip].cpu; }
constexpr descriptor_handle srv() const { return _texture.srv(); }
constexpr ID3D12Resource *const resource() const { return _texture.resource(); }
private:
constexpr void move(d3d12_render_texture& o)
{
_texture = std::move(o._texture);
for(u32 i = 0; i < o._mip_count; ++i) _rtv[i] = o._rtv[i];
o.reset();
}
constexpr void reset()
{
for(u32 i = 0; i < _mip_count; ++i) _rtv[i] = {};
_mip_count = 0;
}
d3d12_texture _texture{};
descriptor_handle _rtv[d3d12_texture::max_mips]{};
u32 _mip_count{0};
};
class d3d12_depth_buffer
{
public :
d3d12_depth_buffer() = default;
explicit d3d12_depth_buffer(d3d12_texture_init_info info);
DISABLE_COPY(d3d12_depth_buffer);
~d3d12_depth_buffer() { release(); }
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)
{
assert(this != &o);
if(this != &o)
{
_texture = std::move(o._texture);
_dsv = o._dsv;
o._dsv = {};
}
return *this;
}
void release();
constexpr D3D12_CPU_DESCRIPTOR_HANDLE dsv() const { return _dsv.cpu; }
constexpr descriptor_handle srv() const { return _texture.srv(); }
constexpr ID3D12Resource *const resource() const { return _texture.resource(); }
private:
d3d12_texture _texture{};
descriptor_handle _dsv{};
};
} // namespace XEngine::graphics::d3d12 } // namespace XEngine::graphics::d3d12

View File

@@ -26,18 +26,18 @@ d3d12_surface::create_swap_chain(IDXGIFactory7* factory, ID3D12CommandQueue* cmd
} }
DXGI_SWAP_CHAIN_DESC1 desc{}; DXGI_SWAP_CHAIN_DESC1 desc{};
desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Alpha通道模式窗口透明度相关 desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Alpha通道模式窗口透明度相关
desc.BufferCount = frame_buffer_count; // 后台缓冲区数量(用于双缓冲/多缓冲) desc.BufferCount = buffer_count; // 后台缓冲区数量(用于双缓冲/多缓冲)
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 缓冲区用途(作为渲染目标输出) desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 缓冲区用途(作为渲染目标输出)
desc.Flags = _allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; // 交换链标志位(无) desc.Flags = _allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; // 交换链标志位(无)
desc.Format = to_non_srgb(format); // 像素格式转换为非SRGB格式 desc.Format = to_non_srgb(format); // 像素格式转换为非SRGB格式
desc.Height = _window.height(); // 交换链高度(与窗口高度一致) desc.Height = _window.height(); // 交换链高度(与窗口高度一致)
desc.Width = _window.width(); // 交换链宽度(与窗口宽度一致) desc.Width = _window.width(); // 交换链宽度(与窗口宽度一致)
desc.SampleDesc.Count = 1; // 多重采样数量1表示禁用MSAA desc.SampleDesc.Count = 1; // 多重采样数量1表示禁用MSAA
desc.SampleDesc.Quality = 0; // 多重采样质量等级0表示禁用 desc.SampleDesc.Quality = 0; // 多重采样质量等级0表示禁用
desc.Scaling = DXGI_SCALING_STRETCH; // 缓冲区缩放模式(窗口大小变化时的处理方式) desc.Scaling = DXGI_SCALING_STRETCH; // 缓冲区缩放模式(窗口大小变化时的处理方式)
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // 交换链效果(翻转并丢弃旧缓冲区) desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // 交换链效果(翻转并丢弃旧缓冲区)
desc.Stereo = false; // 立体显示模式3D显示 desc.Stereo = false; // 立体显示模式3D显示
IDXGISwapChain1* swap_chain; IDXGISwapChain1* swap_chain;
HWND hwnd{ (HWND)_window.handle() }; HWND hwnd{ (HWND)_window.handle() };
@@ -53,7 +53,7 @@ d3d12_surface::create_swap_chain(IDXGIFactory7* factory, ID3D12CommandQueue* cmd
_current_bb_index = _swap_chain->GetCurrentBackBufferIndex(); _current_bb_index = _swap_chain->GetCurrentBackBufferIndex();
for (u32 i = 0; i < frame_buffer_count; ++i) for (u32 i = 0; i < buffer_count; ++i)
{ {
_render_target_data[i].rtv = core::rtv_heap().allocate(); _render_target_data[i].rtv = core::rtv_heap().allocate();
} }
@@ -76,7 +76,7 @@ void
d3d12_surface::finalize() d3d12_surface::finalize()
{ {
// 为每个缓冲区创建渲染目标视图 // 为每个缓冲区创建渲染目标视图
for (u32 i = 0; i < frame_buffer_count; ++i) for (u32 i = 0; i < buffer_count; ++i)
{ {
render_target_data& data{ _render_target_data[i] }; render_target_data& data{ _render_target_data[i] };
assert(!data.resource); assert(!data.resource);
@@ -107,7 +107,7 @@ d3d12_surface::finalize()
void void
d3d12_surface::release() d3d12_surface::release()
{ {
for (u32 i = 0; i < frame_buffer_count; ++i) for (u32 i = 0; i < buffer_count; ++i)
{ {
render_target_data& data{ _render_target_data[i] }; render_target_data& data{ _render_target_data[i] };
core::release(data.resource); core::release(data.resource);

View File

@@ -71,6 +71,9 @@ public:
*/ */
~d3d12_surface(){ release(); } ~d3d12_surface(){ release(); }
constexpr static u32 buffer_count{ 3 };
#if USE_STL_VECTOR #if USE_STL_VECTOR
DISABLE_COPY(d3d12_surface) DISABLE_COPY(d3d12_surface)
@@ -288,7 +291,7 @@ private:
}; };
IDXGISwapChain4* _swap_chain{nullptr}; ///< DXGI 交换链对象 IDXGISwapChain4* _swap_chain{nullptr}; ///< DXGI 交换链对象
render_target_data _render_target_data[frame_buffer_count];///< 后台缓冲区数据数组 render_target_data _render_target_data[buffer_count]; ///< 后台缓冲区数据数组
platform::window _window{}; ///< 关联的平台窗口 platform::window _window{}; ///< 关联的平台窗口
mutable u32 _current_bb_index{0}; ///< 当前后台缓冲区索引 mutable u32 _current_bb_index{0}; ///< 当前后台缓冲区索引
D3D12_VIEWPORT _viewport{}; ///< 视口描述 D3D12_VIEWPORT _viewport{}; ///< 视口描述

View File

@@ -1,14 +1,14 @@
# 变更记录Surface 管理重构与容器文档完善 # 变更记录Surface 管理重构、纹理资源与文档完善
**提交日期**: 2026-03-31 **提交日期**: 2026-04-01
**提交哈希**: `b284d81` **提交哈希**: `b72fcf4`
**变更类型**: Bug修复 + 文档完善 **变更类型**: Bug修复 + 功能新增 + 文档完善
--- ---
## 变更概述 ## 变更概述
本次提交修复了 surface 管理中的资源重复释放问题,重构为使用 `utl::free_list` 容器,并为 `FreeList.h``Vector.h` 添加了完整的国际标准中文注释 本次提交修复了 surface 管理中的资源重复释放问题,新增纹理资源类,并为容器和 D3D12 资源添加了完整的文档
## 修改文件 ## 修改文件
@@ -20,6 +20,14 @@
| `D3D12Surface.h` | 添加移动语义,禁用拷贝,支持撕裂检测 | | `D3D12Surface.h` | 添加移动语义,禁用拷贝,支持撕裂检测 |
| `D3D12Surface.cpp` | 完善交换链创建和资源释放逻辑 | | `D3D12Surface.cpp` | 完善交换链创建和资源释放逻辑 |
### 新增功能
| 文件 | 变更说明 |
|------|----------|
| `D3D12Helpers.h` | 新增堆属性辅助结构 `d3dx::heap_properties` |
| `D3D12Resources.h` | 新增 `d3d12_texture_init_info``d3d12_texture``d3d12_render_texture` 类 |
| `D3D12Resource.cpp` | 实现纹理资源创建和 SRV 绑定 |
### 文档完善 ### 文档完善
| 文件 | 变更说明 | | 文件 | 变更说明 |
@@ -208,6 +216,181 @@ vec.erase_unordered(index); // 适用于顺序不重要的场景
--- ---
## 交换链详解
### 核心职责
交换链连接窗口与渲染管线,管理一组后台缓冲区,并通过 `Present()` 实现缓冲区翻转,将绘制内容显示到窗口。
**交换链只负责**
- 缓冲区的分配与翻转
**开发者需显式完成**
- 绑定渲染目标(`OMSetRenderTargets`
- GPU 同步Fence
- 状态转换(资源屏障)
### 标准使用流程
```
1. 获取当前后台缓冲区GetBuffer
2. 将其绑定为渲染目标OMSetRenderTargets
3. 执行绘制命令(写入该缓冲区)
4. 提交命令并同步Fence
5. 调用 Present翻转缓冲区显示图像
```
### 职责边界
| 操作 | 负责方 | 说明 |
|------|--------|------|
| 缓冲区分配 | 交换链 | 创建指定数量的后台缓冲区 |
| 缓冲区翻转 | 交换链 | `Present()` 切换前后缓冲区 |
| 渲染目标绑定 | 开发者 | `OMSetRenderTargets()` 绑定 RTV |
| GPU 同步 | 开发者 | 使用 Fence 确保 GPU 完成渲染 |
| 状态转换 | 开发者 | 资源屏障管理缓冲区状态 |
| 窗口大小调整 | 开发者 | 调用 `ResizeBuffers()` 重新分配 |
---
## SRV 创建与资源创建方式
### CreateShaderResourceView 参数说明
```cpp
void CreateShaderResourceView(
ID3D12Resource *pResource,
const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc,
D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor
);
```
| 参数 | 说明 |
|------|------|
| `pResource` | 要创建 SRV 的 GPU 资源指针(必须是有效资源,不能为 nullptr |
| `pDesc` | 视图描述符指定格式、维度、Mip 级别范围等。为 `nullptr` 时使用资源默认属性 |
| `DestDescriptor` | SRV 描述符堆中 CPU 描述符句柄的位置 |
**关键点**
- `pDesc` 为 `nullptr` 时,视图描述符默认使用资源本身的格式和全部子资源(空描述符初始化)
- 同一个资源可以创建多个不同的 SRV不同格式、不同 Mip 切片)
### D3D12 资源创建函数对比
| 函数 | 堆类型 | 说明 |
|------|--------|------|
| `CreateCommittedResource` | 隐式堆 | D3D12 自动分配堆,资源直接映射。适用于大多数常规资源 |
| `CreatePlacedResource` | 显式堆 | 资源放置在用户创建的堆的特定偏移位置。用于精确控制内存布局 |
| `CreateReservedResource` | 预留资源 | 仅预留虚拟地址,不提交物理内存。用于稀疏资源,支持流式加载 |
**选择建议**
- **Committed**:最常用,堆由系统隐式管理
- **Placed**:需要显式堆,资源放置于堆的指定偏移
- **Reserved**:仅预留虚拟地址,用于稀疏资源,实现内存的按需提交
---
## 纹理资源类
### d3d12_texture_init_info 结构
纹理初始化信息结构,支持三种资源创建方式:
```cpp
struct d3d12_texture_init_info
{
ID3D12Heap1* heap{nullptr}; // 显式堆Placed Resource
ID3D12Resource* resource{nullptr}; // 已有资源(直接使用)
D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr}; // SRV 描述nullptr 使用默认)
D3D12_RESOURCE_DESC* desc{nullptr}; // 资源描述
D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; // 分配信息(偏移量)
D3D12_RESOURCE_STATES initial_state{}; // 初始状态
D3D12_CLEAR_VALUE clear_value{}; // 清除值RTV/DSV
};
```
### d3d12_texture 类
基础纹理类,封装资源创建和 SRV 绑定:
```cpp
class d3d12_texture
{
public:
constexpr static u32 max_mips{ 14 };
explicit d3d12_texture(d3d12_texture_init_info info);
// 移动语义
d3d12_texture(d3d12_texture&& o);
d3d12_texture& operator=(d3d12_texture&& o);
// 禁用拷贝
DISABLE_COPY(d3d12_texture);
void release();
ID3D12Resource* resource() const;
descriptor_handle srv() const;
private:
ID3D12Resource* _resource{nullptr};
descriptor_handle _srv;
};
```
### 资源创建逻辑
```cpp
d3d12_texture::d3d12_texture(d3d12_texture_init_info info)
{
// 优先级 1使用已有资源
if (info.resource) {
_resource = info.resource;
}
// 优先级 2Placed Resource显式堆
else if (info.heap && info.desc) {
device->CreatePlacedResource(
info.heap,
info.allocation_info.Offset,
info.desc, ...);
}
// 优先级 3Committed Resource隐式堆
else if (info.desc) {
device->CreateCommittedResource(
&d3dx::heap_properties.default_heap,
D3D12_HEAP_FLAG_NONE,
info.desc, ...);
}
// 创建 SRV
_srv = core::srv_heap().allocate();
device->CreateShaderResourceView(_resource, info.srv_desc, _srv.cpu);
}
```
### d3dx::heap_properties 辅助结构
```cpp
namespace XEngine::graphics::d3d12::d3dx
{
constexpr struct {
D3D12_HEAP_PROPERTIES default_heap{
D3D12_HEAP_TYPE_DEFAULT, // GPU 可读写CPU 不可访问
D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
D3D12_MEMORY_POOL_UNKNOWN,
0, // 单 GPU 系统
0
};
} heap_properties;
}
```
---
## 后续工作 ## 后续工作
- [ ] 实现深度模板视图 - [ ] 实现深度模板视图

View File

@@ -0,0 +1,212 @@
# 变更记录:渲染目标纹理与深度缓冲区实现
**提交日期**: 2026-04-01
**提交哈希**: `57afd12`
**变更类型**: 功能新增
---
## 变更概述
本次提交实现了 `d3d12_render_texture`(渲染目标纹理)和 `d3d12_depth_buffer`(深度缓冲区)类,为纹理资源类添加了析构函数。
## 修改文件
| 文件 | 变更说明 |
|------|----------|
| `D3D12Resources.h` | 为纹理类添加析构函数,完善 `d3d12_render_texture``d3d12_depth_buffer` 类定义 |
| `D3D12Resource.cpp` | 实现 `d3d12_render_texture``d3d12_depth_buffer` 构造与释放逻辑 |
---
## d3d12_render_texture 类
### 功能说明
渲染目标纹理类,支持多 Mip 级别的渲染目标视图RTV
```cpp
class d3d12_render_texture
{
public:
d3d12_render_texture() = default;
explicit d3d12_render_texture(d3d12_texture_init_info info);
~d3d12_render_texture() { release(); }
// 移动语义
d3d12_render_texture(d3d12_render_texture&& o);
d3d12_render_texture& operator=(d3d12_render_texture&& o);
// 禁用拷贝
DISABLE_COPY(d3d12_render_texture);
void release();
u32 mip_count() const;
D3D12_CPU_DESCRIPTOR_HANDLE rtv(u32 mip) const;
descriptor_handle srv() const;
ID3D12Resource* resource() const;
private:
d3d12_texture _texture{};
descriptor_handle _rtv[d3d12_texture::max_mips]{}; // 每个 Mip 一个 RTV
u32 _mip_count{0};
};
```
### 构造流程
```cpp
d3d12_render_texture::d3d12_render_texture(d3d12_texture_init_info info)
: _texture(info) // 先创建基础纹理
{
// 获取 Mip 级别数
_mip_count = resource()->GetDesc().MipLevels;
// 为每个 Mip 级别创建 RTV
D3D12_RENDER_TARGET_VIEW_DESC desc{};
desc.Format = info.desc->Format;
desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
for(u32 i = 0; i < _mip_count; ++i)
{
_rtv[i] = rtv_heap.allocate();
device->CreateRenderTargetView(resource(), &desc, _rtv[i].cpu);
++desc.Texture2D.MipSlice; // 下一个 Mip 切片
}
}
```
### 使用场景
- 离屏渲染Render-to-Texture
- 多级渐远纹理生成
- 后处理效果
---
## d3d12_depth_buffer 类
### 功能说明
深度缓冲区类封装深度模板视图DSV和着色器资源视图SRV
```cpp
class d3d12_depth_buffer
{
public:
d3d12_depth_buffer() = default;
explicit d3d12_depth_buffer(d3d12_texture_init_info info);
~d3d12_depth_buffer() { release(); }
// 移动语义
d3d12_depth_buffer(d3d12_depth_buffer&& o);
d3d12_depth_buffer& operator=(d3d12_depth_buffer&& o);
// 禁用拷贝
DISABLE_COPY(d3d12_depth_buffer);
void release();
D3D12_CPU_DESCRIPTOR_HANDLE dsv() const;
descriptor_handle srv() const;
ID3D12Resource* resource() const;
private:
d3d12_texture _texture{};
descriptor_handle _dsv{};
};
```
### 构造流程
```cpp
d3d12_depth_buffer::d3d12_depth_buffer(d3d12_texture_init_info info)
{
// 深度缓冲区需要特殊处理格式
// DSV 使用 D32_FLOATSRV 使用 R32_FLOAT
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc{};
if(info.desc->Format == DXGI_FORMAT_D32_FLOAT)
{
info.desc->Format = DXGI_FORMAT_R32_TYPELESS; // 资源使用无类型格式
srv_desc.Format = DXGI_FORMAT_R32_FLOAT; // SRV 使用浮点格式
}
// 创建纹理和 SRV
info.srv_desc = &srv_desc;
_texture = d3d12_texture(info);
// 创建 DSV
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc{};
dsv_desc.Format = DXGI_FORMAT_D32_FLOAT; // DSV 使用深度格式
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
_dsv = dsv_heap.allocate();
device->CreateDepthStencilView(resource(), &dsv_desc, _dsv.cpu);
}
```
### 格式转换说明
```
┌─────────────────────────────────────────────────────────────┐
│ 深度缓冲区格式处理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户指定格式DXGI_FORMAT_D32_FLOAT │
│ │ │
│ ▼ │
│ 资源格式DXGI_FORMAT_R32_TYPELESS无类型
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ DSV 格式D32_FLOAT SRV 格式R32_FLOAT │
│ (深度测试用) (着色器采样用) │
│ │
└─────────────────────────────────────────────────────────────┘
```
**为什么要这样处理?**
- 深度缓冲区需要作为着色器资源被采样如阴影映射、SSAO
- DSV 格式D32_FLOAT不能直接用于 SRV
- 使用 TYPELESS 格式允许同一资源创建不同格式的视图
---
## 析构函数添加
为所有纹理类添加了析构函数,确保资源自动释放:
```cpp
class d3d12_texture {
public:
~d3d12_texture() { release(); }
// ...
};
class d3d12_render_texture {
public:
~d3d12_render_texture() { release(); }
// ...
};
class d3d12_depth_buffer {
public:
~d3d12_depth_buffer() { release(); }
// ...
};
```
---
## 后续工作
- [ ] 实现根签名和管线状态对象
- [ ] 渲染第一个三角形
- [ ] 实现常量缓冲区
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -489,12 +489,444 @@ void allocate() {
} // 自动解锁 } // 自动解锁
``` ```
### 7.9 着色器资源视图SRV
#### CreateShaderResourceView 函数
```cpp
void CreateShaderResourceView(
ID3D12Resource *pResource,
const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc,
D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor
);
```
| 参数 | 说明 |
|------|------|
| `pResource` | 要创建 SRV 的 GPU 资源指针(必须是有效资源,不能为 nullptr |
| `pDesc` | 视图描述符指定格式、维度、Mip 级别范围等。为 `nullptr` 时使用资源默认属性 |
| `DestDescriptor` | SRV 描述符堆中 CPU 描述符句柄的位置 |
#### 空描述符初始化
`pDesc``nullptr` 时,视图描述符默认使用资源本身的格式和全部子资源:
```cpp
// 使用默认视图属性
device->CreateShaderResourceView(texture, nullptr, rtv_handle);
// 等价于显式指定完整描述
D3D12_SHADER_RESOURCE_VIEW_DESC desc{};
desc.Format = texture->GetDesc().Format;
desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
desc.Texture2D.MostDetailedMip = 0;
desc.Texture2D.MipLevels = texture->GetDesc().MipLevels;
device->CreateShaderResourceView(texture, &desc, rtv_handle);
```
#### 多视图支持
同一个资源可以创建多个不同的 SRV
```cpp
// 原始格式视图
device->CreateShaderResourceView(texture, nullptr, srv_handle0);
// 不同格式视图(如 R32_FLOAT 作为 RGBA 视图)
D3D12_SHADER_RESOURCE_VIEW_DESC rgba_desc{};
rgba_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
// ...
device->CreateShaderResourceView(texture, &rgba_desc, srv_handle1);
// 特定 Mip 切片视图
D3D12_SHADER_RESOURCE_VIEW_DESC mip_desc{};
mip_desc.Texture2D.MostDetailedMip = 2;
mip_desc.Texture2D.MipLevels = 1;
// ...
device->CreateShaderResourceView(texture, &mip_desc, srv_handle2);
```
### 7.10 资源创建方式
D3D12 提供三种资源创建函数,对应不同的堆管理策略:
#### 函数对比
| 函数 | 堆类型 | 说明 |
|------|--------|------|
| `CreateCommittedResource` | 隐式堆 | D3D12 自动分配堆,资源直接映射。适用于大多数常规资源 |
| `CreatePlacedResource` | 显式堆 | 资源放置在用户创建的堆的特定偏移位置。用于精确控制内存布局 |
| `CreateReservedResource` | 预留资源 | 仅预留虚拟地址,不提交物理内存。用于稀疏资源,支持流式加载 |
#### CreateCommittedResource最常用
```cpp
// 系统自动管理堆,最简单的方式
D3D12_HEAP_PROPERTIES heap_props{
.Type = D3D12_HEAP_TYPE_DEFAULT
};
D3D12_RESOURCE_DESC resource_desc{
.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
.Width = width,
.Height = height,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
// ...
};
ID3D12Resource* texture;
device->CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&resource_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&texture)
);
```
#### CreatePlacedResource精确控制
```cpp
// 先创建堆
ID3D12Heap* heap;
D3D12_HEAP_DESC heap_desc{
.SizeInBytes = heap_size,
.Properties = { .Type = D3D12_HEAP_TYPE_DEFAULT },
// ...
};
device->CreateHeap(&heap_desc, IID_PPV_ARGS(&heap));
// 在堆的特定偏移放置资源
ID3D12Resource* texture;
device->CreatePlacedResource(
heap,
0, // 偏移量(必须满足对齐要求)
&resource_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&texture)
);
```
**适用场景**
- 资源复用(同一堆位置放置不同资源)
- 精确内存对齐
- 自定义内存管理
#### CreateReservedResource稀疏资源
```cpp
// 仅预留虚拟地址,不分配物理内存
ID3D12Resource* sparse_texture;
device->CreateReservedResource(
&resource_desc,
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(&sparse_texture)
);
// 后续按需提交物理内存页面
// 使用 UpdateTileMappings 和 MakeResident
```
**适用场景**
- 超大纹理(如地形纹理)按需加载
- 流式资源管理
- 虚拟纹理系统
#### 选择建议
```
┌─────────────────────────────────────────────────────────────┐
│ 资源创建方式选择 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 常规纹理/缓冲区? │
│ │ │
│ ├── 是 ──► CreateCommittedResource │
│ │ (简单、自动管理) │
│ │ │
│ └── 否 ──► 需要精确内存控制? │
│ │ │
│ ├── 是 ──► CreatePlacedResource │
│ │ (资源复用、对齐控制) │
│ │ │
│ └── 否 ──► 超大资源按需加载? │
│ │ │
│ ├── 是 ──► │
│ │ CreateReservedResource│
│ │ (稀疏资源) │
│ │ │
│ └── 否 ──► 重新评估需求 │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 7.11 纹理资源类
#### d3d12_texture_init_info 结构
纹理初始化信息结构,统一管理三种资源创建方式的参数:
```cpp
struct d3d12_texture_init_info
{
ID3D12Heap1* heap{nullptr}; // 显式堆Placed Resource
ID3D12Resource* resource{nullptr}; // 已有资源(直接使用)
D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr}; // SRV 描述nullptr 使用默认)
D3D12_RESOURCE_DESC* desc{nullptr}; // 资源描述
D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; // 分配信息(偏移量)
D3D12_RESOURCE_STATES initial_state{}; // 初始状态
D3D12_CLEAR_VALUE clear_value{}; // 清除值RTV/DSV
};
```
#### d3d12_texture 类
基础纹理类,封装资源创建和 SRV 绑定:
```cpp
class d3d12_texture
{
public:
constexpr static u32 max_mips{ 14 };
explicit d3d12_texture(d3d12_texture_init_info info);
// 移动语义
d3d12_texture(d3d12_texture&& o);
d3d12_texture& operator=(d3d12_texture&& o);
// 禁用拷贝
DISABLE_COPY(d3d12_texture);
void release();
ID3D12Resource* resource() const;
descriptor_handle srv() const;
private:
ID3D12Resource* _resource{nullptr};
descriptor_handle _srv;
};
```
#### 资源创建优先级
```
┌─────────────────────────────────────────────────────────────┐
│ 纹理资源创建优先级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ info.resource != nullptr ? │
│ │ │
│ ├── 是 ──► 直接使用已有资源 │
│ │ (适用于外部管理的资源) │
│ │ │
│ └── 否 ──► info.heap != nullptr ? │
│ │ │
│ ├── 是 ──► CreatePlacedResource │
│ │ (显式堆,精确控制) │
│ │ │
│ └── 否 ──► CreateCommittedResource │
│ (隐式堆,最常用) │
│ │
└─────────────────────────────────────────────────────────────┘
```
#### d3dx::heap_properties 辅助结构
提供常用的堆属性配置:
```cpp
namespace XEngine::graphics::d3d12::d3dx
{
constexpr struct {
D3D12_HEAP_PROPERTIES default_heap{
D3D12_HEAP_TYPE_DEFAULT, // GPU 可读写CPU 不可访问
D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
D3D12_MEMORY_POOL_UNKNOWN,
0, // 单 GPU 系统
0
};
} heap_properties;
}
```
#### 使用示例
```cpp
// 创建默认纹理Committed Resource
D3D12_RESOURCE_DESC desc{
.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
.Width = 1024,
.Height = 1024,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
// ...
};
d3d12_texture_init_info info{
.desc = &desc,
.initial_state = D3D12_RESOURCE_STATE_COPY_DEST,
};
d3d12_texture texture{info};
// 获取资源用于后续操作
ID3D12Resource* resource = texture.resource();
descriptor_handle srv = texture.srv();
```
### 7.12 渲染目标纹理d3d12_render_texture
#### 功能说明
渲染目标纹理类,支持多 Mip 级别的渲染目标视图RTV用于离屏渲染
```cpp
class d3d12_render_texture
{
public:
explicit d3d12_render_texture(d3d12_texture_init_info info);
~d3d12_render_texture() { release(); }
void release();
u32 mip_count() const;
D3D12_CPU_DESCRIPTOR_HANDLE rtv(u32 mip) const; // 获取指定 Mip 的 RTV
descriptor_handle srv() const; // 获取 SRV
ID3D12Resource* resource() const;
private:
d3d12_texture _texture{};
descriptor_handle _rtv[d3d12_texture::max_mips]{}; // 每个 Mip 一个 RTV
u32 _mip_count{0};
};
```
#### 多 Mip RTV 创建
```cpp
d3d12_render_texture::d3d12_render_texture(d3d12_texture_init_info info)
: _texture(info)
{
_mip_count = resource()->GetDesc().MipLevels;
D3D12_RENDER_TARGET_VIEW_DESC desc{};
desc.Format = info.desc->Format;
desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
desc.Texture2D.MipSlice = 0;
for(u32 i = 0; i < _mip_count; ++i)
{
_rtv[i] = rtv_heap.allocate();
device->CreateRenderTargetView(resource(), &desc, _rtv[i].cpu);
++desc.Texture2D.MipSlice;
}
}
```
#### 使用场景
| 场景 | 说明 |
|------|------|
| 离屏渲染 | 将场景渲染到纹理而非屏幕 |
| 多级渐远纹理 | 生成 Mip Chain |
| 后处理 | 渲染结果作为后处理输入 |
### 7.13 深度缓冲区d3d12_depth_buffer
#### 功能说明
深度缓冲区类同时提供深度模板视图DSV和着色器资源视图SRV
```cpp
class d3d12_depth_buffer
{
public:
explicit d3d12_depth_buffer(d3d12_texture_init_info info);
~d3d12_depth_buffer() { release(); }
void release();
D3D12_CPU_DESCRIPTOR_HANDLE dsv() const; // 深度模板视图
descriptor_handle srv() const; // 着色器资源视图
ID3D12Resource* resource() const;
private:
d3d12_texture _texture{};
descriptor_handle _dsv{};
};
```
#### 格式转换处理
深度缓冲区需要特殊处理格式,以支持同时作为 DSV 和 SRV 使用:
```
┌─────────────────────────────────────────────────────────────┐
│ 深度缓冲区格式处理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户指定格式DXGI_FORMAT_D32_FLOAT │
│ │ │
│ ▼ │
│ 资源格式DXGI_FORMAT_R32_TYPELESS无类型
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ DSV 格式D32_FLOAT SRV 格式R32_FLOAT │
│ (深度测试用) (着色器采样用) │
│ │
└─────────────────────────────────────────────────────────────┘
```
#### 构造实现
```cpp
d3d12_depth_buffer::d3d12_depth_buffer(d3d12_texture_init_info info)
{
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc{};
if(info.desc->Format == DXGI_FORMAT_D32_FLOAT)
{
info.desc->Format = DXGI_FORMAT_R32_TYPELESS;
srv_desc.Format = DXGI_FORMAT_R32_FLOAT;
}
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = 1;
info.srv_desc = &srv_desc;
_texture = d3d12_texture(info);
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc{};
dsv_desc.Format = DXGI_FORMAT_D32_FLOAT;
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
_dsv = dsv_heap.allocate();
device->CreateDepthStencilView(resource(), &dsv_desc, _dsv.cpu);
}
```
#### 使用场景
| 场景 | 说明 |
|------|------|
| 深度测试 | 作为 DSV 用于深度缓冲 |
| 阴影映射 | 作为 SRV 采样深度值 |
| SSAO | 采样深度重建位置 |
## 8. 交换链Swap Chain ## 8. 交换链Swap Chain
### 8.1 什么是交换链? ### 8.1 什么是交换链?
交换链是 DXGI 提供的机制,用于管理前后缓冲区的交换,实现流畅的画面显示。 交换链是 DXGI 提供的机制,用于管理前后缓冲区的交换,实现流畅的画面显示。
**核心职责**
- **连接窗口与渲染管线**:将渲染输出与显示器关联
- **管理后台缓冲区**:分配和维护一组用于渲染的缓冲区
- **缓冲区翻转**:通过 `Present()` 实现前后缓冲区交换,将绘制内容显示到窗口
``` ```
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ 交换链工作原理 │ │ 交换链工作原理 │
@@ -520,7 +952,65 @@ void allocate() {
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
``` ```
### 8.2 三重缓冲 ### 8.2 交换链的职责边界
交换链**只负责**缓冲区的分配与翻转,以下操作需开发者显式完成:
| 操作 | 负责方 | 说明 |
|------|--------|------|
| 缓冲区分配 | 交换链 | 创建指定数量的后台缓冲区 |
| 缓冲区翻转 | 交换链 | `Present()` 切换前后缓冲区 |
| 渲染目标绑定 | 开发者 | `OMSetRenderTargets()` 绑定 RTV |
| GPU 同步 | 开发者 | 使用 Fence 确保 GPU 完成渲染 |
| 状态转换 | 开发者 | 资源屏障管理缓冲区状态 |
| 窗口大小调整 | 开发者 | 调用 `ResizeBuffers()` 重新分配 |
### 8.3 标准渲染流程
```
┌─────────────────────────────────────────────────────────────┐
│ 交换链标准使用流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 获取当前后台缓冲区 │
│ ┌─────────────────────────────────────────┐ │
│ │ ID3D12Resource* buffer = swap_chain-> │ │
│ │ GetBuffer(current_index); │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. 创建并绑定渲染目标视图 │
│ ┌─────────────────────────────────────────┐ │
│ │ device->CreateRenderTargetView( │ │
│ │ buffer, nullptr, rtv_handle); │ │
│ │ cmd_list->OMSetRenderTargets( │ │
│ │ 1, &rtv_handle, nullptr); │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. 执行绘制命令(写入后台缓冲区) │
│ ┌─────────────────────────────────────────┐ │
│ │ cmd_list->ClearRenderTargetView(...); │ │
│ │ cmd_list->DrawInstanced(...); │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. 提交命令并同步 │
│ ┌─────────────────────────────────────────┐ │
│ │ cmd_queue->ExecuteCommandLists(...); │ │
│ │ // 等待 GPU 完成Fence 同步) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 5. 呈现(翻转缓冲区) │
│ ┌─────────────────────────────────────────┐ │
│ │ swap_chain->Present(sync_interval, 0); │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 8.5 三重缓冲
项目使用三重缓冲(`frame_buffer_count = 3` 项目使用三重缓冲(`frame_buffer_count = 3`
@@ -530,7 +1020,7 @@ void allocate() {
| **提高并行性** | CPU 可提前录制多帧命令 | | **提高并行性** | CPU 可提前录制多帧命令 |
| **平滑帧率** | 缓冲区平滑帧时间波动 | | **平滑帧率** | 缓冲区平滑帧时间波动 |
### 8.3 d3d12_surface 类 ### 8.6 d3d12_surface 类
```cpp ```cpp
class d3d12_surface class d3d12_surface
@@ -559,7 +1049,7 @@ private:
}; };
``` ```
### 8.4 交换链创建流程 ### 8.7 交换链创建流程
```cpp ```cpp
void create_swap_chain(...) void create_swap_chain(...)
@@ -587,7 +1077,7 @@ void create_swap_chain(...)
} }
``` ```
### 8.5 视口与裁剪矩形 ### 8.8 视口与裁剪矩形
```cpp ```cpp
// 视口:定义光栅化区域 // 视口:定义光栅化区域
@@ -604,7 +1094,7 @@ D3D12_VIEWPORT viewport{
D3D12_RECT scissor_rect{0, 0, width, height}; D3D12_RECT scissor_rect{0, 0, width, height};
``` ```
### 8.6 Surface 管理与 free_list ### 8.9 Surface 管理与 free_list
#### 问题Vector 扩容导致的资源重复释放 #### 问题Vector 扩容导致的资源重复释放