GPUDevice: Add GPUDownloadTexture
Which can also be based in host/client memory. Use it for screenshots and VRAM downloads.
This commit is contained in:
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user