fix: 修复D3D12截图功能 - 使用正确的buffer尺寸和row pitch获取

This commit is contained in:
2026-03-15 13:54:58 +08:00
parent 3d285fa98a
commit 549178de35

View File

@@ -680,8 +680,13 @@ ID3D12GraphicsCommandList* GetCommandList() {
}
void WaitForCompletionOfCommandList() {
if (gFence.GetCompletedValue() < gFenceValue) {
gFence.Wait(gFenceValue);
UINT64 completed = gFence.GetCompletedValue();
UINT64 current = gFenceValue;
Log("[DEBUG] WaitForCompletion: completed=%llu, waiting for=%llu\n", completed, current);
if (completed < current) {
Log("[DEBUG] WaitForCompletion: waiting...\n");
gFence.Wait(current);
Log("[DEBUG] WaitForCompletion: done\n");
}
}
@@ -755,25 +760,39 @@ bool SaveScreenshot(const char* filename, int width, int height) {
Log("[DEBUG] SaveScreenshot: waiting for GPU\n");
WaitForCompletionOfCommandList();
// Get current back buffer index
int currentRTIndex = swapChain->GetCurrentBackBufferIndex();
Log("[DEBUG] SaveScreenshot: currentRTIndex = %d\n", currentRTIndex);
// 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[currentRTIndex];
ID3D12Resource* renderTarget = gColorRTs[captureRTIndex];
Log("[DEBUG] SaveScreenshot: renderTarget = %p\n", renderTarget);
// Need to transition from PRESENT to COPY_SOURCE for reading
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 = (UINT64)width * height * 4;
readbackDesc.Width = totalSize;
readbackDesc.Height = 1;
readbackDesc.DepthOrArraySize = 1;
readbackDesc.MipLevels = 1;
@@ -794,17 +813,11 @@ bool SaveScreenshot(const char* filename, int width, int height) {
if (FAILED(hr)) {
Log("[DEBUG] SaveScreenshot: CreateCommittedResource failed! hr=%08X\n", hr);
cmdAlloc->Release();
return false;
}
Log("[DEBUG] SaveScreenshot: created readback buffer\n");
hr = device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc));
if (FAILED(hr)) {
Log("[DEBUG] SaveScreenshot: CreateCommandAllocator failed! hr=%08X\n", hr);
readbackBuffer->Release();
return false;
}
ID3D12GraphicsCommandList* cmdList = nullptr;
hr = device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList));
if (FAILED(hr)) {
@@ -819,15 +832,33 @@ bool SaveScreenshot(const char* filename, int width, int height) {
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = renderTarget;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
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);
cmdList->CopyResource(readbackBuffer, renderTarget);
// 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_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
cmdList->ResourceBarrier(1, &barrier);
cmdList->Close();
@@ -852,10 +883,15 @@ bool SaveScreenshot(const char* filename, int width, int height) {
return false;
}
D3D12_RESOURCE_DESC desc = readbackBuffer->GetDesc();
UINT rowPitch = width * 4;
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) {
@@ -871,9 +907,9 @@ bool SaveScreenshot(const char* filename, int width, int 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 + 2];
unsigned char g = mappedData[idx + 1];
unsigned char b = mappedData[idx + 0];
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);
@@ -1018,6 +1054,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
int imageWidth, imageHeight, imageChannel;
stbi_uc* pixels = stbi_load("Res/Image/earth_d.jpg", &imageWidth, &imageHeight, &imageChannel, 4);
Log("[DEBUG] Texture loaded: width=%d, height=%d, channels=%d, pixels=%p\n", imageWidth, imageHeight, imageChannel, pixels);
ID3D12Resource* texture = CreateTexture2D(commandList, pixels,
imageWidth * imageHeight * imageChannel, imageWidth, imageHeight, DXGI_FORMAT_R8G8B8A8_UNORM);
delete[] pixels;
@@ -1062,14 +1099,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
DispatchMessage(&msg);
} else {
frameCount++;
if (frameCount == 2) {
Log("[DEBUG] Saving screenshot at frame %d...\n", frameCount);
if (SaveScreenshot("screenshot.ppm", 1280, 720)) {
Log("[DEBUG] Screenshot saved to screenshot.ppm\n");
} else {
Log("[DEBUG] Failed to save screenshot!\n");
}
}
WaitForCompletionOfCommandList();
DWORD current_time = timeGetTime();
DWORD frameTime = current_time - last_time;
@@ -1090,9 +1119,27 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
commandList->SetGraphicsRootShaderResourceView(3, sb->GetGPUVirtualAddress());
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
staticMeshComponent.Render(commandList);
EndRenderToSwapChain(commandList);
// On screenshot frame, don't transition to PRESENT - keep RENDER_TARGET for screenshot
if (frameCount != 120) {
EndRenderToSwapChain(commandList);
}
EndCommandList();
SwapD3D12Buffers();
// On screenshot frame, don't Present - we'll screenshot before next frame
if (frameCount != 120) {
SwapD3D12Buffers();
}
// Screenshot after rendering is done
if (frameCount == 120) {
Log("[DEBUG] Saving screenshot at frame %d...\n", frameCount);
if (SaveScreenshot("screenshot.ppm", 1280, 720)) {
Log("[DEBUG] Screenshot saved to screenshot.ppm\n");
} else {
Log("[DEBUG] Failed to save screenshot!\n");
}
}
}
}
if (gLogFile) {