diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 090a2389d..d3a0b4e1b 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -445,20 +445,25 @@ void Achievements::UpdateGlyphRanges() // To avoid rasterizing all emoji fonts, we get the set of used glyphs in the emoji range for all strings in the // current game's achievement data. using CodepointSet = std::unordered_set; - CodepointSet codepoints; + CodepointSet codepoints, emoji_codepoints; - static constexpr auto add_string = [](const std::string_view str, CodepointSet& codepoints) { + const auto add_string = [&codepoints, &emoji_codepoints](const std::string_view str) { char32_t codepoint; for (size_t offset = 0; offset < str.length();) { offset += StringUtil::DecodeUTF8(str, offset, &codepoint); // Basic Latin + Latin Supplement always included. - if (codepoint != StringUtil::UNICODE_REPLACEMENT_CHARACTER && codepoint >= 0x2000) - codepoints.insert(static_cast(codepoint)); + if (codepoint != StringUtil::UNICODE_REPLACEMENT_CHARACTER && codepoint >= 0x100) + { + CodepointSet& dest = (codepoint >= 0x2000) ? emoji_codepoints : codepoints; + dest.insert(static_cast(codepoint)); + } } }; +#ifndef __ANDROID__ + // We don't need to check rich presence on Android, because we're not displaying it with FullscreenUI. if (rc_client_has_rich_presence(s_state.client)) { std::vector rp_strings; @@ -479,8 +484,9 @@ void Achievements::UpdateGlyphRanges() } for (const char* str : rp_strings) - add_string(str, codepoints); + add_string(str); } +#endif if (rc_client_has_achievements(s_state.client)) { @@ -495,9 +501,9 @@ void Achievements::UpdateGlyphRanges() { const rc_client_achievement_t* achievement = bucket.achievements[j]; if (achievement->title) - add_string(achievement->title, codepoints); + add_string(achievement->title); if (achievement->description) - add_string(achievement->description, codepoints); + add_string(achievement->description); } } rc_client_destroy_achievement_list(achievements); @@ -517,24 +523,29 @@ void Achievements::UpdateGlyphRanges() { const rc_client_leaderboard_t* leaderboard = bucket.leaderboards[j]; if (leaderboard->title) - add_string(leaderboard->title, codepoints); + add_string(leaderboard->title); if (leaderboard->description) - add_string(leaderboard->description, codepoints); + add_string(leaderboard->description); } } rc_client_destroy_leaderboard_list(leaderboards); } } - std::vector sorted_codepoints; + std::vector sorted_codepoints, sorted_emoji_codepoints; sorted_codepoints.reserve(codepoints.size()); sorted_codepoints.insert(sorted_codepoints.begin(), codepoints.begin(), codepoints.end()); std::sort(sorted_codepoints.begin(), sorted_codepoints.end()); + sorted_emoji_codepoints.reserve(codepoints.size()); + sorted_emoji_codepoints.insert(sorted_emoji_codepoints.begin(), emoji_codepoints.begin(), emoji_codepoints.end()); + std::sort(sorted_emoji_codepoints.begin(), sorted_emoji_codepoints.end()); // Compact codepoints to ranges. - GPUThread::RunOnThread([sorted_codepoints = std::move(sorted_codepoints)]() { - ImGuiManager::SetEmojiFontRange(ImGuiManager::CompactFontRange(sorted_codepoints)); - }); + GPUThread::RunOnThread( + [sorted_codepoints = std::move(sorted_codepoints), sorted_emoji_codepoints = std::move(sorted_emoji_codepoints)]() { + ImGuiManager::SetDynamicFontRange(ImGuiManager::CompactFontRange(sorted_codepoints), + ImGuiManager::CompactFontRange(sorted_emoji_codepoints)); + }); } bool Achievements::IsActive() diff --git a/src/duckstation-qt/qttranslations.cpp b/src/duckstation-qt/qttranslations.cpp index b4fb568ce..06da44893 100644 --- a/src/duckstation-qt/qttranslations.cpp +++ b/src/duckstation-qt/qttranslations.cpp @@ -1,9 +1,10 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin and contributors. +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin and contributors. // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "mainwindow.h" #include "qthost.h" +#include "core/gpu_thread.h" #include "core/host.h" #include "util/imgui_manager.h" @@ -256,9 +257,10 @@ void QtHost::UpdateGlyphRangesAndClearCache(QWidget* dialog_parent, std::string_ const char* imgui_font_url = nullptr; std::vector glyph_ranges; glyph_ranges.clear(); + glyph_ranges.reserve(std::size(s_base_latin_range) + 2); // Base Latin range is always included. - glyph_ranges.insert(glyph_ranges.begin(), std::begin(s_base_latin_range), std::end(s_base_latin_range)); + glyph_ranges.insert(glyph_ranges.end(), std::begin(s_base_latin_range), std::end(s_base_latin_range)); if (gi) { @@ -281,7 +283,7 @@ void QtHost::UpdateGlyphRangesAndClearCache(QWidget* dialog_parent, std::string_ // If we don't have any specific glyph range, assume Central European, except if English, then keep the size down. if ((!gi || !gi->used_glyphs) && language != "en") { - glyph_ranges.insert(glyph_ranges.begin(), std::begin(s_central_european_ranges), + glyph_ranges.insert(glyph_ranges.end(), std::begin(s_central_european_ranges), std::end(s_central_european_ranges)); } @@ -311,7 +313,9 @@ void QtHost::UpdateGlyphRangesAndClearCache(QWidget* dialog_parent, std::string_ if (g_emu_thread) { Host::RunOnCPUThread([font_path = std::move(font_path), glyph_ranges = std::move(glyph_ranges)]() mutable { - ImGuiManager::SetFontPathAndRange(std::move(font_path), std::move(glyph_ranges)); + GPUThread::RunOnThread([font_path = std::move(font_path), glyph_ranges = std::move(glyph_ranges)]() mutable { + ImGuiManager::SetFontPathAndRange(std::move(font_path), std::move(glyph_ranges)); + }); Host::ClearTranslationCache(); }); } diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 6b811f62c..f9d0da8ea 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -132,9 +132,6 @@ bool RegTestHost::InitializeConfig() EmuFolders::LoadConfig(*s_base_settings_interface.get()); EmuFolders::EnsureFoldersExist(); - // imgui setup, make sure it doesn't bug out - ImGuiManager::SetFontPathAndRange(std::string(), {0x0020, 0x00FF, 0x2022, 0x2022, 0, 0}); - return true; } diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 0d6b66f3f..02b85c81a 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -74,9 +74,9 @@ static void SetKeyMap(); static bool LoadFontData(Error* error); static void ReloadFontDataIfActive(); static bool AddImGuiFonts(bool debug_font, bool fullscreen_fonts); -static ImFont* AddTextFont(float size, bool full_glyph_range); +static ImFont* AddTextFont(float size, const ImWchar* glyph_range); static ImFont* AddFixedFont(float size); -static bool AddIconFonts(float size); +static bool AddIconFonts(float size, const ImWchar* emoji_range); static void SetCommonIOOptions(ImGuiIO& io); static void SetImKeyState(ImGuiIO& io, ImGuiKey imkey, bool pressed); static const char* GetClipboardTextImpl(void* userdata); @@ -94,7 +94,8 @@ static void DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair s_ascii_font_range = {{0x20, 0x7F, 0x00, 0x00}}; +static constexpr std::array ASCII_FONT_RANGE = {{0x20, 0x7F, 0x00, 0x00}}; +static constexpr std::array DEFAULT_FONT_RANGE = {{0x0020, 0x00FF, 0x2022, 0x2022, 0x0000, 0x0000}}; namespace { @@ -140,7 +141,8 @@ struct ALIGN_TO_CACHE_LINE State std::string font_path; std::vector font_range; - std::vector emoji_range; + std::vector dynamic_font_range; + std::vector dynamic_emoji_range; DynamicHeapArray standard_font_data; DynamicHeapArray fixed_font_data; @@ -166,32 +168,13 @@ void ImGuiManager::SetFontPathAndRange(std::string path, std::vector ReloadFontDataIfActive(); } -void ImGuiManager::SetEmojiFontRange(std::vector range) +void ImGuiManager::SetDynamicFontRange(std::vector font_range, std::vector emoji_range) { - static constexpr size_t builtin_size = std::size(EMOJI_ICON_RANGE); - const size_t runtime_size = range.size(); - - if (runtime_size == 0) - { - if (s_state.emoji_range.empty()) - return; - - s_state.emoji_range = {}; - } - else - { - if (!s_state.emoji_range.empty() && (s_state.emoji_range.size() - builtin_size) == range.size() && - std::memcmp(s_state.emoji_range.data(), range.data(), range.size() * sizeof(ImWchar)) == 0) - { - // no change - return; - } - - s_state.emoji_range = std::move(range); - s_state.emoji_range.resize(s_state.emoji_range.size() + builtin_size); - std::memcpy(&s_state.emoji_range[runtime_size], EMOJI_ICON_RANGE, sizeof(EMOJI_ICON_RANGE)); - } + if (s_state.dynamic_font_range == font_range && s_state.dynamic_emoji_range == emoji_range) + return; + s_state.dynamic_font_range = std::move(font_range); + s_state.dynamic_emoji_range = std::move(emoji_range); ReloadFontDataIfActive(); } @@ -647,13 +630,12 @@ bool ImGuiManager::LoadFontData(Error* error) return true; } -ImFont* ImGuiManager::AddTextFont(float size, bool full_glyph_range) +ImFont* ImGuiManager::AddTextFont(float size, const ImWchar* glyph_range) { ImFontConfig cfg; cfg.FontDataOwnedByAtlas = false; return ImGui::GetIO().Fonts->AddFontFromMemoryTTF( - s_state.standard_font_data.data(), static_cast(s_state.standard_font_data.size()), size, &cfg, - full_glyph_range ? s_state.font_range.data() : s_ascii_font_range.data()); + s_state.standard_font_data.data(), static_cast(s_state.standard_font_data.size()), size, &cfg, glyph_range); } ImFont* ImGuiManager::AddFixedFont(float size) @@ -662,10 +644,10 @@ ImFont* ImGuiManager::AddFixedFont(float size) cfg.FontDataOwnedByAtlas = false; return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_state.fixed_font_data.data(), static_cast(s_state.fixed_font_data.size()), size, &cfg, - s_ascii_font_range.data()); + ASCII_FONT_RANGE.data()); } -bool ImGuiManager::AddIconFonts(float size) +bool ImGuiManager::AddIconFonts(float size, const ImWchar* emoji_range) { { ImFontConfig cfg; @@ -708,9 +690,9 @@ bool ImGuiManager::AddIconFonts(float size) cfg.FontDataOwnedByAtlas = false; cfg.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor | ImGuiFreeTypeBuilderFlags_Bitmap; - if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF( - s_state.emoji_font_data.data(), static_cast(s_state.emoji_font_data.size()), size * 0.9f, &cfg, - s_state.emoji_range.empty() ? EMOJI_ICON_RANGE : s_state.emoji_range.data())) [[unlikely]] + if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_state.emoji_font_data.data(), + static_cast(s_state.emoji_font_data.size()), size * 0.9f, &cfg, + emoji_range)) [[unlikely]] { return false; } @@ -730,12 +712,43 @@ bool ImGuiManager::AddImGuiFonts(bool debug_font, bool fullscreen_fonts) INFO_LOG("Allocating fonts winscale={} globalscale={} debug={} fullscreen={}", window_scale, s_state.global_scale, debug_font, fullscreen_fonts); + // need to generate arrays if dynamic ranges are present + const ImWchar* text_range = s_state.font_range.empty() ? DEFAULT_FONT_RANGE.data() : s_state.font_range.data(); + const ImWchar* emoji_range = EMOJI_ICON_RANGE; + std::vector full_text_range, full_emoji_range; + if (!s_state.dynamic_font_range.empty()) + { + // skip the zeros, we'll add them afterwards + const size_t base_size = s_state.font_range.empty() ? DEFAULT_FONT_RANGE.size() : s_state.font_range.size(); + Assert(base_size > 2); + full_text_range.reserve(base_size + s_state.dynamic_font_range.size()); + full_text_range.insert(full_text_range.end(), &text_range[0], &text_range[base_size - 2]); + full_text_range.insert(full_text_range.end(), s_state.dynamic_font_range.begin(), s_state.dynamic_font_range.end()); + full_text_range.insert(full_text_range.end(), 2, 0); + text_range = full_text_range.data(); + } + if (!s_state.dynamic_emoji_range.empty()) + { + // skip the zeros, we'll add them afterwards + size_t base_size = 0; + for (const ImWchar* c = EMOJI_ICON_RANGE; *c != 0; c++) + base_size++; + + Assert(base_size > 2); + full_emoji_range.reserve(base_size + s_state.dynamic_emoji_range.size()); + full_emoji_range.insert(full_emoji_range.end(), &EMOJI_ICON_RANGE[0], &EMOJI_ICON_RANGE[base_size - 2]); + full_emoji_range.insert(full_emoji_range.end(), s_state.dynamic_emoji_range.begin(), + s_state.dynamic_emoji_range.end()); + full_emoji_range.insert(full_emoji_range.end(), 2, 0); + emoji_range = full_emoji_range.data(); + } + ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); if (debug_font) { - s_state.debug_font = AddTextFont(debug_font_size, false); + s_state.debug_font = AddTextFont(debug_font_size, ASCII_FONT_RANGE.data()); if (!s_state.debug_font) return false; } @@ -744,8 +757,8 @@ bool ImGuiManager::AddImGuiFonts(bool debug_font, bool fullscreen_fonts) if (!s_state.fixed_font) return false; - s_state.osd_font = AddTextFont(osd_font_size, true); - if (!s_state.osd_font || !AddIconFonts(osd_font_size)) + s_state.osd_font = AddTextFont(osd_font_size, text_range); + if (!s_state.osd_font || !AddIconFonts(osd_font_size, emoji_range)) return false; if (!debug_font) s_state.debug_font = s_state.osd_font; @@ -753,13 +766,13 @@ bool ImGuiManager::AddImGuiFonts(bool debug_font, bool fullscreen_fonts) if (fullscreen_fonts) { const float medium_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE); - s_state.medium_font = AddTextFont(medium_font_size, true); - if (!s_state.medium_font || !AddIconFonts(medium_font_size)) + s_state.medium_font = AddTextFont(medium_font_size, text_range); + if (!s_state.medium_font || !AddIconFonts(medium_font_size, emoji_range)) return false; const float large_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE); - s_state.large_font = AddTextFont(large_font_size, true); - if (!s_state.large_font || !AddIconFonts(large_font_size)) + s_state.large_font = AddTextFont(large_font_size, text_range); + if (!s_state.large_font || !AddIconFonts(large_font_size, emoji_range)) return false; } else diff --git a/src/util/imgui_manager.h b/src/util/imgui_manager.h index e1bc96998..59693eb5f 100644 --- a/src/util/imgui_manager.h +++ b/src/util/imgui_manager.h @@ -64,9 +64,9 @@ static constexpr float DEFAULT_SCREEN_MARGIN = 16.0f; /// Sets the path to the font to use. Empty string means to use the default. void SetFontPathAndRange(std::string path, std::vector range); -/// Sets the emoji font range to use. Empty means no glyphs will be rasterized. +/// Sets the normal/emoji font range to use. Empty means no glyphs will be rasterized. /// Should NOT be terminated with zeros, unlike the font range above. -void SetEmojiFontRange(std::vector range); +void SetDynamicFontRange(std::vector font_range, std::vector emoji_range); /// Returns a compacted font range, with adjacent glyphs merged into one pair. std::vector CompactFontRange(std::span range);