From 2342e3fbfc967ceefd81d43cd7659d711f28172e Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 17 Mar 2026 02:32:52 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20D3D12=20=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=B5=8B=E8=AF=95=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/D3D12后端测试设计.md | 596 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 docs/D3D12后端测试设计.md diff --git a/docs/D3D12后端测试设计.md b/docs/D3D12后端测试设计.md new file mode 100644 index 00000000..137fc301 --- /dev/null +++ b/docs/D3D12后端测试设计.md @@ -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 +#include +#include + +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 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 mDevice; + ComPtr mCommandQueue; + ComPtr mCommandAllocator; + ComPtr mCommandList; +}; + +// 静态成员定义 +ComPtr 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 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(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 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 后端测试(复用测试夹具抽象)