# 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.h>  // 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 <wrl.h>  // 提供 Microsoft::WRL::ComPtr

// 使用示例
Microsoft::WRL::ComPtr<ID3D12Device8> 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<u32[]> _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<u32[]> _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<u32> _deferred_free_indices[frame_buffer_count]{};

// 全局延迟释放资源队列（用于 COM 对象）
utl::vector<IUnknown*> 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);  // 自动加锁
    // ... 操作
}  // 自动解锁
```

## 8. 交换链（Swap Chain）

### 8.1 什么是交换链？

交换链是 DXGI 提供的机制，用于管理前后缓冲区的交换，实现流畅的画面显示。

```
┌─────────────────────────────────────────────────────────────┐
│                      交换链工作原理                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐                │
│   │ 后台缓冲 │  │ 后台缓冲 │  │ 后台缓冲 │                │
│   │    0     │  │    1     │  │    2     │                │
│   └────┬─────┘  └────┬─────┘  └────┬─────┘                │
│        │             │             │                       │
│        └─────────────┼─────────────┘                       │
│                      │                                     │
│                      ▼                                     │
│              ┌──────────────┐                              │
│              │   Present()  │                              │
│              └──────┬───────┘                              │
│                     │                                      │
│                     ▼                                      │
│              ┌──────────────┐                              │
│              │  前台缓冲    │  ───► 显示器                 │
│              └──────────────┘                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 8.2 三重缓冲

项目使用三重缓冲（`frame_buffer_count = 3`）：

| 特性 | 说明 |
|------|------|
| **减少撕裂** | 前台缓冲独立于后台缓冲，避免部分更新 |
| **提高并行性** | CPU 可提前录制多帧命令 |
| **平滑帧率** | 缓冲区平滑帧时间波动 |

### 8.3 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.4 交换链创建流程

```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.5 视口与裁剪矩形

```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.6 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<d3d12_surface>;
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)
