D3D12: Add Screenshot wrapper overload and document known limitations

- Add D3D12Screenshot::Capture(D3D12Device&, D3D12CommandQueue&, D3D12Texture&, const char*) wrapper overload
- Update minimal integration test to use encapsulated APIs
- Add DescriptorHeap wrapper unit tests
- Document minimal GetBuffer native call as known limitation in TEST_SPEC.md
This commit is contained in:
2026-03-20 17:36:51 +08:00
parent a40544344b
commit 3cd3b04c7e
5 changed files with 113 additions and 35 deletions

View File

@@ -6,7 +6,9 @@
namespace XCEngine {
namespace RHI {
class D3D12Device;
class D3D12CommandQueue;
class D3D12Texture;
class D3D12Screenshot {
public:
@@ -17,6 +19,11 @@ public:
uint32_t width,
uint32_t height);
static bool Capture(D3D12Device& device,
D3D12CommandQueue& commandQueue,
D3D12Texture& texture,
const char* filename);
private:
static bool CopyToReadbackAndSave(ID3D12Device* device,
ID3D12CommandQueue* commandQueue,

View File

@@ -1,4 +1,7 @@
#include "RHI/D3D12/D3D12Screenshot.h"
#include "RHI/D3D12/D3D12Device.h"
#include "RHI/D3D12/D3D12CommandQueue.h"
#include "RHI/D3D12/D3D12Texture.h"
#include "Debug/Logger.h"
#include <d3d12.h>
#include <stdio.h>
@@ -15,6 +18,20 @@ bool D3D12Screenshot::Capture(ID3D12Device* device,
return CopyToReadbackAndSave(device, commandQueue, renderTarget, filename, width, height);
}
bool D3D12Screenshot::Capture(D3D12Device& device,
D3D12CommandQueue& commandQueue,
D3D12Texture& texture,
const char* filename) {
return Capture(
device.GetDevice(),
commandQueue.GetCommandQueue(),
texture.GetResource(),
filename,
texture.GetWidth(),
texture.GetHeight()
);
}
bool D3D12Screenshot::CopyToReadbackAndSave(ID3D12Device* device,
ID3D12CommandQueue* commandQueue,
ID3D12Resource* renderTarget,

View File

@@ -290,6 +290,22 @@ engine/
**状态**: 待修复
### 7.2 minimal GetBuffer 原生调用
**问题**: `minimal/main.cpp` 第 127-129 行仍使用原生 D3D12 API:
```cpp
ID3D12Resource* buffer = nullptr;
gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer));
gColorRTs[i].InitializeFromExisting(buffer);
```
**原因**: SwapChain back buffer 获取后需要通过 `InitializeFromExisting()` 绑定到已有的 `D3D12Texture` 对象。当前没有优雅的封装方案保留此语义。
**影响**: minimal 集成测试仍包含少量原生 D3D12 代码
**状态**: 标记为已知限制,暂不修复
---
## 8. 规范更新记录

View File

@@ -105,26 +105,8 @@ bool InitD3D12() {
return false;
}
// Create swap chain
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
swapChainDesc.BufferCount = 2;
swapChainDesc.BufferDesc.Width = gWidth;
swapChainDesc.BufferDesc.Height = gHeight;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = gHWND;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = true;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
IDXGISwapChain* dxgiSwapChain = nullptr;
HRESULT hr = factory->CreateSwapChain(gCommandQueue.GetCommandQueue(), &swapChainDesc, &dxgiSwapChain);
if (FAILED(hr)) {
Log("[ERROR] Failed to create swap chain");
return false;
}
if (!gSwapChain.Initialize(dxgiSwapChain, (uint32_t)gWidth, (uint32_t)gHeight)) {
// Create swap chain using encapsulated interface
if (!gSwapChain.Initialize(factory, gCommandQueue.GetCommandQueue(), gHWND, gWidth, gHeight, 2)) {
Log("[ERROR] Failed to initialize swap chain");
return false;
}
@@ -140,21 +122,22 @@ bool InitD3D12() {
gDSVHeap.Initialize(device, DescriptorHeapType::DSV, 1);
gDSVDescriptorSize = gDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType::DSV);
// Create RTVs for back buffers
D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapStart = gRTVHeap.GetCPUDescriptorHandleForHeapStart();
// Create RTVs for back buffers using encapsulated interface
for (int i = 0; i < 2; i++) {
ID3D12Resource* buffer = nullptr;
gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer));
gColorRTs[i].InitializeFromExisting(buffer);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle;
rtvHandle.ptr = rtvHeapStart.ptr + i * gRTVDescriptorSize;
CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr };
gRTVs[i].InitializeAt(device, gColorRTs[i].GetResource(), rtvHandle, nullptr);
}
// Create DSV
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = D3D12DepthStencilView::CreateDesc(Format::D24_UNorm_S8_UInt);
gDSV.InitializeAt(device, gDepthStencil.GetResource(), gDSVHeap.GetCPUDescriptorHandleForHeapStart(), &dsvDesc);
CPUDescriptorHandle dsvCpuHandle = gDSVHeap.GetCPUDescriptorHandle(0);
D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = { dsvCpuHandle.ptr };
gDSV.InitializeAt(device, gDepthStencil.GetResource(), dsvHandle, &dsvDesc);
// Create command allocator and list
gCommandAllocator.Initialize(device, CommandQueueType::Direct);
@@ -184,10 +167,11 @@ void BeginRender() {
gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(),
ResourceStates::Present, ResourceStates::RenderTarget);
// Set render targets
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle;
rtvHandle.ptr = gRTVHeap.GetCPUDescriptorHandleForHeapStart().ptr + gCurrentRTIndex * gRTVDescriptorSize;
D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = gDSVHeap.GetCPUDescriptorHandleForHeapStart();
// Set render targets using encapsulated interface
CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(gCurrentRTIndex);
CPUDescriptorHandle dsvCpuHandle = gDSVHeap.GetCPUDescriptorHandle(0);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr };
D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = { dsvCpuHandle.ptr };
gCommandList.SetRenderTargetsHandle(1, &rtvHandle, &dsvHandle);
@@ -289,12 +273,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
WaitForGPU();
Log("[INFO] GPU idle, taking screenshot...");
bool screenshotResult = D3D12Screenshot::Capture(
gDevice.GetDevice(),
gCommandQueue.GetCommandQueue(),
gColorRTs[gCurrentRTIndex].GetResource(),
"minimal.ppm",
gWidth,
gHeight
gDevice,
gCommandQueue,
gColorRTs[gCurrentRTIndex],
"minimal.ppm"
);
if (screenshotResult) {
Log("[INFO] Screenshot saved to minimal.ppm");

View File

@@ -1,4 +1,60 @@
#include "fixtures/D3D12TestFixture.h"
#include "XCEngine/RHI/D3D12/D3D12DescriptorHeap.h"
using namespace XCEngine::RHI;
TEST_F(D3D12TestFixture, DescriptorHeap_Wrapper_Initialize_RTV) {
D3D12DescriptorHeap heap;
bool result = heap.Initialize(GetDevice(), DescriptorHeapType::RTV, 4);
ASSERT_TRUE(result);
EXPECT_EQ(heap.GetDescriptorCount(), 4u);
EXPECT_EQ(heap.GetType(), DescriptorHeapType::RTV);
heap.Shutdown();
}
TEST_F(D3D12TestFixture, DescriptorHeap_Wrapper_GetCPUDescriptorHandle) {
D3D12DescriptorHeap heap;
ASSERT_TRUE(heap.Initialize(GetDevice(), DescriptorHeapType::RTV, 4));
uint32_t descriptorSize = heap.GetDescriptorSize();
CPUDescriptorHandle handle0 = heap.GetCPUDescriptorHandle(0);
CPUDescriptorHandle handle1 = heap.GetCPUDescriptorHandle(1);
CPUDescriptorHandle handle2 = heap.GetCPUDescriptorHandle(2);
EXPECT_EQ(handle1.ptr - handle0.ptr, descriptorSize);
EXPECT_EQ(handle2.ptr - handle1.ptr, descriptorSize);
heap.Shutdown();
}
TEST_F(D3D12TestFixture, DescriptorHeap_Wrapper_GetGPUDescriptorHandle) {
D3D12DescriptorHeap heap;
ASSERT_TRUE(heap.Initialize(GetDevice(), DescriptorHeapType::CBV_SRV_UAV, 4, true));
uint32_t descriptorSize = heap.GetDescriptorSize();
GPUDescriptorHandle handle0 = heap.GetGPUDescriptorHandle(0);
GPUDescriptorHandle handle1 = heap.GetGPUDescriptorHandle(1);
EXPECT_EQ(handle1.ptr - handle0.ptr, descriptorSize);
heap.Shutdown();
}
TEST_F(D3D12TestFixture, DescriptorHeap_Wrapper_GetDescriptorSize) {
D3D12DescriptorHeap rtvHeap;
ASSERT_TRUE(rtvHeap.Initialize(GetDevice(), DescriptorHeapType::RTV, 4));
EXPECT_GT(rtvHeap.GetDescriptorSize(), 0u);
D3D12DescriptorHeap cbvHeap;
ASSERT_TRUE(cbvHeap.Initialize(GetDevice(), DescriptorHeapType::CBV_SRV_UAV, 4));
EXPECT_GT(cbvHeap.GetDescriptorSize(), 0u);
}
TEST_F(D3D12TestFixture, DescriptorHeap_Wrapper_Shutdown) {
D3D12DescriptorHeap heap;
ASSERT_TRUE(heap.Initialize(GetDevice(), DescriptorHeapType::RTV, 2));
heap.Shutdown();
}
TEST_F(D3D12TestFixture, DescriptorHeap_Create_CBV_SRV_UAV) {
D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};