diff --git a/bin/resources/fullscreenui/flags/NTSC-B.png b/bin/resources/fullscreenui/flags/NTSC-B.png deleted file mode 100644 index e6d94c0cd1..0000000000 Binary files a/bin/resources/fullscreenui/flags/NTSC-B.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/NTSC-C.png b/bin/resources/fullscreenui/flags/NTSC-C.png deleted file mode 100644 index 6d12b6a1e2..0000000000 Binary files a/bin/resources/fullscreenui/flags/NTSC-C.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/NTSC-HK.png b/bin/resources/fullscreenui/flags/NTSC-HK.png deleted file mode 100644 index f23d63ad78..0000000000 Binary files a/bin/resources/fullscreenui/flags/NTSC-HK.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/NTSC-J.png b/bin/resources/fullscreenui/flags/NTSC-J.png deleted file mode 100644 index 9662de455d..0000000000 Binary files a/bin/resources/fullscreenui/flags/NTSC-J.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/NTSC-K.png b/bin/resources/fullscreenui/flags/NTSC-K.png deleted file mode 100644 index 7ec0fb18f7..0000000000 Binary files a/bin/resources/fullscreenui/flags/NTSC-K.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/NTSC-T.png b/bin/resources/fullscreenui/flags/NTSC-T.png deleted file mode 100644 index 515d7cb4cd..0000000000 Binary files a/bin/resources/fullscreenui/flags/NTSC-T.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/NTSC-U.png b/bin/resources/fullscreenui/flags/NTSC-U.png deleted file mode 100644 index e08c415738..0000000000 Binary files a/bin/resources/fullscreenui/flags/NTSC-U.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/Other.png b/bin/resources/fullscreenui/flags/Other.png deleted file mode 100644 index f1819d0a4d..0000000000 Binary files a/bin/resources/fullscreenui/flags/Other.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-A.png b/bin/resources/fullscreenui/flags/PAL-A.png deleted file mode 100644 index 2f5cde7cc3..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-A.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-AF.png b/bin/resources/fullscreenui/flags/PAL-AF.png deleted file mode 100644 index eaacfb7254..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-AF.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-AU.png b/bin/resources/fullscreenui/flags/PAL-AU.png deleted file mode 100644 index 13e2ff7161..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-AU.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-BE.png b/bin/resources/fullscreenui/flags/PAL-BE.png deleted file mode 100644 index 14d41bfc06..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-BE.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-E.png b/bin/resources/fullscreenui/flags/PAL-E.png deleted file mode 100644 index e9ca0a2e4b..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-E.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-F.png b/bin/resources/fullscreenui/flags/PAL-F.png deleted file mode 100644 index 1c67668b77..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-F.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-FI.png b/bin/resources/fullscreenui/flags/PAL-FI.png deleted file mode 100644 index 4d4267aec6..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-FI.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-G.png b/bin/resources/fullscreenui/flags/PAL-G.png deleted file mode 100644 index 3be4785052..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-G.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-GR.png b/bin/resources/fullscreenui/flags/PAL-GR.png deleted file mode 100644 index 2f64ef7372..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-GR.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-I.png b/bin/resources/fullscreenui/flags/PAL-I.png deleted file mode 100644 index c78d99490e..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-I.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-IN.png b/bin/resources/fullscreenui/flags/PAL-IN.png deleted file mode 100644 index b812aba7d5..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-IN.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-M.png b/bin/resources/fullscreenui/flags/PAL-M.png deleted file mode 100644 index e9ca0a2e4b..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-M.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-NL.png b/bin/resources/fullscreenui/flags/PAL-NL.png deleted file mode 100644 index b7cac33dfc..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-NL.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-NO.png b/bin/resources/fullscreenui/flags/PAL-NO.png deleted file mode 100644 index 0fb614d25d..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-NO.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-P.png b/bin/resources/fullscreenui/flags/PAL-P.png deleted file mode 100644 index 3510e8b76d..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-P.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-PL.png b/bin/resources/fullscreenui/flags/PAL-PL.png deleted file mode 100644 index 3681ed9117..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-PL.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-R.png b/bin/resources/fullscreenui/flags/PAL-R.png deleted file mode 100644 index 051aced6e0..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-R.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-S.png b/bin/resources/fullscreenui/flags/PAL-S.png deleted file mode 100644 index c8811fc09c..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-S.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-SC.png b/bin/resources/fullscreenui/flags/PAL-SC.png deleted file mode 100644 index acf3f57072..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-SC.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-SW.png b/bin/resources/fullscreenui/flags/PAL-SW.png deleted file mode 100644 index 8564b1f4bf..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-SW.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-SWI.png b/bin/resources/fullscreenui/flags/PAL-SWI.png deleted file mode 100644 index f0aa23adb5..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-SWI.png and /dev/null differ diff --git a/bin/resources/fullscreenui/flags/PAL-UK.png b/bin/resources/fullscreenui/flags/PAL-UK.png deleted file mode 100644 index 021e411013..0000000000 Binary files a/bin/resources/fullscreenui/flags/PAL-UK.png and /dev/null differ diff --git a/bin/resources/fullscreenui/star-0.png b/bin/resources/fullscreenui/star-0.png deleted file mode 100644 index 5d82bf8f8a..0000000000 Binary files a/bin/resources/fullscreenui/star-0.png and /dev/null differ diff --git a/bin/resources/fullscreenui/star-1.png b/bin/resources/fullscreenui/star-1.png deleted file mode 100644 index 22072f3214..0000000000 Binary files a/bin/resources/fullscreenui/star-1.png and /dev/null differ diff --git a/bin/resources/fullscreenui/star-2.png b/bin/resources/fullscreenui/star-2.png deleted file mode 100644 index 480b9f3576..0000000000 Binary files a/bin/resources/fullscreenui/star-2.png and /dev/null differ diff --git a/bin/resources/fullscreenui/star-3.png b/bin/resources/fullscreenui/star-3.png deleted file mode 100644 index 000e3499d0..0000000000 Binary files a/bin/resources/fullscreenui/star-3.png and /dev/null differ diff --git a/bin/resources/fullscreenui/star-4.png b/bin/resources/fullscreenui/star-4.png deleted file mode 100644 index 2ec1145bfe..0000000000 Binary files a/bin/resources/fullscreenui/star-4.png and /dev/null differ diff --git a/bin/resources/fullscreenui/star-5.png b/bin/resources/fullscreenui/star-5.png deleted file mode 100644 index 9043b2f9fd..0000000000 Binary files a/bin/resources/fullscreenui/star-5.png and /dev/null differ diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 85b498665e..d2dd18b864 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -94,6 +94,7 @@ using ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING; using ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING; using ImGuiFullscreen::LAYOUT_SCREEN_HEIGHT; using ImGuiFullscreen::LAYOUT_SCREEN_WIDTH; +using ImGuiFullscreen::SvgScaling; using ImGuiFullscreen::UIBackgroundColor; using ImGuiFullscreen::UIBackgroundHighlightColor; using ImGuiFullscreen::UIBackgroundLineColor; @@ -131,6 +132,8 @@ using ImGuiFullscreen::EndNavBar; using ImGuiFullscreen::EnumChoiceButton; using ImGuiFullscreen::FloatingButton; using ImGuiFullscreen::ForceKeyNavEnabled; +using ImGuiFullscreen::GetCachedSvgTexture; +using ImGuiFullscreen::GetCachedSvgTextureAsync; using ImGuiFullscreen::GetCachedTexture; using ImGuiFullscreen::GetCachedTextureAsync; using ImGuiFullscreen::GetPlaceholderTexture; @@ -139,6 +142,7 @@ using ImGuiFullscreen::HorizontalMenuItem; using ImGuiFullscreen::IsFocusResetQueued; using ImGuiFullscreen::IsGamepadInputSource; using ImGuiFullscreen::LayoutScale; +using ImGuiFullscreen::LoadSvgTexture; using ImGuiFullscreen::LoadTexture; using ImGuiFullscreen::MenuButton; using ImGuiFullscreen::MenuButtonFrame; @@ -237,6 +241,11 @@ namespace FullscreenUI static void GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel); static void ApplyLayoutSettings(const SettingsInterface* bsi = nullptr); + void DrawSvgTexture(GSTexture* padded_texture, ImVec2 unpadded_size); + void DrawSvgTexture_UV(GSTexture* padded_texture, ImVec2 unpadded_size, ImVec2 unpadded_uv0, ImVec2 unpadded_uv1); + void DrawCachedSvgTexture(const std::string& path, ImVec2 size, SvgScaling mode); + void DrawCachedSvgTextureAsync(const std::string& path, ImVec2 size, SvgScaling mode); + static MainWindowType s_current_main_window = MainWindowType::None; static PauseSubMenu s_current_pause_submenu = PauseSubMenu::None; static bool s_initialized = false; @@ -256,6 +265,7 @@ namespace FullscreenUI // Resources ////////////////////////////////////////////////////////////////////////// static bool LoadResources(); + static bool LoadSvgResources(); static void DestroyResources(); static std::array, static_cast(GameDatabaseSchema::Compatibility::Perfect)> @@ -678,6 +688,34 @@ void FullscreenUI::GamepadLayoutChanged() ApplyLayoutSettings(); } +// When drawing an svg to a non-integer size, we get a padded texture. +// This function crops off this padding by setting the image UV for the draw. +// We currently only use integer sizes for images, but I wrote this before checking that. +void FullscreenUI::DrawSvgTexture(GSTexture* padded_texture, ImVec2 unpadded_size) +{ + if (padded_texture != GetPlaceholderTexture().get()) + { + const ImVec2 padded_size(padded_texture->GetWidth(), padded_texture->GetHeight()); + const ImVec2 uv1 = unpadded_size / padded_size; + ImGui::Image(reinterpret_cast(padded_texture->GetNativeHandle()), unpadded_size, ImVec2(0.0f, 0.0f), uv1); + } + else + { + // Placeholder is a png file and should be scaled by ImGui + ImGui::Image(reinterpret_cast(padded_texture->GetNativeHandle()), unpadded_size); + } +} + +void FullscreenUI::DrawCachedSvgTexture(const std::string& path, ImVec2 size, SvgScaling mode) +{ + DrawSvgTexture(GetCachedSvgTexture(path, size, mode), size); +} + +void FullscreenUI::DrawCachedSvgTextureAsync(const std::string& path, ImVec2 size, SvgScaling mode) +{ + DrawSvgTexture(GetCachedSvgTextureAsync(path, size, mode), size); +} + ////////////////////////////////////////////////////////////////////////// // Main ////////////////////////////////////////////////////////////////////////// @@ -727,6 +765,11 @@ bool FullscreenUI::IsInitialized() return s_initialized; } +void FullscreenUI::ReloadSvgResources() +{ + LoadSvgResources(); +} + bool FullscreenUI::HasActiveWindow() { return s_initialized && (s_current_main_window != MainWindowType::None || AreAnyDialogsOpen()); @@ -1049,10 +1092,15 @@ bool FullscreenUI::LoadResources() s_fallback_disc_texture = LoadTexture("fullscreenui/media-cdrom.png"); s_fallback_exe_texture = LoadTexture("fullscreenui/applications-system.png"); + return LoadSvgResources(); +} + +bool FullscreenUI::LoadSvgResources() +{ for (u32 i = static_cast(GameDatabaseSchema::Compatibility::Nothing); i <= static_cast(GameDatabaseSchema::Compatibility::Perfect); i++) { - s_game_compatibility_textures[i - 1] = LoadTexture(fmt::format("fullscreenui/star-{}.png", i - 1).c_str()); + s_game_compatibility_textures[i - 1] = LoadSvgTexture(fmt::format("icons/star-{}.svg", i - 1).c_str(), LayoutScale(64.0f, 16.0f), SvgScaling::Fit); } return true; @@ -6497,10 +6545,13 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) // region { - std::string flag_texture(fmt::format("fullscreenui/flags/{}.png", GameList::RegionToString(selected_entry->region))); + std::string flag_texture(fmt::format("icons/flags/{}.svg", GameList::RegionToString(selected_entry->region))); ImGui::TextUnformatted(FSUI_CSTR("Region: ")); ImGui::SameLine(); - ImGui::Image(reinterpret_cast(GetCachedTextureAsync(flag_texture.c_str())->GetNativeHandle()), LayoutScale(23.0f, 16.0f)); + if (selected_entry->region == GameList::Region::Other) + DrawCachedSvgTextureAsync(flag_texture, LayoutScale(23.0f, 16.0f), SvgScaling::Fit); + else + DrawCachedSvgTextureAsync(flag_texture, LayoutScale(23.0f, 16.0f), SvgScaling::ZoomFill); ImGui::SameLine(); ImGui::Text(" (%s)", GameList::RegionToString(selected_entry->region)); } @@ -6510,8 +6561,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) ImGui::SameLine(); if (selected_entry->compatibility_rating != GameDatabaseSchema::Compatibility::Unknown) { - ImGui::Image(reinterpret_cast(s_game_compatibility_textures[static_cast(selected_entry->compatibility_rating) - 1]->GetNativeHandle()), - LayoutScale(64.0f, 16.0f)); + DrawSvgTexture(s_game_compatibility_textures[static_cast(selected_entry->compatibility_rating) - 1].get(), LayoutScale(64.0f, 16.0f)); ImGui::SameLine(); } ImGui::Text(" (%s)", GameList::EntryCompatibilityRatingToString(selected_entry->compatibility_rating)); diff --git a/pcsx2/ImGui/FullscreenUI.h b/pcsx2/ImGui/FullscreenUI.h index 5bff49b646..bdf51ff51c 100644 --- a/pcsx2/ImGui/FullscreenUI.h +++ b/pcsx2/ImGui/FullscreenUI.h @@ -17,6 +17,7 @@ namespace FullscreenUI { bool Initialize(); bool IsInitialized(); + void ReloadSvgResources(); bool HasActiveWindow(); void CheckForConfigChanges(const Pcsx2Config& old_config); void OnVMStarted(); diff --git a/pcsx2/ImGui/ImGuiFullscreen.cpp b/pcsx2/ImGui/ImGuiFullscreen.cpp index 616cd24a83..aa78332ed2 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.cpp +++ b/pcsx2/ImGui/ImGuiFullscreen.cpp @@ -26,6 +26,9 @@ #include "imgui_internal.h" #include "imgui_stdlib.h" +#include +#include + #include #include #include @@ -41,6 +44,7 @@ namespace ImGuiFullscreen static std::optional LoadTextureImage(const char* path); static std::shared_ptr UploadTexture(const char* path, const RGBA8Image& image); + static std::optional LoadSvgTextureImage(const char* path, ImVec2 size, SvgScaling mode); static void TextureLoaderThread(); static void DrawFileSelector(); @@ -88,11 +92,13 @@ namespace ImGuiFullscreen static FocusResetType s_focus_reset_queued = FocusResetType::None; static LRUCache> s_texture_cache(128, true); + static LRUCache> s_svg_data_cache(64, true); static std::shared_ptr s_placeholder_texture; static std::atomic_bool s_texture_load_thread_quit{false}; static std::mutex s_texture_load_mutex; static std::condition_variable s_texture_load_cv; static std::deque s_texture_load_queue; + static std::deque> s_svg_texture_load_queue; static std::deque> s_texture_upload_queue; static Threading::Thread s_texture_load_thread; @@ -367,6 +373,166 @@ GSTexture* ImGuiFullscreen::GetCachedTextureAsync(std::string_view name) return tex_ptr->get(); } +// Renders an SVG image to specified size into a texture of ceil(size). +// The region donated by size is positioned top left of the image when size != ceil(size). +// The image is scaled to fit within, and rendered center of, size. +std::optional ImGuiFullscreen::LoadSvgTextureImage(const char* path, ImVec2 size, SvgScaling mode) +{ + std::optional image; + + // Cache SVG files as we may need to re-render at a new size later. + std::vector* data_ptr = s_svg_data_cache.Lookup(path); + if (!data_ptr) + { + std::optional> data; + if (Path::IsAbsolute(path)) + data = FileSystem::ReadBinaryFile(path); + else + data = FileSystem::ReadBinaryFile(Path::Combine(EmuFolders::Resources, path).c_str()); + if (data.has_value()) + data_ptr = s_svg_data_cache.Insert(std::string(path), std::move(data.value())); + } + + if (data_ptr) + { + // Load SVG + plutosvg_document_t* pluto_svg = plutosvg_document_load_from_data(reinterpret_cast(data_ptr->data()), + data_ptr->size(), -1, -1, nullptr, nullptr); + + if (pluto_svg == nullptr) + { + Console.Error("Failed to load svg '%s'", path); + return image; + } + + const float base_width = plutosvg_document_get_width(pluto_svg); + const float base_height = plutosvg_document_get_height(pluto_svg); + + // Create a surface large enough to store the SVG. + const int px_width = std::ceil(size.x); + const int px_height = std::ceil(size.y); + + std::vector pixel_data; + pixel_data.resize(px_width * px_height); + + plutovg_surface_t* pluto_surface = plutovg_surface_create_for_data(reinterpret_cast(pixel_data.data()), + px_width, px_height, px_width * 4); + if (pluto_surface == nullptr) + { + plutosvg_document_destroy(pluto_svg); + Console.Error("Failed to create plutovg surface '%s'", path); + return image; + } + + // Create a drawing canvas. + plutovg_canvas_t* pluto_canvas = plutovg_canvas_create(pluto_surface); + if (pluto_canvas == nullptr) + { + plutovg_surface_destroy(pluto_surface); + plutosvg_document_destroy(pluto_svg); + Console.Error("Failed to create plutovg canvas '%s'", path); + return image; + } + + // Scale & position SVG. + // ImGui positions images from top left, so we can ignore integer size of surface. + switch (mode) + { + case SvgScaling::Stretch: + plutovg_canvas_scale(pluto_canvas, size.x / base_width, size.y / base_height); + break; + case SvgScaling::Fit: + { + const ImRect rect = CenterImage(size, {base_width, base_height}, false); + const float scale = (rect.Max.y - rect.Min.y) / base_height; + plutovg_canvas_scale(pluto_canvas, scale, scale); + plutovg_canvas_translate(pluto_canvas, rect.Min.x / scale, rect.Min.y / scale); + break; + } + case SvgScaling::ZoomFill: + { + const ImRect rect = CenterImage(size, {base_width, base_height}, true); + const float scale = (rect.Max.y - rect.Min.y) / base_height; + plutovg_canvas_scale(pluto_canvas, scale, scale); + plutovg_canvas_translate(pluto_canvas, rect.Min.x / scale, rect.Min.y / scale); + break; + } + } + + // Render + const bool success = plutosvg_document_render(pluto_svg, nullptr, pluto_canvas, nullptr, nullptr, nullptr); + + // Free pluto objects + plutovg_canvas_destroy(pluto_canvas); + plutovg_surface_destroy(pluto_surface); + plutosvg_document_destroy(pluto_svg); + + if (!success) + { + Console.Error("Failed to render svg '%s'", path); + return image; + } + + // Convert to RGBA8Image + plutovg_convert_argb_to_rgba(reinterpret_cast(pixel_data.data()), reinterpret_cast(pixel_data.data()), + px_width, px_height, px_width * 4); + image = RGBA8Image(px_width, px_height, std::move(pixel_data)); + } + else + { + Console.Error("Failed to open texture resource '%s'", path); + } + + return image; +} + +std::shared_ptr ImGuiFullscreen::LoadSvgTexture(std::string_view path, ImVec2 size, SvgScaling mode) +{ + std::string path_str(path); + std::optional image(LoadSvgTextureImage(path_str.c_str(), size, mode)); + if (image.has_value()) + { + std::shared_ptr ret(UploadTexture(path_str.c_str(), image.value())); + if (ret) + return ret; + } + + return s_placeholder_texture; +} + +GSTexture* ImGuiFullscreen::GetCachedSvgTexture(std::string_view name, ImVec2 size, SvgScaling mode) +{ + std::string svg_key = fmt::format("{}_{}x{}_{}", name, size.x, size.y, static_cast(mode)); + + std::shared_ptr* tex_ptr = s_texture_cache.Lookup(svg_key); + if (!tex_ptr) + { + std::shared_ptr tex(LoadSvgTexture(name, size, mode)); + tex_ptr = s_texture_cache.Insert(std::string(svg_key), std::move(tex)); + } + + return tex_ptr->get(); +} + +GSTexture* ImGuiFullscreen::GetCachedSvgTextureAsync(std::string_view name, ImVec2 size, SvgScaling mode) +{ + std::string svg_key = fmt::format("{}_{}x{}_{}", name, size.x, size.y, static_cast(mode)); + + std::shared_ptr* tex_ptr = s_texture_cache.Lookup(svg_key); + if (!tex_ptr) + { + // insert the placeholder + tex_ptr = s_texture_cache.Insert(std::string(svg_key), s_placeholder_texture); + + // queue the actual load + std::unique_lock lock(s_texture_load_mutex); + s_svg_texture_load_queue.emplace_back(std::tuple(name, size, mode)); + s_texture_load_cv.notify_one(); + } + + return tex_ptr->get(); +} + bool ImGuiFullscreen::InvalidateCachedTexture(const std::string& path) { return s_texture_cache.Remove(path); @@ -398,7 +564,12 @@ void ImGuiFullscreen::TextureLoaderThread() for (;;) { s_texture_load_cv.wait( - lock, []() { return (s_texture_load_thread_quit.load(std::memory_order_acquire) || !s_texture_load_queue.empty()); }); + lock, []() { + return ( + s_texture_load_thread_quit.load(std::memory_order_acquire) || + !s_texture_load_queue.empty() || + !s_svg_texture_load_queue.empty()); + }); if (s_texture_load_thread_quit.load(std::memory_order_acquire)) break; @@ -416,9 +587,30 @@ void ImGuiFullscreen::TextureLoaderThread() if (image) s_texture_upload_queue.emplace_back(std::move(path), std::move(image.value())); } + + while (!s_svg_texture_load_queue.empty()) + { + std::string path; + ImVec2 size; + SvgScaling mode; + std::tie(path, size, mode) = std::move(s_svg_texture_load_queue.front()); + s_svg_texture_load_queue.pop_front(); + + lock.unlock(); + std::optional image(LoadSvgTextureImage(path.c_str(), size, mode)); + lock.lock(); + + // don't bother queuing back if it doesn't exist + if (image) + { + std::string svg_key = fmt::format("{}_{}x{}_{}", path, size.x, size.y, static_cast(mode)); + s_texture_upload_queue.emplace_back(std::move(svg_key), std::move(image.value())); + } + } } s_texture_load_queue.clear(); + s_svg_texture_load_queue.clear(); } bool ImGuiFullscreen::UpdateLayoutScale() @@ -451,13 +643,13 @@ bool ImGuiFullscreen::UpdateLayoutScale() return g_layout_scale != old_scale; } -ImRect ImGuiFullscreen::CenterImage(const ImVec2& fit_size, const ImVec2& image_size) +ImRect ImGuiFullscreen::CenterImage(const ImVec2& fit_size, const ImVec2& image_size, bool fill) { const float fit_ar = fit_size.x / fit_size.y; const float image_ar = image_size.x / image_size.y; ImRect ret; - if (fit_ar > image_ar) + if ((fit_ar > image_ar) ^ fill) { // center horizontally const float width = fit_size.y * image_ar; @@ -477,7 +669,7 @@ ImRect ImGuiFullscreen::CenterImage(const ImVec2& fit_size, const ImVec2& image_ return ret; } -ImRect ImGuiFullscreen::CenterImage(const ImRect& fit_rect, const ImVec2& image_size) +ImRect ImGuiFullscreen::CenterImage(const ImRect& fit_rect, const ImVec2& image_size, bool fill) { ImRect ret(CenterImage(fit_rect.Max - fit_rect.Min, image_size)); ret.Translate(fit_rect.Min); diff --git a/pcsx2/ImGui/ImGuiFullscreen.h b/pcsx2/ImGui/ImGuiFullscreen.h index 65d0dd6fc7..239ce0958d 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.h +++ b/pcsx2/ImGui/ImGuiFullscreen.h @@ -86,8 +86,8 @@ namespace ImGuiFullscreen } /// Centers an image within the specified bounds, scaling up or down as needed. - ImRect CenterImage(const ImVec2& fit_size, const ImVec2& image_size); - ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size); + ImRect CenterImage(const ImVec2& fit_size, const ImVec2& image_size, bool fill = false); + ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size, bool fill = false); /// Initializes, setting up any state. bool Initialize(const char* placeholder_image_path); @@ -104,6 +104,15 @@ namespace ImGuiFullscreen std::shared_ptr LoadTexture(std::string_view path); GSTexture* GetCachedTexture(std::string_view name); GSTexture* GetCachedTextureAsync(std::string_view name); + enum class SvgScaling : u8 + { + Stretch, + Fit, + ZoomFill, + }; + std::shared_ptr LoadSvgTexture(std::string_view path, ImVec2 size, SvgScaling mode = SvgScaling::Stretch); + GSTexture* GetCachedSvgTexture(std::string_view name, ImVec2 size, SvgScaling mode = SvgScaling::Stretch); + GSTexture* GetCachedSvgTextureAsync(std::string_view name, ImVec2 size, SvgScaling mode = SvgScaling::Stretch); bool InvalidateCachedTexture(const std::string& path); void UploadAsyncTextures(); diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index 832a4ff497..438ef02e7d 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -255,6 +255,9 @@ void ImGuiManager::UpdateScale() if (!g_gs_device->UpdateImGuiFontTexture()) pxFailRel("Failed to recreate font texture after scale+resize"); + + if (FullscreenUI::IsInitialized()) + FullscreenUI::ReloadSvgResources(); } void ImGuiManager::NewFrame()