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] 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 12e6914882..82567948cf 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 492339a980..60a88c9c50 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 95b6e95f1d..da54b660d4 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 e6ac90388a..7478a921cc 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 d1e6244cb2..77ddf5a429 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]; - v = (v >> 12) & 0xF; // Just grab some bits from the green channel. - bitmapData[entry.bmWidth * y + x] = (uint8_t)(v | (v << 4)); + 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]; + // 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 fbbe454ef3..539e032654 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 00e0a7e0bb..0e736bf551 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 b262d63325..c52adc70e2 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 d33016442c..60b18912c5 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) {