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;
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<float, float> HostDisplay::ConvertWindowCoordinatesToDisplayCoordinat
return std::make_tuple(display_x, display_y);
}
static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& texture_data, u32& texture_data_stride,
HostDisplayPixelFormat format)
bool HostDisplay::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& texture_data,
u32& texture_data_stride, HostDisplayPixelFormat 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,
bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
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;
}
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<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);
}
}
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;
}

View file

@ -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<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;

View file

@ -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<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;
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<u32>(state->GetPosition());
header.screenshot_width = screenshot_size;
header.screenshot_height = screenshot_size;
header.screenshot_size = static_cast<u32>(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<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();
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);

View file

@ -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<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() :
s_placeholder_texture->GetHandle()),