System: Render save state screenshots at fixed resolution

Fixes delays when saving state at high internal resolution.
This commit is contained in:
Connor McLaughlin 2021-04-28 02:37:52 +10:00
parent 67adc986ab
commit c2916e0719
5 changed files with 80 additions and 34 deletions

View file

@ -20,6 +20,12 @@ HostDisplayTexture::~HostDisplayTexture() = default;
HostDisplay::~HostDisplay() = 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) void HostDisplay::SetDisplayMaxFPS(float max_fps)
{ {
m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f; m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f;
@ -299,8 +305,8 @@ std::tuple<float, float> HostDisplay::ConvertWindowCoordinatesToDisplayCoordinat
return std::make_tuple(display_x, display_y); return std::make_tuple(display_x, display_y);
} }
static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& texture_data, u32& texture_data_stride, bool HostDisplay::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& texture_data,
HostDisplayPixelFormat format) u32& texture_data_stride, HostDisplayPixelFormat format)
{ {
switch (format) switch (format)
{ {
@ -382,6 +388,19 @@ static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& t
} }
} }
void HostDisplay::FlipTextureDataRGBA8(u32 width, u32 height, std::vector<u32>& texture_data, u32 texture_data_stride)
{
std::vector<u32> 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, 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, bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
std::vector<u32> texture_data, u32 texture_data_stride, std::vector<u32> texture_data, u32 texture_data_stride,
@ -395,7 +414,7 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil
return false; 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; return false;
if (clear_alpha) if (clear_alpha)
@ -405,17 +424,7 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil
} }
if (flip_y) if (flip_y)
{ HostDisplay::FlipTextureDataRGBA8(width, height, texture_data, texture_data_stride);
std::vector<u32> 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);
}
}
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height)) 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; return false;
} }
const RenderAPI api = GetRenderAPI();
const bool flip_y = (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES);
if (!compress_on_thread) if (!compress_on_thread)
{ {
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true, flip_y, width, height, return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true, UsesLowerLeftOrigin(),
std::move(pixels), pixels_stride, pixels_format); width, height, std::move(pixels), pixels_stride, pixels_format);
} }
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), true, 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(); compress_thread.detach();
return true; return true;
} }

View file

@ -134,6 +134,7 @@ public:
const s32 GetDisplayHeight() const { return m_display_height; } const s32 GetDisplayHeight() const { return m_display_height; }
const float GetDisplayAspectRatio() const { return m_display_aspect_ratio; } const float GetDisplayAspectRatio() const { return m_display_aspect_ratio; }
bool UsesLowerLeftOrigin() const;
void SetDisplayMaxFPS(float max_fps); void SetDisplayMaxFPS(float max_fps);
bool ShouldSkipDisplayingFrame(); bool ShouldSkipDisplayingFrame();
@ -186,6 +187,9 @@ public:
} }
static u32 GetDisplayPixelFormatSize(HostDisplayPixelFormat format); static u32 GetDisplayPixelFormatSize(HostDisplayPixelFormat format);
static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& texture_data, u32& texture_data_stride,
HostDisplayPixelFormat format);
static void FlipTextureDataRGBA8(u32 width, u32 height, std::vector<u32>& texture_data, u32 texture_data_stride);
virtual bool SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const = 0; virtual bool SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const = 0;

View file

@ -1224,7 +1224,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di
return true; return true;
} }
bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) bool SaveState(ByteStream* state, u32 screenshot_size /* = 256 */)
{ {
if (IsShutdown()) if (IsShutdown())
return false; return false;
@ -1254,17 +1254,45 @@ bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */)
// save screenshot // save screenshot
if (screenshot_size > 0) 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<u32>(static_cast<float>(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<u32> screenshot_buffer; std::vector<u32> screenshot_buffer;
if (g_host_interface->GetDisplay()->WriteDisplayTextureToBuffer(&screenshot_buffer, screenshot_size, u32 screenshot_stride;
screenshot_size) && HostDisplayPixelFormat screenshot_format;
!screenshot_buffer.empty()) 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<u32>(state->GetPosition()); if (screenshot_stride != (screenshot_width * sizeof(u32)))
header.screenshot_width = screenshot_size; {
header.screenshot_height = screenshot_size; Log_WarningPrintf("Failed to save %ux%u screenshot for save state due to incorrect stride(%u)",
header.screenshot_size = static_cast<u32>(screenshot_buffer.size() * sizeof(u32)); screenshot_width, screenshot_height, screenshot_stride);
if (!state->Write2(screenshot_buffer.data(), header.screenshot_size)) }
return false; else
{
if (display->UsesLowerLeftOrigin())
display->FlipTextureDataRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride);
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));
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);
} }
} }

View file

@ -149,7 +149,7 @@ void Reset();
void Shutdown(); void Shutdown();
bool LoadState(ByteStream* state, bool update_display = true); 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. /// 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); bool RecreateGPU(GPURenderer renderer, bool update_display = true);

View file

@ -2681,8 +2681,8 @@ void DrawSaveStateSelector(bool is_loading, bool fullscreen)
constexpr float padding = 10.0f; constexpr float padding = 10.0f;
constexpr float button_height = 96.0f; constexpr float button_height = 96.0f;
constexpr float image_width = 128.0f; constexpr float max_image_width = 96.0f;
constexpr float image_height = 96.0f; constexpr float max_image_height = 96.0f;
ImDrawList* dl = ImGui::GetWindowDrawList(); ImDrawList* dl = ImGui::GetWindowDrawList();
for (const SaveStateListEntry& entry : s_save_state_selector_slots) for (const SaveStateListEntry& entry : s_save_state_selector_slots)
@ -2694,8 +2694,15 @@ void DrawSaveStateSelector(bool is_loading, bool fullscreen)
continue; continue;
ImVec2 pos(bb.Min); 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<float>(image->GetWidth()) / static_cast<float>(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<ImTextureID>(entry.preview_texture ? entry.preview_texture->GetHandle() : dl->AddImage(static_cast<ImTextureID>(entry.preview_texture ? entry.preview_texture->GetHandle() :
s_placeholder_texture->GetHandle()), s_placeholder_texture->GetHandle()),