# D3D12 RHI 测试失败问题报告 ## 问题描述 5个D3D12测试在调用 `CreateCommandList` 时失败,错误码 `DXGI_ERROR_NOT_CURRENTLY_AVAILABLE` (0x887A0005) ## 失败的测试 | 测试名称 | 错误位置 | |---------|---------| | `CommandList_ClearRenderTarget_WithRealView` | `CreateCommandList` 返回 nullptr | | `CommandList_ClearDepthStencil_WithRealView` | `CreateTexture` 返回 nullptr (D24_S8_UInt格式) | | `CommandList_SetRenderTargets_WithRealViews` | `CreateCommandList` 返回 nullptr | | `CommandList_BeginEndRenderPass_Basic` | `CreateCommandList` 返回 nullptr | | `CommandList_BeginEndRenderPass_WithClear` | `CreateCommandList` 返回 nullptr | ## 错误码分析 ``` DXGI_ERROR_NOT_CURRENTLY_AVAILABLE (0x887A0005) ``` 根据 Microsoft 文档,此错误表示: > "The resource or operation is not available at the current time. This can be returned when a command is submitted to a queue that is not in a state to process that command." ## 测试代码(最小复现) ```cpp TEST_P(RHITestFixture, CommandList_ClearRenderTarget_WithRealView) { // 1. 创建纹理 - 成功 TextureDesc texDesc = {}; texDesc.width = 256; texDesc.height = 256; texDesc.format = static_cast(Format::R8G8B8A8_UNorm); texDesc.textureType = static_cast(TextureType::Texture2D); RHITexture* texture = GetDevice()->CreateTexture(texDesc); ASSERT_NE(texture, nullptr); // ✅ 通过 // 2. 创建RTV - 成功 RHIResourceView* rtv = GetDevice()->CreateRenderTargetView(texture, {}); ASSERT_NE(rtv, nullptr); // ✅ 通过 // 3. 创建命令列表 - 失败 CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); ASSERT_NE(cmdList, nullptr); // ❌ 失败!cmdList == nullptr } ``` ## 调试日志 ``` CreateTexture: start CreateRenderTargetView: start CreateCommandList: start m_commandQueue=00000180F8F722E0 m_device=00000180F22B5B30 CreateCommandAllocator hr=887A0005 CreateCommandList: allocator init failed ``` ## 关键发现 ### 1. 通过的测试(关键差异) 以下测试**通过**,它们不创建纹理或只创建SRV: ```cpp // ✅ 通过 - 只创建纹理,不创建RTV TEST_P(RHITestFixture, CommandList_Reset_Close) { CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); ASSERT_NE(cmdList, nullptr); // ✅ 通过 cmdList->Shutdown(); delete cmdList; } // ✅ 通过 - 创建纹理+SRV,但不创建RTV TEST_P(RHITestFixture, CommandList_TransitionBarrier_WithRealResource) { RHITexture* texture = GetDevice()->CreateTexture(texDesc); // ✅ RHIResourceView* srv = GetDevice()->CreateShaderResourceView(texture, {}); // ✅ RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); // ✅ 通过 } ``` ### 2. 失败的测试(共同点) 所有失败的测试都**先创建纹理,再创建RTV,然后创建命令列表**。 ### 3. OpenGL vs D3D12 - **OpenGL**: 全部117个测试通过 ✅ - **D3D12 (RHITestFixture)**: 112/117 通过,5个失败 ❌ - **D3D12TestFixture**: 全部53个测试通过 ✅ ## 代码分析 ### CreateCommandList 实现 ```cpp // engine/src/RHI/D3D12/D3D12Device.cpp:409 RHICommandList* D3D12Device::CreateCommandList(const CommandListDesc& desc) { auto* allocator = new D3D12CommandAllocator(); // 失败发生在这里 if (!allocator->Initialize(m_device.Get(), static_cast(desc.commandListType))) { delete allocator; return nullptr; // 返回 nullptr } auto* cmdList = new D3D12CommandList(); if (!cmdList->Initialize(m_device.Get(), ...)) { delete allocator; delete cmdList; return nullptr; } return cmdList; } ``` ### CreateCommandAllocator 实现 ```cpp // engine/src/RHI/D3D12/D3D12CommandAllocator.cpp:15 bool D3D12CommandAllocator::Initialize(ID3D12Device* device, CommandQueueType type) { m_type = type; HRESULT hResult = device->CreateCommandAllocator( ToD3D12(type), // D3D12_COMMAND_LIST_TYPE_DIRECT IID_PPV_ARGS(&m_commandAllocator)); return SUCCEEDED(hResult); // 返回 false,hr=887A0005 } ``` ### CreateRenderTargetView 实现 ```cpp // engine/src/RHI/D3D12/D3D12Device.cpp:455 RHIResourceView* D3D12Device::CreateRenderTargetView(RHITexture* texture, const ResourceViewDesc& desc) { auto* view = new D3D12ResourceView(); auto* d3d12Texture = static_cast(texture); ID3D12Resource* resource = d3d12Texture->GetResource(); D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; rtvDesc.Format = static_cast(desc.format); // desc.format = 0 (Unknown!) rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; // 创建非 shader-visible 的 descriptor heap DescriptorHeapDesc heapDesc = {}; heapDesc.descriptorCount = 1; heapDesc.heapType = static_cast(DescriptorHeapType::RTV); heapDesc.shaderVisible = false; // 非 shader-visible auto* heapPool = CreateDescriptorHeap(heapDesc); view->InitializeAsRenderTarget(m_device.Get(), resource, &rtvDesc, heap, 0); return view; } ``` ## 已排除的原因 1. ❌ **调试层问题** - 禁用后仍然失败 2. ❌ **命令队列类型不匹配** - 使用相同的 `CommandQueueType::Direct` 3. ❌ **静态全局状态污染** - 每个测试创建独立设备 4. ❌ **内存泄漏导致descriptor heap耗尽** - 内存问题不会导致此错误码 5. ❌ **测试顺序依赖** - 每个测试有独立的 SetUp/TearDown ## 疑点 1. 为什么 `CreateCommandAllocator` 在创建 RTV(非shader-visible descriptor heap)之后会失败? 2. D3D12TestFixture(使用相同设备创建方式)为什么全部通过? 3. 为什么 SRV(shader-visible descriptor heap)不会触发这个问题,只有 RTV 会? ## 相关文件 - `engine/src/RHI/D3D12/D3D12Device.cpp` - CreateCommandList, CreateTexture, CreateRenderTargetView - `engine/src/RHI/D3D12/D3D12CommandAllocator.cpp` - CreateCommandAllocator - `engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp` - CreateDescriptorHeap - `engine/src/RHI/D3D12/D3D12ResourceView.cpp` - InitializeAsRenderTarget - `tests/RHI/unit/test_command_list.cpp` - 失败的测试 - `tests/RHI/unit/fixtures/RHITestFixture.cpp` - 测试fixture ## 测试环境 - 平台: Windows - RHI后端: D3D12 - 测试框架: Google Test - 编译配置: Debug