Achievements: Include any codepoints above 0x100 in dynamic range

This commit is contained in:
Stenzek 2025-01-31 13:48:57 +10:00
parent 03181d1179
commit b5925ab139
No known key found for this signature in database
5 changed files with 90 additions and 65 deletions

View File

@ -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 // 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. // current game's achievement data.
using CodepointSet = std::unordered_set<ImGuiManager::WCharType>; using CodepointSet = std::unordered_set<ImGuiManager::WCharType>;
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; char32_t codepoint;
for (size_t offset = 0; offset < str.length();) for (size_t offset = 0; offset < str.length();)
{ {
offset += StringUtil::DecodeUTF8(str, offset, &codepoint); offset += StringUtil::DecodeUTF8(str, offset, &codepoint);
// Basic Latin + Latin Supplement always included. // Basic Latin + Latin Supplement always included.
if (codepoint != StringUtil::UNICODE_REPLACEMENT_CHARACTER && codepoint >= 0x2000) if (codepoint != StringUtil::UNICODE_REPLACEMENT_CHARACTER && codepoint >= 0x100)
codepoints.insert(static_cast<ImGuiManager::WCharType>(codepoint)); {
CodepointSet& dest = (codepoint >= 0x2000) ? emoji_codepoints : codepoints;
dest.insert(static_cast<ImGuiManager::WCharType>(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)) if (rc_client_has_rich_presence(s_state.client))
{ {
std::vector<const char*> rp_strings; std::vector<const char*> rp_strings;
@ -479,8 +484,9 @@ void Achievements::UpdateGlyphRanges()
} }
for (const char* str : rp_strings) for (const char* str : rp_strings)
add_string(str, codepoints); add_string(str);
} }
#endif
if (rc_client_has_achievements(s_state.client)) if (rc_client_has_achievements(s_state.client))
{ {
@ -495,9 +501,9 @@ void Achievements::UpdateGlyphRanges()
{ {
const rc_client_achievement_t* achievement = bucket.achievements[j]; const rc_client_achievement_t* achievement = bucket.achievements[j];
if (achievement->title) if (achievement->title)
add_string(achievement->title, codepoints); add_string(achievement->title);
if (achievement->description) if (achievement->description)
add_string(achievement->description, codepoints); add_string(achievement->description);
} }
} }
rc_client_destroy_achievement_list(achievements); rc_client_destroy_achievement_list(achievements);
@ -517,23 +523,28 @@ void Achievements::UpdateGlyphRanges()
{ {
const rc_client_leaderboard_t* leaderboard = bucket.leaderboards[j]; const rc_client_leaderboard_t* leaderboard = bucket.leaderboards[j];
if (leaderboard->title) if (leaderboard->title)
add_string(leaderboard->title, codepoints); add_string(leaderboard->title);
if (leaderboard->description) if (leaderboard->description)
add_string(leaderboard->description, codepoints); add_string(leaderboard->description);
} }
} }
rc_client_destroy_leaderboard_list(leaderboards); rc_client_destroy_leaderboard_list(leaderboards);
} }
} }
std::vector<ImGuiManager::WCharType> sorted_codepoints; std::vector<ImGuiManager::WCharType> sorted_codepoints, sorted_emoji_codepoints;
sorted_codepoints.reserve(codepoints.size()); sorted_codepoints.reserve(codepoints.size());
sorted_codepoints.insert(sorted_codepoints.begin(), codepoints.begin(), codepoints.end()); sorted_codepoints.insert(sorted_codepoints.begin(), codepoints.begin(), codepoints.end());
std::sort(sorted_codepoints.begin(), sorted_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. // Compact codepoints to ranges.
GPUThread::RunOnThread([sorted_codepoints = std::move(sorted_codepoints)]() { GPUThread::RunOnThread(
ImGuiManager::SetEmojiFontRange(ImGuiManager::CompactFontRange(sorted_codepoints)); [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));
}); });
} }

View File

