# Direct3D 12 学习 Wiki ## 概述 本文档是项目中 Direct3D 12 学习的基础知识汇总,帮助理解 D3D12 的核心概念和项目中的实现方式。 ## 1. D3D12 核心概念 ### 1.1 什么是 Direct3D 12? Direct3D 12 是微软推出的底层图形 API,相比 D3D11,它提供了: - **更底层的硬件控制**:开发者可以更精细地控制 GPU - **更低的 CPU 开销**:减少驱动程序的 CPU 时间 - **更好的多线程支持**:支持多线程命令录制 - **显式的资源管理**:开发者完全控制资源生命周期 ### 1.2 核心组件 | 组件 | 说明 | |------|------| | **Device(设备)** | 代表物理 GPU 的逻辑抽象,用于创建所有 D3D12 对象 | | **Command Queue(命令队列)** | GPU 执行命令的队列 | | **Command List(命令列表)** | 记录 GPU 命令的容器 | | **Swap Chain(交换链)** | 管理后台缓冲区和前台显示 | | **Descriptor Heap(描述符堆)** | 存储资源描述符(视图)的内存池 | | **Root Signature(根签名)** | 定义着色器如何访问资源 | ## 2. DXGI(DirectX Graphics Infrastructure) ### 2.1 DXGI 的作用 DXGI 是 DirectX 与图形硬件之间的抽象层,负责: - 枚举显示适配器 - 管理显示输出 - 创建交换链 - 处理全屏/窗口切换 ### 2.2 关键接口 ```cpp IDXGIFactory6 // DXGI 工厂,创建其他 DXGI 对象 IDXGIAdapter4 // 代表物理 GPU 适配器 IDXGIOutput // 代表显示器输出 IDXGISwapChain // 交换链接口 ``` ### 2.3 项目中的使用 在 [D3D12CommonHeader.h](file:///d:/AllWX/AllC/FeatureExtractDemo/Engine/Graphics/Direct3D12/D3D12CommonHeader.h) 中引入了 `dxgi_6.h`: ```cpp #include // DXGI 6.0,支持枚举 GPU、查询显示模式 ``` ## 3. D3D12 初始化流程 ### 3.1 标准初始化步骤 ``` 1. 创建 DXGI Factory ↓ 2. 枚举并选择适配器(GPU) ↓ 3. 创建 D3D12 Device ↓ 4. 创建命令队列 ↓ 5. 创建交换链 ↓ 6. 创建描述符堆 ↓ 7. 创建命令分配器和命令列表 ↓ 8. 创建同步对象(Fence) ``` ### 3.2 项目当前状态 [D3D12Core.cpp](file:///d:/AllWX/AllC/FeatureExtractDemo/Engine/Graphics/Direct3D12/D3D12Core.cpp) 已实现完整的设备初始化: ```cpp namespace { ID3D12Device8* main_device{ nullptr }; IDXGIFactory7* dxgi_factory{ nullptr }; } bool initialize() { // 1. 启用调试层 (DEBUG 模式) // 2. 创建 DXGI 工厂 // 3. 枚举并选择 GPU 适配器 // 4. 获取最高特性级别 // 5. 创建 D3D12 设备 // 6. 配置信息队列 (DEBUG 模式) } ``` ### 3.3 调试宏 项目定义了两个重要的调试宏: ```cpp // DXCall - 检查 HRESULT 并断点 #define DXCall(x) if(FAILED(x)) { ... __debugbreak(); } // NAME_D3D12_OBJECT - 为对象设置调试名称 #define NAME_D3D12_OBJECT(obj, name) obj->SetName(name); ``` ## 4. COM 对象管理 ### 4.1 什么是 COM? COM(Component Object Model)是微软的组件对象模型,D3D12 对象都是 COM 对象。 ### 4.2 智能指针 项目使用 WRL 库的 `ComPtr` 管理 COM 对象生命周期: ```cpp #include // 提供 Microsoft::WRL::ComPtr // 使用示例 Microsoft::WRL::ComPtr device; ``` ### 4.3 ComPtr 的优势 - **自动引用计数**:无需手动 AddRef/Release - **异常安全**:即使发生异常也能正确释放 - **代码简洁**:减少内存管理代码 ## 5. 平台抽象设计 ### 5.1 设计理念 项目采用**平台抽象层**设计,将图形 API 的差异封装在统一接口后: ``` ┌────────────────────────────────────┐ │ 上层渲染代码 │ │ graphics::initialize(platform) │ └──────────────┬─────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ platform_interface │ │ - initialize() │ │ - shutdown() │ └──────────────┬─────────────────────┘ │ ┌──────────┼──────────┐ ▼ ▼ ▼ ┌───────┐ ┌───────┐ ┌───────┐ │ D3D12 │ │Vulkan │ │OpenGL │ └───────┘ └───────┘ └───────┘ ``` ### 5.2 接口绑定机制 ```cpp // D3D12Interface.cpp void get_platform_interface(platform_interface& pi) { pi.initialize = core::initialize; pi.shutdown = core::shutdown; pi.render = core::render; } ``` 这种设计允许: - 编译时或运行时切换图形后端 - 各后端独立开发和测试 - 上层代码与具体 API 解耦 ## 6. 命令队列与多帧缓冲 ### 6.1 d3d12_command 类 项目实现了命令队列管理类,支持多帧缓冲渲染: ```cpp class d3d12_command { void begin_frame(); // 等待帧完成,重置分配器和命令列表 void end_frame(); // 关闭命令列表,提交执行 private: ID3D12CommandQueue* _cmd_queue; ID3D12GraphicsCommandList6* _cmd_list; command_frame _cmd_frames[frame_buffer_count]; u32 _frame_index; }; ``` ### 6.2 多帧缓冲原理 ```cpp constexpr u32 frame_buffer_count{ 3 }; ``` 采用三重缓冲设计: - CPU 提前录制命令 - GPU 异步执行 - 最大化硬件利用率 ### 6.3 帧索引轮转 ```cpp _frame_index = (_frame_index + 1) % frame_buffer_count; ``` 环形缓冲区管理帧资源,确保 CPU 不会超前 GPU 超过 3 帧。 ### 6.4 Fence 同步机制 项目实现了 Fence(围栏)同步,确保 CPU-GPU 帧同步: ```cpp struct command_frame { ID3D12CommandAllocator* cmd_allocator{ nullptr }; u64 fence_value{ 0 }; // 该帧的围栏值 void wait(HANDLE fence_event, ID3D12Fence1* fence); }; ``` **同步流程**: 1. `begin_frame()` - 检查 GPU 是否完成当前帧,未完成则等待 2. `end_frame()` - 递增围栏值,向 GPU 发送信号 ```cpp // 帧结束信号 ++_fence_value; _cmd_frames[_frame_index].fence_value = _fence_value; _cmd_queue->Signal(_fence, _fence_value); ``` **围栏值溢出**:64位无符号整数,每秒1000帧需要5.8亿年才回绕,无需担心。 ## 7. 描述符堆 ### 7.1 什么是描述符堆? 描述符堆是一块连续内存,用于存储描述符(Descriptor)。描述符是告诉 GPU 如何访问资源的数据结构。 ### 7.2 描述符堆类型 | 类型 | 用途 | 着色器可见 | |------|------|------------| | `CBV_SRV_UAV` | 常量缓冲区、着色器资源、无序访问 | 可选 | | `SAMPLER` | 采样器 | 可选 | | `RTV` | 渲染目标视图 | 否 | | `DSV` | 深度模板视图 | 否 | ### 7.3 descriptor_handle 结构 项目封装了描述符句柄: ```cpp struct descriptor_handle { D3D12_CPU_DESCRIPTOR_HANDLE cpu{}; // CPU 句柄 D3D12_GPU_DESCRIPTOR_HANDLE gpu{}; // GPU 句柄 constexpr bool is_valid() const { return cpu.ptr != 0; } constexpr bool is_shader_visible() const { return gpu.ptr != 0; } }; ``` ### 7.4 descriptor_heap 类 ```cpp class descriptor_heap { bool initialize(u32 capacity, bool is_shader_visible); descriptor_handle allocate(); // 分配描述符 void free(descriptor_handle); // 释放描述符 private: ID3D12DescriptorHeap* _heap; std::unique_ptr _free_handles{}; // 空闲索引池 std::mutex _mutex; // 线程安全 }; ``` ### 7.5 内存模型 ``` 描述符堆内存布局: ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │...│ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ ↑ _cpu_start / _gpu_start _free_handles[] = [0, 1, 2, 3, ...] // 空闲索引池 ``` ### 7.6 FreeList 栈式索引管理 #### 核心数据结构 ```cpp // 空闲索引栈(预分配数组) std::unique_ptr _free_handles{}; // 栈顶指针(同时也是已分配数量) u32 _size{0}; // 总容量 u32 _capacity{0}; ``` #### 工作原理 `_free_handles` 是一个**预分配数组**,同时充当**栈**的角色: | 概念 | 说明 | |------|------| | `_free_handles` | 存储所有可用索引的数组 | | `_size` | 已分配数量 + 栈顶指针 | | 分配 | `_free_handles[_size++]`,从栈顶弹出 | | 释放 | `_free_handles[--_size] = index`,压入栈顶 | #### 状态演变示例 ``` 初始化状态(capacity = 5): ┌─────────────────────────────────────┐ │ _free_handles = [0, 1, 2, 3, 4] │ │ _size = 0 (栈顶指向位置 0) │ │ 可用索引: 0, 1, 2, 3, 4 │ └─────────────────────────────────────┘ 分配 2 个描述符后: ┌─────────────────────────────────────┐ │ _free_handles = [0, 1, 2, 3, 4] │ │ _size = 2 (栈顶指向位置 2) │ │ 已分配索引: 0, 1 │ │ 可用索引: 2, 3, 4 │ └─────────────────────────────────────┘ 释放索引 0 后(延迟处理完成): ┌─────────────────────────────────────┐ │ _free_handles = [0, 0, 2, 3, 4] │ │ _size = 1 (栈顶指向位置 1) │ │ 已分配索引: 1 │ │ 可用索引: 0, 2, 3, 4 │ └─────────────────────────────────────┘ ``` #### 分配算法 ```cpp descriptor_handle allocate() { std::lock_guard lock(_mutex); // 从栈顶取出索引 const u32 index = _free_handles[_size]; ++_size; // 栈顶指针上移 // 计算句柄地址 const u32 offset = index * _descriptor_size; handle.cpu.ptr = _cpu_start.ptr + offset; return handle; } ``` **时间复杂度**: O(1) #### 释放算法(延迟释放) ```cpp // 释放时不立即回收,放入延迟队列 void free(descriptor_handle handle) { const u32 frame_index = current_frame_index(); _deferred_free_indices[frame_index].push_back(handle.index); } // GPU 完成帧后处理延迟释放 void process_deferred_release(u32 frame_index) { for(auto index : _deferred_free_indices[frame_index]) { --_size; // 栈顶指针下移 _free_handles[_size] = index; // 索引压入栈顶 } } ``` #### 设计优势 | 特性 | 说明 | |------|------| | **O(1) 分配** | 栈顶弹出,无需遍历查找 | | **O(1) 释放** | 栈顶压入,无需查找位置 | | **内存高效** | 预分配数组,无动态分配开销 | | **简单可靠** | 栈结构天然保证索引不重复 | ### 7.7 延迟释放机制 #### 为什么需要延迟释放? ``` 问题场景(无延迟释放): 帧 0: CPU 分配描述符索引 0 → 绑定纹理 A 帧 1: CPU 释放描述符索引 0 → 立即重用 → 绑定纹理 B 帧 0: GPU 还在执行,访问描述符索引 0 → 读到纹理 B 的数据! ↑ GPU 危险!数据竞争! ``` #### 解决方案 ``` 帧 0: CPU 分配描述符索引 0 → 绑定纹理 A 帧 1: CPU 释放描述符索引 0 → 放入延迟队列(帧 1) 帧 2: CPU 继续工作... 帧 0: GPU 完成帧 0 的执行(Fence 同步确认) 帧 3: CPU 处理帧 0 的延迟释放 → 索引 0 回到空闲池 ↑ 安全!GPU 已完成使用 ``` #### 数据结构 ```cpp // 每帧一个延迟释放队列 utl::vector _deferred_free_indices[frame_buffer_count]{}; // 全局延迟释放资源队列(用于 COM 对象) utl::vector deferred_releases[frame_buffer_count]{}; // 延迟释放标志 u32 deferred_release_flag[frame_buffer_count]{}; ``` #### 完整流程图 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 初始化 │ │ _free_handles = [0, 1, 2, 3, 4], _size = 0 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ allocate() │ │ index = _free_handles[_size] // 取索引 0 │ │ _size++ // _size = 1 │ │ 返回描述符句柄(索引 0) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ free(索引 0) │ │ 放入延迟队列: _deferred_free_indices[当前帧].push_back(0) │ │ _size 不变,_free_handles 不变 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ GPU 完成帧(Fence 同步) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ process_deferred_release(frame_index) │ │ --_size // _size = 0 │ │ _free_handles[_size] = 0 // 索引 0 压入栈顶 │ │ _free_handles = [0, 1, 2, 3, 4] // 索引 0 可重用 │ └─────────────────────────────────────────────────────────────────┘ ``` ### 7.8 线程安全 描述符堆可能被多线程并发访问,使用互斥锁保护: ```cpp std::mutex _mutex{}; void allocate() { std::lock_guard lock(_mutex); // 自动加锁 // ... 操作 } // 自动解锁 ``` ### 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.1 什么是交换链? 交换链是 DXGI 提供的机制,用于管理前后缓冲区的交换,实现流畅的画面显示。 **核心职责**: - **连接窗口与渲染管线**:将渲染输出与显示器关联 - **管理后台缓冲区**:分配和维护一组用于渲染的缓冲区 - **缓冲区翻转**:通过 `Present()` 实现前后缓冲区交换,将绘制内容显示到窗口 ``` ┌─────────────────────────────────────────────────────────────┐ │ 交换链工作原理 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 后台缓冲 │ │ 后台缓冲 │ │ 后台缓冲 │ │ │ │ 0 │ │ 1 │ │ 2 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ └─────────────┼─────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ Present() │ │ │ └──────┬───────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ 前台缓冲 │ ───► 显示器 │ │ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 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`): | 特性 | 说明 | |------|------| | **减少撕裂** | 前台缓冲独立于后台缓冲,避免部分更新 | | **提高并行性** | CPU 可提前录制多帧命令 | | **平滑帧率** | 缓冲区平滑帧时间波动 | ### 8.6 d3d12_surface 类 ```cpp class d3d12_surface { public: // 创建交换链 void create_swap_chain(IDXGIFactory7* factory, ID3D12CommandQueue* cmd_queue, DXGI_FORMAT format); // 呈现当前帧 void present() const; // 调整大小 void resize(); // 获取当前后台缓冲区 ID3D12Resource* back_buffer() const; descriptor_handle rtv() const; private: IDXGISwapChain4* _swap_chain; render_target_data _render_target_data[frame_buffer_count]; D3D12_VIEWPORT _viewport; D3D12_RECT _scissor_rect; }; ``` ### 8.7 交换链创建流程 ```cpp void create_swap_chain(...) { // 1. 配置描述 DXGI_SWAP_CHAIN_DESC1 desc{}; desc.BufferCount = frame_buffer_count; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.Width = window.width(); desc.Height = window.height(); desc.SampleDesc.Count = 1; // 禁用 MSAA // 2. 创建交换链 factory->CreateSwapChainForHwnd(cmd_queue, hwnd, &desc, ...); // 3. 禁用 Alt+Enter(由应用处理) factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER); // 4. 创建渲染目标视图 for(u32 i = 0; i < frame_buffer_count; ++i) { _swap_chain->GetBuffer(i, &resource); device->CreateRenderTargetView(resource, &desc, rtv.cpu); } } ``` ### 8.8 视口与裁剪矩形 ```cpp // 视口:定义光栅化区域 D3D12_VIEWPORT viewport{ .TopLeftX = 0.0f, .TopLeftY = 0.0f, .Width = (float)width, .Height = (float)height, .MinDepth = 0.0f, .MaxDepth = 1.0f }; // 裁剪矩形:定义像素输出区域 D3D12_RECT scissor_rect{0, 0, width, height}; ``` ### 8.9 Surface 管理与 free_list #### 问题:Vector 扩容导致的资源重复释放 使用 `utl::vector` 管理 surface 时,扩容会触发元素移动: ``` vector 扩容流程: 1. 分配新内存块 2. 移动元素到新内存(默认移动是浅拷贝) 3. 析构旧位置的元素 → 调用 release() 4. 新位置的元素持有悬空指针 → 崩溃! ``` #### 解决方案:使用 free_list `utl::free_list` 是带槽位复用机制的容器: ```cpp // 定义 surface 集合类型 using surface_collection = utl::free_list; surface_collection surfaces; // 创建 surface surface create_surface(platform::window window) { surfaces.emplace_back(window); surface_id id{ (u32)surfaces.size() - 1 }; surfaces[id].create_swap_chain(...); return surface{id}; } // 删除 surface(槽位被回收) void remove_surface(surface_id id) { gfx_command.flush(); surfaces.remove(id); } ``` #### free_list 数据结构 ``` 初始状态: _array: [ 空 | 空 | 空 | 空 ] _next_free_index = invalid_id 添加元素 A、B、C 后: _array: [ A | B | C | 空 ] _next_free_index = invalid_id 删除元素 B 后: _array: [ A | ->2 | C | 空 ] // 槽位1存储下一个空闲索引 _next_free_index = 1 添加新元素 D: _array: [ D | ->2 | C | 空 ] // 复用槽位1 _next_free_index = 2 ``` #### free_list vs vector | 特性 | free_list | vector | |------|-----------|--------| | 删除复杂度 | O(1) | O(n) | | 索引稳定性 | 删除后可复用 | 删除后失效 | | 内存管理 | 槽位复用 | 可能扩容移动 | | 适用场景 | 资源句柄管理 | 顺序数据存储 | ## 9. 渲染表面与窗口 ### 9.1 render_surface 结构 ```cpp struct render_surface { platform::window window{}; // 平台窗口 surface surface{}; // 渲染表面 }; ``` ### 9.2 多窗口支持 TestRenderer 测试展示了多窗口渲染: ```cpp graphics::render_surface _surfaces[4]; // 支持 4 个窗口 // 创建多个渲染表面 for (u32 i{0}; i < _countof(_surfaces); ++i) { create_render_surface(_surfaces[i], info[i]); } ``` ### 9.3 全屏切换 通过 `WM_SYSCHAR` 消息处理 Alt+Enter: ```cpp if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN)) { win.set_fullscreen(!win.is_fullscreen()); } ``` ## 10. 后续学习路径 ### 10.1 基础阶段 - [x] 完成设备创建和适配器枚举 - [x] 创建命令队列和命令列表 - [x] 描述符堆管理 - [x] 实现交换链和后台缓冲区 - [ ] 渲染第一个三角形 ### 10.2 进阶阶段 - [ ] 根签名和管线状态对象 - [ ] 资源屏障和同步 - [ ] 常量缓冲区和着色器资源 ### 10.3 高级阶段 - [ ] 多线程渲染 - [ ] 资源绑定策略 - [ ] 动态资源管理 - [ ] 性能优化 ## 11. 参考资源 ### 11.1 官方文档 - [Microsoft D3D12 文档](https://docs.microsoft.com/en-us/windows/win32/direct3d12/direct3d-12-graphics) - [DXGI 文档](https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dx-graphics-dxgi) ### 11.2 推荐书籍 - 《Introduction to 3D Game Programming with DirectX 12》 - 《Real-Time 3D Rendering with DirectX and HLSL》 ### 11.3 项目相关文档 - [Graphics渲染架构分析](./Graphics渲染架构分析.md) - [项目约定规范](./项目约定规范.md)