190 lines
6.5 KiB
Markdown
190 lines
6.5 KiB
Markdown
# 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); // 返回 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<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. 为什么 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
|