feat: implement Fence synchronization for CPU-GPU frame sync

D3D12 Core:
- Add ID3D12Fence1 and fence_value to d3d12_command
- Add fence_event for CPU waiting
- Implement wait() in command_frame for frame sync
- Implement flush() to wait all frames complete
- Add fence_value tracking per frame
- Signal fence at end_frame with incremented value

TestRenderer:
- Call graphics::render() in run()

Documentation:
- Add changelog for Fence sync implementation
- Update D3D12 Wiki with Fence sync section
This commit is contained in:
SpecialX
2026-03-27 18:56:03 +08:00
parent 7da17ccadd
commit f1584ec3c6
5 changed files with 287 additions and 16 deletions

View File

@@ -0,0 +1,154 @@
# 变更记录Fence 同步机制实现
**提交日期**: 2026-03-27
**提交哈希**: `b00a906`
**变更类型**: 功能实现
---
## 变更概述
本次提交实现了 D3D12 Fence围栏同步机制完成 CPU-GPU 帧同步,确保命令列表执行顺序正确,避免资源冲突。
## 修改文件
### Engine/Graphics/Direct3D12/
| 文件 | 变更说明 |
|------|----------|
| `D3D12Core.cpp` | 添加 Fence 同步机制,实现 `wait()``flush()` 方法 |
### EngineTest/
| 文件 | 变更说明 |
|------|----------|
| `TestRenderer.cpp` | 在 `run()` 中调用 `graphics::render()` |
---
## 技术要点
### 1. Fence 对象
```cpp
ID3D12Fence1* _fence{ nullptr };
u64 _fence_value{ 0 };
HANDLE _fence_event{ nullptr };
```
- **Fence**: GPU 可设置的计数器,用于同步
- **Fence Value**: 64位无符号整数每帧递增
- **Fence Event**: Windows 事件对象,用于 CPU 等待
### 2. command_frame 结构
```cpp
struct command_frame
{
ID3D12CommandAllocator* cmd_allocator{ nullptr };
u64 fence_value{ 0 }; // 该帧的围栏值
void wait(HANDLE fence_event, ID3D12Fence1* fence);
};
```
每帧记录其围栏值,用于判断 GPU 是否完成该帧。
### 3. 帧同步等待
```cpp
void wait(HANDLE fence_event, ID3D12Fence1* fence)
{
if(fence->GetCompletedValue() < fence_value)
{
fence->SetEventOnCompletion(fence_value, fence_event);
WaitForSingleObject(fence_event, INFINITE);
}
}
```
- 检查 GPU 是否完成到目标围栏值
- 未完成则设置事件并等待
### 4. 帧结束信号
```cpp
void end_frame()
{
// ... 提交命令列表 ...
++_fence_value;
_cmd_frames[_frame_index].fence_value = _fence_value;
_cmd_queue->Signal(_fence, _fence_value);
_frame_index = (_frame_index + 1) % frame_buffer_count;
}
```
- 递增围栏值
- 记录当前帧的围栏值
- 向 GPU 发送信号
### 5. flush 方法
```cpp
void flush()
{
for(u32 i{ 0 }; i < frame_buffer_count; ++i)
{
_cmd_frames[i].wait(_fence_event, _fence);
}
_frame_index = 0;
}
```
等待所有帧完成,用于资源释放前确保 GPU 完成。
---
## 同步流程
```
begin_frame()
├─► 检查当前帧的 fence_value
└─► 如果 GPU 未完成CPU 等待
└─► 重置分配器和命令列表
end_frame()
├─► 提交命令列表
├─► ++fence_value
├─► 记录当前帧的 fence_value
├─► Signal(fence, fence_value)
└─► 递增帧索引
```
---
## 围栏值溢出问题
```cpp
// 64位无符号整数即便每秒1000帧也需要5.8亿年才能回绕
u64 _fence_value{ 0 };
```
无需担心溢出问题。
---
## 后续工作
- [ ] 交换链实现
- [ ] 描述符堆
- [ ] 渲染目标视图
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)

View File

@@ -16,6 +16,7 @@ changelogs/
| 日期 | 提交 | 变更内容 |
|------|------|----------|
| 2026-03-27 | [Fence同步机制](./2026-03/20260327-d3d12-fence-sync.md) | D3D12 Fence CPU-GPU 帧同步实现 |
| 2026-03-26 | [命令队列与多帧缓冲](./2026-03/20260326-d3d12-command-queue.md) | D3D12 命令队列和多帧渲染架构 |
| 2026-03-26 | [D3D12设备初始化](./2026-03/20260326-d3d12-device-init.md) | D3D12 设备创建与调试层实现 |
| 2026-03-26 | [Graphics模块](./2026-03/20260326-d3d12-foundation.md) | Graphics 模块与 D3D12 后端框架 |

View File

@@ -211,6 +211,33 @@ _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 render_surface 结构