From a477ca3f050d80dcdb64ca8d3e7b83e1bfa332ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 6 Aug 2023 11:35:13 +0200 Subject: [PATCH 1/2] Add a couple of emoji to UI in debug mode --- Common/Data/Encoding/Utf8.cpp | 6 ++++++ Common/Data/Encoding/Utf8.h | 2 ++ UI/DevScreens.cpp | 4 +++- UI/MiscScreens.cpp | 5 ++++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Common/Data/Encoding/Utf8.cpp b/Common/Data/Encoding/Utf8.cpp index 760fe6b07de3..12e691488240 100644 --- a/Common/Data/Encoding/Utf8.cpp +++ b/Common/Data/Encoding/Utf8.cpp @@ -558,6 +558,12 @@ std::u16string ConvertUTF8ToUCS2(const std::string &source) { return dst; } +std::string CodepointToUTF8(uint32_t codePoint) { + char temp[16]{}; + UTF8::encode(temp, codePoint); + return std::string(temp); +} + #ifndef _WIN32 // Replacements for the Win32 wstring functions. Not to be used from emulation code! diff --git a/Common/Data/Encoding/Utf8.h b/Common/Data/Encoding/Utf8.h index 29d09f67e91c..492339a9800a 100644 --- a/Common/Data/Encoding/Utf8.h +++ b/Common/Data/Encoding/Utf8.h @@ -89,6 +89,8 @@ bool UTF8StringHasNonASCII(const char *utf8string); // Removes overlong encodings and similar. std::string SanitizeUTF8(const std::string &utf8string); +std::string CodepointToUTF8(uint32_t codePoint); + // UTF8 to Win32 UTF-16 // Should be used when calling Win32 api calls diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 2a9541f4fdea..00e0a7e0bbd4 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -39,6 +39,7 @@ #endif #include "Common/File/AndroidStorage.h" #include "Common/Data/Text/I18n.h" +#include "Common/Data/Encoding/Utf8.h" #include "Common/Net/HTTPClient.h" #include "Common/UI/Context.h" #include "Common/UI/View.h" @@ -834,7 +835,8 @@ void SystemInfoScreen::CreateTabs() { internals->Add(new ItemHeader(si->T("Notification tests"))); internals->Add(new Choice(si->T("Error")))->OnClick.Add([&](UI::EventParams &) { - g_OSD.Show(OSDType::MESSAGE_ERROR, "Error"); + std::string str = "Error " + CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1FAB0) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1FAB2); + g_OSD.Show(OSDType::MESSAGE_ERROR, str); return UI::EVENT_DONE; }); internals->Add(new Choice(si->T("Warning")))->OnClick.Add([&](UI::EventParams &) { diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 0f928332e214..b262d63325de 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -34,6 +34,7 @@ #include "Common/File/VFS/VFS.h" #include "Common/Data/Color/RGBAUtil.h" +#include "Common/Data/Encoding/Utf8.h" #include "Common/Data/Text/I18n.h" #include "Common/Data/Random/Rng.h" #include "Common/TimeUtil.h" @@ -798,7 +799,9 @@ void LogoScreen::render() { // Draw the graphics API, except on UWP where it's always D3D11 std::string apiName = screenManager()->getDrawContext()->GetInfoString(InfoField::APINAME); #ifdef _DEBUG - apiName += ", debug build"; + apiName += ", debug build "; + // Add some bug emoji for testing. + apiName += CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1FAB0) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1FAB2); #endif dc.DrawText(gr->T(apiName), bounds.centerX(), ppsspp_org_y + 50, textColor, ALIGN_CENTER); #endif From 63cfe28f6127a97b6c9aa3a525439d4e156b49b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 6 Aug 2023 10:48:46 +0200 Subject: [PATCH 2/2] Implement color emoji support on Android --- Common/Data/Encoding/Utf8.cpp | 11 ++++++ Common/Data/Encoding/Utf8.h | 8 +++++ Common/GPU/Vulkan/thin3d_vulkan.cpp | 1 + Common/GPU/thin3d.h | 1 + Common/Render/Text/draw_text_android.cpp | 36 +++++++++++++------ Common/Render/Text/draw_text_uwp.cpp | 1 - UI/DevScreens.cpp | 2 +- UI/MiscScreens.cpp | 4 +-- .../src/org/ppsspp/ppsspp/TextRenderer.java | 2 +- 9 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Common/Data/Encoding/Utf8.cpp b/Common/Data/Encoding/Utf8.cpp index 12e691488240..82567948cfea 100644 --- a/Common/Data/Encoding/Utf8.cpp +++ b/Common/Data/Encoding/Utf8.cpp @@ -426,6 +426,17 @@ int u8_is_locale_utf8(const char *locale) return 0; } +bool AnyEmojiInString(const char *s, size_t byteCount) { + int i = 0; + while (i < byteCount) { + uint32_t c = u8_nextchar(s, &i); + if (CodepointIsProbablyEmoji(c)) { + return true; + } + } + return false; +} + int UTF8StringNonASCIICount(const char *utf8string) { UTF8 utf(utf8string); int count = 0; diff --git a/Common/Data/Encoding/Utf8.h b/Common/Data/Encoding/Utf8.h index 492339a9800a..60a88c9c505a 100644 --- a/Common/Data/Encoding/Utf8.h +++ b/Common/Data/Encoding/Utf8.h @@ -26,6 +26,14 @@ int u8_strlen(const char *s); void u8_inc(const char *s, int *i); void u8_dec(const char *s, int *i); +// ranges grabbed from https://stackoverflow.com/a/62898106, ignoring the two bogus ranges. +// there's probably more. Doesn't need to be perfect. +inline bool CodepointIsProbablyEmoji(uint32_t c) { + return (c >= 127744 && c <= 129782) || (c >= 126980 && c <= 127569); +} + +bool AnyEmojiInString(const char *s, size_t byteCount); + class UTF8 { public: static const uint32_t INVALID = (uint32_t)-1; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 95b6e95f1d86..da54b660d4d8 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -897,6 +897,7 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.standard.logicOp != 0; caps_.multiViewSupported = vulkan->GetDeviceFeatures().enabled.multiview.multiview != 0; caps_.sampleRateShadingSupported = vulkan->GetDeviceFeatures().enabled.standard.sampleRateShading != 0; + caps_.textureSwizzleSupported = true; const auto &limits = vulkan->GetPhysicalDeviceProperties().properties.limits; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index e6ac90388a24..7478a921ccff 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -604,6 +604,7 @@ struct DeviceCaps { bool isTilingGPU; // This means that it benefits from correct store-ops, msaa without backing memory, etc. bool sampleRateShadingSupported; bool setMaxFrameLatencySupported; + bool textureSwizzleSupported; bool verySlowShaderCompiler; diff --git a/Common/Render/Text/draw_text_android.cpp b/Common/Render/Text/draw_text_android.cpp index d1e6244cb27e..77ddf5a42985 100644 --- a/Common/Render/Text/draw_text_android.cpp +++ b/Common/Render/Text/draw_text_android.cpp @@ -29,8 +29,10 @@ TextDrawerAndroid::TextDrawerAndroid(Draw::DrawContext *draw) : TextDrawer(draw) } dpiScale_ = CalculateDPIScale(); + // Pick between the two supported formats, of which at least one is supported on each platform. Prefer R8 (but only if swizzle is supported) use4444Format_ = (draw->GetDataFormatSupport(Draw::DataFormat::R4G4B4A4_UNORM_PACK16) & Draw::FMT_TEXTURE) != 0; - + if ((draw->GetDataFormatSupport(Draw::DataFormat::R8_UNORM) & Draw::FMT_TEXTURE) != 0 && draw->GetDeviceCaps().textureSwizzleSupported) + use4444Format_ = false; INFO_LOG(G3D, "Initializing TextDrawerAndroid with DPI scale %f, use4444=%d", dpiScale_, (int)use4444Format_); } @@ -206,20 +208,30 @@ void TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextS if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) { bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; - for (int x = 0; x < entry.bmWidth; x++) { - for (int y = 0; y < entry.bmHeight; y++) { + for (int y = 0; y < entry.bmHeight; y++) { + for (int x = 0; x < entry.bmWidth; x++) { uint32_t v = jimage[imageWidth * y + x]; - v = 0xFFF0 | ((v >> 12) & 0xF); // Just grab some bits from the green channel. + v = 0xFFF0 | ((v >> 28) & 0xF); // Grab the upper bits from the alpha channel, and put directly in the 16-bit alpha channel. bitmapData16[entry.bmWidth * y + x] = (uint16_t)v; } } } else if (texFormat == Draw::DataFormat::R8_UNORM) { bitmapData.resize(entry.bmWidth * entry.bmHeight); - for (int x = 0; x < entry.bmWidth; x++) { - for (int y = 0; y < entry.bmHeight; y++) { + for (int y = 0; y < entry.bmHeight; y++) { + for (int x = 0; x < entry.bmWidth; x++) { + uint32_t v = jimage[imageWidth * y + x]; + bitmapData[entry.bmWidth * y + x] = (uint8_t)(v >> 24); + } + } + } else if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM) { + bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint32_t)); + uint32_t *bitmapData32 = (uint32_t *)&bitmapData[0]; + for (int y = 0; y < entry.bmHeight; y++) { + for (int x = 0; x < entry.bmWidth; x++) { uint32_t v = jimage[imageWidth * y + x]; - v = (v >> 12) & 0xF; // Just grab some bits from the green channel. - bitmapData[entry.bmWidth * y + x] = (uint8_t)(v | (v << 4)); + // Swap R and B, for some reason. + v = (v & 0xFF00FF00) | ((v >> 16) & 0xFF) | ((v << 16) & 0xFF0000); + bitmapData32[entry.bmWidth * y + x] = v; } } } else { @@ -238,6 +250,8 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, if (text.empty()) return; + bool emoji = AnyEmojiInString(text.c_str(), text.size()); + CacheKey key{ std::string(str), fontHash_ }; target.Flush(true); @@ -248,8 +262,10 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, entry = iter->second.get(); entry->lastUsedFrame = frameCount_; } else { - // Actually, I don't know why we don't always use R8_UNORM.. DataFormat texFormat = use4444Format_ ? Draw::DataFormat::R4G4B4A4_UNORM_PACK16 : Draw::DataFormat::R8_UNORM; + if (emoji) { + texFormat = Draw::DataFormat::R8G8B8A8_UNORM; + } entry = new TextStringEntry(); @@ -265,7 +281,7 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, desc.depth = 1; desc.mipLevels = 1; desc.generateMips = false; - desc.swizzle = use4444Format_ ? Draw::TextureSwizzle::DEFAULT : Draw::TextureSwizzle::R8_AS_ALPHA, + desc.swizzle = texFormat == Draw::DataFormat::R8_UNORM ? Draw::TextureSwizzle::R8_AS_ALPHA : Draw::TextureSwizzle::DEFAULT, desc.tag = "TextDrawer"; entry->texture = draw_->CreateTexture(desc); cache_[key] = std::unique_ptr(entry); diff --git a/Common/Render/Text/draw_text_uwp.cpp b/Common/Render/Text/draw_text_uwp.cpp index fbbe454ef36e..539e03265436 100644 --- a/Common/Render/Text/draw_text_uwp.cpp +++ b/Common/Render/Text/draw_text_uwp.cpp @@ -137,7 +137,6 @@ TextDrawerUWP::TextDrawerUWP(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(n ); m_d2dContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), &m_d2dWhiteBrush); - } TextDrawerUWP::~TextDrawerUWP() { diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 00e0a7e0bbd4..0e736bf551af 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -835,7 +835,7 @@ void SystemInfoScreen::CreateTabs() { internals->Add(new ItemHeader(si->T("Notification tests"))); internals->Add(new Choice(si->T("Error")))->OnClick.Add([&](UI::EventParams &) { - std::string str = "Error " + CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1FAB0) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1FAB2); + std::string str = "Error " + CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914); g_OSD.Show(OSDType::MESSAGE_ERROR, str); return UI::EVENT_DONE; }); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index b262d63325de..c52adc70e254 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -800,8 +800,8 @@ void LogoScreen::render() { std::string apiName = screenManager()->getDrawContext()->GetInfoString(InfoField::APINAME); #ifdef _DEBUG apiName += ", debug build "; - // Add some bug emoji for testing. - apiName += CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1FAB0) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1FAB2); + // Add some emoji for testing. + apiName += CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914); #endif dc.DrawText(gr->T(apiName), bounds.centerX(), ppsspp_org_y + 50, textColor, ALIGN_CENTER); #endif diff --git a/android/src/org/ppsspp/ppsspp/TextRenderer.java b/android/src/org/ppsspp/ppsspp/TextRenderer.java index d33016442c29..60b18912c58f 100644 --- a/android/src/org/ppsspp/ppsspp/TextRenderer.java +++ b/android/src/org/ppsspp/ppsspp/TextRenderer.java @@ -14,7 +14,7 @@ public class TextRenderer { p = new Paint(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG); p.setColor(Color.WHITE); bg = new Paint(); - bg.setColor(Color.BLACK); + bg.setColor(0); } public static void init(Context ctx) {