fix: capture save thumbnails directly from D3D11 backbuffer

The new 4JLibs CaptureThumbnail produces all-black 64x64 PNGs because
its internal m_backBufferTexture copy fails silently. Bypass it entirely
and capture from the swap chain backbuffer using the same proven approach
as our F2 screenshot, with center-crop and downsample to 64x64 PNG.
This commit is contained in:
itsRevela
2026-04-17 07:06:02 -05:00
parent ec5ec84407
commit bc6e445f0c

View File

@@ -9,6 +9,11 @@
#include "../../Minecraft.World/LevelSettings.h"
#include "../../Minecraft.World/BiomeSource.h"
#include "../../Minecraft.World/LevelType.h"
#include "stb_image_write.h"
extern ID3D11Device* g_pd3dDevice;
extern ID3D11DeviceContext* g_pImmediateContext;
extern IDXGISwapChain* g_pSwapChain;
CConsoleMinecraftApp app;
@@ -33,9 +38,120 @@ void CConsoleMinecraftApp::FatalLoadError()
{
}
static const int THUMBNAIL_SIZE = 64;
void CConsoleMinecraftApp::CaptureSaveThumbnail()
{
RenderManager.CaptureThumbnail(&m_ThumbnailBuffer);
if (!g_pSwapChain || !g_pd3dDevice || !g_pImmediateContext)
return;
// Release any previous capture
if (m_ThumbnailBuffer.Allocated())
m_ThumbnailBuffer.Release();
// Get the backbuffer
ID3D11Texture2D* pBackBuffer = nullptr;
HRESULT hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer);
if (FAILED(hr))
return;
D3D11_TEXTURE2D_DESC backDesc = {};
pBackBuffer->GetDesc(&backDesc);
// Create a staging texture at backbuffer size to read pixels
D3D11_TEXTURE2D_DESC stagingDesc = backDesc;
stagingDesc.Usage = D3D11_USAGE_STAGING;
stagingDesc.BindFlags = 0;
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
stagingDesc.MiscFlags = 0;
ID3D11Texture2D* pStaging = nullptr;
hr = g_pd3dDevice->CreateTexture2D(&stagingDesc, nullptr, &pStaging);
if (FAILED(hr))
{
pBackBuffer->Release();
return;
}
g_pImmediateContext->CopyResource(pStaging, pBackBuffer);
pBackBuffer->Release();
D3D11_MAPPED_SUBRESOURCE mapped = {};
hr = g_pImmediateContext->Map(pStaging, 0, D3D11_MAP_READ, 0, &mapped);
if (FAILED(hr))
{
pStaging->Release();
return;
}
// Downsample to THUMBNAIL_SIZE x THUMBNAIL_SIZE with simple box filter
unsigned char* thumb = new unsigned char[THUMBNAIL_SIZE * THUMBNAIL_SIZE * 4];
// Crop to square (center crop), then scale down
UINT srcSize = (backDesc.Width < backDesc.Height) ? backDesc.Width : backDesc.Height;
UINT offsetX = (backDesc.Width - srcSize) / 2;
UINT offsetY = (backDesc.Height - srcSize) / 2;
for (int ty = 0; ty < THUMBNAIL_SIZE; ty++)
{
for (int tx = 0; tx < THUMBNAIL_SIZE; tx++)
{
// Map thumbnail pixel to source region
UINT sx = offsetX + (tx * srcSize) / THUMBNAIL_SIZE;
UINT sy = offsetY + (ty * srcSize) / THUMBNAIL_SIZE;
const unsigned char* src = (const unsigned char*)mapped.pData + sy * mapped.RowPitch + sx * 4;
unsigned char* dst = thumb + (ty * THUMBNAIL_SIZE + tx) * 4;
dst[0] = src[0]; // R (or B depending on format, but BGRA->RGBA swap below)
dst[1] = src[1]; // G
dst[2] = src[2]; // B
dst[3] = 0xFF; // A
}
}
g_pImmediateContext->Unmap(pStaging, 0);
pStaging->Release();
// If backbuffer is BGRA, swap to RGBA for PNG
if (backDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM || backDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB)
{
for (int i = 0; i < THUMBNAIL_SIZE * THUMBNAIL_SIZE; i++)
{
unsigned char tmp = thumb[i * 4];
thumb[i * 4] = thumb[i * 4 + 2];
thumb[i * 4 + 2] = tmp;
}
}
// Encode to PNG in memory using stbi_write_png_to_func
struct PngBuffer { unsigned char* data; int size; int capacity; } pngBuf = {};
pngBuf.capacity = THUMBNAIL_SIZE * THUMBNAIL_SIZE * 4 + 256;
pngBuf.data = (unsigned char*)malloc(pngBuf.capacity);
pngBuf.size = 0;
stbi_write_png_to_func([](void* ctx, void* data, int size) {
PngBuffer* buf = (PngBuffer*)ctx;
if (buf->size + size > buf->capacity)
{
buf->capacity = (buf->size + size) * 2;
buf->data = (unsigned char*)realloc(buf->data, buf->capacity);
}
memcpy(buf->data + buf->size, data, size);
buf->size += size;
}, &pngBuf, THUMBNAIL_SIZE, THUMBNAIL_SIZE, 4, thumb, THUMBNAIL_SIZE * 4);
delete[] thumb;
if (pngBuf.size > 0)
{
m_ThumbnailBuffer.m_type = ImageFileBuffer::e_typePNG;
m_ThumbnailBuffer.m_pBuffer = pngBuf.data;
m_ThumbnailBuffer.m_bufferSize = pngBuf.size;
}
else
{
free(pngBuf.data);
}
}
void CConsoleMinecraftApp::GetSaveThumbnail(PBYTE *pbData,DWORD *pdwSize)
{