From 4881aee70a9f9ffd166a6da19f25846e51bfd9bf Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 15 Mar 2026 15:39:15 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=E6=88=AA=E5=9B=BE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E7=A7=BB=E5=88=B0RHI=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E7=9A=84D3D12Screenshot=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/CMakeLists.txt | 2 + .../XCEngine/RHI/D3D12/D3D12Screenshot.h | 28 +++ engine/src/RHI/D3D12Screenshot.cpp | 168 ++++++++++++++++ tests/D3D12/main.cpp | 187 ++---------------- 4 files changed, 209 insertions(+), 176 deletions(-) create mode 100644 engine/include/XCEngine/RHI/D3D12/D3D12Screenshot.h create mode 100644 engine/src/RHI/D3D12Screenshot.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 2c07f6a6..4cda1815 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -88,10 +88,12 @@ add_library(XCEngine STATIC include/XCEngine/RHI/D3D12/D3D12CommandQueue.h include/XCEngine/RHI/D3D12/D3D12CommandAllocator.h include/XCEngine/RHI/D3D12/D3D12Fence.h + include/XCEngine/RHI/D3D12/D3D12Screenshot.h src/RHI/D3D12Device.cpp src/RHI/D3D12CommandQueue.cpp src/RHI/D3D12CommandAllocator.cpp src/RHI/D3D12Fence.cpp + src/RHI/D3D12Screenshot.cpp ) target_include_directories(XCEngine PUBLIC diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Screenshot.h b/engine/include/XCEngine/RHI/D3D12/D3D12Screenshot.h new file mode 100644 index 00000000..4702c61d --- /dev/null +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Screenshot.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace XCEngine { +namespace RHI { + +class D3D12Screenshot { +public: + static bool Capture(ID3D12Device* device, + ID3D12CommandQueue* commandQueue, + ID3D12Resource* renderTarget, + const char* filename, + uint32_t width, + uint32_t height); + +private: + static bool CopyToReadbackAndSave(ID3D12Device* device, + ID3D12CommandQueue* commandQueue, + ID3D12Resource* renderTarget, + const char* filename, + uint32_t width, + uint32_t height); +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/D3D12Screenshot.cpp b/engine/src/RHI/D3D12Screenshot.cpp new file mode 100644 index 00000000..0f3d5117 --- /dev/null +++ b/engine/src/RHI/D3D12Screenshot.cpp @@ -0,0 +1,168 @@ +#include "RHI/D3D12/D3D12Screenshot.h" +#include "Debug/Logger.h" +#include +#include + +namespace XCEngine { +namespace RHI { + +bool D3D12Screenshot::Capture(ID3D12Device* device, + ID3D12CommandQueue* commandQueue, + ID3D12Resource* renderTarget, + const char* filename, + uint32_t width, + uint32_t height) { + return CopyToReadbackAndSave(device, commandQueue, renderTarget, filename, width, height); +} + +bool D3D12Screenshot::CopyToReadbackAndSave(ID3D12Device* device, + ID3D12CommandQueue* commandQueue, + ID3D12Resource* renderTarget, + const char* filename, + uint32_t width, + uint32_t height) { + D3D12_RESOURCE_DESC rtDesc = renderTarget->GetDesc(); + + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {}; + UINT64 totalSize = 0; + device->GetCopyableFootprints(&rtDesc, 0, 1, 0, &layout, nullptr, nullptr, &totalSize); + UINT rowPitch = layout.Footprint.RowPitch; + + ID3D12CommandAllocator* cmdAlloc = nullptr; + HRESULT hr = device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); + if (FAILED(hr)) { + return false; + } + + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_READBACK; + D3D12_RESOURCE_DESC readbackDesc = {}; + readbackDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + readbackDesc.Alignment = 0; + readbackDesc.Width = totalSize; + readbackDesc.Height = 1; + readbackDesc.DepthOrArraySize = 1; + readbackDesc.MipLevels = 1; + readbackDesc.Format = DXGI_FORMAT_UNKNOWN; + readbackDesc.SampleDesc.Count = 1; + readbackDesc.SampleDesc.Quality = 0; + readbackDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + readbackDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + + ID3D12Resource* readbackBuffer = nullptr; + hr = device->CreateCommittedResource( + &heapProps, + D3D12_HEAP_FLAG_NONE, + &readbackDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(&readbackBuffer)); + + if (FAILED(hr)) { + cmdAlloc->Release(); + return false; + } + + ID3D12GraphicsCommandList* cmdList = nullptr; + hr = device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList)); + if (FAILED(hr)) { + cmdAlloc->Release(); + readbackBuffer->Release(); + return false; + } + + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = renderTarget; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + cmdList->ResourceBarrier(1, &barrier); + + D3D12_TEXTURE_COPY_LOCATION srcLoc = {}; + srcLoc.pResource = renderTarget; + srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + srcLoc.SubresourceIndex = 0; + + D3D12_TEXTURE_COPY_LOCATION dstLoc = {}; + dstLoc.pResource = readbackBuffer; + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + dstLoc.PlacedFootprint = layout; + + D3D12_BOX srcBox = {}; + srcBox.left = 0; + srcBox.top = 0; + srcBox.front = 0; + srcBox.right = rtDesc.Width; + srcBox.bottom = rtDesc.Height; + srcBox.back = 1; + cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, &srcBox); + + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + cmdList->ResourceBarrier(1, &barrier); + + cmdList->Close(); + ID3D12CommandList* ppCmdLists[] = { cmdList }; + commandQueue->ExecuteCommandLists(1, ppCmdLists); + + HANDLE fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + ID3D12Fence* fence = nullptr; + hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); + if (SUCCEEDED(hr)) { + UINT64 fenceValue = 1; + commandQueue->Signal(fence, fenceValue); + if (fence->GetCompletedValue() < fenceValue) { + fence->SetEventOnCompletion(fenceValue, fenceEvent); + WaitForSingleObject(fenceEvent, INFINITE); + } + fence->Release(); + } + CloseHandle(fenceEvent); + + D3D12_RANGE readRange = { 0, 0 }; + unsigned char* mappedData = nullptr; + hr = readbackBuffer->Map(0, &readRange, (void**)&mappedData); + if (FAILED(hr)) { + cmdList->Release(); + cmdAlloc->Release(); + readbackBuffer->Release(); + return false; + } + + FILE* fp = fopen(filename, "wb"); + if (!fp) { + readbackBuffer->Unmap(0, nullptr); + cmdList->Release(); + cmdAlloc->Release(); + readbackBuffer->Release(); + return false; + } + + fprintf(fp, "P6\n%d %d\n255\n", width, height); + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + int idx = y * rowPitch + x * 4; + unsigned char r = mappedData[idx + 0]; + unsigned char g = mappedData[idx + 1]; + unsigned char b = mappedData[idx + 2]; + fwrite(&r, 1, 1, fp); + fwrite(&g, 1, 1, fp); + fwrite(&b, 1, 1, fp); + } + } + fclose(fp); + + D3D12_RANGE writeRange = { 0, 0 }; + readbackBuffer->Unmap(0, &writeRange); + + cmdList->Release(); + cmdAlloc->Release(); + readbackBuffer->Release(); + + return true; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/tests/D3D12/main.cpp b/tests/D3D12/main.cpp index d8ee8223..2257ec37 100644 --- a/tests/D3D12/main.cpp +++ b/tests/D3D12/main.cpp @@ -18,6 +18,7 @@ #include "XCEngine/RHI/D3D12/D3D12CommandQueue.h" #include "XCEngine/RHI/D3D12/D3D12CommandAllocator.h" #include "XCEngine/RHI/D3D12/D3D12Fence.h" +#include "XCEngine/RHI/D3D12/D3D12Screenshot.h" #include "XCEngine/Debug/Logger.h" #include "XCEngine/Debug/ConsoleLogSink.h" #include "XCEngine/Debug/FileLogSink.h" @@ -743,187 +744,21 @@ void SwapD3D12Buffers() { //================================================================================= // 截图保存 Debug 工具 -// 使用 Readback 方式读取渲染目标并保存为 PPM 格式 +// 使用 RHI 模块的 D3D12Screenshot 类 //================================================================================= bool SaveScreenshot(const char* filename, int width, int height) { Log("[DEBUG] SaveScreenshot: start\n"); - XCEngine::RHI::D3D12Device& deviceWrapper = gDevice; - ID3D12Device* device = deviceWrapper.GetDevice(); - Log("[DEBUG] SaveScreenshot: device = %p\n", device); - - IDXGISwapChain3* swapChain = gSwapChain; - - // Wait for previous frame to finish - Log("[DEBUG] SaveScreenshot: waiting for GPU\n"); - WaitForCompletionOfCommandList(); - - // Don't call Present() - capture directly from RENDER_TARGET state - int captureRTIndex = gCurrentRTIndex; - Log("[DEBUG] SaveScreenshot: gCurrentRTIndex = %d\n", gCurrentRTIndex); - - // Use existing render target - ID3D12Resource* renderTarget = gColorRTs[captureRTIndex]; - Log("[DEBUG] SaveScreenshot: renderTarget = %p\n", renderTarget); - - D3D12_RESOURCE_DESC rtDesc = renderTarget->GetDesc(); - Log("[DEBUG] SaveScreenshot: rtDesc.Width=%llu, Height=%u, Format=%d\n", rtDesc.Width, rtDesc.Height, rtDesc.Format); - - // Create readback buffer - size must include row pitch padding - D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {}; - UINT64 totalSize = 0; - device->GetCopyableFootprints(&rtDesc, 0, 1, 0, &layout, nullptr, nullptr, &totalSize); - UINT rowPitch = layout.Footprint.RowPitch; - Log("[DEBUG] SaveScreenshot: GPU rowPitch = %u, totalSize = %llu\n", rowPitch, totalSize); - - // Create buffer with proper size - HRESULT hr = S_OK; - ID3D12CommandAllocator* cmdAlloc = nullptr; - hr = device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); - if (FAILED(hr)) { - Log("[DEBUG] SaveScreenshot: CreateCommandAllocator failed! hr=%08X\n", hr); - return false; - } - - D3D12_HEAP_PROPERTIES heapProps = {}; - heapProps.Type = D3D12_HEAP_TYPE_READBACK; - D3D12_RESOURCE_DESC readbackDesc = {}; - readbackDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - readbackDesc.Alignment = 0; - readbackDesc.Width = totalSize; - readbackDesc.Height = 1; - readbackDesc.DepthOrArraySize = 1; - readbackDesc.MipLevels = 1; - readbackDesc.Format = DXGI_FORMAT_UNKNOWN; - readbackDesc.SampleDesc.Count = 1; - readbackDesc.SampleDesc.Quality = 0; - readbackDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - readbackDesc.Flags = D3D12_RESOURCE_FLAG_NONE; - - ID3D12Resource* readbackBuffer = nullptr; - hr = device->CreateCommittedResource( - &heapProps, - D3D12_HEAP_FLAG_NONE, - &readbackDesc, - D3D12_RESOURCE_STATE_COPY_DEST, - nullptr, - IID_PPV_ARGS(&readbackBuffer)); - - if (FAILED(hr)) { - Log("[DEBUG] SaveScreenshot: CreateCommittedResource failed! hr=%08X\n", hr); - cmdAlloc->Release(); - return false; - } - Log("[DEBUG] SaveScreenshot: created readback buffer\n"); - - ID3D12GraphicsCommandList* cmdList = nullptr; - hr = device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList)); - if (FAILED(hr)) { - Log("[DEBUG] SaveScreenshot: CreateCommandList failed! hr=%08X\n", hr); - cmdAlloc->Release(); - readbackBuffer->Release(); - return false; - } - - Log("[DEBUG] SaveScreenshot: copying resource\n"); - D3D12_RESOURCE_BARRIER barrier = {}; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = renderTarget; - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - cmdList->ResourceBarrier(1, &barrier); - - // Use CopyTextureRegion to copy from texture to buffer - D3D12_TEXTURE_COPY_LOCATION srcLoc = {}; - srcLoc.pResource = renderTarget; - srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - srcLoc.SubresourceIndex = 0; - - D3D12_TEXTURE_COPY_LOCATION dstLoc = {}; - dstLoc.pResource = readbackBuffer; - dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; - dstLoc.PlacedFootprint = layout; - - D3D12_BOX srcBox = {}; - srcBox.left = 0; - srcBox.top = 0; - srcBox.front = 0; - srcBox.right = (UINT)rtDesc.Width; - srcBox.bottom = rtDesc.Height; - srcBox.back = 1; - cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, &srcBox); - - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; - cmdList->ResourceBarrier(1, &barrier); - - cmdList->Close(); - ID3D12CommandList* ppCmdLists[] = { cmdList }; + ID3D12Device* device = gDevice.GetDevice(); ID3D12CommandQueue* queue = gCommandQueue.GetCommandQueue(); - queue->ExecuteCommandLists(1, ppCmdLists); - - Log("[DEBUG] SaveScreenshot: waiting for GPU copy\n"); - gFenceValue += 1; - queue->Signal(gFence.GetFence(), gFenceValue); - gFence.Wait(gFenceValue); - - Log("[DEBUG] SaveScreenshot: mapping memory\n"); - D3D12_RANGE readRange = { 0, 0 }; - unsigned char* mappedData = nullptr; - hr = readbackBuffer->Map(0, &readRange, (void**)&mappedData); - if (FAILED(hr)) { - Log("[DEBUG] SaveScreenshot: Map failed! hr=%08X\n", hr); - cmdList->Release(); - cmdAlloc->Release(); - readbackBuffer->Release(); - return false; - } - - Log("[DEBUG] SaveScreenshot: rowPitch = %u\n", rowPitch); - - // Debug: check some pixel values - int centerIdx = (height / 2) * rowPitch + (width / 2) * 4; - Log("[DEBUG] SaveScreenshot: center pixel RGBA = %02X %02X %02X %02X\n", - mappedData[centerIdx], mappedData[centerIdx+1], mappedData[centerIdx+2], mappedData[centerIdx+3]); - Log("[DEBUG] SaveScreenshot: first pixel RGBA = %02X %02X %02X %02X\n", - mappedData[0], mappedData[1], mappedData[2], mappedData[3]); - - Log("[DEBUG] SaveScreenshot: writing file\n"); - FILE* fp = fopen(filename, "wb"); - if (!fp) { - Log("[DEBUG] SaveScreenshot: fopen failed!\n"); - readbackBuffer->Unmap(0, nullptr); - cmdList->Release(); - cmdAlloc->Release(); - readbackBuffer->Release(); - return false; - } - - fprintf(fp, "P6\n%d %d\n255\n", width, height); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int idx = y * rowPitch + x * 4; - unsigned char r = mappedData[idx + 0]; // R8G8B8A8: R is at offset 0 - unsigned char g = mappedData[idx + 1]; // G is at offset 1 - unsigned char b = mappedData[idx + 2]; // B is at offset 2 - fwrite(&r, 1, 1, fp); - fwrite(&g, 1, 1, fp); - fwrite(&b, 1, 1, fp); - } - } - fclose(fp); - - D3D12_RANGE writeRange = { 0, 0 }; - readbackBuffer->Unmap(0, &writeRange); - - cmdList->Release(); - cmdAlloc->Release(); - readbackBuffer->Release(); - - Log("[DEBUG] SaveScreenshot: done!\n"); - return true; + ID3D12Resource* renderTarget = gColorRTs[gCurrentRTIndex]; + + Log("[DEBUG] SaveScreenshot: calling D3D12Screenshot::Capture\n"); + bool result = XCEngine::RHI::D3D12Screenshot::Capture( + device, queue, renderTarget, filename, (uint32_t)width, (uint32_t)height); + + Log("[DEBUG] SaveScreenshot: done, result=%d\n", result); + return result; } //=================================================================================