diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp index 3bf681032..d0469fd3b 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -20,6 +20,12 @@ HostDisplayTexture::~HostDisplayTexture() = default; HostDisplay::~HostDisplay() = default; +bool HostDisplay::UsesLowerLeftOrigin() const +{ + const RenderAPI api = GetRenderAPI(); + return (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES); +} + void HostDisplay::SetDisplayMaxFPS(float max_fps) { m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f; @@ -299,8 +305,8 @@ std::tuple HostDisplay::ConvertWindowCoordinatesToDisplayCoordinat return std::make_tuple(display_x, display_y); } -static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32& texture_data_stride, - HostDisplayPixelFormat format) +bool HostDisplay::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, + u32& texture_data_stride, HostDisplayPixelFormat format) { switch (format) { @@ -382,6 +388,19 @@ static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& t } } +void HostDisplay::FlipTextureDataRGBA8(u32 width, u32 height, std::vector& texture_data, u32 texture_data_stride) +{ + std::vector temp(width); + for (u32 flip_row = 0; flip_row < (height / 2); flip_row++) + { + u32* top_ptr = &texture_data[flip_row * width]; + u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width]; + std::memcpy(temp.data(), top_ptr, texture_data_stride); + std::memcpy(top_ptr, bottom_ptr, texture_data_stride); + std::memcpy(bottom_ptr, temp.data(), texture_data_stride); + } +} + 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 texture_data, u32 texture_data_stride, @@ -395,7 +414,7 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil return false; } - if (!ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format)) + if (!HostDisplay::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format)) return false; if (clear_alpha) @@ -405,17 +424,7 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil } if (flip_y) - { - std::vector temp(width); - for (u32 flip_row = 0; flip_row < (height / 2); flip_row++) - { - u32* top_ptr = &texture_data[flip_row * width]; - u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width]; - std::memcpy(temp.data(), top_ptr, texture_data_stride); - std::memcpy(top_ptr, bottom_ptr, texture_data_stride); - std::memcpy(bottom_ptr, temp.data(), texture_data_stride); - } - } + HostDisplay::FlipTextureDataRGBA8(width, height, texture_data, texture_data_stride); if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height)) { @@ -643,16 +652,14 @@ bool HostDisplay::WriteScreenshotToFile(std::string filename, bool compress_on_t return false; } - const RenderAPI api = GetRenderAPI(); - const bool flip_y = (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES); if (!compress_on_thread) { - return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true, flip_y, width, height, - std::move(pixels), pixels_stride, pixels_format); + return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true, UsesLowerLeftOrigin(), + width, height, std::move(pixels), pixels_stride, pixels_format); } std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), true, - flip_y, width, height, std::move(pixels), pixels_stride, pixels_format); + UsesLowerLeftOrigin(), width, height, std::move(pixels), pixels_stride, pixels_format); compress_thread.detach(); return true; } diff --git a/src/core/host_display.h b/src/core/host_display.h index 1c683eb74..445c64494 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -134,6 +134,7 @@ public: const s32 GetDisplayHeight() const { return m_display_height; } const float GetDisplayAspectRatio() const { return m_display_aspect_ratio; } + bool UsesLowerLeftOrigin() const; void SetDisplayMaxFPS(float max_fps); bool ShouldSkipDisplayingFrame(); @@ -186,6 +187,9 @@ public: } static u32 GetDisplayPixelFormatSize(HostDisplayPixelFormat format); + static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32& texture_data_stride, + HostDisplayPixelFormat format); + static void FlipTextureDataRGBA8(u32 width, u32 height, std::vector& texture_data, u32 texture_data_stride); virtual bool SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const = 0; diff --git a/src/core/system.cpp b/src/core/system.cpp index c34b077e4..6c5d1097e 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1224,7 +1224,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di return true; } -bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) +bool SaveState(ByteStream* state, u32 screenshot_size /* = 256 */) { if (IsShutdown()) return false; @@ -1254,17 +1254,45 @@ bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) // save screenshot if (screenshot_size > 0) { + // assume this size is the width + HostDisplay* display = g_host_interface->GetDisplay(); + const float display_aspect_ratio = display->GetDisplayAspectRatio(); + const u32 screenshot_width = screenshot_size; + const u32 screenshot_height = + std::max(1u, static_cast(static_cast(screenshot_width) / + ((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; - if (g_host_interface->GetDisplay()->WriteDisplayTextureToBuffer(&screenshot_buffer, screenshot_size, - screenshot_size) && - !screenshot_buffer.empty()) + u32 screenshot_stride; + HostDisplayPixelFormat screenshot_format; + if (display->RenderScreenshot(screenshot_width, screenshot_height, &screenshot_buffer, &screenshot_stride, + &screenshot_format) || + !display->ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride, + HostDisplayPixelFormat::RGBA8)) { - header.offset_to_screenshot = static_cast(state->GetPosition()); - header.screenshot_width = screenshot_size; - header.screenshot_height = screenshot_size; - header.screenshot_size = static_cast(screenshot_buffer.size() * sizeof(u32)); - if (!state->Write2(screenshot_buffer.data(), header.screenshot_size)) - return false; + if (screenshot_stride != (screenshot_width * sizeof(u32))) + { + Log_WarningPrintf("Failed to save %ux%u screenshot for save state due to incorrect stride(%u)", + screenshot_width, screenshot_height, screenshot_stride); + } + else + { + if (display->UsesLowerLeftOrigin()) + display->FlipTextureDataRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride); + + header.offset_to_screenshot = static_cast(state->GetPosition()); + header.screenshot_width = screenshot_width; + header.screenshot_height = screenshot_height; + header.screenshot_size = static_cast(screenshot_buffer.size() * sizeof(u32)); + if (!state->Write2(screenshot_buffer.data(), header.screenshot_size)) + return false; + } + } + else + { + Log_WarningPrintf("Failed to save %ux%u screenshot for save state due to render/conversion failure", + screenshot_width, screenshot_height); } } diff --git a/src/core/system.h b/src/core/system.h index 8b2d1e689..460dd5f8d 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -149,7 +149,7 @@ void Reset(); void Shutdown(); bool LoadState(ByteStream* state, bool update_display = true); -bool SaveState(ByteStream* state, u32 screenshot_size = 128); +bool SaveState(ByteStream* state, u32 screenshot_size = 256); /// Recreates the GPU component, saving/loading the state so it is preserved. Call when the GPU renderer changes. bool RecreateGPU(GPURenderer renderer, bool update_display = true); diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index e5986f92e..bbb250f93 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -2681,8 +2681,8 @@ void DrawSaveStateSelector(bool is_loading, bool fullscreen) constexpr float padding = 10.0f; constexpr float button_height = 96.0f; - constexpr float image_width = 128.0f; - constexpr float image_height = 96.0f; + constexpr float max_image_width = 96.0f; + constexpr float max_image_height = 96.0f; ImDrawList* dl = ImGui::GetWindowDrawList(); for (const SaveStateListEntry& entry : s_save_state_selector_slots) @@ -2694,8 +2694,15 @@ void DrawSaveStateSelector(bool is_loading, bool fullscreen) continue; ImVec2 pos(bb.Min); - const ImRect image_bb(pos, pos + LayoutScale(image_width, image_height)); - pos.x += LayoutScale(image_width + padding); + + // use aspect ratio of screenshot to determine height + const HostDisplayTexture* image = entry.preview_texture ? entry.preview_texture.get() : s_placeholder_texture.get(); + const float image_height = + max_image_width / (static_cast(image->GetWidth()) / static_cast(image->GetHeight())); + const float image_margin = (max_image_height - image_height) / 2.0f; + const ImRect image_bb(ImVec2(pos.x, pos.y + LayoutScale(image_margin)), + pos + LayoutScale(max_image_width, image_margin + image_height)); + pos.x += LayoutScale(max_image_width + padding); dl->AddImage(static_cast(entry.preview_texture ? entry.preview_texture->GetHandle() : s_placeholder_texture->GetHandle()),