From 35acb7e62c4a6de10061799932a61b70d7aa7d52 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 14 May 2024 14:23:40 +0200 Subject: [PATCH] ui: load savestate thumbnails asynchronously. limit thumbnail size GetLastFrame can take max width or height. Limit width of savestate screenshot to 640. Load savestate thumbnail in async task. --- core/hw/pvr/Renderer_if.h | 2 ++ core/rend/dx11/dx11_renderer.cpp | 25 ++++++++++++------ core/rend/dx9/d3d_renderer.cpp | 25 ++++++++++++------ core/rend/gles/gldraw.cpp | 25 ++++++++++++------ core/rend/vulkan/vulkan_context.cpp | 25 ++++++++++++------ core/ui/gui.cpp | 7 +++--- core/ui/gui_util.cpp | 39 +++++++++++++++++++---------- core/ui/gui_util.h | 9 +++++++ 8 files changed, 109 insertions(+), 48 deletions(-) diff --git a/core/hw/pvr/Renderer_if.h b/core/hw/pvr/Renderer_if.h index 191368630..d7f7f17a8 100644 --- a/core/hw/pvr/Renderer_if.h +++ b/core/hw/pvr/Renderer_if.h @@ -65,6 +65,8 @@ struct Renderer virtual bool RenderLastFrame() { return false; } // Get the last rendered frame pixel data in RGB format // The returned image is rotated and scaled (upward orientation and square pixels) + // If both width and height are zero, the internal render resolution will be used. + // Otherwise either width or height will be used as the maximum width or height respectively. virtual bool GetLastFrame(std::vector& data, int& width, int& height) { return false; } virtual bool Present() { return true; } diff --git a/core/rend/dx11/dx11_renderer.cpp b/core/rend/dx11/dx11_renderer.cpp index cd5cb76fa..623529782 100644 --- a/core/rend/dx11/dx11_renderer.cpp +++ b/core/rend/dx11/dx11_renderer.cpp @@ -1350,16 +1350,25 @@ bool DX11Renderer::GetLastFrame(std::vector& data, int& width, int& height) if (!frameRenderedOnce) return false; - width = this->width; - height = this->height; - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = aspectRatio * height; - if (width > w) + if (width != 0) { height = width / aspectRatio; + } + else if (height != 0) { + width = aspectRatio * height; + } else - width = w; + { + width = this->width; + height = this->height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = aspectRatio * height; + if (width > w) + height = width / aspectRatio; + else + width = w; + } ComPtr dstTex; ComPtr dstRenderTarget; diff --git a/core/rend/dx9/d3d_renderer.cpp b/core/rend/dx9/d3d_renderer.cpp index ac58ab35a..63085ff42 100644 --- a/core/rend/dx9/d3d_renderer.cpp +++ b/core/rend/dx9/d3d_renderer.cpp @@ -1427,16 +1427,25 @@ bool D3DRenderer::GetLastFrame(std::vector& data, int& width, int& height) if (!frameRenderedOnce || !theDXContext.isReady()) return false; - width = this->width; - height = this->height; - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = aspectRatio * height; - if (width > w) + if (width != 0) { height = width / aspectRatio; + } + else if (height != 0) { + width = aspectRatio * height; + } else - width = w; + { + width = this->width; + height = this->height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = aspectRatio * height; + if (width > w) + height = width / aspectRatio; + else + width = w; + } backbuffer.reset(); device->GetRenderTarget(0, &backbuffer.get()); diff --git a/core/rend/gles/gldraw.cpp b/core/rend/gles/gldraw.cpp index 9108bfeae..85fbb874b 100644 --- a/core/rend/gles/gldraw.cpp +++ b/core/rend/gles/gldraw.cpp @@ -822,16 +822,25 @@ bool OpenGLRenderer::GetLastFrame(std::vector& data, int& width, int& height GlFramebuffer *framebuffer = gl.ofbo2.ready ? gl.ofbo2.framebuffer.get() : gl.ofbo.framebuffer.get(); if (framebuffer == nullptr) return false; - width = framebuffer->getWidth(); - height = framebuffer->getHeight(); - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = gl.ofbo.aspectRatio * height; - if (width > w) + if (width != 0) { height = width / gl.ofbo.aspectRatio; + } + else if (height != 0) { + width = gl.ofbo.aspectRatio * height; + } else - width = w; + { + width = framebuffer->getWidth(); + height = framebuffer->getHeight(); + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = gl.ofbo.aspectRatio * height; + if (width > w) + height = width / gl.ofbo.aspectRatio; + else + width = w; + } GlFramebuffer dstFramebuffer(width, height, false, false); diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index 6aede4e8e..f10e0fde2 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -1238,16 +1238,25 @@ bool VulkanContext::GetLastFrame(std::vector& data, int& width, int& height) if (!lastFrameView) return false; - width = lastFrameExtent.width; - height = lastFrameExtent.height; - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = lastFrameAR * height; - if (width > w) + if (width != 0) { height = width / lastFrameAR; + } + else if (height != 0) { + width = lastFrameAR * height; + } else - width = w; + { + width = lastFrameExtent.width; + height = lastFrameExtent.height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = lastFrameAR * height; + if (width > w) + height = width / lastFrameAR; + else + width = w; + } // color attachment FramebufferAttachment attachment(physicalDevice, *device); attachment.Init(width, height, vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc, "screenshot"); diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index b448dfa1f..49d717644 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -591,11 +591,11 @@ static void appendVectorData(void *context, void *data, int size) v.insert(v.end(), bytes, bytes + size); } -static void getScreenshot(std::vector& data) +static void getScreenshot(std::vector& data, int width = 0) { data.clear(); std::vector rawData; - int width, height; + int height = 0; if (renderer == nullptr || !renderer->GetLastFrame(rawData, width, height)) return; stbi_flip_vertically_on_write(0); @@ -621,8 +621,9 @@ static std::string timeToString(time_t time) static void savestate() { + // TODO save state async: png compression, savestate file compression/write std::vector pngData; - getScreenshot(pngData); + getScreenshot(pngData, 640); dc_savestate(config::SavestateSlot, pngData.empty() ? nullptr : &pngData[0], pngData.size()); ImguiStateTexture savestatePic; savestatePic.invalidate(); diff --git a/core/ui/gui_util.cpp b/core/ui/gui_util.cpp index a037d0e78..d9ee7f84b 100644 --- a/core/ui/gui_util.cpp +++ b/core/ui/gui_util.cpp @@ -795,6 +795,8 @@ ImTextureID ImguiFileTexture::getId() return id; } +std::future ImguiStateTexture::asyncLoad; + bool ImguiStateTexture::exists() { std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); @@ -810,28 +812,39 @@ ImTextureID ImguiStateTexture::getId() { std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); ImTextureID texid = imguiDriver->getTexture(path); - if (texid == ImTextureID()) + if (texid != ImTextureID()) + return texid; + if (asyncLoad.valid()) { - // load savestate info - std::vector pngData; - dc_getStateScreenshot(config::SavestateSlot, pngData); - if (pngData.empty()) + if (asyncLoad.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) return {}; - - int width, height, channels; - stbi_set_flip_vertically_on_load(0); - u8 *imgData = stbi_load_from_memory(&pngData[0], pngData.size(), &width, &height, &channels, STBI_rgb_alpha); - if (imgData != nullptr) + LoadedPic loadedPic = asyncLoad.get(); + if (loadedPic.data != nullptr) { try { - texid = imguiDriver->updateTextureAndAspectRatio(path, imgData, width, height, nearestSampling); + texid = imguiDriver->updateTextureAndAspectRatio(path, loadedPic.data, loadedPic.width, loadedPic.height, nearestSampling); } catch (...) { // vulkan can throw during resizing } - free(imgData); + free(loadedPic.data); } + return texid; } - return texid; + asyncLoad = std::async(std::launch::async, []() { + LoadedPic loadedPic{}; + // load savestate info + std::vector pngData; + dc_getStateScreenshot(config::SavestateSlot, pngData); + if (pngData.empty()) + return loadedPic; + + int channels; + stbi_set_flip_vertically_on_load(0); + loadedPic.data = stbi_load_from_memory(&pngData[0], pngData.size(), &loadedPic.width, &loadedPic.height, &channels, STBI_rgb_alpha); + + return loadedPic; + }); + return {}; } void ImguiStateTexture::invalidate() diff --git a/core/ui/gui_util.h b/core/ui/gui_util.h index 957ce994e..8bb28f6ed 100644 --- a/core/ui/gui_util.h +++ b/core/ui/gui_util.h @@ -246,6 +246,15 @@ class ImguiStateTexture : public ImguiTexture bool exists(); void invalidate(); + +private: + struct LoadedPic + { + u8 *data; + int width; + int height; + }; + static std::future asyncLoad; }; class ImguiVmuTexture : public ImguiTexture