@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors. // SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "mainwindow.h" #include "mainwindow.h"
#include "qthost.h" #include "qthost.h"
#include "core/gpu_thread.h"
#include "core/host.h" #include "core/host.h"
#include "util/imgui_manager.h" #include "util/imgui_manager.h"
@ -256,9 +257,10 @@ void QtHost::UpdateGlyphRangesAndClearCache(QWidget* dialog_parent, std::string_
const char* imgui_font_url = nullptr; const char* imgui_font_url = nullptr;
std::vector<ImWchar> glyph_ranges; std::vector<ImWchar> glyph_ranges;
glyph_ranges.clear(); glyph_ranges.clear();
glyph_ranges.reserve(std::size(s_base_latin_range) + 2);
// Base Latin range is always included. // 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) 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 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") 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)); std::end(s_central_european_ranges));
} }
@ -311,7 +313,9 @@ void QtHost::UpdateGlyphRangesAndClearCache(QWidget* dialog_parent, std::string_
if (g_emu_thread) if (g_emu_thread)
{ {
Host::RunOnCPUThread([font_path = std::move(font_path), glyph_ranges = std::move(glyph_ranges)]() mutable { Host::RunOnCPUThread([font_path = std::move(font_path), glyph_ranges = std::move(glyph_ranges)]() mutable {
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)); ImGuiManager::SetFontPathAndRange(std::move(font_path), std::move(glyph_ranges));
});
Host::ClearTranslationCache(); Host::ClearTranslationCache();
}); });
} }

View File

@ -132,9 +132,6 @@ bool RegTestHost::InitializeConfig()
EmuFolders::LoadConfig(*s_base_settings_interface.get()); EmuFolders::LoadConfig(*s_base_settings_interface.get());
EmuFolders::EnsureFoldersExist(); EmuFolders::EnsureFoldersExist();
// imgui setup, make sure it doesn't bug out
ImGuiManager::SetFontPathAndRange(std::string(), {0x0020, 0x00FF, 0x2022, 0x2022, 0, 0});
return true; return true;
} }

View File

