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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
Loading…
Reference in New Issue