System: Render save state screenshots at fixed resolution
Fixes delays when saving state at high internal resolution.
This commit is contained in:
parent
67adc986ab
commit
c2916e0719
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,19 +1254,47 @@ 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))
|
||||
{
|
||||
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_size;
|
||||
header.screenshot_height = screenshot_size;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// write data
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()),
|
||||
|
|
Loading…
Reference in New Issue