docs: 添加 D3D12 后端测试设计文档

This commit is contained in:
2026-03-17 02:32:52 +08:00
parent 0418c61db6
commit 2342e3fbfc

View File

@@ -0,0 +1,596 @@
# D3D12 后端测试设计
## 1. 概述
本文档描述 XCEngine D3D12 渲染后端的测试框架设计,旨在为 D3D12 各组件提供全面、规范的自动化测试覆盖。
### 1.1 测试目标
- 验证 D3D12 各组件 API 的正确性
- 确保组件间的协作正常工作
- 捕获资源泄漏和内存错误
- 支持持续集成CI自动化测试
### 1.2 现有问题
当前 `tests/D3D12/main.cpp` 存在以下问题:
| 问题 | 说明 |
|------|------|
| 非自动化测试 | 截图和对比需手动触发 |
| 缺乏单元测试 | 只有一个 Win32 图形程序,无法使用 Google Test |
| 维护困难 | GT.ppm 是纯黑色图像,对比无实际意义 |
| 容差问题 | 1% 阈值对硬件差异过于敏感 |
## 2. 测试目录结构
```
tests/D3D12/
├── CMakeLists.txt # 构建配置(需改造为 Google Test
├── fixtures/
│ └── D3D12TestFixture.h # 基础测试夹具
├── test_device.cpp # D3D12Device 测试
├── test_command_queue.cpp # D3D12CommandQueue 测试
├── test_command_allocator.cpp # D3D12CommandAllocator 测试
├── test_command_list.cpp # D3D12CommandList 测试
├── test_buffer.cpp # D3D12Buffer 测试
├── test_texture.cpp # D3D12Texture 测试
├── test_descriptor_heap.cpp # D3D12DescriptorHeap 测试
├── test_pipeline_state.cpp # D3D12PipelineState 测试
├── test_root_signature.cpp # D3D12RootSignature 测试
├── test_fence.cpp # D3D12Fence 测试
├── test_swap_chain.cpp # D3D12SwapChain 测试(需窗口)
├── test_shader.cpp # D3D12Shader 测试
├── test_views.cpp # RTV/DSV/SRV/UAV 测试
└── test_screenshot.cpp # 渲染结果测试
```
## 3. 测试夹具设计
### 3.1 基础夹具
```cpp
// fixtures/D3D12TestFixture.h
#pragma once
#include <gtest/gtest.h>
#include <d3d12.h>
#include <wrl/client.h>
using namespace Microsoft::WRL;
class D3D12TestFixture : public ::testing::Test {
protected:
static void SetUpTestSuite() {
// 创建全局 D3D12 设备(所有测试共享)
HRESULT hr = D3D12CreateDevice(
nullptr, // 默认适配器
D3D_FEATURE_LEVEL_12_0, // 最低支持特性等级
IID_PPV_ARGS(&mDevice)
);
ASSERT_TRUE(SUCCEEDED(hr)) << "Failed to create D3D12 device";
}
static void TearDownTestSuite() {
mDevice.Reset();
}
void SetUp() override {
// 创建命令队列
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
HRESULT hr = mDevice->CreateCommandQueue(
&queueDesc,
IID_PPV_ARGS(&mCommandQueue)
);
ASSERT_TRUE(SUCCEEDED(hr));
// 创建命令分配器
hr = mDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(&mCommandAllocator)
);
ASSERT_TRUE(SUCCEEDED(hr));
// 创建命令列表
hr = mDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mCommandAllocator.Get(),
nullptr,
IID_PPV_ARGS(&mCommandList)
);
ASSERT_TRUE(SUCCEEDED(hr));
}
void TearDown() override {
// 等待所有命令执行完成
WaitForGPU();
mCommandList.Reset();
mCommandAllocator.Reset();
mCommandQueue.Reset();
}
// 常用辅助方法
ID3D12Device* GetDevice() { return mDevice.Get(); }
ID3D12CommandQueue* GetCommandQueue() { return mCommandQueue.Get(); }
ID3D12CommandList* GetCommandList() { return mCommandList.Get(); }
void WaitForGPU() {
ComPtr<ID3D12Fence> fence;
UINT64 fenceValue = 1;
HRESULT hr = mDevice->CreateFence(
0,
D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(&fence)
);
if (SUCCEEDED(hr)) {
mCommandQueue->Signal(fence.Get(), fenceValue);
HANDLE eventHandle = CreateEvent(nullptr, FALSE, FALSE, nullptr);
fence->SetEventOnCompletion(fenceValue, eventHandle);
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
private:
static ComPtr<ID3D12Device> mDevice;
ComPtr<ID3D12CommandQueue> mCommandQueue;
ComPtr<ID3D12CommandAllocator> mCommandAllocator;
ComPtr<ID3D12CommandList> mCommandList;
};
// 静态成员定义
ComPtr<ID3D12Device> D3D12TestFixture::mDevice;
```
### 3.2 资源测试夹具
```cpp
// fixtures/D3D12ResourceFixture.h
#pragma once
#include "D3D12TestFixture.h"
class D3D12ResourceFixture : public D3D12TestFixture {
protected:
void CreateUploadBuffer(size_t size, ID3D12Resource** outResource) {
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(size);
HRESULT hr = GetDevice()->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(outResource)
);
ASSERT_TRUE(SUCCEEDED(hr));
}
void CreateDefaultBuffer(size_t size, ID3D12Resource** outResource) {
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(size);
HRESULT hr = GetDevice()->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(outResource)
);
ASSERT_TRUE(SUCCEEDED(hr));
}
};
```
## 4. 测试分类
### 4.1 单元测试
测试单一 API 的基本功能,不依赖图形硬件渲染结果。
```cpp
TEST(D3D12_Buffer, Create_ValidSize_ReturnsSuccess) {
const size_t bufferSize = 1024;
D3D12Buffer buffer;
bool result = buffer.Initialize(GetDevice(), bufferSize,
D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_HEAP_TYPE_UPLOAD);
ASSERT_TRUE(result);
ASSERT_NE(buffer.GetResource(), nullptr);
ASSERT_EQ(buffer.GetResource()->GetDesc().Width, bufferSize);
}
TEST(D3D12_Buffer, Map_ValidRange_ReturnsValidPointer) {
D3D12Buffer buffer;
buffer.Initialize(GetDevice(), 256, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_HEAP_TYPE_UPLOAD);
void* mappedData = buffer.Map(0, nullptr);
ASSERT_NE(mappedData, nullptr);
// 写入测试数据
memset(mappedData, 0xAB, 256);
buffer.Unmap(0, nullptr);
}
TEST(D3D12_DescriptorHeap, Create_RTVHeap_ReturnsValidHeap) {
D3D12DescriptorHeap heap;
bool result = heap.Initialize(GetDevice(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 4);
ASSERT_TRUE(result);
ASSERT_NE(heap.GetDescriptorHeap(), nullptr);
}
```
### 4.2 集成测试
测试多个组件的协作,例如 Buffer 数据上传到 GPU。
```cpp
TEST(D3D12_Buffer, UploadToGPU_DataIntegrity) {
const size_t dataSize = sizeof(float) * 4;
float testData[] = { 1.0f, 2.0f, 3.0f, 4.0f };
// 创建默认缓冲GPU 只读)
D3D12Buffer gpuBuffer;
gpuBuffer.Initialize(GetDevice(), dataSize, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_HEAP_TYPE_DEFAULT);
// 创建上传缓冲CPU 可写)
D3D12Buffer uploadBuffer;
uploadBuffer.Initialize(GetDevice(), dataSize, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_HEAP_TYPE_UPLOAD);
// 复制数据到上传缓冲
void* mappedData = uploadBuffer.Map(0, nullptr);
memcpy(mappedData, testData, dataSize);
uploadBuffer.Unmap(0, nullptr);
// 通过命令列表复制
GetCommandList()->CopyBufferRegion(
gpuBuffer.GetResource(), 0,
uploadBuffer.GetResource(), 0,
dataSize
);
// 执行并等待
GetCommandList()->Close();
ID3D12CommandList* cmdLists[] = { GetCommandList() };
GetCommandQueue()->ExecuteCommandLists(1, cmdLists);
WaitForGPU();
// 验证:使用映射读取返回的数据
// (需要转换状态到 GENERIC_READ
}
```
### 4.3 渲染结果测试
渲染到纹理并验证像素数据,而非渲染到屏幕。
#### 4.3.1 测试策略
| 方案 | 优点 | 缺点 |
|------|------|------|
| 渲染到 RTT | 无窗口依赖,可 CI | 需要 Mipmap 比较算法 |
| Shader 输出测试值 | 精确验证 | 需要特殊 Shader |
| Golden Image 对比 | 直观 | 维护成本高 |
#### 4.3.2 推荐方案:渲染特定图案
生成一个已知的测试图案(如渐变、棋盘格),渲染后读取像素验证:
```cpp
TEST(D3D12_RenderTarget, ClearColor_VerifyPixelValue) {
// 创建渲染目标纹理
const uint32_t width = 64;
const uint32_t height = 64;
D3D12Texture renderTarget;
renderTarget.InitializeAsRenderTarget(
GetDevice(), width, height,
DXGI_FORMAT_R8G8B8A8_UNORM
);
// 创建 RTV
D3D12DescriptorHeap rtvHeap;
rtvHeap.Initialize(GetDevice(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 1);
D3D12RenderTargetView rtv;
rtv.InitializeAt(
GetDevice(),
renderTarget.GetResource(),
rtvHeap.GetCPUDescriptorHandleForHeapStart(),
nullptr
);
// 清空为特定颜色
float clearColor[] = { 0.25f, 0.5f, 0.75f, 1.0f }; // R=64, G=128, B=192
GetCommandList()->ClearRenderTargetView(
rtvHeap.GetCPUDescriptorHandleForHeapStart(),
clearColor, 0, nullptr
);
// 转换状态用于读取
GetCommandList()->TransitionBarrier(
renderTarget.GetResource(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_COPY_SOURCE
);
GetCommandList()->Close();
ID3D12CommandList* cmdLists[] = { GetCommandList() };
GetCommandQueue()->ExecuteCommandLists(1, cmdLists);
WaitForGPU();
// 读取像素数据
std::vector<uint8_t> pixels(width * height * 4);
ReadBackTexture(GetDevice(), GetCommandQueue(),
renderTarget.GetResource(), pixels.data(), pixels.size());
// 验证中心像素
uint32_t centerIndex = (height / 2 * width + width / 2) * 4;
EXPECT_NEAR(pixels[centerIndex + 0], 64, 2); // R
EXPECT_NEAR(pixels[centerIndex + 1], 128, 2); // G
EXPECT_NEAR(pixels[centerIndex + 2], 192, 2); // B
}
```
#### 4.3.3 图案化渲染测试
使用纯色 Shader 渲染特定图案:
```hlsl
// TestPattern.hlsl - 输出棋盘格图案
float4 PSMain(PSInput input) : SV_Target {
uint2 pixelPos = uint2(input.Position.xy);
bool isWhite = ((pixelPos.x / 8) % 2) == ((pixelPos.y / 8) % 2);
return isWhite ? float4(1,1,1,1) : float4(0,0,0,1);
}
```
### 4.4 性能测试
使用 Google Benchmark 或简单计时:
```cpp
TEST(D3D12_Performance, BufferCreation_1000Times) {
const int iterations = 1000;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
D3D12Buffer buffer;
buffer.Initialize(GetDevice(), 1024,
D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_HEAP_TYPE_UPLOAD);
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
printf("Created %d buffers in %ld ms\n", iterations, duration.count());
}
```
## 5. 组件测试详情
### 5.1 D3D12Device
```cpp
TEST(D3D12_Device, CheckFeatureSupport_D3D12OK) {
D3D12_FEATURE_DATA_FEATURE_LEVELS featureLevels = {};
featureLevels.NumFeatureLevels = 1;
featureLevels.pFeatureLevelsRequested = &mFeatureLevel;
HRESULT hr = GetDevice()->CheckFeatureSupport(
D3D12_FEATURE_FEATURE_LEVELS,
&featureLevels,
sizeof(featureLevels)
);
ASSERT_TRUE(SUCCEEDED(hr));
ASSERT_EQ(featureLevels.MaxSupportedFeatureLevel, D3D_FEATURE_LEVEL_12_0);
}
```
### 5.2 D3D12CommandList
```cpp
TEST(D3D12_CommandList, Reset_AfterClose_Succeeds) {
// 第一次使用
GetCommandList()->Close();
// 重置并再次使用
HRESULT hr = GetCommandList()->Reset(mCommandAllocator.Get(), nullptr);
ASSERT_TRUE(SUCCEEDED(hr));
}
```
### 5.3 D3D12Fence
```cpp
TEST(D3D12_Fence, SignalAndWait) {
ComPtr<ID3D12Fence> fence;
UINT64 fenceValue = 1;
HRESULT hr = GetDevice()->CreateFence(
0, D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(&fence)
);
ASSERT_TRUE(SUCCEEDED(hr));
// Signal
GetCommandQueue()->Signal(fence.Get(), fenceValue);
// 等待
ASSERT_EQ(fence->GetCompletedValue(), fenceValue);
}
```
## 6. 资源泄漏检测
### 6.1 使用 D3D12 Debug Layer
```cpp
#ifdef _DEBUG
class D3D12LeakDetector {
public:
static void BeginFrame() {
if (sDebugDevice) {
sDebugDevice->SetName(L"Leak Detection Frame Start");
}
}
static void EndFrame() {
if (sDebugDevice) {
sDebugDevice->SetName(L"Leak Detection Frame End");
sDebugDevice->ReportLiveDeviceObjects(
D3D12_RDO_FLAGS::D3D12_RDO_FLAG_NONE
);
}
}
static void SetDebugDevice(ID3D12DebugDevice* device) {
sDebugDevice = device;
}
private:
static ID3D12DebugDevice* sDebugDevice;
};
#endif
```
### 6.2 在测试夹具中使用
```cpp
class D3D12LeakCheckFixture : public D3D12TestFixture {
protected:
void TearDown() override {
D3D12TestFixture::TearDown();
#ifdef _DEBUG
D3D12LeakDetector::EndFrame();
#endif
}
};
```
## 7. 构建配置
### 7.1 CMakeLists.txt 修改
```cmake
cmake_minimum_required(VERSION 3.15)
project(D3D12Tests)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Google Test
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://gitee.com/mirrors/googletest.git
GIT_TAG v1.14.0
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
# 测试源文件
set(TEST_SOURCES
test_device.cpp
test_command_queue.cpp
test_command_allocator.cpp
test_command_list.cpp
test_buffer.cpp
test_texture.cpp
test_descriptor_heap.cpp
test_pipeline_state.cpp
test_root_signature.cpp
test_fence.cpp
test_shader.cpp
test_views.cpp
)
add_executable(d3d12_tests ${TEST_SOURCES})
target_link_libraries(d3d12_tests PRIVATE
d3d12
dxgi
d3dcompiler
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(d3d12_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_CURRENT_SOURCE_DIR}/fixtures
)
add_test(NAME D3D12Tests COMMAND d3d12_tests)
```
## 8. CI 集成
### 8.1 Windows CI 配置示例
```yaml
# .github/workflows/d3d12-tests.yml
name: D3D12 Tests
on: [push, pull_request]
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Configure
run: cmake -B build -S . -G "Visual Studio 17 2022"
- name: Build
run: cmake --build build --config Debug
- name: Run Tests
run: ctest --test-dir build -C Debug --output-on-failure
```
## 9. 测试覆盖矩阵
| 组件 | 单元测试 | 集成测试 | 渲染测试 | 性能测试 |
|------|:--------:|:--------:|:--------:|:--------:|
| D3D12Device | ✓ | - | - | ✓ |
| D3D12CommandQueue | ✓ | ✓ | - | ✓ |
| D3D12CommandAllocator | ✓ | - | - | - |
| D3D12CommandList | ✓ | ✓ | - | - |
| D3D12Buffer | ✓ | ✓ | - | ✓ |
| D3D12Texture | ✓ | ✓ | ✓ | ✓ |
| D3D12DescriptorHeap | ✓ | - | - | - |
| D3D12PipelineState | ✓ | - | - | - |
| D3D12RootSignature | ✓ | - | - | - |
| D3D12Fence | ✓ | ✓ | - | - |
| D3D12SwapChain | - | - | ✓ | - |
| D3D12Shader | ✓ | - | - | - |
| RTV/DSV/SRV/UAV | ✓ | ✓ | ✓ | - |
## 10. 后续改进
- [ ] 实现所有组件的基础单元测试
- [ ] 添加资源泄漏检测工具
- [ ] 完善渲染结果测试图案
- [ ] 添加性能基准测试
- [ ] 配置 CI 自动测试
- [ ] 支持 Vulkan 后端测试(复用测试夹具抽象)