From 533f8f06bee7b79a313f9aa1c34031d49c705506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 31 May 2024 15:19:45 +0200 Subject: [PATCH 1/4] Unify DrawString between Windows, UWP and Android. More to come. --- Common/Render/Text/draw_text.cpp | 79 +++++++++++++++++++++- Common/Render/Text/draw_text.h | 14 ++-- Common/Render/Text/draw_text_android.cpp | 62 +----------------- Common/Render/Text/draw_text_android.h | 6 +- Common/Render/Text/draw_text_cocoa.h | 3 +- Common/Render/Text/draw_text_cocoa.mm | 27 ++++---- Common/Render/Text/draw_text_qt.cpp | 14 ++-- Common/Render/Text/draw_text_qt.h | 3 +- Common/Render/Text/draw_text_sdl.cpp | 14 ++-- Common/Render/Text/draw_text_sdl.h | 3 +- Common/Render/Text/draw_text_uwp.cpp | 83 ++++-------------------- Common/Render/Text/draw_text_uwp.h | 5 +- Common/Render/Text/draw_text_win.cpp | 71 +------------------- Common/Render/Text/draw_text_win.h | 5 +- Core/Util/PPGeDraw.cpp | 2 +- 15 files changed, 137 insertions(+), 254 deletions(-) diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index 7cdefb4072..9b9fc93d5b 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -47,6 +47,80 @@ float TextDrawer::CalculateDPIScale() { return scale; } +void TextDrawer::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) { + using namespace Draw; + if (str.empty()) { + return; + } + + CacheKey key{ std::string(str), fontHash_ }; + target.Flush(true); + + TextStringEntry *entry; + + auto iter = cache_.find(key); + if (iter != cache_.end()) { + entry = iter->second.get(); + entry->lastUsedFrame = frameCount_; + } else { + DataFormat texFormat; + // Pick between the supported formats, of which at least one is supported on each platform. Prefer R8 (but only if swizzle is supported) + bool emoji = AnyEmojiInString(str.data(), str.length()); + if (emoji) { + texFormat = Draw::DataFormat::R8G8B8A8_UNORM; + } else if ((draw_->GetDataFormatSupport(Draw::DataFormat::R8_UNORM) & Draw::FMT_TEXTURE) != 0 && draw_->GetDeviceCaps().textureSwizzleSupported) { + texFormat = Draw::DataFormat::R8_UNORM; + } else if (draw_->GetDataFormatSupport(Draw::DataFormat::R4G4B4A4_UNORM_PACK16) & FMT_TEXTURE) { + texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; + } else if (draw_->GetDataFormatSupport(Draw::DataFormat::A4R4G4B4_UNORM_PACK16) & FMT_TEXTURE) { + texFormat = Draw::DataFormat::A4R4G4B4_UNORM_PACK16; + } else if (draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16) & FMT_TEXTURE) { + texFormat = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; + } else { + texFormat = Draw::DataFormat::R8G8B8A8_UNORM; + } + + entry = new TextStringEntry(frameCount_); + + // Convert the bitmap to a Thin3D compatible array of 16-bit pixels. Can't use a single channel format + // because we need white. Well, we could using swizzle, but not all our backends support that. + TextureDesc desc{}; + std::vector bitmapData; + if (!DrawStringBitmap(bitmapData, *entry, texFormat, str, align, emoji)) { + // Nothing drawn. Store that fact in the cache. + cache_[key] = std::unique_ptr(entry); + return; + } + + desc.initData.push_back(&bitmapData[0]); + + desc.type = TextureType::LINEAR2D; + desc.format = texFormat; + desc.width = entry->bmWidth; + desc.height = entry->bmHeight; + desc.depth = 1; + desc.mipLevels = 1; + desc.tag = "TextDrawer"; + desc.swizzle = texFormat == Draw::DataFormat::R8_UNORM ? Draw::TextureSwizzle::R8_AS_ALPHA : Draw::TextureSwizzle::DEFAULT, + entry->texture = draw_->CreateTexture(desc); + cache_[key] = std::unique_ptr(entry); + } + + if (entry->texture) { + draw_->BindTexture(0, entry->texture); + + // Okay, the texture is bound, let's draw. + float w = entry->width * fontScaleX_ * dpiScale_; + float h = entry->height * fontScaleY_ * dpiScale_; + float u = entry->width / (float)entry->bmWidth; + float v = entry->height / (float)entry->bmHeight; + DrawBuffer::DoAlign(align, &x, &y, &w, &h); + + target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); + target.Flush(true); + } +} + void TextDrawer::DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align) { float x = bounds.x; float y = bounds.y; @@ -71,15 +145,14 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, std::string_view str, const DrawString(target, toDraw.c_str(), x, y, color, align); } -bool TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align) { +bool TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align, bool fullColor) { std::string toDraw(str); int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap); } - - return DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align); + return DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align, fullColor); } TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) { diff --git a/Common/Render/Text/draw_text.h b/Common/Render/Text/draw_text.h index cfdb109c39..ae1b343c6f 100644 --- a/Common/Render/Text/draw_text.h +++ b/Common/Render/Text/draw_text.h @@ -51,13 +51,11 @@ public: virtual void MeasureString(std::string_view str, float *w, float *h) = 0; virtual void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) = 0; - // TODO: This one we should be able to make a default implementation for, calling the specialized DrawBitmap. - // Only problem is that we need to make sure that the texFormats are all supported by all the backends, or we explicitly limit. - virtual void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) = 0; + virtual void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT); void DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align); - virtual bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) = 0; - bool DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align); + virtual bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) = 0; + bool DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align, bool fullColor); // Use for housekeeping like throwing out old strings. virtual void OncePerFrame() = 0; @@ -73,8 +71,8 @@ public: protected: TextDrawer(Draw::DrawContext *draw); - Draw::DrawContext *draw_; virtual void ClearCache() = 0; + void WrapString(std::string &out, std::string_view str, float maxWidth, int flags); struct CacheKey { @@ -89,12 +87,16 @@ protected: uint32_t fontHash; }; + Draw::DrawContext *draw_; + int frameCount_ = 0; float fontScaleX_ = 1.0f; float fontScaleY_ = 1.0f; float dpiScale_ = 1.0f; bool ignoreGlobalDpi_ = false; + uint32_t fontHash_ = 0; + std::map> cache_; std::map> sizeCache_; }; diff --git a/Common/Render/Text/draw_text_android.cpp b/Common/Render/Text/draw_text_android.cpp index 89349df44d..751c381f4f 100644 --- a/Common/Render/Text/draw_text_android.cpp +++ b/Common/Render/Text/draw_text_android.cpp @@ -29,11 +29,7 @@ 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_); + INFO_LOG(G3D, "Initializing TextDrawerAndroid with DPI scale %f", dpiScale_); } TextDrawerAndroid::~TextDrawerAndroid() { @@ -171,7 +167,7 @@ void TextDrawerAndroid::MeasureStringRect(std::string_view str, const Bounds &bo *h = total_h * fontScaleY_ * dpiScale_; } -bool TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) { +bool TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { if (str.empty()) { bitmapData.clear(); return false; @@ -244,60 +240,6 @@ bool TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextS return true; } -void TextDrawerAndroid::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (str.empty()) - return; - - CacheKey key{ std::string(str), fontHash_ }; - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(key); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - } else { - DataFormat texFormat = use4444Format_ ? Draw::DataFormat::R4G4B4A4_UNORM_PACK16 : Draw::DataFormat::R8_UNORM; - bool emoji = AnyEmojiInString(str.data(), str.length()); - if (emoji) { - texFormat = Draw::DataFormat::R8G8B8A8_UNORM; - } - - entry = new TextStringEntry(frameCount_); - - TextureDesc desc{}; - std::vector bitmapData; - DrawStringBitmap(bitmapData, *entry, texFormat, str, align); - desc.initData.push_back(&bitmapData[0]); - - desc.type = TextureType::LINEAR2D; - desc.format = texFormat; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - desc.generateMips = false; - 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); - } - - if (entry->texture) { - draw_->BindTexture(0, entry->texture); - } - - float w = entry->bmWidth * fontScaleX_ * dpiScale_; - float h = entry->bmHeight * fontScaleY_ * dpiScale_; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - if (entry->texture) { - target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); - target.Flush(true); - } -} - void TextDrawerAndroid::ClearCache() { for (auto &iter : cache_) { if (iter.second->texture) diff --git a/Common/Render/Text/draw_text_android.h b/Common/Render/Text/draw_text_android.h index 3e52c53d15..d2b558e73f 100644 --- a/Common/Render/Text/draw_text_android.h +++ b/Common/Render/Text/draw_text_android.h @@ -23,8 +23,7 @@ public: void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; - void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override; + bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -37,9 +36,6 @@ private: jmethodID method_measureText; jmethodID method_renderText; - uint32_t fontHash_; - bool use4444Format_ = false; - std::map fontMap_; }; diff --git a/Common/Render/Text/draw_text_cocoa.h b/Common/Render/Text/draw_text_cocoa.h index 9af640785a..008f68a064 100644 --- a/Common/Render/Text/draw_text_cocoa.h +++ b/Common/Render/Text/draw_text_cocoa.h @@ -21,7 +21,7 @@ public: void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override; + bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -32,7 +32,6 @@ protected: TextDrawerContext *ctx_; std::map> fontMap_; - uint32_t fontHash_; std::map> cache_; std::map> sizeCache_; }; diff --git a/Common/Render/Text/draw_text_cocoa.mm b/Common/Render/Text/draw_text_cocoa.mm index 148a52ad8c..a3636a59ed 100644 --- a/Common/Render/Text/draw_text_cocoa.mm +++ b/Common/Render/Text/draw_text_cocoa.mm @@ -283,21 +283,19 @@ void TextDrawerCocoa::DrawString(DrawBuffer &target, std::string_view str, float if (entry->texture) { draw_->BindTexture(0, entry->texture); - } - // Okay, the texture is bound, let's draw. - float w = entry->width * fontScaleX_ * dpiScale_; - float h = entry->height * fontScaleY_ * dpiScale_; - float u = entry->width / (float)entry->bmWidth; - float v = entry->height / (float)entry->bmHeight; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - if (entry->texture) { + // Okay, the texture is bound, let's draw. + float w = entry->width * fontScaleX_ * dpiScale_; + float h = entry->height * fontScaleY_ * dpiScale_; + float u = entry->width / (float)entry->bmWidth; + float v = entry->height / (float)entry->bmHeight; + DrawBuffer::DoAlign(align, &x, &y, &w, &h); target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); target.Flush(true); } } -bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) { +bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { if (str.empty()) { bitmapData.clear(); return false; @@ -365,15 +363,20 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr // because we need white. Well, we could using swizzle, but not all our backends support that. if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM || texFormat == Draw::DataFormat::B8G8R8A8_UNORM) { bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint32_t)); - // If we chose this format, emoji are involved. Pass straight through. 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 color = bitmap[width * y + x]; - bitmapData32[entry.bmWidth * y + x] = color; + if (fullColor) { + bitmapData32[entry.bmWidth * y + x] = color; + } else { + // Don't know why we'd end up here, but let's support it. + bitmapData32[entry.bmWidth * y + x] = (color << 24) | 0xFFFFFF; + } } } } else if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) { + _dbg_assert_(!fullColor); bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; for (int y = 0; y < entry.bmHeight; y++) { @@ -383,6 +386,7 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr } } } else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) { + _dbg_assert_(!fullColor); bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; for (int y = 0; y < entry.bmHeight; y++) { @@ -392,6 +396,7 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr } } } else if (texFormat == Draw::DataFormat::R8_UNORM) { + _dbg_assert_(!fullColor); bitmapData.resize(entry.bmWidth * entry.bmHeight); for (int y = 0; y < entry.bmHeight; y++) { for (int x = 0; x < entry.bmWidth; x++) { diff --git a/Common/Render/Text/draw_text_qt.cpp b/Common/Render/Text/draw_text_qt.cpp index 188e70922e..7d1a18e5d3 100644 --- a/Common/Render/Text/draw_text_qt.cpp +++ b/Common/Render/Text/draw_text_qt.cpp @@ -89,7 +89,9 @@ void TextDrawerQt::MeasureStringRect(std::string_view str, const Bounds &bounds, *h = (float)size.height() * fontScaleY_ * dpiScale_; } -bool TextDrawerQt::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) { +bool TextDrawerQt::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { + _dbg_assert_(!fullColor); + if (str.empty()) { bitmapData.clear(); return false; @@ -161,7 +163,7 @@ void TextDrawerQt::DrawString(DrawBuffer &target, std::string_view str, float x, TextureDesc desc{}; std::vector bitmapData; - DrawStringBitmap(bitmapData, *entry, texFormat, str, align); + DrawStringBitmap(bitmapData, *entry, texFormat, str, align, false); desc.initData.push_back(&bitmapData[0]); desc.type = TextureType::LINEAR2D; @@ -177,12 +179,10 @@ void TextDrawerQt::DrawString(DrawBuffer &target, std::string_view str, float x, if (entry->texture) { draw_->BindTexture(0, entry->texture); - } - float w = entry->bmWidth * fontScaleX_ * dpiScale_; - float h = entry->bmHeight * fontScaleY_ * dpiScale_; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - if (entry->texture) { + float w = entry->bmWidth * fontScaleX_ * dpiScale_; + float h = entry->bmHeight * fontScaleY_ * dpiScale_; + DrawBuffer::DoAlign(align, &x, &y, &w, &h); target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); target.Flush(true); } diff --git a/Common/Render/Text/draw_text_qt.h b/Common/Render/Text/draw_text_qt.h index 33de3578a4..c145862ee7 100644 --- a/Common/Render/Text/draw_text_qt.h +++ b/Common/Render/Text/draw_text_qt.h @@ -19,14 +19,13 @@ public: void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override; + bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; protected: void ClearCache() override; - uint32_t fontHash_; std::map fontMap_; }; diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index a44b56630d..6dd115ae58 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -389,7 +389,7 @@ void TextDrawerSDL::DrawString(DrawBuffer &target, std::string_view str, float x TextureDesc desc{}; std::vector bitmapData; - DrawStringBitmap(bitmapData, *entry, texFormat, str, align); + DrawStringBitmap(bitmapData, *entry, texFormat, str, align, false); desc.initData.push_back(&bitmapData[0]); desc.type = TextureType::LINEAR2D; @@ -405,18 +405,18 @@ void TextDrawerSDL::DrawString(DrawBuffer &target, std::string_view str, float x if (entry->texture) { draw_->BindTexture(0, entry->texture); - } - float w = entry->bmWidth * fontScaleX_ * dpiScale_; - float h = entry->bmHeight * fontScaleY_ * dpiScale_; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - if (entry->texture) { + float w = entry->bmWidth * fontScaleX_ * dpiScale_; + float h = entry->bmHeight * fontScaleY_ * dpiScale_; + DrawBuffer::DoAlign(align, &x, &y, &w, &h); target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); target.Flush(true); } } -bool TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) { +bool TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { + _dbg_assert_(!fullColor) + if (str.empty()) { bitmapData.clear(); return false; diff --git a/Common/Render/Text/draw_text_sdl.h b/Common/Render/Text/draw_text_sdl.h index 30b44ca191..c00994101c 100644 --- a/Common/Render/Text/draw_text_sdl.h +++ b/Common/Render/Text/draw_text_sdl.h @@ -22,7 +22,7 @@ public: void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override; + bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -32,7 +32,6 @@ protected: uint32_t CheckMissingGlyph(const std::string& text); int FindFallbackFonts(uint32_t missingGlyph, int ptSize); - uint32_t fontHash_; std::map fontMap_; std::vector<_TTF_Font *> fallbackFonts_; diff --git a/Common/Render/Text/draw_text_uwp.cpp b/Common/Render/Text/draw_text_uwp.cpp index ee9e3e758e..e0c1235c40 100644 --- a/Common/Render/Text/draw_text_uwp.cpp +++ b/Common/Render/Text/draw_text_uwp.cpp @@ -312,8 +312,8 @@ void TextDrawerUWP::MeasureStringRect(std::string_view str, const Bounds &bounds *h = total_h * fontScaleY_ * dpiScale_; } -bool TextDrawerUWP::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) { - if (!str.empty()) { +bool TextDrawerUWP::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { + if (str.empty()) { bitmapData.clear(); return false; } @@ -401,12 +401,17 @@ bool TextDrawerUWP::DrawStringBitmap(std::vector &bitmapData, TextStrin uint32_t *bmpLine = (uint32_t *)&map.bits[map.pitch * y]; for (int x = 0; x < entry.bmWidth; x++) { uint32_t v = bmpLine[x]; - if (swap) - v = (v & 0xFF00FF00) | ((v >> 16) & 0xFF) | ((v << 16) & 0xFF0000); - bitmapData32[entry.bmWidth * y + x] = v; + if (fullColor) { + if (swap) + v = (v & 0xFF00FF00) | ((v >> 16) & 0xFF) | ((v << 16) & 0xFF0000); + bitmapData32[entry.bmWidth * y + x] = v; + } else { + bitmapData32[entry.bmWidth * y + x] = (v << 24) | 0xFFFFFF; + } } } } else if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) { + _dbg_assert_(!fullColor); bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; for (int y = 0; y < entry.bmHeight; y++) { @@ -416,6 +421,7 @@ bool TextDrawerUWP::DrawStringBitmap(std::vector &bitmapData, TextStrin } } } else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) { + _dbg_assert_(!fullColor); bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; for (int y = 0; y < entry.bmHeight; y++) { @@ -425,6 +431,7 @@ bool TextDrawerUWP::DrawStringBitmap(std::vector &bitmapData, TextStrin } } } else if (texFormat == Draw::DataFormat::R8_UNORM) { + _dbg_assert_(!fullColor); bitmapData.resize(entry.bmWidth * entry.bmHeight); for (int y = 0; y < entry.bmHeight; y++) { for (int x = 0; x < entry.bmWidth; x++) { @@ -440,72 +447,6 @@ bool TextDrawerUWP::DrawStringBitmap(std::vector &bitmapData, TextStrin return true; } -void TextDrawerUWP::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (str.empty()) { - return; - } - - CacheKey key{ std::string(str), fontHash_ }; - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(key); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - } else { - DataFormat texFormat; - - // For our purposes these are equivalent, so just choose the supported one. D3D can emulate them. - if (draw_->GetDataFormatSupport(Draw::DataFormat::A4R4G4B4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::A4R4G4B4_UNORM_PACK16; - else if (draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; - else - texFormat = Draw::DataFormat::R8G8B8A8_UNORM; - - bool emoji = AnyEmojiInString(key.text.c_str(), key.text.size()); - if (emoji) - texFormat = Draw::DataFormat::R8G8B8A8_UNORM; - - entry = new TextStringEntry(frameCount_); - - // Convert the bitmap to a Thin3D compatible array of 16-bit pixels. Can't use a single channel format - // because we need white. Well, we could using swizzle, but not all our backends support that. - TextureDesc desc{}; - std::vector bitmapData; - DrawStringBitmap(bitmapData, *entry, texFormat, str, align); - desc.initData.push_back(&bitmapData[0]); - - desc.type = TextureType::LINEAR2D; - desc.format = texFormat; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - desc.tag = "TextDrawer"; - entry->texture = draw_->CreateTexture(desc); - cache_[key] = std::unique_ptr(entry); - } - - if (entry->texture) { - draw_->BindTexture(0, entry->texture); - } - - // Okay, the texture is bound, let's draw. - float w = entry->width * fontScaleX_ * dpiScale_; - float h = entry->height * fontScaleY_ * dpiScale_; - float u = entry->width / (float)entry->bmWidth; - float v = entry->height / (float)entry->bmHeight; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - if (entry->texture) { - target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); - target.Flush(true); - } -} - void TextDrawerUWP::RecreateFonts() { for (auto &iter : fontMap_) { iter.second->dpiScale = dpiScale_; diff --git a/Common/Render/Text/draw_text_uwp.h b/Common/Render/Text/draw_text_uwp.h index fa5b8998df..efaa3acbfb 100644 --- a/Common/Render/Text/draw_text_uwp.h +++ b/Common/Render/Text/draw_text_uwp.h @@ -23,8 +23,7 @@ public: void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; - void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override; + bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -35,8 +34,6 @@ protected: TextDrawerContext *ctx_; std::map> fontMap_; - uint32_t fontHash_; - // Direct2D drawing components. ID2D1Factory5* m_d2dFactory; ID2D1Device4* m_d2dDevice; diff --git a/Common/Render/Text/draw_text_win.cpp b/Common/Render/Text/draw_text_win.cpp index 4a3af462b3..c888f15914 100644 --- a/Common/Render/Text/draw_text_win.cpp +++ b/Common/Render/Text/draw_text_win.cpp @@ -211,14 +211,13 @@ void TextDrawerWin32::MeasureStringRect(std::string_view str, const Bounds &boun *h = total_h * fontScaleY_ * dpiScale_; } -bool TextDrawerWin32::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) { +bool TextDrawerWin32::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { if (str.empty()) { bitmapData.clear(); return false; } std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n")); - SIZE size; auto iter = fontMap_.find(fontHash_); if (iter != fontMap_.end()) { @@ -234,6 +233,7 @@ bool TextDrawerWin32::DrawStringBitmap(std::vector &bitmapData, TextStr RECT textRect = { 0 }; DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &textRect, DT_NOPREFIX | DT_TOP | dtAlign | DT_CALCRECT, 0); + SIZE size; size.cx = textRect.right; size.cy = textRect.bottom; @@ -303,73 +303,6 @@ bool TextDrawerWin32::DrawStringBitmap(std::vector &bitmapData, TextStr return true; } -void TextDrawerWin32::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (str.empty()) { - return; - } - - CacheKey key{ std::string(str), fontHash_ }; - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(key); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - } else { - DataFormat texFormat; - // For our purposes these are equivalent, so just choose the supported one. D3D can emulate them. - if (draw_->GetDataFormatSupport(Draw::DataFormat::A4R4G4B4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::A4R4G4B4_UNORM_PACK16; - else if (draw_->GetDataFormatSupport(Draw::DataFormat::R4G4B4A4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; - else if (draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; - else - texFormat = Draw::DataFormat::R8G8B8A8_UNORM; - - entry = new TextStringEntry(frameCount_); - - // Convert the bitmap to a Thin3D compatible array of 16-bit pixels. Can't use a single channel format - // because we need white. Well, we could using swizzle, but not all our backends support that. - TextureDesc desc{}; - std::vector bitmapData; - if (!DrawStringBitmap(bitmapData, *entry, texFormat, str, align)) { - // Nothing drawn. Store that fact in the cache. - cache_[key] = std::unique_ptr(entry); - return; - } - - desc.initData.push_back(&bitmapData[0]); - - desc.type = TextureType::LINEAR2D; - desc.format = texFormat; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - desc.tag = "TextDrawer"; - entry->texture = draw_->CreateTexture(desc); - cache_[key] = std::unique_ptr(entry); - } - - if (entry->texture) { - draw_->BindTexture(0, entry->texture); - - // Okay, the texture is bound, let's draw. - float w = entry->width * fontScaleX_ * dpiScale_; - float h = entry->height * fontScaleY_ * dpiScale_; - float u = entry->width / (float)entry->bmWidth; - float v = entry->height / (float)entry->bmHeight; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - - target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); - target.Flush(true); - } -} - void TextDrawerWin32::RecreateFonts() { for (auto &iter : fontMap_) { iter.second->dpiScale = dpiScale_; diff --git a/Common/Render/Text/draw_text_win.h b/Common/Render/Text/draw_text_win.h index f53ae7084b..b3e93f1d99 100644 --- a/Common/Render/Text/draw_text_win.h +++ b/Common/Render/Text/draw_text_win.h @@ -23,8 +23,7 @@ public: void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; - void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override; + bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -34,8 +33,6 @@ protected: TextDrawerContext *ctx_; std::map> fontMap_; - - uint32_t fontHash_; }; #endif diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index c4eb9a611a..784a893b90 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -917,7 +917,7 @@ static PPGeTextDrawerImage PPGeGetTextImage(const char *text, const PPGeStyle &s textDrawer->SetFontScale(style.scale, style.scale); Bounds b(0, 0, maxWidth, 272.0f); std::string cleaned = ReplaceAll(text, "\r", ""); - textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, cleaned.c_str(), b, tdalign); + textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, cleaned.c_str(), b, tdalign, false); int bufwBytes = ((im.entry.bmWidth + 31) / 32) * 16; u32 sz = bufwBytes * (im.entry.bmHeight + 1); From 423f7620dd67778ddfb0c6e6152cc3c36aa95045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 31 May 2024 17:37:00 +0200 Subject: [PATCH 2/4] Also implement for Cocoa. --- Common/Render/Text/draw_text.cpp | 24 +++--- Common/Render/Text/draw_text_cocoa.h | 4 - Common/Render/Text/draw_text_cocoa.mm | 106 +++++--------------------- 3 files changed, 31 insertions(+), 103 deletions(-) diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index 9b9fc93d5b..ac010bd74e 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -62,6 +62,9 @@ void TextDrawer::DrawString(DrawBuffer &target, std::string_view str, float x, f if (iter != cache_.end()) { entry = iter->second.get(); entry->lastUsedFrame = frameCount_; + if (!entry->texture) { + return; + } } else { DataFormat texFormat; // Pick between the supported formats, of which at least one is supported on each platform. Prefer R8 (but only if swizzle is supported) @@ -106,19 +109,18 @@ void TextDrawer::DrawString(DrawBuffer &target, std::string_view str, float x, f cache_[key] = std::unique_ptr(entry); } - if (entry->texture) { - draw_->BindTexture(0, entry->texture); + _dbg_assert_(entry->texture); + draw_->BindTexture(0, entry->texture); - // Okay, the texture is bound, let's draw. - float w = entry->width * fontScaleX_ * dpiScale_; - float h = entry->height * fontScaleY_ * dpiScale_; - float u = entry->width / (float)entry->bmWidth; - float v = entry->height / (float)entry->bmHeight; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); + // Okay, the texture is bound, let's draw. + float w = entry->width * fontScaleX_ * dpiScale_; + float h = entry->height * fontScaleY_ * dpiScale_; + float u = entry->width / (float)entry->bmWidth; + float v = entry->height / (float)entry->bmHeight; + DrawBuffer::DoAlign(align, &x, &y, &w, &h); - target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); - target.Flush(true); - } + target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); + target.Flush(true); } void TextDrawer::DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align) { diff --git a/Common/Render/Text/draw_text_cocoa.h b/Common/Render/Text/draw_text_cocoa.h index 008f68a064..d0b04b7c58 100644 --- a/Common/Render/Text/draw_text_cocoa.h +++ b/Common/Render/Text/draw_text_cocoa.h @@ -20,7 +20,6 @@ public: void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; - void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -31,9 +30,6 @@ protected: TextDrawerContext *ctx_; std::map> fontMap_; - - std::map> cache_; - std::map> sizeCache_; }; #endif diff --git a/Common/Render/Text/draw_text_cocoa.mm b/Common/Render/Text/draw_text_cocoa.mm index a3636a59ed..b80111129f 100644 --- a/Common/Render/Text/draw_text_cocoa.mm +++ b/Common/Render/Text/draw_text_cocoa.mm @@ -140,10 +140,8 @@ void TextDrawerCocoa::MeasureString(std::string_view str, float *w, float *h) { attributes = iter->second->attributes; } - std::string toMeasure = ReplaceAll(std::string(str), "&&", "&"); - std::vector lines; - SplitString(toMeasure, '\n', lines); + SplitString(str, '\n', lines); int extW = 0, extH = 0; for (auto &line : lines) { @@ -172,7 +170,6 @@ void TextDrawerCocoa::MeasureString(std::string_view str, float *w, float *h) { *h = entry->height * fontScaleY_ * dpiScale_; } - void TextDrawerCocoa::MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align) { auto iter = fontMap_.find(fontHash_); NSDictionary *attributes = nil; @@ -228,73 +225,6 @@ void TextDrawerCocoa::MeasureStringRect(std::string_view str, const Bounds &boun *h = total_h * fontScaleY_ * dpiScale_; } -void TextDrawerCocoa::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (str.empty()) { - return; - } - - CacheKey key{ std::string(str), fontHash_ }; - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(key); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - } else { - DataFormat texFormat; - // For our purposes these are equivalent, so just choose the supported one. D3D can emulate them. - if (draw_->GetDataFormatSupport(Draw::DataFormat::A4R4G4B4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::A4R4G4B4_UNORM_PACK16; - else if (draw_->GetDataFormatSupport(Draw::DataFormat::R4G4B4A4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; - else if (draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16) & FMT_TEXTURE) - texFormat = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; - else - texFormat = Draw::DataFormat::R8G8B8A8_UNORM; - - entry = new TextStringEntry(frameCount_); - - bool emoji = AnyEmojiInString(key.text.c_str(), key.text.size()); - if (emoji) - texFormat = Draw::DataFormat::R8G8B8A8_UNORM; - - // Convert the bitmap to a Thin3D compatible array of 16-bit pixels. Can't use a single channel format - // because we need white. Well, we could using swizzle, but not all our backends support that. - TextureDesc desc{}; - std::vector bitmapData; - if (!DrawStringBitmap(bitmapData, *entry, texFormat, str, align)) { - return; - } - desc.initData.push_back(&bitmapData[0]); - - desc.type = TextureType::LINEAR2D; - desc.format = texFormat; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - desc.tag = "TextDrawer"; - entry->texture = draw_->CreateTexture(desc); - cache_[key] = std::unique_ptr(entry); - } - - if (entry->texture) { - draw_->BindTexture(0, entry->texture); - - // Okay, the texture is bound, let's draw. - float w = entry->width * fontScaleX_ * dpiScale_; - float h = entry->height * fontScaleY_ * dpiScale_; - float u = entry->width / (float)entry->bmWidth; - float v = entry->height / (float)entry->bmHeight; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); - target.Flush(true); - } -} - bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { if (str.empty()) { bitmapData.clear(); @@ -317,25 +247,25 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr double fWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); // On iOS 4.0 and Mac OS X v10.6 you can pass null for data - size_t width = (size_t)ceilf(fWidth) + 1; - size_t height = (size_t)ceilf(ascent + descent) + 1; + int width = (int)ceilf(fWidth); + int height = (int)ceilf(ascent + descent); - // Round width and height upwards to the closest multiple of 4. - width = (width + 3) & ~3; - height = (height + 3) & ~3; - - if (!width || !height) { + if (width <= 0 || height <= 0) { WARN_LOG(G3D, "Text '%.*s' caused a zero size image", (int)str.length(), str.data()); return false; } - uint32_t *bitmap = new uint32_t[width * height]; - memset(bitmap, 0, width * height * 4); + // Round width and height upwards to the closest multiple of 4. + int bmWidth = (width + 1 + 3) & ~3; + int bmHeight = (height + 1 + 3) & ~3; + + uint32_t *bitmap = new uint32_t[bmWidth * bmHeight]; + memset(bitmap, 0, bmWidth * bmHeight * 4); // Create the context and fill it with white background CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast; - CGContextRef ctx = CGBitmapContextCreate(bitmap, width, height, 8, width*4, space, bitmapInfo); + CGContextRef ctx = CGBitmapContextCreate(bitmap, bmWidth, bmHeight, 8, bmWidth*4, space, bitmapInfo); CGColorSpaceRelease(space); // CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 0.0); // white background // CGContextFillRect(ctx, CGRectMake(0.0, 0.0, width, height)); @@ -346,7 +276,7 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr // Draw the text CGFloat x = 0.0; - CGFloat y = descent; + CGFloat y = descent + (bmHeight - height); // from bottom??? CGContextSetTextPosition(ctx, x, y); CTLineDraw(line, ctx); CFRelease(line); @@ -354,8 +284,8 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr entry.texture = nullptr; entry.width = width; entry.height = height; - entry.bmWidth = width; - entry.bmHeight = height; + entry.bmWidth = bmWidth; + entry.bmHeight = bmHeight; entry.lastUsedFrame = frameCount_; // data now contains the bytes in RGBA, presumably. @@ -366,7 +296,7 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr 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 color = bitmap[width * y + x]; + uint32_t color = bitmap[bmWidth * y + x]; if (fullColor) { bitmapData32[entry.bmWidth * y + x] = color; } else { @@ -381,7 +311,7 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; for (int y = 0; y < entry.bmHeight; y++) { for (int x = 0; x < entry.bmWidth; x++) { - uint8_t bAlpha = (uint8_t)((bitmap[width * y + x] & 0xff) >> 4); + uint8_t bAlpha = (uint8_t)((bitmap[bmWidth * y + x] & 0xff) >> 4); bitmapData16[entry.bmWidth * y + x] = (bAlpha) | 0xfff0; } } @@ -391,7 +321,7 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; for (int y = 0; y < entry.bmHeight; y++) { for (int x = 0; x < entry.bmWidth; x++) { - uint8_t bAlpha = (uint8_t)((bitmap[width * y + x] & 0xff) >> 4); + uint8_t bAlpha = (uint8_t)((bitmap[bmWidth * y + x] & 0xff) >> 4); bitmapData16[entry.bmWidth * y + x] = (bAlpha << 12) | 0x0fff; } } @@ -400,7 +330,7 @@ bool TextDrawerCocoa::DrawStringBitmap(std::vector &bitmapData, TextStr bitmapData.resize(entry.bmWidth * entry.bmHeight); for (int y = 0; y < entry.bmHeight; y++) { for (int x = 0; x < entry.bmWidth; x++) { - uint8_t bAlpha = bitmap[width * y + x] & 0xff; + uint8_t bAlpha = bitmap[bmWidth * y + x] & 0xff; bitmapData[entry.bmWidth * y + x] = bAlpha; } } From 2061f0488575f6b69a4a7a64aac5a7d0db9c1c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 31 May 2024 19:08:25 +0200 Subject: [PATCH 3/4] Also remove DrawString in the SDL text backend --- Common/Render/Text/draw_text.cpp | 2 +- Common/Render/Text/draw_text.h | 1 + Common/Render/Text/draw_text_android.h | 2 ++ Common/Render/Text/draw_text_cocoa.h | 2 ++ Common/Render/Text/draw_text_qt.h | 2 ++ Common/Render/Text/draw_text_sdl.cpp | 50 -------------------------- Common/Render/Text/draw_text_sdl.h | 3 +- Common/Render/Text/draw_text_uwp.h | 2 ++ Common/Render/Text/draw_text_win.h | 2 ++ 9 files changed, 14 insertions(+), 52 deletions(-) diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index ac010bd74e..983d2111d5 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -68,7 +68,7 @@ void TextDrawer::DrawString(DrawBuffer &target, std::string_view str, float x, f } else { DataFormat texFormat; // Pick between the supported formats, of which at least one is supported on each platform. Prefer R8 (but only if swizzle is supported) - bool emoji = AnyEmojiInString(str.data(), str.length()); + bool emoji = SupportsColorEmoji() && AnyEmojiInString(str.data(), str.length()); if (emoji) { texFormat = Draw::DataFormat::R8G8B8A8_UNORM; } else if ((draw_->GetDataFormatSupport(Draw::DataFormat::R8_UNORM) & Draw::FMT_TEXTURE) != 0 && draw_->GetDeviceCaps().textureSwizzleSupported) { diff --git a/Common/Render/Text/draw_text.h b/Common/Render/Text/draw_text.h index ae1b343c6f..7f651c1f65 100644 --- a/Common/Render/Text/draw_text.h +++ b/Common/Render/Text/draw_text.h @@ -71,6 +71,7 @@ public: protected: TextDrawer(Draw::DrawContext *draw); + virtual bool SupportsColorEmoji() const = 0; virtual void ClearCache() = 0; void WrapString(std::string &out, std::string_view str, float maxWidth, int flags); diff --git a/Common/Render/Text/draw_text_android.h b/Common/Render/Text/draw_text_android.h index d2b558e73f..236cffb7fa 100644 --- a/Common/Render/Text/draw_text_android.h +++ b/Common/Render/Text/draw_text_android.h @@ -28,6 +28,8 @@ public: void OncePerFrame() override; protected: + bool SupportsColorEmoji() const { return true; } + void ClearCache() override; private: diff --git a/Common/Render/Text/draw_text_cocoa.h b/Common/Render/Text/draw_text_cocoa.h index d0b04b7c58..685a42c677 100644 --- a/Common/Render/Text/draw_text_cocoa.h +++ b/Common/Render/Text/draw_text_cocoa.h @@ -25,6 +25,8 @@ public: void OncePerFrame() override; protected: + bool SupportsColorEmoji() const override { return true; } + void ClearCache() override; void RecreateFonts(); // On DPI change diff --git a/Common/Render/Text/draw_text_qt.h b/Common/Render/Text/draw_text_qt.h index c145862ee7..e0c3490e9c 100644 --- a/Common/Render/Text/draw_text_qt.h +++ b/Common/Render/Text/draw_text_qt.h @@ -24,6 +24,8 @@ public: void OncePerFrame() override; protected: + bool SupportsColorEmoji() const override { return false; } + void ClearCache() override; std::map fontMap_; diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index 6dd115ae58..3fe229e403 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -364,56 +364,6 @@ void TextDrawerSDL::MeasureStringRect(std::string_view str, const Bounds &bounds *h = total_h * fontScaleY_ * dpiScale_; } -void TextDrawerSDL::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (str.empty()) - return; - - CacheKey key{ std::string(str), fontHash_ }; - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(key); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - } else { - DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; - if ((draw_->GetDataFormatSupport(texFormat) & Draw::FMT_TEXTURE) == 0) { - // This is always supported in Vulkan. The other format is the common OpenGL one. - texFormat = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; - } - - entry = new TextStringEntry(frameCount_); - - TextureDesc desc{}; - std::vector bitmapData; - DrawStringBitmap(bitmapData, *entry, texFormat, str, align, false); - desc.initData.push_back(&bitmapData[0]); - - desc.type = TextureType::LINEAR2D; - desc.format = texFormat; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - desc.tag = "TextDrawer"; - entry->texture = draw_->CreateTexture(desc); - cache_[key] = std::unique_ptr(entry); - } - - if (entry->texture) { - draw_->BindTexture(0, entry->texture); - - float w = entry->bmWidth * fontScaleX_ * dpiScale_; - float h = entry->bmHeight * fontScaleY_ * dpiScale_; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); - target.Flush(true); - } -} - bool TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) { _dbg_assert_(!fullColor) diff --git a/Common/Render/Text/draw_text_sdl.h b/Common/Render/Text/draw_text_sdl.h index c00994101c..b5fb4f339d 100644 --- a/Common/Render/Text/draw_text_sdl.h +++ b/Common/Render/Text/draw_text_sdl.h @@ -21,12 +21,13 @@ public: void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; - void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; protected: + bool SupportsColorEmoji() const override { return false; } + void ClearCache() override; void PrepareFallbackFonts(std::string_view locale); uint32_t CheckMissingGlyph(const std::string& text); diff --git a/Common/Render/Text/draw_text_uwp.h b/Common/Render/Text/draw_text_uwp.h index efaa3acbfb..91b8fc1686 100644 --- a/Common/Render/Text/draw_text_uwp.h +++ b/Common/Render/Text/draw_text_uwp.h @@ -28,6 +28,8 @@ public: void OncePerFrame() override; protected: + bool SupportsColorEmoji() const override { return true; } + void ClearCache() override; void RecreateFonts(); // On DPI change diff --git a/Common/Render/Text/draw_text_win.h b/Common/Render/Text/draw_text_win.h index b3e93f1d99..c857fdf7de 100644 --- a/Common/Render/Text/draw_text_win.h +++ b/Common/Render/Text/draw_text_win.h @@ -28,6 +28,8 @@ public: void OncePerFrame() override; protected: + bool SupportsColorEmoji() const override { return false; } + void ClearCache() override; void RecreateFonts(); // On DPI change From 06f7f39e3eccfcf781b7082c992f5c2483dcde1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 31 May 2024 19:23:02 +0200 Subject: [PATCH 4/4] Unify SDL's version of the function too. --- Common/Render/Text/draw_text.h | 3 +- Common/Render/Text/draw_text_qt.cpp | 50 +---------------------------- Common/Render/Text/draw_text_qt.h | 1 - 3 files changed, 2 insertions(+), 52 deletions(-) diff --git a/Common/Render/Text/draw_text.h b/Common/Render/Text/draw_text.h index 7f651c1f65..f1d1a39ab8 100644 --- a/Common/Render/Text/draw_text.h +++ b/Common/Render/Text/draw_text.h @@ -51,8 +51,7 @@ public: virtual void MeasureString(std::string_view str, float *w, float *h) = 0; virtual void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) = 0; - virtual void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT); - + void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT); void DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align); virtual bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) = 0; bool DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align, bool fullColor); diff --git a/Common/Render/Text/draw_text_qt.cpp b/Common/Render/Text/draw_text_qt.cpp index 7d1a18e5d3..e0570c1e5f 100644 --- a/Common/Render/Text/draw_text_qt.cpp +++ b/Common/Render/Text/draw_text_qt.cpp @@ -16,8 +16,7 @@ #include #include -TextDrawerQt::TextDrawerQt(Draw::DrawContext *draw) : TextDrawer(draw) { -} +TextDrawerQt::TextDrawerQt(Draw::DrawContext *draw) : TextDrawer(draw) {} TextDrawerQt::~TextDrawerQt() { ClearCache(); @@ -141,53 +140,6 @@ bool TextDrawerQt::DrawStringBitmap(std::vector &bitmapData, TextString return true; } -void TextDrawerQt::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (str.empty()) { - return; - } - - CacheKey key{ std::string(str), fontHash_ }; - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(key); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - } else { - DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; - - entry = new TextStringEntry(frameCount_); - - TextureDesc desc{}; - std::vector bitmapData; - DrawStringBitmap(bitmapData, *entry, texFormat, str, align, false); - desc.initData.push_back(&bitmapData[0]); - - desc.type = TextureType::LINEAR2D; - desc.format = texFormat; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - desc.tag = "TextDrawer"; - entry->texture = draw_->CreateTexture(desc); - cache_[key] = std::unique_ptr(entry); - } - - if (entry->texture) { - draw_->BindTexture(0, entry->texture); - - float w = entry->bmWidth * fontScaleX_ * dpiScale_; - float h = entry->bmHeight * fontScaleY_ * dpiScale_; - DrawBuffer::DoAlign(align, &x, &y, &w, &h); - target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); - target.Flush(true); - } -} - void TextDrawerQt::ClearCache() { for (auto &iter : cache_) { if (iter.second->texture) diff --git a/Common/Render/Text/draw_text_qt.h b/Common/Render/Text/draw_text_qt.h index e0c3490e9c..300ed09a60 100644 --- a/Common/Render/Text/draw_text_qt.h +++ b/Common/Render/Text/draw_text_qt.h @@ -18,7 +18,6 @@ public: void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. void MeasureString(std::string_view str, float *w, float *h) override; void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; - void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; bool DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override;