From 250b1bea8f5f64400cfaab31e326f955e49a4572 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 6 Mar 2024 16:09:55 +1000 Subject: [PATCH] GPU: Use Image class for screenshot saving --- src/core/gpu.cpp | 159 ++++++++------------------------------- src/core/gpu.h | 5 +- src/core/settings.cpp | 6 -- src/core/system.cpp | 5 +- src/core/types.h | 2 - src/util/gpu_texture.cpp | 24 +++--- src/util/gpu_texture.h | 4 +- 7 files changed, 51 insertions(+), 154 deletions(-) diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 9fdfeda77..a07140aad 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "gpu.h" @@ -12,6 +12,7 @@ #include "timers.h" #include "util/gpu_device.h" +#include "util/image.h" #include "util/imgui_manager.h" #include "util/postprocessing.h" #include "util/shadergen.h" @@ -24,9 +25,6 @@ #include "common/small_string.h" #include "common/string_util.h" -#include "stb_image_resize.h" -#include "stb_image_write.h" - #include #include @@ -1906,9 +1904,8 @@ Common::Rectangle GPU::CalculateDrawRect(s32 window_width, s32 window_heigh } static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp, - u8 quality, bool clear_alpha, bool flip_y, u32 resize_width, - u32 resize_height, std::vector texture_data, u32 texture_data_stride, - GPUTexture::Format texture_format) + u8 quality, bool clear_alpha, bool flip_y, std::vector texture_data, + u32 texture_data_stride, GPUTexture::Format texture_format) { const char* extension = std::strrchr(filename.c_str(), '.'); @@ -1923,65 +1920,16 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil if (clear_alpha) { - 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); - } - } + for (u32& pixel : texture_data) + pixel |= 0xFF000000u; } if (flip_y) - GPUTexture::FlipTextureDataRGBA8(width, height, texture_data, texture_data_stride); + GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast(texture_data.data()), texture_data_stride); - if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height)) - { - std::vector resized_texture_data(resize_width * resize_height * sizeof(u32)); - u32 resized_texture_stride = sizeof(u32) * resize_width; - 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; - } - - width = resize_width; - height = resize_height; - texture_data = std::move(resized_texture_data); - texture_data_stride = resized_texture_stride; - } - - const auto write_func = [](void* context, void* data, int size) { - std::fwrite(data, 1, size, static_cast(context)); - }; - - bool result = false; - if (StringUtil::Strcasecmp(extension, ".png") == 0) - { - // TODO: Use quality... libpng is better. - result = - (stbi_write_png_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), texture_data_stride) != 0); - } - else if (StringUtil::Strcasecmp(extension, ".jpg") == 0) - { - result = (stbi_write_jpg_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), quality) != 0); - } - else if (StringUtil::Strcasecmp(extension, ".tga") == 0) - { - result = (stbi_write_tga_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0); - } - else if (StringUtil::Strcasecmp(extension, ".bmp") == 0) - { - result = (stbi_write_bmp_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0); - } - - if (!result) + Assert(texture_data_stride == sizeof(u32) * width); + RGBA8Image image(width, height, std::move(texture_data)); + if (!image.SaveToFile(filename.c_str(), fp.get(), quality)) { Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename.c_str(), extension); return false; @@ -1990,43 +1938,11 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil return true; } -bool GPU::WriteDisplayTextureToFile(std::string filename, bool full_resolution /* = true */, - bool apply_aspect_ratio /* = true */, bool compress_on_thread /* = false */) +bool GPU::WriteDisplayTextureToFile(std::string filename, bool compress_on_thread /* = false */) { if (!m_display_texture) return false; - s32 resize_width = 0; - s32 resize_height = std::abs(m_display_texture_view_height); - if (apply_aspect_ratio) - { - const float ss_width_scale = static_cast(m_display_active_width) / static_cast(m_display_width); - const float ss_height_scale = static_cast(m_display_active_height) / static_cast(m_display_height); - const float ss_aspect_ratio = m_display_aspect_ratio * ss_width_scale / ss_height_scale; - resize_width = g_settings.display_stretch_vertically ? - m_display_texture_view_width : - static_cast(static_cast(resize_height) * ss_aspect_ratio); - resize_height = g_settings.display_stretch_vertically ? - static_cast(static_cast(resize_height) / - (m_display_aspect_ratio / - (static_cast(m_display_width) / static_cast(m_display_height)))) : - resize_height; - } - else - { - resize_width = m_display_texture_view_width; - } - - if (!full_resolution) - { - const s32 resolution_scale = std::abs(m_display_texture_view_height) / m_display_active_height; - resize_height /= resolution_scale; - resize_width /= resolution_scale; - } - - if (resize_width <= 0 || resize_height <= 0) - return false; - const u32 read_x = static_cast(m_display_texture_view_x); const u32 read_y = static_cast(m_display_texture_view_y); const u32 read_width = static_cast(m_display_texture_view_width); @@ -2034,13 +1950,14 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool full_resolution / const u32 texture_data_stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(m_display_texture->GetFormat()) * read_width, 4); - std::vector texture_data(texture_data_stride * read_height); + std::vector texture_data((texture_data_stride * read_height) / sizeof(u32)); std::unique_ptr 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); + dltex = + g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(), texture_data.data(), + texture_data.size() * sizeof(u32), texture_data_stride); } if (!dltex) { @@ -2074,21 +1991,19 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool full_resolution / if (!compress_on_thread) { return CompressAndWriteTextureToFile(read_width, read_height, std::move(filename), std::move(fp), - g_settings.display_screenshot_quality, clear_alpha, flip_y, resize_width, - resize_height, std::move(texture_data), texture_data_stride, - m_display_texture->GetFormat()); + g_settings.display_screenshot_quality, clear_alpha, flip_y, + std::move(texture_data), texture_data_stride, m_display_texture->GetFormat()); } std::thread compress_thread(CompressAndWriteTextureToFile, read_width, read_height, std::move(filename), - std::move(fp), g_settings.display_screenshot_quality, clear_alpha, flip_y, resize_width, - resize_height, std::move(texture_data), texture_data_stride, - m_display_texture->GetFormat()); + std::move(fp), g_settings.display_screenshot_quality, clear_alpha, flip_y, + std::move(texture_data), texture_data_stride, m_display_texture->GetFormat()); compress_thread.detach(); return true; } bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangle& draw_rect, bool postfx, - std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) { const GPUTexture::Format hdformat = g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8; @@ -2103,14 +2018,14 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangl // TODO: this should use copy shader instead. RenderDisplay(render_texture.get(), draw_rect, postfx); - const u32 stride = GPUTexture::GetPixelSize(hdformat) * width; - out_pixels->resize(height * stride); + const u32 stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(hdformat) * width, sizeof(u32)); + out_pixels->resize((height * stride) / sizeof(u32)); std::unique_ptr dltex; if (g_gpu_device->GetFeatures().memory_import) { - dltex = - g_gpu_device->CreateDownloadTexture(width, height, hdformat, out_pixels->data(), out_pixels->size(), stride); + dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, out_pixels->data(), + out_pixels->size() * sizeof(u32), stride); } if (!dltex) { @@ -2195,7 +2110,7 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod if (width == 0 || height == 0) return false; - std::vector pixels; + std::vector pixels; u32 pixels_stride; GPUTexture::Format pixels_format; if (!RenderScreenshotToBuffer(width, height, draw_rect, !internal_resolution, &pixels, &pixels_stride, @@ -2215,13 +2130,13 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod if (!compress_on_thread) { return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), quality, true, - g_gpu_device->UsesLowerLeftOrigin(), width, height, std::move(pixels), - pixels_stride, pixels_format); + g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride, + pixels_format); } std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), quality, - true, g_gpu_device->UsesLowerLeftOrigin(), width, height, std::move(pixels), - pixels_stride, pixels_format); + true, g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride, + pixels_format); compress_thread.detach(); return true; } @@ -2248,20 +2163,13 @@ bool GPU::DumpVRAMToFile(const char* filename) bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha) { - auto fp = FileSystem::OpenManagedCFile(filename, "wb"); - if (!fp) - { - Log_ErrorPrintf("Can't open file '%s'", filename); - return false; - } - - auto rgba8_buf = std::make_unique(width * height); + RGBA8Image image(width, height); const char* ptr_in = static_cast(buffer); - u32* ptr_out = rgba8_buf.get(); for (u32 row = 0; row < height; row++) { const char* row_ptr_in = ptr_in; + u32* ptr_out = image.GetRowPixels(row); for (u32 col = 0; col < width; col++) { @@ -2274,10 +2182,7 @@ bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride ptr_in += stride; } - const auto write_func = [](void* context, void* data, int size) { - std::fwrite(data, 1, size, static_cast(context)); - }; - return (stbi_write_png_to_func(write_func, fp.get(), width, height, 4, rgba8_buf.get(), sizeof(u32) * width) != 0); + return image.SaveToFile(filename); } void GPU::DrawDebugStateWindow() diff --git a/src/core/gpu.h b/src/core/gpu.h index 02ca0f0b3..61a01b4f8 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -201,12 +201,11 @@ public: Common::Rectangle CalculateDrawRect(s32 window_width, s32 window_height, bool apply_aspect_ratio = true) const; /// Helper function to save current display texture to PNG. - bool WriteDisplayTextureToFile(std::string filename, bool full_resolution = true, bool apply_aspect_ratio = true, - bool compress_on_thread = false); + bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false); /// Renders the display, optionally with postprocessing to the specified image. bool RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangle& draw_rect, bool postfx, - std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format); + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format); /// Helper function to save screenshot to PNG. bool RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 4bfaf687e..e693918c3 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1471,20 +1471,14 @@ const char* Settings::GetDisplayScreenshotModeDisplayName(DisplayScreenshotMode static constexpr const std::array s_display_screenshot_format_names = { "PNG", "JPEG", - "TGA", - "BMP", }; static constexpr const std::array s_display_screenshot_format_display_names = { TRANSLATE_NOOP("Settings", "PNG"), TRANSLATE_NOOP("Settings", "JPEG"), - TRANSLATE_NOOP("Settings", "TGA"), - TRANSLATE_NOOP("Settings", "BMP"), }; static constexpr const std::array s_display_screenshot_format_extensions = { "png", "jpg", - "tga", - "bmp", }; std::optional Settings::ParseDisplayScreenshotFormat(const char* str) diff --git a/src/core/system.cpp b/src/core/system.cpp index a4583d0c8..dff56bb56 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2446,7 +2446,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 screenshot_buffer; + std::vector screenshot_buffer; u32 screenshot_stride; GPUTexture::Format screenshot_format; if (g_gpu->RenderScreenshotToBuffer(screenshot_width, screenshot_height, @@ -2464,7 +2464,8 @@ bool System::SaveStateToStream(ByteStream* state, u32 screenshot_size /* = 256 * { if (g_gpu_device->UsesLowerLeftOrigin()) { - GPUTexture::FlipTextureDataRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride); + GPUTexture::FlipTextureDataRGBA8(screenshot_width, screenshot_height, + reinterpret_cast(screenshot_buffer.data()), screenshot_stride); } header.offset_to_screenshot = static_cast(state->GetPosition()); diff --git a/src/core/types.h b/src/core/types.h index 4832b0eb9..151506b96 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -172,8 +172,6 @@ enum class DisplayScreenshotFormat : u8 { PNG, JPEG, - TGA, - BMP, Count }; diff --git a/src/util/gpu_texture.cpp b/src/util/gpu_texture.cpp index e6786ecd1..ca7a31197 100644 --- a/src/util/gpu_texture.cpp +++ b/src/util/gpu_texture.cpp @@ -230,7 +230,7 @@ bool GPUTexture::ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u return true; } -bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, +bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32& texture_data_stride, GPUTexture::Format format) { switch (format) @@ -239,7 +239,7 @@ bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector(texture_data.data()) + (y * texture_data_stride); for (u32 x = 0; x < width; x++) { u32 pixel; @@ -258,12 +258,12 @@ bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector temp(width * height * sizeof(u32)); + std::vector temp(width * height); for (u32 y = 0; y < height; y++) { - const u8* pixels_in = texture_data.data() + (y * texture_data_stride); - u8* pixels_out = &temp[y * width * sizeof(u32)]; + const u8* pixels_in = reinterpret_cast(texture_data.data()) + (y * texture_data_stride); + u8* pixels_out = reinterpret_cast(temp.data()) + (y * width * sizeof(u32)); for (u32 x = 0; x < width; x++) { @@ -288,12 +288,12 @@ bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector temp(width * height * sizeof(u32)); + std::vector temp(width * height); for (u32 y = 0; y < height; y++) { - const u8* pixels_in = texture_data.data() + (y * texture_data_stride); - u8* pixels_out = &temp[y * width]; + const u8* pixels_in = reinterpret_cast(texture_data.data()) + (y * texture_data_stride); + u8* pixels_out = reinterpret_cast(temp.data()) + (y * width * sizeof(u32)); for (u32 x = 0; x < width; x++) { @@ -323,16 +323,16 @@ bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32 texture_data_stride) +void GPUTexture::FlipTextureDataRGBA8(u32 width, u32 height, u8* texture_data, u32 texture_data_stride) { - std::vector temp(width * sizeof(u32)); + std::unique_ptr temp = std::make_unique(texture_data_stride); for (u32 flip_row = 0; flip_row < (height / 2); flip_row++) { u8* top_ptr = &texture_data[flip_row * texture_data_stride]; u8* bottom_ptr = &texture_data[((height - 1) - flip_row) * texture_data_stride]; - std::memcpy(temp.data(), top_ptr, texture_data_stride); + std::memcpy(temp.get(), top_ptr, texture_data_stride); std::memcpy(top_ptr, bottom_ptr, texture_data_stride); - std::memcpy(bottom_ptr, temp.data(), texture_data_stride); + std::memcpy(bottom_ptr, temp.get(), texture_data_stride); } } diff --git a/src/util/gpu_texture.h b/src/util/gpu_texture.h index d0369b946..a7dafccf2 100644 --- a/src/util/gpu_texture.h +++ b/src/util/gpu_texture.h @@ -89,9 +89,9 @@ public: static bool ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format); - static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32& texture_data_stride, + static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32& texture_data_stride, GPUTexture::Format format); - static void FlipTextureDataRGBA8(u32 width, u32 height, std::vector& texture_data, u32 texture_data_stride); + static void FlipTextureDataRGBA8(u32 width, u32 height, u8* texture_data, u32 texture_data_stride); ALWAYS_INLINE u32 GetWidth() const { return m_width; } ALWAYS_INLINE u32 GetHeight() const { return m_height; }