From 85d2702304dc2314a61a30c61dc8f87585dc0c91 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 08:58:15 -0700 Subject: [PATCH 01/10] UI: Refactor TextDrawer to provide bitmaps too. --- ext/native/gfx_es2/draw_text.cpp | 23 +++ ext/native/gfx_es2/draw_text.h | 5 +- ext/native/gfx_es2/draw_text_android.cpp | 132 +++++++-------- ext/native/gfx_es2/draw_text_android.h | 2 +- ext/native/gfx_es2/draw_text_qt.cpp | 112 +++++++------ ext/native/gfx_es2/draw_text_qt.h | 4 +- ext/native/gfx_es2/draw_text_win.cpp | 203 +++++++++++------------ ext/native/gfx_es2/draw_text_win.h | 4 +- 8 files changed, 248 insertions(+), 237 deletions(-) diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index 168f84f1516f..149e9ed57db9 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -42,6 +42,29 @@ float TextDrawer::CalculateDPIScale() { return scale; } +void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) { + float x = bounds.x; + float y = bounds.y; + if (align & ALIGN_HCENTER) { + x = bounds.centerX(); + } else if (align & ALIGN_RIGHT) { + x = bounds.x2(); + } + if (align & ALIGN_VCENTER) { + y = bounds.centerY(); + } else if (align & ALIGN_BOTTOM) { + y = bounds.y2(); + } + + std::string toDraw = str; + if (align & FLAG_WRAP_TEXT) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toDraw, str, rotated ? bounds.h : bounds.w); + } + + DrawString(target, toDraw.c_str(), x, y, color, align); +} + TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) { TextDrawer *drawer = nullptr; #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index a36db98ad234..ed1af7afd8e9 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -58,7 +58,8 @@ class TextDrawer { virtual void MeasureString(const char *str, size_t len, float *w, float *h) = 0; virtual void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) = 0; virtual void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) = 0; - virtual void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) = 0; + void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align); + virtual void DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) = 0; // Use for housekeeping like throwing out old strings. virtual void OncePerFrame() = 0; @@ -102,4 +103,4 @@ class TextDrawerWordWrapper : public WordWrapper { float MeasureWidth(const char *str, size_t bytes) override; TextDrawer *drawer_; -}; \ No newline at end of file +}; diff --git a/ext/native/gfx_es2/draw_text_android.cpp b/ext/native/gfx_es2/draw_text_android.cpp index b2e15e86e310..fecad36bb935 100644 --- a/ext/native/gfx_es2/draw_text_android.cpp +++ b/ext/native/gfx_es2/draw_text_android.cpp @@ -154,6 +154,59 @@ void TextDrawerAndroid::MeasureStringRect(const char *str, size_t len, const Bou *h = total_h * dpiScale_; } +void TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) { + if (!strlen(str)) { + bitmapData.clear(); + return; + } + + double size = 0.0; + auto iter = fontMap_.find(fontHash_); + if (iter != fontMap_.end()) { + size = iter->second.size; + } else { + ELOG("Missing font"); + } + + jstring jstr = env_->NewStringUTF(str); + uint32_t textSize = env_->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, size); + int imageWidth = (short)(textSize >> 16); + int imageHeight = (short)(textSize & 0xFFFF); + if (imageWidth <= 0) + imageWidth = 1; + if (imageHeight <= 0) + imageHeight = 1; + + jintArray imageData = (jintArray)env_->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, size); + env_->DeleteLocalRef(jstr); + + entry.texture = nullptr; + entry.bmWidth = imageWidth; + entry.width = imageWidth; + entry.bmHeight = imageHeight; + entry.height = imageHeight; + entry.lastUsedFrame = frameCount_; + + jint *jimage = env_->GetIntArrayElements(imageData, nullptr); + assert(env_->GetArrayLength(imageData) == imageWidth * imageHeight); + 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++) { + uint32_t v = jimage[imageWidth * y + x]; + v = 0xFFF0 | ((v >> 12) & 0xF); // Just grab some bits from the green channel. + bitmapData16[entry.bmWidth * y + x] = (uint16_t)v; + } + } + } else { + assert(false); + } + env_->ReleaseIntArrayElements(imageData, jimage, 0); + env_->DeleteLocalRef(imageData); +} + void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { using namespace Draw; std::string text(NormalizeString(std::string(str))); @@ -169,66 +222,32 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, if (iter != cache_.end()) { entry = iter->second.get(); entry->lastUsedFrame = frameCount_; - if (entry->texture) - draw_->BindTexture(0, entry->texture); } else { - double size = 0.0; - auto iter = fontMap_.find(fontHash_); - if (iter != fontMap_.end()) { - size = iter->second.size; - } else { - ELOG("Missing font"); - } - - jstring jstr = env_->NewStringUTF(text.c_str()); - uint32_t textSize = env_->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, size); - int imageWidth = (short)(textSize >> 16); - int imageHeight = (short)(textSize & 0xFFFF); - if (imageWidth <= 0) - imageWidth = 1; - if (imageHeight <= 0) - imageHeight = 1; - - jintArray imageData = (jintArray)env_->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, size); - env_->DeleteLocalRef(jstr); + DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; entry = new TextStringEntry(); - entry->bmWidth = imageWidth; - entry->width = imageWidth; - entry->bmHeight = imageHeight; - entry->height = imageHeight; - entry->lastUsedFrame = frameCount_; TextureDesc desc{}; + std::vector bitmapData; + DrawStringBitmap(bitmapData, *entry, texFormat, text.c_str(), align); + desc.initData.push_back(&bitmapData[0]); + desc.type = TextureType::LINEAR2D; - desc.format = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; + desc.format = texFormat; desc.width = entry->bmWidth; desc.height = entry->bmHeight; desc.depth = 1; desc.mipLevels = 1; desc.generateMips = false; desc.tag = "TextDrawer"; - - uint16_t *bitmapData = new uint16_t[entry->bmWidth * entry->bmHeight]; - jint* jimage = env_->GetIntArrayElements(imageData, nullptr); - assert(env_->GetArrayLength(imageData) == imageWidth * imageHeight); - for (int x = 0; x < entry->bmWidth; x++) { - for (int y = 0; y < entry->bmHeight; y++) { - uint32_t v = jimage[imageWidth * y + x]; - v = 0xFFF0 | ((v >> 12) & 0xF); // Just grab some bits from the green channel. - bitmapData[entry->bmWidth * y + x] = (uint16_t)v; - } - } - env_->ReleaseIntArrayElements(imageData, jimage, 0); - env_->DeleteLocalRef(imageData); - desc.initData.push_back((uint8_t *)bitmapData); entry->texture = draw_->CreateTexture(desc); - delete[] bitmapData; cache_[key] = std::unique_ptr(entry); - if (entry->texture) { - draw_->BindTexture(0, entry->texture); - } } + + 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); @@ -247,29 +266,6 @@ void TextDrawerAndroid::ClearCache() { sizeCache_.clear(); } -void TextDrawerAndroid::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) { - float x = bounds.x; - float y = bounds.y; - if (align & ALIGN_HCENTER) { - x = bounds.centerX(); - } else if (align & ALIGN_RIGHT) { - x = bounds.x2(); - } - if (align & ALIGN_VCENTER) { - y = bounds.centerY(); - } else if (align & ALIGN_BOTTOM) { - y = bounds.y2(); - } - - std::string toDraw = str; - if (align & FLAG_WRAP_TEXT) { - bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toDraw, str, rotated ? bounds.h : bounds.w); - } - - DrawString(target, toDraw.c_str(), x, y, color, align); -} - void TextDrawerAndroid::OncePerFrame() { frameCount_++; // If DPI changed (small-mode, future proper monitor DPI support), drop everything. diff --git a/ext/native/gfx_es2/draw_text_android.h b/ext/native/gfx_es2/draw_text_android.h index 9ed3fdbd2214..dff54dade450 100644 --- a/ext/native/gfx_es2/draw_text_android.h +++ b/ext/native/gfx_es2/draw_text_android.h @@ -24,7 +24,7 @@ class TextDrawerAndroid : public TextDrawer { void MeasureString(const char *str, size_t len, float *w, float *h) override; void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) override; + void DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index 41d4009d3a61..7884fb0f8bd6 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -1,3 +1,4 @@ +#include #include "base/display.h" #include "base/logging.h" #include "base/stringutil.h" @@ -66,6 +67,47 @@ void TextDrawerQt::MeasureStringRect(const char *str, size_t len, const Bounds & *h = (float)size.height() * fontScaleY_; } +void TextDrawerQt::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) { + if (!strlen(str)) { + bitmapData.clear(); + return; + } + + QFont *font = fontMap_.find(fontHash_)->second; + QFontMetrics fm(*font); + QSize size = fm.size(0, QString::fromUtf8(str)); + QImage image((size.width() + 3) & ~3, (size.height() + 3) & ~3, QImage::Format_ARGB32_Premultiplied); + if (image.isNull()) { + return; + } + image.fill(0); + + QPainter painter; + painter.begin(&image); + painter.setFont(*font); + painter.setPen(0xFFFFFFFF); + // TODO: Involve ALIGN_HCENTER (bounds etc.) + painter.drawText(image.rect(), Qt::AlignTop | Qt::AlignLeft, QString::fromUtf8(str).replace("&&", "&")); + painter.end(); + + entry.texture = nullptr; + entry.bmWidth = entry.width = image.width(); + entry.bmHeight = entry.height = image.height(); + entry.lastUsedFrame = frameCount_; + + 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++) { + bitmapData16[entry.bmWidth * y + x] = 0xfff0 | (image.pixel(x, y) >> 28); + } + } + } else { + assert(false); + } +} + void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { using namespace Draw; if (!strlen(str)) @@ -81,56 +123,39 @@ void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, floa auto iter = cache_.find(entryHash); if (iter != cache_.end()) { entry = iter->second.get(); + entry->lastUsedFrame = frameCount_; } else { - QFont *font = fontMap_.find(fontHash_)->second; - QFontMetrics fm(*font); - QSize size = fm.size(0, QString::fromUtf8(str)); - QImage image((size.width() + 3) & ~3, (size.height() + 3) & ~3, QImage::Format_ARGB32_Premultiplied); - if (image.isNull()) { - return; - } - image.fill(0); - - QPainter painter; - painter.begin(&image); - painter.setFont(*font); - painter.setPen(color); - // TODO: Involve ALIGN_HCENTER (bounds etc.) - painter.drawText(image.rect(), Qt::AlignTop | Qt::AlignLeft, QString::fromUtf8(str).replace("&&", "&")); - painter.end(); + DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; entry = new TextStringEntry(); - entry->bmWidth = entry->width = image.width(); - entry->bmHeight = entry->height = image.height(); TextureDesc desc{}; + std::vector bitmapData; + DrawStringBitmap(bitmapData, *entry, texFormat, str, align); + desc.initData.push_back(&bitmapData[0]); + desc.type = TextureType::LINEAR2D; - desc.format = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; + desc.format = texFormat; desc.width = entry->bmWidth; desc.height = entry->bmHeight; desc.depth = 1; desc.mipLevels = 1; desc.tag = "TextDrawer"; - - uint16_t *bitmapData = new uint16_t[entry->bmWidth * entry->bmHeight]; - for (int x = 0; x < entry->bmWidth; x++) { - for (int y = 0; y < entry->bmHeight; y++) { - bitmapData[entry->bmWidth * y + x] = 0xfff0 | image.pixel(x, y) >> 28; - } - } - desc.initData.push_back((uint8_t *)bitmapData); entry->texture = draw_->CreateTexture(desc); - delete[] bitmapData; cache_[entryHash] = std::unique_ptr(entry); } + if (entry->texture) { + draw_->BindTexture(0, entry->texture); + } + float w = entry->bmWidth * fontScaleX_; float h = entry->bmHeight * fontScaleY_; - entry->lastUsedFrame = frameCount_; - draw_->BindTexture(0, entry->texture); 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); + if (entry->texture) { + target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); + target.Flush(true); + } } void TextDrawerQt::ClearCache() { @@ -148,29 +173,6 @@ void TextDrawerQt::ClearCache() { fontHash_ = 0; } -void TextDrawerQt::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) { - float x = bounds.x; - float y = bounds.y; - if (align & ALIGN_HCENTER) { - x = bounds.centerX(); - } else if (align & ALIGN_RIGHT) { - x = bounds.x2(); - } - if (align & ALIGN_VCENTER) { - y = bounds.centerY(); - } else if (align & ALIGN_BOTTOM) { - y = bounds.y2(); - } - - std::string toDraw = str; - if (align & FLAG_WRAP_TEXT) { - bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toDraw, str, rotated ? bounds.h : bounds.w); - } - - DrawString(target, toDraw.c_str(), x, y, color, align); -} - void TextDrawerQt::OncePerFrame() { frameCount_++; // If DPI changed (small-mode, future proper monitor DPI support), drop everything. diff --git a/ext/native/gfx_es2/draw_text_qt.h b/ext/native/gfx_es2/draw_text_qt.h index 815a76a13aa5..f07731ec7367 100644 --- a/ext/native/gfx_es2/draw_text_qt.h +++ b/ext/native/gfx_es2/draw_text_qt.h @@ -17,7 +17,7 @@ class TextDrawerQt : public TextDrawer { void MeasureString(const char *str, size_t len, float *w, float *h) override; void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) override; + void DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -32,4 +32,4 @@ class TextDrawerQt : public TextDrawer { std::map> sizeCache_; }; -#endif \ No newline at end of file +#endif diff --git a/ext/native/gfx_es2/draw_text_win.cpp b/ext/native/gfx_es2/draw_text_win.cpp index f6c1113d5f9a..ddfb1444320c 100644 --- a/ext/native/gfx_es2/draw_text_win.cpp +++ b/ext/native/gfx_es2/draw_text_win.cpp @@ -190,13 +190,96 @@ void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bound *h = total_h * dpiScale_; } +void TextDrawerWin32::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) { + if (!strlen(str)) { + bitmapData.clear(); + return; + } + + // Render the string to our bitmap and save to a GL texture. + std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n")); + SIZE size; + + auto iter = fontMap_.find(fontHash_); + if (iter != fontMap_.end()) { + SelectObject(ctx_->hDC, iter->second->hFont); + } + // Set text properties + SetTextColor(ctx_->hDC, 0xFFFFFF); + SetBkColor(ctx_->hDC, 0); + SetTextAlign(ctx_->hDC, TA_TOP); + + // This matters for multi-line text - DT_CENTER is horizontal only. + UINT dtAlign = (align & ALIGN_HCENTER) == 0 ? DT_LEFT : DT_CENTER; + + RECT textRect = { 0 }; + DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &textRect, DT_HIDEPREFIX | DT_TOP | dtAlign | DT_CALCRECT, 0); + size.cx = textRect.right; + size.cy = textRect.bottom; + + if (size.cx > MAX_TEXT_WIDTH) + size.cx = MAX_TEXT_WIDTH; + if (size.cy > MAX_TEXT_HEIGHT) + size.cy = MAX_TEXT_HEIGHT; + // Prevent zero-sized textures, which can occur. Not worth to avoid + // creating the texture altogether in this case. One example is a string + // containing only '\r\n', see issue #10764. + if (size.cx == 0) + size.cx = 1; + if (size.cy == 0) + size.cy = 1; + + entry.texture = nullptr; + entry.width = size.cx; + entry.height = size.cy; + entry.bmWidth = (size.cx + 3) & ~3; + entry.bmHeight = (size.cy + 3) & ~3; + entry.lastUsedFrame = frameCount_; + + RECT rc = { 0 }; + rc.right = entry.bmWidth; + rc.bottom = entry.bmHeight; + FillRect(ctx_->hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); + DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &rc, DT_HIDEPREFIX | DT_TOP | dtAlign, 0); + + // 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. + if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM || texFormat == Draw::DataFormat::B8G8R8A8_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++) { + uint8_t bAlpha = (uint8_t)(ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff); + bitmapData32[entry.bmWidth * y + x] = (bAlpha << 24) | 0x00ffffff; + } + } + } else 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 y = 0; y < entry.bmHeight; y++) { + for (int x = 0; x < entry.bmWidth; x++) { + uint8_t bAlpha = (uint8_t)((ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff) >> 4); + bitmapData16[entry.bmWidth * y + x] = (bAlpha) | 0xfff0; + } + } + } else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) { + bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); + 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)((ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff) >> 4); + bitmapData16[entry.bmWidth * y + x] = (bAlpha << 12) | 0x0fff; + } + } + } +} + void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { using namespace Draw; if (!strlen(str)) return; CacheKey key{ std::string(str), fontHash_ }; - target.Flush(true); TextStringEntry *entry; @@ -206,52 +289,6 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f entry = iter->second.get(); entry->lastUsedFrame = frameCount_; } else { - // Render the string to our bitmap and save to a GL texture. - std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n")); - SIZE size; - - auto iter = fontMap_.find(fontHash_); - if (iter != fontMap_.end()) { - SelectObject(ctx_->hDC, iter->second->hFont); - } - // Set text properties - SetTextColor(ctx_->hDC, 0xFFFFFF); - SetBkColor(ctx_->hDC, 0); - SetTextAlign(ctx_->hDC, TA_TOP); - - // This matters for multi-line text - DT_CENTER is horizontal only. - UINT dtAlign = (align & ALIGN_HCENTER) == 0 ? DT_LEFT : DT_CENTER; - - RECT textRect = { 0 }; - DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &textRect, DT_HIDEPREFIX | DT_TOP | dtAlign | DT_CALCRECT, 0); - size.cx = textRect.right; - size.cy = textRect.bottom; - - if (size.cx > MAX_TEXT_WIDTH) - size.cx = MAX_TEXT_WIDTH; - if (size.cy > MAX_TEXT_HEIGHT) - size.cy = MAX_TEXT_HEIGHT; - // Prevent zero-sized textures, which can occur. Not worth to avoid - // creating the texture altogether in this case. One example is a string - // containing only '\r\n', see issue #10764. - if (size.cx == 0) - size.cx = 1; - if (size.cy == 0) - size.cy = 1; - - entry = new TextStringEntry(); - entry->width = size.cx; - entry->height = size.cy; - entry->bmWidth = (size.cx + 3) & ~3; - entry->bmHeight = (size.cy + 3) & ~3; - entry->lastUsedFrame = frameCount_; - - RECT rc = { 0 }; - rc.right = entry->bmWidth; - rc.bottom = entry->bmHeight; - FillRect(ctx_->hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); - DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &rc, DT_HIDEPREFIX | DT_TOP | dtAlign, 0); - 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) @@ -263,39 +300,14 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f else texFormat = Draw::DataFormat::R8G8B8A8_UNORM; + entry = new TextStringEntry(); + // 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{}; - uint32_t *bitmapData32 = nullptr; - uint16_t *bitmapData16 = nullptr; - if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM || texFormat == Draw::DataFormat::B8G8R8A8_UNORM) { - bitmapData32 = new uint32_t[entry->bmWidth * entry->bmHeight]; - for (int y = 0; y < entry->bmHeight; y++) { - for (int x = 0; x < entry->bmWidth; x++) { - uint8_t bAlpha = (uint8_t)(ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff); - bitmapData32[entry->bmWidth * y + x] = (bAlpha << 24) | 0x00ffffff; - } - } - desc.initData.push_back((uint8_t *)bitmapData32); - } else if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) { - bitmapData16 = new uint16_t[entry->bmWidth * entry->bmHeight]; - for (int y = 0; y < entry->bmHeight; y++) { - for (int x = 0; x < entry->bmWidth; x++) { - uint8_t bAlpha = (uint8_t)((ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff) >> 4); - bitmapData16[entry->bmWidth * y + x] = (bAlpha) | 0xfff0; - } - } - desc.initData.push_back((uint8_t *)bitmapData16); - } else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) { - bitmapData16 = new uint16_t[entry->bmWidth * entry->bmHeight]; - for (int y = 0; y < entry->bmHeight; y++) { - for (int x = 0; x < entry->bmWidth; x++) { - uint8_t bAlpha = (uint8_t)((ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff) >> 4); - bitmapData16[entry->bmWidth * y + x] = (bAlpha << 12) | 0x0fff; - } - } - desc.initData.push_back((uint8_t *)bitmapData16); - } + std::vector bitmapData; + DrawStringBitmap(bitmapData, *entry, texFormat, str, align); + desc.initData.push_back(&bitmapData[0]); desc.type = TextureType::LINEAR2D; desc.format = texFormat; @@ -305,14 +317,12 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f desc.mipLevels = 1; desc.tag = "TextDrawer"; entry->texture = draw_->CreateTexture(desc); - if (bitmapData16) - delete[] bitmapData16; - if (bitmapData32) - delete[] bitmapData32; cache_[key] = std::unique_ptr(entry); } - draw_->BindTexture(0, entry->texture); + if (entry->texture) { + draw_->BindTexture(0, entry->texture); + } // Okay, the texture is bound, let's draw. float w = entry->width * fontScaleX_ * dpiScale_; @@ -320,8 +330,10 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f 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); + if (entry->texture) { + target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color); + target.Flush(true); + } } void TextDrawerWin32::RecreateFonts() { @@ -340,29 +352,6 @@ void TextDrawerWin32::ClearCache() { sizeCache_.clear(); } -void TextDrawerWin32::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) { - float x = bounds.x; - float y = bounds.y; - if (align & ALIGN_HCENTER) { - x = bounds.centerX(); - } else if (align & ALIGN_RIGHT) { - x = bounds.x2(); - } - if (align & ALIGN_VCENTER) { - y = bounds.centerY(); - } else if (align & ALIGN_BOTTOM) { - y = bounds.y2(); - } - - std::string toDraw = str; - if (align & FLAG_WRAP_TEXT) { - bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toDraw, str, rotated ? bounds.h : bounds.w); - } - - DrawString(target, toDraw.c_str(), x, y, color, align); -} - void TextDrawerWin32::OncePerFrame() { frameCount_++; // If DPI changed (small-mode, future proper monitor DPI support), drop everything. diff --git a/ext/native/gfx_es2/draw_text_win.h b/ext/native/gfx_es2/draw_text_win.h index ec76d3ef2bd8..41489eefbbe4 100644 --- a/ext/native/gfx_es2/draw_text_win.h +++ b/ext/native/gfx_es2/draw_text_win.h @@ -24,7 +24,7 @@ class TextDrawerWin32 : public TextDrawer { void MeasureString(const char *str, size_t len, float *w, float *h) override; void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; - void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) override; + void DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override; // Use for housekeeping like throwing out old strings. void OncePerFrame() override; @@ -40,4 +40,4 @@ class TextDrawerWin32 : public TextDrawer { std::map> sizeCache_; }; -#endif \ No newline at end of file +#endif From 70b07f20c91663f0ee9c24736a634c0d71762651 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 09:12:17 -0700 Subject: [PATCH 02/10] Qt: Cleanup differences in text hashing. We don't want to use just adler to cache strings. Also, port over the DPI handling to be consistent. Not tested. --- ext/native/gfx_es2/draw_text_qt.cpp | 48 ++++++++++++++++++++--------- ext/native/gfx_es2/draw_text_qt.h | 5 ++- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index 7884fb0f8bd6..a6bcbcebee21 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -34,7 +34,7 @@ uint32_t TextDrawerQt::SetFont(const char *fontName, int size, int flags) { return fontHash; } - QFont* font = fontName ? new QFont(fontName) : new QFont(); + QFont *font = fontName ? new QFont(fontName) : new QFont(); font->setPixelSize(size + 6); fontMap_[fontHash] = font; fontHash_ = fontHash; @@ -42,15 +42,36 @@ uint32_t TextDrawerQt::SetFont(const char *fontName, int size, int flags) { } void TextDrawerQt::SetFont(uint32_t fontHandle) { - + uint32_t fontHash = fontHandle; + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHandle; + } else { + ELOG("Invalid font handle %08x", fontHandle); + } } void TextDrawerQt::MeasureString(const char *str, size_t len, float *w, float *h) { - QFont* font = fontMap_.find(fontHash_)->second; - QFontMetrics fm(*font); - QSize size = fm.size(0, QString::fromUtf8(str, (int)len)); - *w = (float)size.width() * fontScaleX_; - *h = (float)size.height() * fontScaleY_; + CacheKey key{ std::string(str, len), fontHash_ }; + + TextMeasureEntry *entry; + auto iter = sizeCache_.find(key); + if (iter != sizeCache_.end()) { + entry = iter->second.get(); + } else { + QFont* font = fontMap_.find(fontHash_)->second; + QFontMetrics fm(*font); + QSize size = fm.size(0, QString::fromUtf8(str, (int)len)); + + entry = new TextMeasureEntry(); + entry->width = size.width(); + entry->height = size.height(); + sizeCache_[key] = std::unique_ptr(entry); + } + + entry->lastUsedFrame = frameCount_; + *w = entry->width * fontScaleX_ * dpiScale_; + *h = entry->height * fontScaleY_ * dpiScale_; } void TextDrawerQt::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { @@ -78,6 +99,7 @@ void TextDrawerQt::DrawStringBitmap(std::vector &bitmapData, TextString QSize size = fm.size(0, QString::fromUtf8(str)); QImage image((size.width() + 3) & ~3, (size.height() + 3) & ~3, QImage::Format_ARGB32_Premultiplied); if (image.isNull()) { + bitmapData.clear(); return; } image.fill(0); @@ -113,14 +135,12 @@ void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, floa if (!strlen(str)) return; - uint32_t stringHash = hash::Adler32((const uint8_t *)str, strlen(str)); - uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); - + CacheKey key{ std::string(str), fontHash_ }; target.Flush(true); TextStringEntry *entry; - auto iter = cache_.find(entryHash); + auto iter = cache_.find(key); if (iter != cache_.end()) { entry = iter->second.get(); entry->lastUsedFrame = frameCount_; @@ -142,15 +162,15 @@ void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, floa desc.mipLevels = 1; desc.tag = "TextDrawer"; entry->texture = draw_->CreateTexture(desc); - cache_[entryHash] = std::unique_ptr(entry); + cache_[key] = std::unique_ptr(entry); } if (entry->texture) { draw_->BindTexture(0, entry->texture); } - float w = entry->bmWidth * fontScaleX_; - float h = entry->bmHeight * fontScaleY_; + 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); diff --git a/ext/native/gfx_es2/draw_text_qt.h b/ext/native/gfx_es2/draw_text_qt.h index f07731ec7367..c75cd51ca06b 100644 --- a/ext/native/gfx_es2/draw_text_qt.h +++ b/ext/native/gfx_es2/draw_text_qt.h @@ -27,9 +27,8 @@ class TextDrawerQt : public TextDrawer { uint32_t fontHash_; std::map fontMap_; - // The key is the CityHash of the string xor the fontHash_. - std::map> cache_; - std::map> sizeCache_; + std::map> cache_; + std::map> sizeCache_; }; #endif From 5141dc7e91f7f0adc2a4639bfca39fd1bcdd9496 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 09:20:14 -0700 Subject: [PATCH 03/10] PPGe: Allow overriding text drawer DPI scale. --- ext/native/gfx_es2/draw_text.cpp | 2 ++ ext/native/gfx_es2/draw_text.h | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index 149e9ed57db9..69096e071cc3 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -35,6 +35,8 @@ void TextDrawer::SetFontScale(float xscale, float yscale) { } float TextDrawer::CalculateDPIScale() { + if (ignoreGlobalDpi_) + return dpiScale_; float scale = g_dpi_scale_y; if (scale >= 1.0f) { scale = 1.0f; diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index ed1af7afd8e9..bf6fe1bf07e5 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -64,6 +64,10 @@ class TextDrawer { virtual void OncePerFrame() = 0; float CalculateDPIScale(); + void SetForcedDPIScale(float dpi) { + dpiScale_ = dpi; + ignoreGlobalDpi_ = true; + } // Factory function that selects implementation. static TextDrawer *Create(Draw::DrawContext *draw); @@ -87,10 +91,11 @@ class TextDrawer { uint32_t fontHash; }; - int frameCount_; - float fontScaleX_; - float fontScaleY_; - float dpiScale_; + int frameCount_ = 0; + float fontScaleX_ = 1.0f; + float fontScaleY_ = 1.0f; + float dpiScale_ = 1.0f; + bool ignoreGlobalDpi_ = false; }; From fbf4769ea6b8057dadf08071b7cb4d27a44aa7e5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 09:29:13 -0700 Subject: [PATCH 04/10] PPGe: Support 8-bit text bitmaps from TextDrawer. --- ext/native/gfx_es2/draw_text_android.cpp | 11 ++++++++++- ext/native/gfx_es2/draw_text_qt.cpp | 8 ++++++++ ext/native/gfx_es2/draw_text_win.cpp | 12 ++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ext/native/gfx_es2/draw_text_android.cpp b/ext/native/gfx_es2/draw_text_android.cpp index fecad36bb935..b5afccbf967a 100644 --- a/ext/native/gfx_es2/draw_text_android.cpp +++ b/ext/native/gfx_es2/draw_text_android.cpp @@ -191,7 +191,6 @@ void TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextS assert(env_->GetArrayLength(imageData) == imageWidth * imageHeight); 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++) { @@ -200,7 +199,17 @@ void TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextS 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++) { + 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)); + } + } } else { + ELOG("Bad TextDrawer format"); assert(false); } env_->ReleaseIntArrayElements(imageData, jimage, 0); diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index a6bcbcebee21..406507518ff5 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -125,7 +125,15 @@ void TextDrawerQt::DrawStringBitmap(std::vector &bitmapData, TextString bitmapData16[entry.bmWidth * y + x] = 0xfff0 | (image.pixel(x, y) >> 28); } } + } 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++) { + bitmapData[entry.bmWidth * y + x] = image.pixel(x, y) >> 24; + } + } } else { + ELOG("Bad TextDrawer format"); assert(false); } } diff --git a/ext/native/gfx_es2/draw_text_win.cpp b/ext/native/gfx_es2/draw_text_win.cpp index ddfb1444320c..9da3cda741aa 100644 --- a/ext/native/gfx_es2/draw_text_win.cpp +++ b/ext/native/gfx_es2/draw_text_win.cpp @@ -1,3 +1,4 @@ +#include #include "base/display.h" #include "base/logging.h" #include "base/stringutil.h" @@ -271,6 +272,17 @@ void TextDrawerWin32::DrawStringBitmap(std::vector &bitmapData, TextStr bitmapData16[entry.bmWidth * y + x] = (bAlpha << 12) | 0x0fff; } } + } else if (texFormat == Draw::DataFormat::R8_UNORM) { + 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 = ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff; + bitmapData[entry.bmWidth * y + x] = bAlpha; + } + } + } else { + ELOG("Bad TextDrawer format"); + assert(false); } } From f2c88d6626e8e2bff584795515443e0b90edd4e3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 10:06:14 -0700 Subject: [PATCH 05/10] PPGe: Make some of the API internal only. We don't use these externally and probably won't. --- Core/Util/PPGeDraw.cpp | 15 +++++++++++++++ Core/Util/PPGeDraw.h | 23 ----------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index 421959b22330..0181e36dff57 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -103,6 +103,21 @@ static AtlasCharLine char_one_line; static AtlasLineArray char_lines; static AtlasTextMetrics char_lines_metrics; +// Overwrite the current text lines buffer so it can be drawn later. +void PPGePrepareText(const char *text, float x, float y, int align, float scale, float lineHeightScale, + int WrapType = PPGE_LINE_NONE, int wrapWidth = 0); + +// Get the metrics of the bounding box of the currently stated text. +void PPGeMeasureCurrentText(float *x, float *y, float *w, float *h, int *n); + +// These functions must be called between PPGeBegin and PPGeEnd. + +// Draw currently buffered text using the state from PPGeGetTextBoundingBox() call. +// Clears the buffer and state when done. +void PPGeDrawCurrentText(u32 color = 0xFFFFFFFF); + +void PPGeSetTexture(u32 dataAddr, int width, int height); + //only 0xFFFFFF of data is used static void WriteCmd(u8 cmd, u32 data) { Memory::Write_U32((cmd << 24) | (data & 0xFFFFFF), dlWritePtr); diff --git a/Core/Util/PPGeDraw.h b/Core/Util/PPGeDraw.h index a599c45e37b7..2032f7e14fb4 100644 --- a/Core/Util/PPGeDraw.h +++ b/Core/Util/PPGeDraw.h @@ -45,10 +45,6 @@ void __PPGeShutdown(); void PPGeBegin(); void PPGeEnd(); -// If you want to draw using this texture but not go through the PSP GE emulation, -// jsut call this. Will bind the texture to unit 0. -void PPGeBindTexture(); - enum { PPGE_ALIGN_LEFT = 0, PPGE_ALIGN_RIGHT = 16, @@ -65,11 +61,6 @@ enum { PPGE_ALIGN_BOTTOMRIGHT = PPGE_ALIGN_BOTTOM | PPGE_ALIGN_RIGHT, }; -enum { - PPGE_ESCAPE_NONE, - PPGE_ESCAPE_BACKSLASHED, -}; - enum { PPGE_LINE_NONE = 0, PPGE_LINE_USE_ELLIPSIS = 1, // use ellipses in too long words @@ -81,19 +72,6 @@ enum { void PPGeMeasureText(float *w, float *h, int *n, const char *text, float scale, int WrapType = PPGE_LINE_NONE, int wrapWidth = 0); -// Overwrite the current text lines buffer so it can be drawn later. -void PPGePrepareText(const char *text, float x, float y, int align, float scale, float lineHeightScale, - int WrapType = PPGE_LINE_NONE, int wrapWidth = 0); - -// Get the metrics of the bounding box of the currently stated text. -void PPGeMeasureCurrentText(float *x, float *y, float *w, float *h, int *n); - -// These functions must be called between PPGeBegin and PPGeEnd. - -// Draw currently buffered text using the state from PPGeGetTextBoundingBox() call. -// Clears the buffer and state when done. -void PPGeDrawCurrentText(u32 color = 0xFFFFFFFF); - // Draws some text using the one font we have. // Clears the text buffer when done. void PPGeDrawText(const char *text, float x, float y, int align, float scale = 1.0f, u32 color = 0xFFFFFFFF); @@ -152,6 +130,5 @@ class PPGeImage { void PPGeDrawRect(float x1, float y1, float x2, float y2, u32 color); void PPGeSetDefaultTexture(); -void PPGeSetTexture(u32 dataAddr, int width, int height); void PPGeDisableTexture(); From 6652fe261f5af6e454b60ef30731ac2ceb3717ad Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 17:53:30 -0700 Subject: [PATCH 06/10] PPGe: Use TextDrawer for save UI if available. This should result in better spacing for non-Latin characters, and less missing letters. Basically the same benefits as for the UI. --- Core/HLE/sceDisplay.cpp | 2 + Core/Util/PPGeDraw.cpp | 256 ++++++++++++++++++++++++--- Core/Util/PPGeDraw.h | 5 +- ext/native/gfx_es2/draw_text.cpp | 10 ++ ext/native/gfx_es2/draw_text.h | 1 + ext/native/gfx_es2/draw_text_win.cpp | 8 +- 6 files changed, 255 insertions(+), 27 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 883cc7a587e9..2871be814019 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -49,6 +49,7 @@ #include "Core/HLE/sceKernel.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelInterrupt.h" +#include "Core/Util/PPGeDraw.h" #include "GPU/GPU.h" #include "GPU/GPUState.h" @@ -832,6 +833,7 @@ void __DisplayFlip(int cyclesLate) { void hleAfterFlip(u64 userdata, int cyclesLate) { gpu->BeginFrame(); // doesn't really matter if begin or end of frame. + PPGeNotifyFrame(); // This seems like as good a time as any to check if the config changed. if (lagSyncScheduled != g_Config.bForceLagSync) { diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index 0181e36dff57..b310c28b2cfc 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -20,6 +20,7 @@ #include "base/stringutil.h" #include "file/vfs.h" #include "gfx/texture_atlas.h" +#include "gfx_es2/draw_text.h" #include "image/zim_load.h" #include "image/png_load.h" #include "util/text/utf8.h" @@ -103,6 +104,25 @@ static AtlasCharLine char_one_line; static AtlasLineArray char_lines; static AtlasTextMetrics char_lines_metrics; +static TextDrawer *textDrawer = nullptr; +struct PPGeTextDrawerCacheKey { + bool operator < (const PPGeTextDrawerCacheKey &other) const { + if (align != other.align) + return align < other.align; + if (wrapWidth != other.wrapWidth) + return wrapWidth < other.wrapWidth; + return text < other.text; + } + std::string text; + int align; + float wrapWidth; +}; +struct PPGeTextDrawerImage { + TextStringEntry entry; + u32 ptr; +}; +std::map textDrawerImages; + // Overwrite the current text lines buffer so it can be drawn later. void PPGePrepareText(const char *text, float x, float y, int align, float scale, float lineHeightScale, int WrapType = PPGE_LINE_NONE, int wrapWidth = 0); @@ -144,18 +164,18 @@ static void BeginVertexData() { vertexStart = dataWritePtr; } -static void Vertex(float x, float y, float u, float v, int tw, int th, u32 color = 0xFFFFFFFF) { +static void Vertex(float x, float y, float u, float v, int tw, int th, u32 color = 0xFFFFFFFF, float off = -0.5f) { if (g_RemasterMode) { PPGeRemasterVertex vtx; - vtx.x = x - 0.5f; vtx.y = y - 0.5f; vtx.z = 0; - vtx.u = u * tw - 0.5f; vtx.v = v * th - 0.5f; + vtx.x = x + off; vtx.y = y + off; vtx.z = 0; + vtx.u = u * tw + off; vtx.v = v * th + off; vtx.color = color; Memory::WriteStruct(dataWritePtr, &vtx); dataWritePtr += sizeof(vtx); } else { PPGeVertex vtx; - vtx.x = x - 0.5f; vtx.y = y - 0.5f; vtx.z = 0; - vtx.u = u * tw - 0.5f; vtx.v = v * th - 0.5f; + vtx.x = x + off; vtx.y = y + off; vtx.z = 0; + vtx.u = u * tw + off; vtx.v = v * th + off; vtx.color = color; Memory::WriteStruct(dataWritePtr, &vtx); dataWritePtr += sizeof(vtx); @@ -247,13 +267,22 @@ void __PPGeInit() free(imageData[0]); + // TODO: Should we pass a draw_? + textDrawer = TextDrawer::Create(nullptr); + if (textDrawer) { + textDrawer->SetFontScale(1.0f, 1.0f); + textDrawer->SetForcedDPIScale(1.0f); + textDrawer->SetFont(g_Config.sFont.c_str(), 20, 0); + } + textDrawerImages.clear(); + DEBUG_LOG(SCEGE, "PPGe drawing library initialized. DL: %08x Data: %08x Atlas: %08x (%i) Args: %08x", dlPtr, dataPtr, atlasPtr, atlasSize, listArgs.ptr); } void __PPGeDoState(PointerWrap &p) { - auto s = p.Section("PPGeDraw", 1, 2); + auto s = p.Section("PPGeDraw", 1, 3); if (!s) return; @@ -271,6 +300,30 @@ void __PPGeDoState(PointerWrap &p) p.Do(listArgs); } + if (s >= 3) { + uint32_t sz = (uint32_t)textDrawerImages.size(); + p.Do(sz); + + switch (p.mode) { + case PointerWrap::MODE_READ: + textDrawerImages.clear(); + for (uint32_t i = 0; i < sz; ++i) { + // We only care about the pointers, so we can free them. We'll decimate right away. + PPGeTextDrawerCacheKey key{ StringFromFormat("__savestate__%d", i), -1, -1 }; + textDrawerImages[key] = PPGeTextDrawerImage{}; + p.Do(textDrawerImages[key].ptr); + } + break; + default: + for (const auto &im : textDrawerImages) { + p.Do(im.second.ptr); + } + break; + } + } else { + textDrawerImages.clear(); + } + p.Do(dlPtr); p.Do(dlWritePtr); p.Do(dlSize); @@ -306,6 +359,9 @@ void __PPGeShutdown() dlPtr = 0; savedContextPtr = 0; listArgs = 0; + + delete textDrawer; + textDrawer = nullptr; } void PPGeBegin() @@ -643,6 +699,29 @@ static AtlasTextMetrics BreakLines(const char *text, const AtlasFont &atlasfont, void PPGeMeasureText(float *w, float *h, int *n, const char *text, float scale, int WrapType, int wrapWidth) { + if (textDrawer) { + float mw, mh; + textDrawer->SetFontScale(scale, scale); + int dtalign = (WrapType & PPGE_LINE_WRAP_WORD) ? FLAG_WRAP_TEXT : 0; + Bounds b(0, 0, wrapWidth <= 0 ? 480.0f : wrapWidth, 272.0f); + textDrawer->MeasureStringRect(text, strlen(text), b, &mw, &mh, dtalign); + + if (w) + *w = mw; + if (h) + *h = mh; + if (n) { + // Cheap way to get the n. + float oneLine, twoLines; + textDrawer->MeasureString("|", 1, &mw, &oneLine); + textDrawer->MeasureStringRect("|\n|", 3, Bounds(0, 0, 480, 272), &mw, &twoLines); + + float lineHeight = twoLines - oneLine; + *n = (int)((mh + (lineHeight - 1)) / lineHeight); + } + return; + } + const AtlasFont &atlasfont = g_ppge_atlas.fonts[0]; AtlasTextMetrics metrics = BreakLines(text, atlasfont, 0, 0, 0, scale, scale, WrapType, wrapWidth, true); if (w) *w = metrics.maxWidth; @@ -698,8 +777,104 @@ void PPGeDrawCurrentText(u32 color) PPGeResetCurrentText(); } -void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color) -{ +// Return a value such that (1 << value) >= x +int GetPow2(int x) { +#ifdef __GNUC__ + int ret = 31 - __builtin_clz(x | 1); + if ((1 << ret) < x) +#else + int ret = 0; + while ((1 << ret) < x) +#endif + ret++; + return ret; +} + +static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float scale, float maxWidth, bool wrap) { + int tdalign = (align & PPGE_ALIGN_HCENTER) ? ALIGN_HCENTER : 0; + if (wrap) { + tdalign |= FLAG_WRAP_TEXT; + } + + PPGeTextDrawerCacheKey key{ text, tdalign, maxWidth / scale }; + PPGeTextDrawerImage im; + + auto cacheItem = textDrawerImages.find(key); + if (cacheItem != textDrawerImages.end()) { + im = cacheItem->second; + cacheItem->second.entry.lastUsedFrame = gpuStats.numFlips; + } else { + std::vector bitmapData; + textDrawer->SetFontScale(scale, scale); + // TODO: Ellipsis on long lines... + Bounds b(0, 0, maxWidth, 272.0f); + textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, text, b, tdalign); + + int bufwBytes = ((im.entry.bmWidth + 31) / 32) * 16; + u32 sz = bufwBytes * im.entry.bmHeight; + u32 origSz = sz; + im.ptr = __PPGeDoAlloc(sz, true, "PPGeText"); + + if (bitmapData.size() & 1) + bitmapData.resize(bitmapData.size() + 1); + + if (im.ptr) { + u8 *ramPtr = (u8 *)Memory::GetPointer(im.ptr); + for (int y = 0; y < im.entry.bmHeight; ++y) { + for (int x = 0; x < (im.entry.bmWidth + 1) / 2; ++x) { + uint8_t c1 = bitmapData[y * im.entry.bmWidth + x * 2]; + uint8_t c2 = bitmapData[y * im.entry.bmWidth + x * 2 + 1]; + // Convert this to 4-bit palette values. + ramPtr[y * bufwBytes + x] = (c2 & 0xF0) | (c1 >> 4); + } + } + } + + im.entry.lastUsedFrame = gpuStats.numFlips; + textDrawerImages[key] = im; + } + + return im; +} + +static void PPGeDrawTextImage(PPGeTextDrawerImage im, float x, float y, int align, float scale, u32 color) { + int bufw = ((im.entry.bmWidth + 31) / 32) * 32; + int wp2 = GetPow2(im.entry.bmWidth); + int hp2 = GetPow2(im.entry.bmHeight); + WriteCmd(GE_CMD_TEXADDR0, im.ptr & 0xFFFFF0); + WriteCmd(GE_CMD_TEXBUFWIDTH0, bufw | ((im.ptr & 0xFF000000) >> 8)); + WriteCmd(GE_CMD_TEXSIZE0, wp2 | (hp2 << 8)); + WriteCmd(GE_CMD_TEXFLUSH, 0); + + float w = im.entry.width * scale; + float h = im.entry.height * scale; + + if (align & PPGE_ALIGN_HCENTER) + x -= w / 2.0f; + else if (align & PPGE_ALIGN_RIGHT) + x -= w; + if (align & PPGE_ALIGN_VCENTER) + y -= h / 2.0f; + else if (align & PPGE_ALIGN_BOTTOM) + y -= h; + + BeginVertexData(); + float u1 = (float)im.entry.width / (1 << wp2); + float v1 = (float)im.entry.height / (1 << hp2); + Vertex(x, y, 0, 0, 1 << wp2, 1 << hp2, color, 0.0f); + Vertex(x + w, y + h, u1, v1, 1 << wp2, 1 << hp2, color, 0.0f); + EndVertexDataAndDraw(GE_PRIM_RECTANGLES); + + PPGeSetDefaultTexture(); +} + +void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color) { + if (textDrawer) { + PPGeTextDrawerImage im = PPGeGetTextImage(text, align, scale, 480.0f - x, false); + PPGeDrawTextImage(im, x, y, align, scale, color); + return; + } + PPGePrepareText(text, x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS); PPGeDrawCurrentText(color); } @@ -733,10 +908,40 @@ void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, fl s = StripTrailingWhite(s); } - PPGePrepareText(s.c_str(), x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); - int zoom = (PSP_CoreParameter().pixelHeight + 479) / 480; float maxScaleDown = zoom == 1 ? 1.3f : 2.0f; + + if (textDrawer) { + float actualWidth, actualHeight; + Bounds b(0, 0, wrapWidth <= 0 ? 480.0f - x : wrapWidth, wrapHeight); + int tdalign = (align & PPGE_ALIGN_HCENTER) ? ALIGN_HCENTER : 0; + textDrawer->SetFontScale(scale, scale); + textDrawer->MeasureStringRect(s.c_str(), s.size(), b, &actualWidth, &actualHeight, tdalign | FLAG_WRAP_TEXT); + if (wrapHeight != 0.0f && actualHeight > wrapHeight) { + // Cheap way to get the line height. + float oneLine, twoLines; + textDrawer->MeasureString("|", 1, &actualWidth, &oneLine); + textDrawer->MeasureStringRect("|\n|", 3, Bounds(0, 0, 480, 272), &actualWidth, &twoLines); + + float lineHeight = twoLines - oneLine; + if (actualHeight > wrapHeight * maxScaleDown) { + float maxLines = floor(wrapHeight * maxScaleDown / lineHeight); + actualHeight = (maxLines + 1) * lineHeight; + // Add an ellipsis if it's just too long to be readable. + // On a PSP, it does this without scaling it down. + s = StripTrailingWhite(CropLinesToCount(s, (int)maxLines)) + "\n..."; + } + + scale *= wrapHeight / actualHeight; + } + + PPGeTextDrawerImage im = PPGeGetTextImage(s.c_str(), align, scale, wrapWidth <= 0 ? 480.0f - x : wrapWidth, true); + PPGeDrawTextImage(im, x, y, align, scale, color); + return; + } + + PPGePrepareText(s.c_str(), x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); + float actualHeight = char_lines_metrics.lineHeight * char_lines_metrics.numLines; if (wrapHeight != 0.0f && actualHeight > wrapHeight) { if (actualHeight > wrapHeight * maxScaleDown) { @@ -852,20 +1057,6 @@ void PPGeDrawImage(float x, float y, float w, float h, float u1, float v1, float EndVertexDataAndDraw(GE_PRIM_RECTANGLES); } -// Return a value such that (1 << value) >= x -int GetPow2(int x) -{ -#ifdef __GNUC__ - int ret = 31 - __builtin_clz(x|1); - if ((1 << ret) < x) -#else - int ret = 0; - while ((1 << ret) < x) -#endif - ret++; - return ret; -} - void PPGeSetDefaultTexture() { WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1); @@ -1019,3 +1210,20 @@ void PPGeImage::SetTexture() { } } +void PPGeNotifyFrame() { + if (textDrawer) { + textDrawer->OncePerFrame(); + } + + // Do this always, in case the platform has no TextDrawer but save state did. + for (auto it = textDrawerImages.begin(); it != textDrawerImages.end(); ) { + if (it->second.entry.lastUsedFrame - gpuStats.numFlips >= 97) { + kernelMemory.Free(it->second.ptr); + it = textDrawerImages.erase(it); + } else { + ++it; + } + } + + PPGeImage::Decimate(); +} diff --git a/Core/Util/PPGeDraw.h b/Core/Util/PPGeDraw.h index 2032f7e14fb4..8962fa7f9904 100644 --- a/Core/Util/PPGeDraw.h +++ b/Core/Util/PPGeDraw.h @@ -85,6 +85,8 @@ void PPGeDrawImage(ImageID atlasImage, float x, float y, int align, u32 color = void PPGeDrawImage(ImageID atlasImage, float x, float y, float w, float h, int align, u32 color = 0xFFFFFFFF); void PPGeDrawImage(float x, float y, float w, float h, float u1, float v1, float u2, float v2, int tw, int th, u32 color); +void PPGeNotifyFrame(); + class PPGeImage { public: PPGeImage(const std::string &pspFilename); @@ -110,8 +112,9 @@ class PPGeImage { return height_; } -private: static void Decimate(); + +private: static std::vector loadedTextures_; std::string filename_; diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index 69096e071cc3..239f4556ce24 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -67,6 +67,16 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bound DrawString(target, toDraw.c_str(), x, y, color, align); } +void TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, const Bounds &bounds, int align) { + std::string toDraw = str; + if (align & FLAG_WRAP_TEXT) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toDraw, str, rotated ? bounds.h : bounds.w); + } + + DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align); +} + TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) { TextDrawer *drawer = nullptr; #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index bf6fe1bf07e5..a9a5a3592788 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -60,6 +60,7 @@ class TextDrawer { virtual void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) = 0; void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align); virtual void DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) = 0; + void DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, const Bounds &bounds, int align); // Use for housekeeping like throwing out old strings. virtual void OncePerFrame() = 0; diff --git a/ext/native/gfx_es2/draw_text_win.cpp b/ext/native/gfx_es2/draw_text_win.cpp index 9da3cda741aa..3062c052254e 100644 --- a/ext/native/gfx_es2/draw_text_win.cpp +++ b/ext/native/gfx_es2/draw_text_win.cpp @@ -159,6 +159,9 @@ void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bound WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); } + TEXTMETRIC metrics{}; + GetTextMetrics(ctx_->hDC, &metrics); + std::vector lines; SplitString(toMeasure, '\n', lines); float total_w = 0.0f; @@ -185,8 +188,10 @@ void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bound if (total_w < entry->width * fontScaleX_) { total_w = entry->width * fontScaleX_; } - total_h += entry->height * fontScaleY_; + int h = i == lines.size() - 1 ? entry->height : metrics.tmHeight + metrics.tmExternalLeading; + total_h += h * fontScaleY_; } + *w = total_w * dpiScale_; *h = total_h * dpiScale_; } @@ -197,7 +202,6 @@ void TextDrawerWin32::DrawStringBitmap(std::vector &bitmapData, TextStr return; } - // Render the string to our bitmap and save to a GL texture. std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n")); SIZE size; From 08a6047768e032a8644caf4d57685e9ccd9dcb25 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 19:06:30 -0700 Subject: [PATCH 07/10] PPGe: Show ellipsis for overly long lines. To match PPGE_LINE_USE_ELLIPSIS when using TextDrawer. --- Core/Util/PPGeDraw.cpp | 4 +- ext/native/gfx_es2/draw_buffer.cpp | 12 ++-- ext/native/gfx_es2/draw_buffer.h | 1 + ext/native/gfx_es2/draw_text.cpp | 14 ++-- ext/native/gfx_es2/draw_text.h | 4 +- ext/native/gfx_es2/draw_text_android.cpp | 5 +- ext/native/gfx_es2/draw_text_qt.cpp | 5 +- ext/native/gfx_es2/draw_text_win.cpp | 5 +- ext/native/util/text/wrap_text.cpp | 85 +++++++++++++++++++----- ext/native/util/text/wrap_text.h | 10 ++- 10 files changed, 106 insertions(+), 39 deletions(-) diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index b310c28b2cfc..93db095e5909 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -703,6 +703,8 @@ void PPGeMeasureText(float *w, float *h, int *n, float mw, mh; textDrawer->SetFontScale(scale, scale); int dtalign = (WrapType & PPGE_LINE_WRAP_WORD) ? FLAG_WRAP_TEXT : 0; + if (WrapType & PPGE_LINE_USE_ELLIPSIS) + dtalign |= FLAG_ELLIPSIZE_TEXT; Bounds b(0, 0, wrapWidth <= 0 ? 480.0f : wrapWidth, 272.0f); textDrawer->MeasureStringRect(text, strlen(text), b, &mw, &mh, dtalign); @@ -792,6 +794,7 @@ int GetPow2(int x) { static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float scale, float maxWidth, bool wrap) { int tdalign = (align & PPGE_ALIGN_HCENTER) ? ALIGN_HCENTER : 0; + tdalign |= FLAG_ELLIPSIZE_TEXT; if (wrap) { tdalign |= FLAG_WRAP_TEXT; } @@ -806,7 +809,6 @@ static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float s } else { std::vector bitmapData; textDrawer->SetFontScale(scale, scale); - // TODO: Ellipsis on long lines... Bounds b(0, 0, maxWidth, 272.0f); textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, text, b, tdalign); diff --git a/ext/native/gfx_es2/draw_buffer.cpp b/ext/native/gfx_es2/draw_buffer.cpp index 78eb6733ab72..dbe024fe2487 100644 --- a/ext/native/gfx_es2/draw_buffer.cpp +++ b/ext/native/gfx_es2/draw_buffer.cpp @@ -365,7 +365,7 @@ void DrawBuffer::DrawImage2GridH(ImageID atlas_image, float x1, float y1, float class AtlasWordWrapper : public WordWrapper { public: // Note: maxW may be height if rotated. - AtlasWordWrapper(const AtlasFont &atlasfont, float scale, const char *str, float maxW) : WordWrapper(str, maxW), atlasfont_(atlasfont), scale_(scale) { + AtlasWordWrapper(const AtlasFont &atlasfont, float scale, const char *str, float maxW, int flags) : WordWrapper(str, maxW, flags), atlasfont_(atlasfont), scale_(scale) { } protected: @@ -442,14 +442,15 @@ void DrawBuffer::MeasureTextRect(FontID font_id, const char *text, int count, co } std::string toMeasure = std::string(text, count); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { const AtlasFont *font = atlas->getFont(font_id); if (!font) { *w = 0.0f; *h = 0.0f; return; } - AtlasWordWrapper wrapper(*font, fontscalex, toMeasure.c_str(), bounds.w); + AtlasWordWrapper wrapper(*font, fontscalex, toMeasure.c_str(), bounds.w, wrap); toMeasure = wrapper.Wrapped(); } MeasureTextCount(font_id, toMeasure.c_str(), (int)toMeasure.length(), w, h); @@ -491,8 +492,9 @@ void DrawBuffer::DrawTextRect(FontID font, const char *text, float x, float y, f } std::string toDraw = text; - if (align & FLAG_WRAP_TEXT) { - AtlasWordWrapper wrapper(*atlas->getFont(font), fontscalex, toDraw.c_str(), w); + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { + AtlasWordWrapper wrapper(*atlas->getFont(font), fontscalex, toDraw.c_str(), w, wrap); toDraw = wrapper.Wrapped(); } diff --git a/ext/native/gfx_es2/draw_buffer.h b/ext/native/gfx_es2/draw_buffer.h index 211fc4fb95ba..a702fd669301 100644 --- a/ext/native/gfx_es2/draw_buffer.h +++ b/ext/native/gfx_es2/draw_buffer.h @@ -39,6 +39,7 @@ enum { FLAG_DYNAMIC_ASCII = 2048, FLAG_NO_PREFIX = 4096, // means to not process ampersands FLAG_WRAP_TEXT = 8192, + FLAG_ELLIPSIZE_TEXT = 16384, }; namespace Draw { diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index 239f4556ce24..ce9b51b379a1 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -24,8 +24,8 @@ float TextDrawerWordWrapper::MeasureWidth(const char *str, size_t bytes) { return w; } -void TextDrawer::WrapString(std::string &out, const char *str, float maxW) { - TextDrawerWordWrapper wrapper(this, str, maxW); +void TextDrawer::WrapString(std::string &out, const char *str, float maxW, int flags) { + TextDrawerWordWrapper wrapper(this, str, maxW, flags); out = wrapper.Wrapped(); } @@ -59,9 +59,10 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bound } std::string toDraw = str; - if (align & FLAG_WRAP_TEXT) { + 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); + WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap); } DrawString(target, toDraw.c_str(), x, y, color, align); @@ -69,9 +70,10 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bound void TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, const Bounds &bounds, int align) { std::string toDraw = str; - if (align & FLAG_WRAP_TEXT) { + 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); + WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap); } DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align); diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index a9a5a3592788..f6bf8e3e3486 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -78,7 +78,7 @@ class TextDrawer { Draw::DrawContext *draw_; virtual void ClearCache() = 0; - void WrapString(std::string &out, const char *str, float maxWidth); + void WrapString(std::string &out, const char *str, float maxWidth, int flags); struct CacheKey { bool operator < (const CacheKey &other) const { @@ -102,7 +102,7 @@ class TextDrawer { class TextDrawerWordWrapper : public WordWrapper { public: - TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW) : WordWrapper(str, maxW), drawer_(drawer) { + TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW, int flags) : WordWrapper(str, maxW, flags), drawer_(drawer) { } protected: diff --git a/ext/native/gfx_es2/draw_text_android.cpp b/ext/native/gfx_es2/draw_text_android.cpp index b5afccbf967a..f2513b99e396 100644 --- a/ext/native/gfx_es2/draw_text_android.cpp +++ b/ext/native/gfx_es2/draw_text_android.cpp @@ -115,9 +115,10 @@ void TextDrawerAndroid::MeasureStringRect(const char *str, size_t len, const Bou } std::string toMeasure = std::string(str, len); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); } std::vector lines; diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index 406507518ff5..d792f656d8ec 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -76,9 +76,10 @@ void TextDrawerQt::MeasureString(const char *str, size_t len, float *w, float *h void TextDrawerQt::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { std::string toMeasure = std::string(str, len); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); } QFont* font = fontMap_.find(fontHash_)->second; diff --git a/ext/native/gfx_es2/draw_text_win.cpp b/ext/native/gfx_es2/draw_text_win.cpp index 3062c052254e..2570236feb3d 100644 --- a/ext/native/gfx_es2/draw_text_win.cpp +++ b/ext/native/gfx_es2/draw_text_win.cpp @@ -154,9 +154,10 @@ void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bound } std::string toMeasure = std::string(str, len); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); } TEXTMETRIC metrics{}; diff --git a/ext/native/util/text/wrap_text.cpp b/ext/native/util/text/wrap_text.cpp index edf335467323..c0520560345d 100644 --- a/ext/native/util/text/wrap_text.cpp +++ b/ext/native/util/text/wrap_text.cpp @@ -1,4 +1,5 @@ #include +#include "gfx_es2/draw_buffer.h" #include "util/text/utf8.h" #include "util/text/wrap_text.h" @@ -75,39 +76,58 @@ std::string WordWrapper::Wrapped() { } bool WordWrapper::WrapBeforeWord() { - if (x_ + wordWidth_ > maxW_ && out_.size() > 0) { - if (IsShy(out_[out_.size() - 1])) { - // Soft hyphen, replace it with a real hyphen since we wrapped at it. - // TODO: There's an edge case here where the hyphen might not fit. - out_[out_.size() - 1] = '-'; + if (flags_ & FLAG_WRAP_TEXT) { + if (x_ + wordWidth_ > maxW_ && !out_.empty()) { + if (IsShy(out_[out_.size() - 1])) { + // Soft hyphen, replace it with a real hyphen since we wrapped at it. + // TODO: There's an edge case here where the hyphen might not fit. + out_[out_.size() - 1] = '-'; + } + out_ += "\n"; + lastLineStart_ = out_.size(); + x_ = 0.0f; + forceEarlyWrap_ = false; + return true; + } + } + if (flags_ & FLAG_ELLIPSIZE_TEXT) { + if (x_ + wordWidth_ > maxW_) { + if (!out_.empty() && IsSpace(out_[out_.size() - 1])) { + out_[out_.size() - 1] = '.'; + out_ += ".."; + } else { + out_ += "..."; + } + x_ = maxW_; } - out_ += "\n"; - lastLineStart_ = out_.size(); - x_ = 0.0f; - forceEarlyWrap_ = false; - return true; } return false; } void WordWrapper::AppendWord(int endIndex, bool addNewline) { - int nextWordIndex = lastIndex_; + int lastWordStartIndex = lastIndex_; if (WrapBeforeWord()) { // Advance to the first non-whitespace UTF-8 character in the following word (if any) to prevent starting the new line with a whitespace - UTF8 utf8Word(str_, nextWordIndex); - while (nextWordIndex < endIndex) { + UTF8 utf8Word(str_, lastWordStartIndex); + while (lastWordStartIndex < endIndex) { const uint32_t c = utf8Word.next(); if (!IsSpace(c)) { break; } - nextWordIndex = utf8Word.byteIndex(); + lastWordStartIndex = utf8Word.byteIndex(); } } + // This will include the newline. - out_.append(str_ + nextWordIndex, str_ + endIndex); - if (addNewline) { + if (x_ < maxW_) { + out_.append(str_ + lastWordStartIndex, str_ + endIndex); + } else { + scanForNewline_ = true; + } + if (addNewline && (flags_ & FLAG_WRAP_TEXT)) { out_ += "\n"; lastLineStart_ = out_.size(); + scanForNewline_ = false; } else { // We may have appended a newline - check. size_t pos = out_.substr(lastLineStart_).find_last_of("\n"); @@ -129,6 +149,10 @@ void WordWrapper::Wrap() { return; } + if (flags_ & FLAG_ELLIPSIZE_TEXT) { + ellipsisWidth_ = MeasureWidth("...", 3); + } + for (UTF8 utf(str_); !utf.end(); ) { int beforeIndex = utf.byteIndex(); uint32_t c = utf.next(); @@ -142,6 +166,13 @@ void WordWrapper::Wrap() { wordWidth_ = 0.0f; // We wrapped once, so stop forcing. forceEarlyWrap_ = false; + scanForNewline_ = false; + continue; + } + + if (scanForNewline_) { + // We're discarding the rest of the characters until a newline (no wrapping.) + lastIndex_ = afterIndex; continue; } @@ -175,13 +206,33 @@ void WordWrapper::Wrap() { } // Now, add the word so far (without this latest character) and break. AppendWord(beforeIndex, true); - x_ = 0.0f; + if (lastLineStart_ != out_.size()) { + x_ = MeasureWidth(out_.c_str() + lastLineStart_, out_.size() - lastLineStart_); + } else { + x_ = 0.0f; + } wordWidth_ = 0.0f; forceEarlyWrap_ = false; // The current character will be handled as part of the next word. continue; } + if ((flags_ & FLAG_ELLIPSIZE_TEXT) && wordWidth_ > 0.0f && x_ + newWordWidth + ellipsisWidth_ > maxW_) { + if ((flags_ & FLAG_WRAP_TEXT) == 0) { + // Now, add the word so far (without this latest character) and show the ellipsis. + AppendWord(beforeIndex, true); + if (lastLineStart_ != out_.size()) { + x_ = MeasureWidth(out_.c_str() + lastLineStart_, out_.size() - lastLineStart_); + } else { + x_ = 0.0f; + } + wordWidth_ = 0.0f; + forceEarlyWrap_ = false; + // The current character will be handled as part of the next word. + continue; + } + } + wordWidth_ = newWordWidth; // Is this the end of a word via punctuation / CJK? diff --git a/ext/native/util/text/wrap_text.h b/ext/native/util/text/wrap_text.h index 61049bccef4f..554210e3cfef 100644 --- a/ext/native/util/text/wrap_text.h +++ b/ext/native/util/text/wrap_text.h @@ -4,8 +4,8 @@ class WordWrapper { public: - WordWrapper(const char *str, float maxW) - : str_(str), maxW_(maxW) { + WordWrapper(const char *str, float maxW, int flags) + : str_(str), maxW_(maxW), flags_(flags) { } std::string Wrapped(); @@ -23,7 +23,9 @@ class WordWrapper { const char *const str_; const float maxW_; + const int flags_; std::string out_; + // Index of last output / start of current word. int lastIndex_ = 0; // Index of last line start. @@ -32,6 +34,10 @@ class WordWrapper { float x_ = 0.0f; // Most recent width of word since last index. float wordWidth_ = 0.0f; + // Width of "..." when flag is set, zero otherwise. + float ellipsisWidth_ = 0.0f; // Force the next word to cut partially and wrap. bool forceEarlyWrap_ = false; + // Skip all characters until the next newline. + bool scanForNewline_ = false; }; From 9c9ace6e1a4916688228ffcd3a3a900185fb3f2d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 21:40:47 -0700 Subject: [PATCH 08/10] Android: Delay TextDrawer init in PPGe. We need it to init on the correct thread. --- Core/Util/PPGeDraw.cpp | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index 93db095e5909..397ee429eb80 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -104,6 +104,7 @@ static AtlasCharLine char_one_line; static AtlasLineArray char_lines; static AtlasTextMetrics char_lines_metrics; +static bool textDrawerInited = false; static TextDrawer *textDrawer = nullptr; struct PPGeTextDrawerCacheKey { bool operator < (const PPGeTextDrawerCacheKey &other) const { @@ -267,13 +268,9 @@ void __PPGeInit() free(imageData[0]); - // TODO: Should we pass a draw_? - textDrawer = TextDrawer::Create(nullptr); - if (textDrawer) { - textDrawer->SetFontScale(1.0f, 1.0f); - textDrawer->SetForcedDPIScale(1.0f); - textDrawer->SetFont(g_Config.sFont.c_str(), 20, 0); - } + // We can't create it here, because Android needs it on the right thread. + textDrawerInited = false; + textDrawer = nullptr; textDrawerImages.clear(); DEBUG_LOG(SCEGE, "PPGe drawing library initialized. DL: %08x Data: %08x Atlas: %08x (%i) Args: %08x", @@ -696,10 +693,27 @@ static AtlasTextMetrics BreakLines(const char *text, const AtlasFont &atlasfont, return metrics; } +static bool HasTextDrawer() { + // We create this on first use so it's on the correct thread. + if (textDrawerInited) { + return textDrawer != nullptr; + } + + // TODO: Should we pass a draw_? + textDrawer = TextDrawer::Create(nullptr); + if (textDrawer) { + textDrawer->SetFontScale(1.0f, 1.0f); + textDrawer->SetForcedDPIScale(1.0f); + textDrawer->SetFont(g_Config.sFont.c_str(), 20, 0); + } + textDrawerInited = true; + return textDrawer != nullptr; +} + void PPGeMeasureText(float *w, float *h, int *n, const char *text, float scale, int WrapType, int wrapWidth) { - if (textDrawer) { + if (HasTextDrawer()) { float mw, mh; textDrawer->SetFontScale(scale, scale); int dtalign = (WrapType & PPGE_LINE_WRAP_WORD) ? FLAG_WRAP_TEXT : 0; @@ -871,7 +885,7 @@ static void PPGeDrawTextImage(PPGeTextDrawerImage im, float x, float y, int alig } void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color) { - if (textDrawer) { + if (HasTextDrawer()) { PPGeTextDrawerImage im = PPGeGetTextImage(text, align, scale, 480.0f - x, false); PPGeDrawTextImage(im, x, y, align, scale, color); return; @@ -913,7 +927,7 @@ void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, fl int zoom = (PSP_CoreParameter().pixelHeight + 479) / 480; float maxScaleDown = zoom == 1 ? 1.3f : 2.0f; - if (textDrawer) { + if (HasTextDrawer()) { float actualWidth, actualHeight; Bounds b(0, 0, wrapWidth <= 0 ? 480.0f - x : wrapWidth, wrapHeight); int tdalign = (align & PPGE_ALIGN_HCENTER) ? ALIGN_HCENTER : 0; From 5f65c075e257ff3060fabeb0bb84f49caf71b8f7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 11 Mar 2020 06:37:18 -0700 Subject: [PATCH 09/10] PPGe: Correct double spacing in some save text. --- Core/Util/PPGeDraw.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index 397ee429eb80..354d77ab8636 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -824,7 +824,8 @@ static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float s std::vector bitmapData; textDrawer->SetFontScale(scale, scale); Bounds b(0, 0, maxWidth, 272.0f); - textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, text, b, tdalign); + std::string cleaned = ReplaceAll(text, "\r", ""); + textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, cleaned.c_str(), b, tdalign); int bufwBytes = ((im.entry.bmWidth + 31) / 32) * 16; u32 sz = bufwBytes * im.entry.bmHeight; From de81b706c4b4847daeb94fce3970171c6853535a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 11 Mar 2020 06:48:11 -0700 Subject: [PATCH 10/10] PPGe: Clear unused parts of text image buffers. Prevents artifacts when the RAM previously had other data in it. --- Core/Util/PPGeDraw.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index 354d77ab8636..bf64fb17253f 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -828,7 +828,7 @@ static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float s textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, cleaned.c_str(), b, tdalign); int bufwBytes = ((im.entry.bmWidth + 31) / 32) * 16; - u32 sz = bufwBytes * im.entry.bmHeight; + u32 sz = bufwBytes * (im.entry.bmHeight + 1); u32 origSz = sz; im.ptr = __PPGeDoAlloc(sz, true, "PPGeText"); @@ -836,15 +836,20 @@ static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float s bitmapData.resize(bitmapData.size() + 1); if (im.ptr) { + int wBytes = (im.entry.bmWidth + 1) / 2; u8 *ramPtr = (u8 *)Memory::GetPointer(im.ptr); for (int y = 0; y < im.entry.bmHeight; ++y) { - for (int x = 0; x < (im.entry.bmWidth + 1) / 2; ++x) { + for (int x = 0; x < wBytes; ++x) { uint8_t c1 = bitmapData[y * im.entry.bmWidth + x * 2]; uint8_t c2 = bitmapData[y * im.entry.bmWidth + x * 2 + 1]; // Convert this to 4-bit palette values. ramPtr[y * bufwBytes + x] = (c2 & 0xF0) | (c1 >> 4); } + if (bufwBytes != wBytes) { + memset(ramPtr + y * bufwBytes + wBytes, 0, bufwBytes - wBytes); + } } + memset(ramPtr + im.entry.bmHeight * bufwBytes, 0, bufwBytes + sz - origSz); } im.entry.lastUsedFrame = gpuStats.numFlips;