refactor: 将截图功能移到RHI模块的D3D12Screenshot类

This commit is contained in:
2026-03-15 15:39:15 +08:00
parent c79533c436
commit 4881aee70a
4 changed files with 209 additions and 176 deletions

View File

@@ -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

View File

@@ -0,0 +1,28 @@
#pragma once
#include <d3d12.h>
#include <string>
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

View File

@@ -0,0 +1,168 @@
#include "RHI/D3D12/D3D12Screenshot.h"
#include "Debug/Logger.h"
#include <d3d12.h>
#include <stdio.h>
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

View File

@@ -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);
ID3D12Resource* renderTarget = gColorRTs[gCurrentRTIndex];
Log("[DEBUG] SaveScreenshot: waiting for GPU copy\n");
gFenceValue += 1;
queue->Signal(gFence.GetFence(), gFenceValue);
gFence.Wait(gFenceValue);
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: 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;
Log("[DEBUG] SaveScreenshot: done, result=%d\n", result);
return result;
}
//=================================================================================