Files
XCEngine/docs/used/D3D12_RHI_Test_Issue.md

190 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<uint32_t>(Format::R8G8B8A8_UNorm);
texDesc.textureType = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<CommandQueueType>(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); // 返回 falsehr=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<D3D12Texture*>(texture);
ID3D12Resource* resource = d3d12Texture->GetResource();
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
rtvDesc.Format = static_cast<DXGI_FORMAT>(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<uint32_t>(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. 为什么 SRVshader-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