@ -74,9 +74,9 @@ static void SetKeyMap();
static bool LoadFontData(Error* error); static bool LoadFontData(Error* error);
static void ReloadFontDataIfActive(); static void ReloadFontDataIfActive();
static bool AddImGuiFonts(bool debug_font, bool fullscreen_fonts); 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 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 SetCommonIOOptions(ImGuiIO& io);
static void SetImKeyState(ImGuiIO& io, ImGuiKey imkey, bool pressed); static void SetImKeyState(ImGuiIO& io, ImGuiKey imkey, bool pressed);
static const char* GetClipboardTextImpl(void* userdata); static const char* GetClipboardTextImpl(void* userdata);
@ -94,7 +94,8 @@ static void DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<float,
static constexpr float OSD_FADE_IN_TIME = 0.1f; static constexpr float OSD_FADE_IN_TIME = 0.1f;
static constexpr float OSD_FADE_OUT_TIME = 0.4f; static constexpr float OSD_FADE_OUT_TIME = 0.4f;
static constexpr std::array<ImWchar, 4> s_ascii_font_range = {{0x20, 0x7F, 0x00, 0x00}}; static constexpr std::array<ImWchar, 4> ASCII_FONT_RANGE = {{0x20, 0x7F, 0x00, 0x00}};
static constexpr std::array<ImWchar, 6> DEFAULT_FONT_RANGE = {{0x0020, 0x00FF, 0x2022, 0x2022, 0x0000, 0x0000}};
namespace { namespace {
@ -140,7 +141,8 @@ struct ALIGN_TO_CACHE_LINE State
std::string font_path; std::string font_path;
std::vector<WCharType> font_range; std::vector<WCharType> font_range;
std::vector<WCharType> emoji_range; std::vector<WCharType> dynamic_font_range;
std::vector<WCharType> dynamic_emoji_range;
DynamicHeapArray<u8> standard_font_data; DynamicHeapArray<u8> standard_font_data;
DynamicHeapArray<u8> fixed_font_data; DynamicHeapArray<u8> fixed_font_data;
@ -166,32 +168,13 @@ void ImGuiManager::SetFontPathAndRange(std::string path, std::vector<WCharType>
ReloadFontDataIfActive(); ReloadFontDataIfActive();
} }
void ImGuiManager::SetEmojiFontRange(std::vector<WCharType> range) void ImGuiManager::SetDynamicFontRange(std::vector<WCharType> font_range, std::vector<WCharType> emoji_range)
{ {
static constexpr size_t builtin_size = std::size(EMOJI_ICON_RANGE); if (s_state.dynamic_font_range == font_range && s_state.dynamic_emoji_range == emoji_range)
const size_t runtime_size = range.size();
if (runtime_size == 0)
{
if (s_state.emoji_range.empty())
return; return;
s_state.emoji_range = {}; s_state.dynamic_font_range = std::move(font_range);
} s_state.dynamic_emoji_range = std::move(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));
}
ReloadFontDataIfActive(); ReloadFontDataIfActive();
} }
@ -647,13 +630,12 @@ bool ImGuiManager::LoadFontData(Error* error)
return true; return true;
} }
ImFont* ImGuiManager::AddTextFont(float size, bool full_glyph_range) ImFont* ImGuiManager::AddTextFont(float size, const ImWchar* glyph_range)
{ {
ImFontConfig cfg; ImFontConfig cfg;
cfg.FontDataOwnedByAtlas = false; cfg.FontDataOwnedByAtlas = false;
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF( return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
s_state.standard_font_data.data(), static_cast<int>(s_state.standard_font_data.size()), size, &cfg, s_state.standard_font_data.data(), static_cast<int>(s_state.standard_font_data.size()), size, &cfg, glyph_range);
full_glyph_range ? s_state.font_range.data() : s_ascii_font_range.data());
} }
ImFont* ImGuiManager::AddFixedFont(float size) ImFont* ImGuiManager::AddFixedFont(float size)
@ -662,10 +644,10 @@ ImFont* ImGuiManager::AddFixedFont(float size)
cfg.FontDataOwnedByAtlas = false; cfg.FontDataOwnedByAtlas = false;
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_state.fixed_font_data.data(), return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_state.fixed_font_data.data(),
static_cast<int>(s_state.fixed_font_data.size()), size, &cfg, static_cast<int>(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; ImFontConfig cfg;
@ -708,9 +690,9 @@ bool ImGuiManager::AddIconFonts(float size)
cfg.FontDataOwnedByAtlas = false; cfg.FontDataOwnedByAtlas = false;
cfg.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor | ImGuiFreeTypeBuilderFlags_Bitmap; cfg.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor | ImGuiFreeTypeBuilderFlags_Bitmap;
if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF( if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_state.emoji_font_data.data(),
s_state.emoji_font_data.data(), static_cast<int>(s_state.emoji_font_data.size()), size * 0.9f, &cfg, static_cast<int>(s_state.emoji_font_data.size()), size * 0.9f, &cfg,
s_state.emoji_range.empty() ? EMOJI_ICON_RANGE : s_state.emoji_range.data())) [[unlikely]] emoji_range)) [[unlikely]]
{ {
return false; 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, INFO_LOG("Allocating fonts winscale={} globalscale={} debug={} fullscreen={}", window_scale, s_state.global_scale,
debug_font, fullscreen_fonts); 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<ImWchar> 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(); ImGuiIO& io = ImGui::GetIO();
io.Fonts->Clear(); io.Fonts->Clear();
if (debug_font) 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) if (!s_state.debug_font)
return false; return false;
} }
@ -744,8 +757,8 @@ bool ImGuiManager::AddImGuiFonts(bool debug_font, bool fullscreen_fonts)
if (!s_state.fixed_font) if (!s_state.fixed_font)
return false; return false;
s_state.osd_font = AddTextFont(osd_font_size, true); s_state.osd_font = AddTextFont(osd_font_size, text_range);
if (!s_state.osd_font || !AddIconFonts(osd_font_size)) if (!s_state.osd_font || !AddIconFonts(osd_font_size, emoji_range))
return false; return false;
if (!debug_font) if (!debug_font)
s_state.debug_font = s_state.osd_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) if (fullscreen_fonts)
{ {
const float medium_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE); const float medium_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE);
s_state.medium_font = AddTextFont(medium_font_size, true); s_state.medium_font = AddTextFont(medium_font_size, text_range);
if (!s_state.medium_font || !AddIconFonts(medium_font_size)) if (!s_state.medium_font || !AddIconFonts(medium_font_size, emoji_range))
return false; return false;
const float large_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE); const float large_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE);
s_state.large_font = AddTextFont(large_font_size, true); s_state.large_font = AddTextFont(large_font_size, text_range);
if (!s_state.large_font || !AddIconFonts(large_font_size)) if (!s_state.large_font || !AddIconFonts(large_font_size, emoji_range))
return false; return false;
} }
else else

View File

@ -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. /// Sets the path to the font to use. Empty string means to use the default.
void SetFontPathAndRange(std::string path, std::vector<WCharType> range); void SetFontPathAndRange(std::string path, std::vector<WCharType> 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. /// Should NOT be terminated with zeros, unlike the font range above.
void SetEmojiFontRange(std::vector<WCharType> range); void SetDynamicFontRange(std::vector<WCharType> font_range, std::vector<WCharType> emoji_range);
/// Returns a compacted font range, with adjacent glyphs merged into one pair. /// Returns a compacted font range, with adjacent glyphs merged into one pair.
std::vector<WCharType> CompactFontRange(std::span<const WCharType> range); std::vector<WCharType> CompactFontRange(std::span<const WCharType> range);