GPUDevice: Add GPUDownloadTexture

Which can also be based in host/client memory.
Use it for screenshots and VRAM downloads.
This commit is contained in:
Stenzek
2024-02-28 16:13:50 +10:00
parent 4309d8ebf3
commit f936a36c85
27 changed files with 1501 additions and 527 deletions

View File

@ -1907,7 +1907,7 @@ Common::Rectangle<s32> GPU::CalculateDrawRect(s32 window_width, s32 window_heigh
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
std::vector<u32> texture_data, u32 texture_data_stride,
std::vector<u8> texture_data, u32 texture_data_stride,
GPUTexture::Format texture_format)
{
@ -1923,8 +1923,18 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil
if (clear_alpha)
{
for (u32& pixel : texture_data)
pixel |= 0xFF000000;
for (u32 y = 0; y < height; y++)
{
u8* pixels = &texture_data[y * texture_data_stride];
for (u32 x = 0; x < width; x++)
{
u32 pixel;
std::memcpy(&pixel, pixels, sizeof(pixel));
pixel |= 0xFF000000u;
std::memcpy(pixels, &pixel, sizeof(pixel));
pixels += sizeof(pixel);
}
}
}
if (flip_y)
@ -1932,11 +1942,10 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
{
std::vector<u32> resized_texture_data(resize_width * resize_height);
std::vector<u8> resized_texture_data(resize_width * resize_height * sizeof(u32));
u32 resized_texture_stride = sizeof(u32) * resize_width;
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
resized_texture_stride, 4))
if (!stbir_resize_uint8(texture_data.data(), width, height, texture_data_stride, resized_texture_data.data(),
resize_width, resize_height, resized_texture_stride, 4))
{
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
return false;
@ -2022,13 +2031,29 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool full_resolution /
const u32 read_width = static_cast<u32>(m_display_texture_view_width);
const u32 read_height = static_cast<u32>(m_display_texture_view_height);
std::vector<u32> texture_data(read_width * read_height);
const u32 texture_data_stride =
Common::AlignUpPow2(GPUTexture::GetPixelSize(m_display_texture->GetFormat()) * read_width, 4);
if (!g_gpu_device->DownloadTexture(m_display_texture, read_x, read_y, read_width, read_height, texture_data.data(),
texture_data_stride))
std::vector<u8> texture_data(texture_data_stride * read_height);
std::unique_ptr<GPUDownloadTexture> dltex;
if (g_gpu_device->GetFeatures().memory_import)
{
dltex = g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(),
texture_data.data(), texture_data.size(), texture_data_stride);
}
if (!dltex)
{
if (!(dltex = g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat())))
{
Log_ErrorFmt("Failed to create {}x{} {} download texture", read_width, read_height,
GPUTexture::GetFormatName(m_display_texture->GetFormat()));
return false;
}
}
dltex->CopyFromTexture(0, 0, m_display_texture, read_x, read_y, read_width, read_height, 0, 0, !dltex->IsImported());
if (!dltex->ReadTexels(0, 0, read_width, read_height, texture_data.data(), texture_data_stride))
{
Log_ErrorPrintf("Texture download failed");
RestoreDeviceContext();
return false;
}
@ -2060,7 +2085,7 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool full_resolution /
}
bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, bool postfx,
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format)
std::vector<u8>* out_pixels, u32* out_stride, GPUTexture::Format* out_format)
{
const GPUTexture::Format hdformat =
g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8;
@ -2076,8 +2101,25 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangl
RenderDisplay(render_texture.get(), draw_rect, postfx);
const u32 stride = GPUTexture::GetPixelSize(hdformat) * width;
out_pixels->resize(width * height);
if (!g_gpu_device->DownloadTexture(render_texture.get(), 0, 0, width, height, out_pixels->data(), stride))
out_pixels->resize(height * stride);
std::unique_ptr<GPUDownloadTexture> dltex;
if (g_gpu_device->GetFeatures().memory_import)
{
dltex =
g_gpu_device->CreateDownloadTexture(width, height, hdformat, out_pixels->data(), out_pixels->size(), stride);
}
if (!dltex)
{
if (!(dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat)))
{
Log_ErrorFmt("Failed to create {}x{} download texture", width, height);
return false;
}
}
dltex->CopyFromTexture(0, 0, render_texture.get(), 0, 0, width, height, 0, 0, false);
if (!dltex->ReadTexels(0, 0, width, height, out_pixels->data(), stride))
{
RestoreDeviceContext();
return false;
@ -2142,7 +2184,7 @@ bool GPU::RenderScreenshotToFile(std::string filename, bool internal_resolution
if (width == 0 || height == 0)
return false;
std::vector<u32> pixels;
std::vector<u8> pixels;
u32 pixels_stride;
GPUTexture::Format pixels_format;
if (!RenderScreenshotToBuffer(width, height, draw_rect, !internal_resolution, &pixels, &pixels_stride,

View File

@ -206,7 +206,7 @@ public:
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, bool postfx,
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
std::vector<u8>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
/// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false);

View File

@ -663,6 +663,26 @@ bool GPU_HW::CreateBuffers()
GL_OBJECT_NAME(m_vram_read_texture, "VRAM Read Texture");
GL_OBJECT_NAME(m_vram_readback_texture, "VRAM Readback Texture");
if (g_gpu_device->GetFeatures().memory_import)
{
Log_DevPrint("Trying to import guest VRAM buffer for downloads...");
m_vram_readback_download_texture = g_gpu_device->CreateDownloadTexture(
m_vram_readback_texture->GetWidth(), m_vram_readback_texture->GetHeight(), m_vram_readback_texture->GetFormat(),
g_vram, sizeof(g_vram), VRAM_WIDTH * sizeof(u16));
if (!m_vram_readback_download_texture)
Log_ErrorPrint("Failed to create imported readback buffer");
}
if (!m_vram_readback_download_texture)
{
m_vram_readback_download_texture = g_gpu_device->CreateDownloadTexture(
m_vram_readback_texture->GetWidth(), m_vram_readback_texture->GetHeight(), m_vram_readback_texture->GetFormat());
if (!m_vram_readback_download_texture)
{
Log_ErrorPrint("Failed to create readback download texture");
return false;
}
}
if (g_gpu_device->GetFeatures().supports_texture_buffers)
{
if (!(m_vram_upload_buffer =
@ -703,6 +723,7 @@ void GPU_HW::DestroyBuffers()
ClearDisplayTexture();
m_vram_upload_buffer.reset();
m_vram_readback_download_texture.reset();
g_gpu_device->RecycleTexture(std::move(m_downsample_texture));
g_gpu_device->RecycleTexture(std::move(m_vram_read_texture));
g_gpu_device->RecycleTexture(std::move(m_vram_depth_texture));
@ -2405,8 +2426,18 @@ void GPU_HW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
}
// Get bounds with wrap-around handled.
const Common::Rectangle<u32> copy_rect = GetVRAMTransferBounds(x, y, width, height);
const u32 encoded_width = (copy_rect.GetWidth() + 1) / 2;
Common::Rectangle<u32> copy_rect = GetVRAMTransferBounds(x, y, width, height);
// Has to be aligned to an even pixel for the download, due to 32-bit packing.
if (copy_rect.left & 1)
copy_rect.left--;
if (copy_rect.right & 1)
copy_rect.right++;
DebugAssert((copy_rect.left % 2) == 0 && (copy_rect.GetWidth() % 2) == 0);
const u32 encoded_left = copy_rect.left / 2;
const u32 encoded_top = copy_rect.top;
const u32 encoded_width = copy_rect.GetWidth() / 2;
const u32 encoded_height = copy_rect.GetHeight();
// Encode the 24-bit texture as 16-bit.
@ -2421,9 +2452,22 @@ void GPU_HW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
GL_POP();
// Stage the readback and copy it into our shadow buffer.
g_gpu_device->DownloadTexture(m_vram_readback_texture.get(), 0, 0, encoded_width, encoded_height,
reinterpret_cast<u32*>(&g_vram[copy_rect.top * VRAM_WIDTH + copy_rect.left]),
VRAM_WIDTH * sizeof(u16));
if (m_vram_readback_download_texture->IsImported())
{
// Fast path, read directly.
m_vram_readback_download_texture->CopyFromTexture(encoded_left, encoded_top, m_vram_readback_texture.get(), 0, 0,
encoded_width, encoded_height, 0, 0, false);
m_vram_readback_download_texture->Flush();
}
else
{
// Copy to staging buffer, then to VRAM.
m_vram_readback_download_texture->CopyFromTexture(0, 0, m_vram_readback_texture.get(), 0, 0, encoded_width,
encoded_height, 0, 0, true);
m_vram_readback_download_texture->ReadTexels(0, 0, encoded_width, encoded_height,
&g_vram[copy_rect.top * VRAM_WIDTH + copy_rect.left],
VRAM_WIDTH * sizeof(u16));
}
RestoreDeviceContext();
}

View File

@ -216,6 +216,7 @@ private:
std::unique_ptr<GPUTexture> m_vram_depth_texture;
std::unique_ptr<GPUTexture> m_vram_read_texture;
std::unique_ptr<GPUTexture> m_vram_readback_texture;
std::unique_ptr<GPUDownloadTexture> m_vram_readback_download_texture;
std::unique_ptr<GPUTexture> m_vram_replacement_texture;
std::unique_ptr<GPUTexture> m_display_private_texture; // TODO: Move to base.

View File

@ -265,7 +265,7 @@ bool System::Internal::ProcessStartup()
InitializeDiscordPresence();
#endif
return true;
return true;
}
void System::Internal::ProcessShutdown()
@ -2430,7 +2430,7 @@ bool System::SaveStateToStream(ByteStream* state, u32 screenshot_size /* = 256 *
((display_aspect_ratio > 0.0f) ? display_aspect_ratio : 1.0f)));
Log_VerbosePrintf("Saving %ux%u screenshot for state", screenshot_width, screenshot_height);
std::vector<u32> screenshot_buffer;
std::vector<u8> screenshot_buffer;
u32 screenshot_stride;
GPUTexture::Format screenshot_format;
if (g_gpu->RenderScreenshotToBuffer(screenshot_width, screenshot_height,
@ -2454,7 +2454,7 @@ bool System::SaveStateToStream(ByteStream* state, u32 screenshot_size /* = 256 *
header.offset_to_screenshot = static_cast<u32>(state->GetPosition());
header.screenshot_width = screenshot_width;
header.screenshot_height = screenshot_height;
header.screenshot_size = static_cast<u32>(screenshot_buffer.size() * sizeof(u32));
header.screenshot_size = static_cast<u32>(screenshot_buffer.size());
if (!state->Write2(screenshot_buffer.data(), header.screenshot_size))
return false;
}