Implement color emoji support on Android

This commit is contained in:
Henrik Rydgård 2023-08-06 10:48:46 +02:00
parent a477ca3f05
commit 63cfe28f61
9 changed files with 51 additions and 15 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<uint8_t> &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<TextStringEntry>(entry);

View File

@ -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() {

View File

@ -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;
});

View File

@ -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

View File

@ -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) {