From 32ae86f70815844e1ccfd7029d4be25e8cdb9681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 4 Jun 2017 00:35:37 +0200 Subject: [PATCH 1/3] Split TextDrawer into interface and implementation. --- ext/native/gfx_es2/draw_text.cpp | 105 +++++++++++++++++++++++-------- ext/native/gfx_es2/draw_text.h | 48 ++++++-------- ext/native/ui/ui_context.cpp | 6 +- 3 files changed, 97 insertions(+), 62 deletions(-) diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index a081da4073..afad2e2824 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -14,6 +14,50 @@ #include #endif +TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) { +} +TextDrawer::~TextDrawer() { +} + +class TextDrawerWin32 : public TextDrawer { +public: + TextDrawerWin32(Draw::DrawContext *draw); + ~TextDrawerWin32(); + + uint32_t SetFont(const char *fontName, int size, int flags) override; + void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. + void SetFontScale(float xscale, float yscale) override; + 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; + // Use for housekeeping like throwing out old strings. + void OncePerFrame() override; + float CalculateDPIScale() override; + +protected: + void ClearCache() override; + void RecreateFonts() override; // On DPI change + void WrapString(std::string &out, const char *str, float maxWidth) override; + + int frameCount_; + float fontScaleX_; + float fontScaleY_; + float dpiScale_; + + TextDrawerContext *ctx_; +#if defined(USING_QT_UI) + std::map fontMap_; +#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + std::map> fontMap_; +#endif + + uint32_t fontHash_; + // The key is the CityHash of the string xor the fontHash_. + std::map> cache_; + std::map> sizeCache_; +}; + class TextDrawerWordWrapper : public WordWrapper { public: TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW) : WordWrapper(str, maxW), drawer_(drawer) { @@ -76,7 +120,7 @@ struct TextDrawerContext { int *pBitmapBits; }; -TextDrawer::TextDrawer(Draw::DrawContext *thin3d) : draw_(thin3d), ctx_(nullptr) { +TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(nullptr) { // These probably shouldn't be state. fontScaleX_ = 1.0f; fontScaleY_ = 1.0f; @@ -100,7 +144,7 @@ TextDrawer::TextDrawer(Draw::DrawContext *thin3d) : draw_(thin3d), ctx_(nullptr) SelectObject(ctx_->hDC, ctx_->hbmBitmap); } -TextDrawer::~TextDrawer() { +TextDrawerWin32::~TextDrawerWin32() { ClearCache(); fontMap_.clear(); @@ -110,7 +154,7 @@ TextDrawer::~TextDrawer() { delete ctx_; } -uint32_t TextDrawer::SetFont(const char *fontName, int size, int flags) { +uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) { uint32_t fontHash = fontName ? hash::Fletcher((const uint8_t *)fontName, strlen(fontName)) : 0; fontHash ^= size; fontHash ^= flags << 10; @@ -139,18 +183,14 @@ uint32_t TextDrawer::SetFont(const char *fontName, int size, int flags) { return fontHash; } -void TextDrawer::SetFont(uint32_t fontHandle) { +void TextDrawerWin32::SetFont(uint32_t fontHandle) { auto iter = fontMap_.find(fontHandle); if (iter != fontMap_.end()) { fontHash_ = fontHandle; } } -void TextDrawer::MeasureString(const char *str, float *w, float *h) { - MeasureString(str, strlen(str), w, h); -} - -void TextDrawer::MeasureString(const char *str, size_t len, float *w, float *h) { +void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float *h) { uint32_t stringHash = hash::Fletcher((const uint8_t *)str, len); uint32_t entryHash = stringHash ^ fontHash_; @@ -179,7 +219,7 @@ void TextDrawer::MeasureString(const char *str, size_t len, float *w, float *h) *h = entry->height * fontScaleY_ * dpiScale_; } -void TextDrawer::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { +void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { auto iter = fontMap_.find(fontHash_); if (iter != fontMap_.end()) { SelectObject(ctx_->hDC, iter->second->hFont); @@ -224,7 +264,7 @@ void TextDrawer::MeasureStringRect(const char *str, size_t len, const Bounds &bo *h = total_h * dpiScale_; } -void TextDrawer::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { +void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { using namespace Draw; if (!strlen(str)) return; @@ -349,7 +389,7 @@ void TextDrawer::DrawString(DrawBuffer &target, const char *str, float x, float target.Flush(true); } -void TextDrawer::RecreateFonts() { +void TextDrawerWin32::RecreateFonts() { for (auto &iter : fontMap_) { iter.second->dpiScale = dpiScale_; iter.second->Create(); @@ -358,16 +398,16 @@ void TextDrawer::RecreateFonts() { #else -TextDrawer::TextDrawer(Draw::DrawContext *thin3d) : draw_(thin3d), ctx_(NULL) { +TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *thin3d) : draw_(thin3d), ctx_(NULL) { fontScaleX_ = 1.0f; fontScaleY_ = 1.0f; } -TextDrawer::~TextDrawer() { +TextDrawerWin32::~TextDrawerWin32() { ClearCache(); } -uint32_t TextDrawer::SetFont(const char *fontName, int size, int flags) { +uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) { #ifdef USING_QT_UI // We will only use the default font uint32_t fontHash = 0; //hash::Fletcher((const uint8_t *)fontName, strlen(fontName)); @@ -391,19 +431,19 @@ uint32_t TextDrawer::SetFont(const char *fontName, int size, int flags) { #endif } -void TextDrawer::SetFont(uint32_t fontHandle) { +void TextDrawerWin32::SetFont(uint32_t fontHandle) { } -void TextDrawer::RecreateFonts() { +void TextDrawerWin32::RecreateFonts() { } -void TextDrawer::MeasureString(const char *str, float *w, float *h) { +void TextDrawerWin32::MeasureString(const char *str, float *w, float *h) { MeasureString(str, strlen(str), w, h); } -void TextDrawer::MeasureString(const char *str, size_t len, float *w, float *h) { +void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float *h) { #ifdef USING_QT_UI QFont* font = fontMap_.find(fontHash_)->second; QFontMetrics fm(*font); @@ -416,7 +456,7 @@ void TextDrawer::MeasureString(const char *str, size_t len, float *w, float *h) #endif } -void TextDrawer::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { +void TextDrawerWin32::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) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; @@ -435,7 +475,7 @@ void TextDrawer::MeasureStringRect(const char *str, size_t len, const Bounds &bo #endif } -void TextDrawer::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { +void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { using namespace Draw; if (!strlen(str)) return; @@ -505,7 +545,7 @@ void TextDrawer::DrawString(DrawBuffer &target, const char *str, float x, float #endif -void TextDrawer::ClearCache() { +void TextDrawerWin32::ClearCache() { for (auto &iter : cache_) { if (iter.second->texture) iter.second->texture->Release(); @@ -514,17 +554,17 @@ void TextDrawer::ClearCache() { sizeCache_.clear(); } -void TextDrawer::WrapString(std::string &out, const char *str, float maxW) { +void TextDrawerWin32::WrapString(std::string &out, const char *str, float maxW) { TextDrawerWordWrapper wrapper(this, str, maxW); out = wrapper.Wrapped(); } -void TextDrawer::SetFontScale(float xscale, float yscale) { +void TextDrawerWin32::SetFontScale(float xscale, float yscale) { fontScaleX_ = xscale; fontScaleY_ = yscale; } -void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) { +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) { @@ -547,7 +587,7 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bound DrawString(target, toDraw.c_str(), x, y, color, align); } -void TextDrawer::OncePerFrame() { +void TextDrawerWin32::OncePerFrame() { frameCount_++; // If DPI changed (small-mode, future proper monitor DPI support), drop everything. float newDpiScale = CalculateDPIScale(); @@ -579,10 +619,21 @@ void TextDrawer::OncePerFrame() { } } -float TextDrawer::CalculateDPIScale() { +float TextDrawerWin32::CalculateDPIScale() { float scale = g_dpi_scale; if (scale >= 1.0f) { scale = 1.0f; } return scale; } + + +TextDrawer *CreateTextDrawer(Draw::DrawContext *draw) { +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + return new TextDrawerWin32(draw); +#elif defined(USING_QT_UI) + return new TextDrawerWin32(draw); +#else + return nullptr; +#endif +} \ No newline at end of file diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index f970be81fc..89944e0c74 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -50,45 +50,33 @@ enum { struct TextDrawerContext; class TextDrawerFontContext; + class TextDrawer { public: - TextDrawer(Draw::DrawContext *thin3d); - ~TextDrawer(); + TextDrawer(Draw::DrawContext *draw); + virtual ~TextDrawer(); - uint32_t SetFont(const char *fontName, int size, int flags); - void SetFont(uint32_t fontHandle); // Shortcut once you've set the font once. - - void SetFontScale(float xscale, float yscale); - void MeasureString(const char *str, float *w, float *h); - void MeasureString(const char *str, size_t len, float *w, float *h); - void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT); - void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT); - void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align); + virtual uint32_t SetFont(const char *fontName, int size, int flags) = 0; + virtual void SetFont(uint32_t fontHandle) = 0; // Shortcut once you've set the font once. + virtual void SetFontScale(float xscale, float yscale) = 0; + 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; // Use for housekeeping like throwing out old strings. - void OncePerFrame(); - float CalculateDPIScale(); + virtual void OncePerFrame() = 0; + virtual float CalculateDPIScale() = 0; -private: +protected: Draw::DrawContext *draw_; - - void ClearCache(); - void RecreateFonts(); // On DPI change - void WrapString(std::string &out, const char *str, float maxWidth); + virtual void ClearCache() = 0; + virtual void RecreateFonts() = 0; // On DPI change + virtual void WrapString(std::string &out, const char *str, float maxWidth) = 0; int frameCount_; float fontScaleX_; float fontScaleY_; float dpiScale_; - - TextDrawerContext *ctx_; -#if defined(USING_QT_UI) - std::map fontMap_; -#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - std::map> fontMap_; -#endif - - uint32_t fontHash_; - // The key is the CityHash of the string xor the fontHash_. - std::map> cache_; - std::map> sizeCache_; }; + +TextDrawer *CreateTextDrawer(Draw::DrawContext *draw); \ No newline at end of file diff --git a/ext/native/ui/ui_context.cpp b/ext/native/ui/ui_context.cpp index c4e0b06c67..e7f59df691 100644 --- a/ext/native/ui/ui_context.cpp +++ b/ext/native/ui/ui_context.cpp @@ -28,11 +28,7 @@ void UIContext::Init(Draw::DrawContext *thin3d, Draw::Pipeline *uipipe, Draw::Pi ui_pipeline_notex_ = uipipenotex; uidrawbuffer_ = uidrawbuffer; uidrawbufferTop_ = uidrawbufferTop; -#if (defined(_WIN32) && !PPSSPP_PLATFORM(UWP)) || defined(USING_QT_UI) - textDrawer_ = new TextDrawer(thin3d); -#else - textDrawer_ = nullptr; -#endif + textDrawer_ = CreateTextDrawer(thin3d); // May return nullptr } void UIContext::FrameSetup(Draw::Texture *uiTexture) { From 8d3b18478cf326599bab81fcaa142369c3f8c8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 4 Jun 2017 10:43:34 +0200 Subject: [PATCH 2/3] Split out DrawTextWin32 into its own file, to be further split later. --- ext/native/gfx_es2/draw_text.cpp | 601 +-------------------------- ext/native/gfx_es2/draw_text.h | 26 +- ext/native/gfx_es2/draw_text_qt.cpp | 0 ext/native/gfx_es2/draw_text_qt.h | 1 + ext/native/gfx_es2/draw_text_win.cpp | 538 ++++++++++++++++++++++++ ext/native/gfx_es2/draw_text_win.h | 44 ++ ext/native/native.vcxproj | 7 +- ext/native/native.vcxproj.filters | 12 + 8 files changed, 625 insertions(+), 604 deletions(-) create mode 100644 ext/native/gfx_es2/draw_text_qt.cpp create mode 100644 ext/native/gfx_es2/draw_text_qt.h create mode 100644 ext/native/gfx_es2/draw_text_win.cpp create mode 100644 ext/native/gfx_es2/draw_text_win.h diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index afad2e2824..62f878b389 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -6,620 +6,32 @@ #include "util/text/wrap_text.h" #include "util/text/utf8.h" #include "gfx_es2/draw_text.h" - -#ifdef USING_QT_UI -#include -#include -#include -#include -#endif +#include "gfx_es2/draw_text_win.h" TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) { + // These probably shouldn't be state. + dpiScale_ = CalculateDPIScale(); } TextDrawer::~TextDrawer() { } -class TextDrawerWin32 : public TextDrawer { -public: - TextDrawerWin32(Draw::DrawContext *draw); - ~TextDrawerWin32(); - - uint32_t SetFont(const char *fontName, int size, int flags) override; - void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. - void SetFontScale(float xscale, float yscale) override; - 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; - // Use for housekeeping like throwing out old strings. - void OncePerFrame() override; - float CalculateDPIScale() override; - -protected: - void ClearCache() override; - void RecreateFonts() override; // On DPI change - void WrapString(std::string &out, const char *str, float maxWidth) override; - - int frameCount_; - float fontScaleX_; - float fontScaleY_; - float dpiScale_; - - TextDrawerContext *ctx_; -#if defined(USING_QT_UI) - std::map fontMap_; -#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - std::map> fontMap_; -#endif - - uint32_t fontHash_; - // The key is the CityHash of the string xor the fontHash_. - std::map> cache_; - std::map> sizeCache_; -}; - -class TextDrawerWordWrapper : public WordWrapper { -public: - TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW) : WordWrapper(str, maxW), drawer_(drawer) { - } - -protected: - float MeasureWidth(const char *str, size_t bytes) override; - - TextDrawer *drawer_; -}; - float TextDrawerWordWrapper::MeasureWidth(const char *str, size_t bytes) { float w, h; drawer_->MeasureString(str, bytes, &w, &h); return w; } -#if defined(_WIN32) && !defined(USING_QT_UI) && !PPSSPP_PLATFORM(UWP) - -#define WIN32_LEAN_AND_MEAN -#include - -enum { - MAX_TEXT_WIDTH = 4096, - MAX_TEXT_HEIGHT = 512 -}; - -class TextDrawerFontContext { -public: - ~TextDrawerFontContext() { - Destroy(); - } - - void Create() { - if (hFont) { - Destroy(); - } - // We apparently specify all font sizes in pts (1pt = 1.33px), so divide by only 72 for pixels. - int nHeight = -MulDiv(height, (int)(96.0f * (1.0f / dpiScale)), 72); - hFont = CreateFont(nHeight, 0, 0, 0, bold, 0, - FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, PROOF_QUALITY, - VARIABLE_PITCH, fname.c_str()); - } - void Destroy() { - DeleteObject(hFont); - hFont = 0; - } - - HFONT hFont; - std::wstring fname; - int height; - int bold; - float dpiScale; -}; - -struct TextDrawerContext { - HDC hDC; - HBITMAP hbmBitmap; - int *pBitmapBits; -}; - -TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(nullptr) { - // These probably shouldn't be state. - fontScaleX_ = 1.0f; - fontScaleY_ = 1.0f; - dpiScale_ = CalculateDPIScale(); - - ctx_ = new TextDrawerContext(); - ctx_->hDC = CreateCompatibleDC(NULL); - - BITMAPINFO bmi; - ZeroMemory( &bmi.bmiHeader, sizeof(BITMAPINFOHEADER) ); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = MAX_TEXT_WIDTH; - bmi.bmiHeader.biHeight = -MAX_TEXT_HEIGHT; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biCompression = BI_RGB; - bmi.bmiHeader.biBitCount = 32; - - ctx_->hbmBitmap = CreateDIBSection(ctx_->hDC, &bmi, DIB_RGB_COLORS, (VOID**)&ctx_->pBitmapBits, NULL, 0); - SetMapMode(ctx_->hDC, MM_TEXT); - - SelectObject(ctx_->hDC, ctx_->hbmBitmap); -} - -TextDrawerWin32::~TextDrawerWin32() { - ClearCache(); - - fontMap_.clear(); - - DeleteObject(ctx_->hbmBitmap); - DeleteDC(ctx_->hDC); - delete ctx_; -} - -uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) { - uint32_t fontHash = fontName ? hash::Fletcher((const uint8_t *)fontName, strlen(fontName)) : 0; - fontHash ^= size; - fontHash ^= flags << 10; - - auto iter = fontMap_.find(fontHash); - if (iter != fontMap_.end()) { - fontHash_ = fontHash; - return fontHash; - } - - std::wstring fname; - if (fontName) - fname = ConvertUTF8ToWString(fontName); - else - fname = L"Tahoma"; - - TextDrawerFontContext *font = new TextDrawerFontContext(); - font->bold = FW_LIGHT; - font->height = size; - font->fname = fname; - font->dpiScale = dpiScale_; - font->Create(); - - fontMap_[fontHash] = std::unique_ptr(font); - fontHash_ = fontHash; - return fontHash; -} - -void TextDrawerWin32::SetFont(uint32_t fontHandle) { - auto iter = fontMap_.find(fontHandle); - if (iter != fontMap_.end()) { - fontHash_ = fontHandle; - } -} - -void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float *h) { - uint32_t stringHash = hash::Fletcher((const uint8_t *)str, len); - uint32_t entryHash = stringHash ^ fontHash_; - - TextMeasureEntry *entry; - auto iter = sizeCache_.find(entryHash); - if (iter != sizeCache_.end()) { - entry = iter->second.get(); - } else { - auto iter = fontMap_.find(fontHash_); - if (iter != fontMap_.end()) { - SelectObject(ctx_->hDC, iter->second->hFont); - } - - SIZE size; - std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(std::string(str, len), "\n", "\r\n")); - GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size); - - entry = new TextMeasureEntry(); - entry->width = size.cx; - entry->height = size.cy; - sizeCache_[entryHash] = std::unique_ptr(entry); - } - - entry->lastUsedFrame = frameCount_; - *w = entry->width * fontScaleX_ * dpiScale_; - *h = entry->height * fontScaleY_ * dpiScale_; -} - -void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { - auto iter = fontMap_.find(fontHash_); - if (iter != fontMap_.end()) { - SelectObject(ctx_->hDC, iter->second->hFont); - } - - std::string toMeasure = std::string(str, len); - if (align & FLAG_WRAP_TEXT) { - bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); - } - - std::vector lines; - SplitString(toMeasure, '\n', lines); - float total_w = 0.0f; - float total_h = 0.0f; - for (size_t i = 0; i < lines.size(); i++) { - uint32_t stringHash = hash::Fletcher((const uint8_t *)&lines[i][0], lines[i].length()); - uint32_t entryHash = stringHash ^ fontHash_; - - TextMeasureEntry *entry; - auto iter = sizeCache_.find(entryHash); - if (iter != sizeCache_.end()) { - entry = iter->second.get(); - } else { - SIZE size; - std::wstring wstr = ConvertUTF8ToWString(lines[i].length() == 0 ? " " : lines[i]); - GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size); - - entry = new TextMeasureEntry(); - entry->width = size.cx; - entry->height = size.cy; - sizeCache_[entryHash] = std::unique_ptr(entry); - } - entry->lastUsedFrame = frameCount_; - - if (total_w < entry->width * fontScaleX_) { - total_w = entry->width * fontScaleX_; - } - total_h += entry->height * fontScaleY_; - } - *w = total_w * dpiScale_; - *h = total_h * dpiScale_; -} - -void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (!strlen(str)) - return; - - uint32_t stringHash = hash::Fletcher((const uint8_t *)str, strlen(str)); - uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); - - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(entryHash); - if (iter != cache_.end()) { - 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; - - // GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size); - RECT rc = {0}; - rc.right = size.cx + 4; - rc.bottom = size.cy + 4; - FillRect(ctx_->hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); - //ExtTextOut(ctx_->hDC, 0, 0, ETO_OPAQUE | ETO_CLIPPED, NULL, wstr.c_str(), (int)wstr.size(), NULL); - DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &rc, DT_HIDEPREFIX|DT_TOP|dtAlign, 0); - - if (size.cx > MAX_TEXT_WIDTH) - size.cx = MAX_TEXT_WIDTH; - if (size.cy > MAX_TEXT_HEIGHT) - size.cy = MAX_TEXT_HEIGHT; - - 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_; - - 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; - - // 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) { - 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); - } - - desc.type = TextureType::LINEAR2D; - desc.format = texFormat; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - entry->texture = draw_->CreateTexture(desc); - if (bitmapData16) - delete[] bitmapData16; - if (bitmapData32) - delete[] bitmapData32; - cache_[entryHash] = std::unique_ptr(entry); - } - - draw_->BindTexture(0, entry->texture); - - // Okay, the texture is bound, let's draw. - 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 TextDrawerWin32::RecreateFonts() { - for (auto &iter : fontMap_) { - iter.second->dpiScale = dpiScale_; - iter.second->Create(); - } -} - -#else - -TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *thin3d) : draw_(thin3d), ctx_(NULL) { - fontScaleX_ = 1.0f; - fontScaleY_ = 1.0f; -} - -TextDrawerWin32::~TextDrawerWin32() { - ClearCache(); -} - -uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) { -#ifdef USING_QT_UI - // We will only use the default font - uint32_t fontHash = 0; //hash::Fletcher((const uint8_t *)fontName, strlen(fontName)); - fontHash ^= size; - fontHash ^= flags << 10; - - auto iter = fontMap_.find(fontHash); - if (iter != fontMap_.end()) { - fontHash_ = fontHash; - return fontHash; - } - - QFont* font = new QFont(); - font->setPixelSize(size + 6); - fontMap_[fontHash] = font; - fontHash_ = fontHash; - return fontHash; -#else - ELOG("System fonts not supported on this platform"); - return 0; -#endif -} - -void TextDrawerWin32::SetFont(uint32_t fontHandle) { - -} - -void TextDrawerWin32::RecreateFonts() { - -} - -void TextDrawerWin32::MeasureString(const char *str, float *w, float *h) { - MeasureString(str, strlen(str), w, h); -} - -void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float *h) { -#ifdef USING_QT_UI - 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_; -#else - *w = 0; - *h = 0; -#endif -} - -void TextDrawerWin32::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) { - bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); - } - -#ifdef USING_QT_UI - QFont* font = fontMap_.find(fontHash_)->second; - QFontMetrics fm(*font); - QSize size = fm.size(0, QString::fromUtf8(toMeasure.c_str(), (int)toMeasure.size())); - *w = (float)size.width() * fontScaleX_; - *h = (float)size.height() * fontScaleY_; -#else - *w = 0; - *h = 0; -#endif -} - -void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (!strlen(str)) - return; - -#ifdef USING_QT_UI - uint32_t stringHash = hash::Fletcher((const uint8_t *)str, strlen(str)); - uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); - - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(entryHash); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - draw_->BindTexture(0, entry->texture); - } 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(); - - entry = new TextStringEntry(); - entry->bmWidth = entry->width = image.width(); - entry->bmHeight = entry->height = image.height(); - entry->lastUsedFrame = frameCount_; - - TextureDesc desc{}; - desc.type = TextureType::LINEAR2D; - desc.format = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - - 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); - } - float w = entry->bmWidth * fontScaleX_; - float h = entry->bmHeight * fontScaleY_; - 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); -#endif -} - -#endif - -void TextDrawerWin32::ClearCache() { - for (auto &iter : cache_) { - if (iter.second->texture) - iter.second->texture->Release(); - } - cache_.clear(); - sizeCache_.clear(); -} - -void TextDrawerWin32::WrapString(std::string &out, const char *str, float maxW) { +void TextDrawer::WrapString(std::string &out, const char *str, float maxW) { TextDrawerWordWrapper wrapper(this, str, maxW); out = wrapper.Wrapped(); } -void TextDrawerWin32::SetFontScale(float xscale, float yscale) { +void TextDrawer::SetFontScale(float xscale, float yscale) { fontScaleX_ = xscale; fontScaleY_ = yscale; } -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. - float newDpiScale = CalculateDPIScale(); - if (newDpiScale != dpiScale_) { - dpiScale_ = newDpiScale; - ClearCache(); - RecreateFonts(); - } - - // Drop old strings. Use a prime number to reduce clashing with other rhythms - if (frameCount_ % 23 == 0) { - for (auto iter = cache_.begin(); iter != cache_.end();) { - if (frameCount_ - iter->second->lastUsedFrame > 100) { - if (iter->second->texture) - iter->second->texture->Release(); - cache_.erase(iter++); - } else { - iter++; - } - } - - for (auto iter = sizeCache_.begin(); iter != sizeCache_.end(); ) { - if (frameCount_ - iter->second->lastUsedFrame > 100) { - sizeCache_.erase(iter++); - } else { - iter++; - } - } - } -} - -float TextDrawerWin32::CalculateDPIScale() { +float TextDrawer::CalculateDPIScale() { float scale = g_dpi_scale; if (scale >= 1.0f) { scale = 1.0f; @@ -627,7 +39,6 @@ float TextDrawerWin32::CalculateDPIScale() { return scale; } - TextDrawer *CreateTextDrawer(Draw::DrawContext *draw) { #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) return new TextDrawerWin32(draw); diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index 89944e0c74..ff6ca26c53 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -15,6 +15,7 @@ #include "base/basictypes.h" #include "gfx_es2/draw_buffer.h" +#include "util/text/wrap_text.h" namespace Draw { class DrawContext; @@ -46,11 +47,6 @@ enum { FONTSTYLE_ITALIC = 2, }; -// Internal struct but all details in .cpp file (pimpl to avoid pulling in excessive headers here) -struct TextDrawerContext; -class TextDrawerFontContext; - - class TextDrawer { public: TextDrawer(Draw::DrawContext *draw); @@ -58,20 +54,21 @@ public: virtual uint32_t SetFont(const char *fontName, int size, int flags) = 0; virtual void SetFont(uint32_t fontHandle) = 0; // Shortcut once you've set the font once. - virtual void SetFontScale(float xscale, float yscale) = 0; + void SetFontScale(float xscale, float yscale); 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; // Use for housekeeping like throwing out old strings. virtual void OncePerFrame() = 0; - virtual float CalculateDPIScale() = 0; + + float CalculateDPIScale(); protected: Draw::DrawContext *draw_; virtual void ClearCache() = 0; virtual void RecreateFonts() = 0; // On DPI change - virtual void WrapString(std::string &out, const char *str, float maxWidth) = 0; + void WrapString(std::string &out, const char *str, float maxWidth); int frameCount_; float fontScaleX_; @@ -79,4 +76,17 @@ protected: float dpiScale_; }; + +class TextDrawerWordWrapper : public WordWrapper { +public: + TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW) : WordWrapper(str, maxW), drawer_(drawer) { + } + +protected: + float MeasureWidth(const char *str, size_t bytes) override; + + TextDrawer *drawer_; +}; + + TextDrawer *CreateTextDrawer(Draw::DrawContext *draw); \ No newline at end of file diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/native/gfx_es2/draw_text_qt.h b/ext/native/gfx_es2/draw_text_qt.h new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/ext/native/gfx_es2/draw_text_qt.h @@ -0,0 +1 @@ +#pragma once diff --git a/ext/native/gfx_es2/draw_text_win.cpp b/ext/native/gfx_es2/draw_text_win.cpp new file mode 100644 index 0000000000..94c3cb1009 --- /dev/null +++ b/ext/native/gfx_es2/draw_text_win.cpp @@ -0,0 +1,538 @@ +#include "base/display.h" +#include "base/logging.h" +#include "base/stringutil.h" +#include "thin3d/thin3d.h" +#include "util/hash/hash.h" +#include "util/text/wrap_text.h" +#include "util/text/utf8.h" +#include "gfx_es2/draw_text.h" +#include "gfx_es2/draw_text_win.h" + +#if defined(_WIN32) && !defined(USING_QT_UI) && !PPSSPP_PLATFORM(UWP) + +#define WIN32_LEAN_AND_MEAN +#include + +enum { + MAX_TEXT_WIDTH = 4096, + MAX_TEXT_HEIGHT = 512 +}; + +class TextDrawerFontContext { +public: + ~TextDrawerFontContext() { + Destroy(); + } + + void Create() { + if (hFont) { + Destroy(); + } + // We apparently specify all font sizes in pts (1pt = 1.33px), so divide by only 72 for pixels. + int nHeight = -MulDiv(height, (int)(96.0f * (1.0f / dpiScale)), 72); + hFont = CreateFont(nHeight, 0, 0, 0, bold, 0, + FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, PROOF_QUALITY, + VARIABLE_PITCH, fname.c_str()); + } + void Destroy() { + DeleteObject(hFont); + hFont = 0; + } + + HFONT hFont; + std::wstring fname; + int height; + int bold; + float dpiScale; +}; + +struct TextDrawerContext { + HDC hDC; + HBITMAP hbmBitmap; + int *pBitmapBits; +}; + +TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(nullptr) { + ctx_ = new TextDrawerContext(); + ctx_->hDC = CreateCompatibleDC(NULL); + + BITMAPINFO bmi; + ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = MAX_TEXT_WIDTH; + bmi.bmiHeader.biHeight = -MAX_TEXT_HEIGHT; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biBitCount = 32; + + ctx_->hbmBitmap = CreateDIBSection(ctx_->hDC, &bmi, DIB_RGB_COLORS, (VOID**)&ctx_->pBitmapBits, NULL, 0); + SetMapMode(ctx_->hDC, MM_TEXT); + + SelectObject(ctx_->hDC, ctx_->hbmBitmap); +} + +TextDrawerWin32::~TextDrawerWin32() { + ClearCache(); + + fontMap_.clear(); + + DeleteObject(ctx_->hbmBitmap); + DeleteDC(ctx_->hDC); + delete ctx_; +} + +uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) { + uint32_t fontHash = fontName ? hash::Fletcher((const uint8_t *)fontName, strlen(fontName)) : 0; + fontHash ^= size; + fontHash ^= flags << 10; + + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHash; + return fontHash; + } + + std::wstring fname; + if (fontName) + fname = ConvertUTF8ToWString(fontName); + else + fname = L"Tahoma"; + + TextDrawerFontContext *font = new TextDrawerFontContext(); + font->bold = FW_LIGHT; + font->height = size; + font->fname = fname; + font->dpiScale = dpiScale_; + font->Create(); + + fontMap_[fontHash] = std::unique_ptr(font); + fontHash_ = fontHash; + return fontHash; +} + +void TextDrawerWin32::SetFont(uint32_t fontHandle) { + auto iter = fontMap_.find(fontHandle); + if (iter != fontMap_.end()) { + fontHash_ = fontHandle; + } +} + +void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float *h) { + uint32_t stringHash = hash::Fletcher((const uint8_t *)str, len); + uint32_t entryHash = stringHash ^ fontHash_; + + TextMeasureEntry *entry; + auto iter = sizeCache_.find(entryHash); + if (iter != sizeCache_.end()) { + entry = iter->second.get(); + } else { + auto iter = fontMap_.find(fontHash_); + if (iter != fontMap_.end()) { + SelectObject(ctx_->hDC, iter->second->hFont); + } + + SIZE size; + std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(std::string(str, len), "\n", "\r\n")); + GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size); + + entry = new TextMeasureEntry(); + entry->width = size.cx; + entry->height = size.cy; + sizeCache_[entryHash] = std::unique_ptr(entry); + } + + entry->lastUsedFrame = frameCount_; + *w = entry->width * fontScaleX_ * dpiScale_; + *h = entry->height * fontScaleY_ * dpiScale_; +} + +void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { + auto iter = fontMap_.find(fontHash_); + if (iter != fontMap_.end()) { + SelectObject(ctx_->hDC, iter->second->hFont); + } + + std::string toMeasure = std::string(str, len); + if (align & FLAG_WRAP_TEXT) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + } + + std::vector lines; + SplitString(toMeasure, '\n', lines); + float total_w = 0.0f; + float total_h = 0.0f; + for (size_t i = 0; i < lines.size(); i++) { + uint32_t stringHash = hash::Fletcher((const uint8_t *)&lines[i][0], lines[i].length()); + uint32_t entryHash = stringHash ^ fontHash_; + + TextMeasureEntry *entry; + auto iter = sizeCache_.find(entryHash); + if (iter != sizeCache_.end()) { + entry = iter->second.get(); + } else { + SIZE size; + std::wstring wstr = ConvertUTF8ToWString(lines[i].length() == 0 ? " " : lines[i]); + GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size); + + entry = new TextMeasureEntry(); + entry->width = size.cx; + entry->height = size.cy; + sizeCache_[entryHash] = std::unique_ptr(entry); + } + entry->lastUsedFrame = frameCount_; + + if (total_w < entry->width * fontScaleX_) { + total_w = entry->width * fontScaleX_; + } + total_h += entry->height * fontScaleY_; + } + *w = total_w * dpiScale_; + *h = total_h * dpiScale_; +} + +void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { + using namespace Draw; + if (!strlen(str)) + return; + + uint32_t stringHash = hash::Fletcher((const uint8_t *)str, strlen(str)); + uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); + + target.Flush(true); + + TextStringEntry *entry; + + auto iter = cache_.find(entryHash); + if (iter != cache_.end()) { + 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; + + // GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size); + RECT rc = { 0 }; + rc.right = size.cx + 4; + rc.bottom = size.cy + 4; + FillRect(ctx_->hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); + //ExtTextOut(ctx_->hDC, 0, 0, ETO_OPAQUE | ETO_CLIPPED, NULL, wstr.c_str(), (int)wstr.size(), NULL); + DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &rc, DT_HIDEPREFIX | DT_TOP | dtAlign, 0); + + if (size.cx > MAX_TEXT_WIDTH) + size.cx = MAX_TEXT_WIDTH; + if (size.cy > MAX_TEXT_HEIGHT) + size.cy = MAX_TEXT_HEIGHT; + + 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_; + + 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; + + // 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) { + 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); + } + + desc.type = TextureType::LINEAR2D; + desc.format = texFormat; + desc.width = entry->bmWidth; + desc.height = entry->bmHeight; + desc.depth = 1; + desc.mipLevels = 1; + entry->texture = draw_->CreateTexture(desc); + if (bitmapData16) + delete[] bitmapData16; + if (bitmapData32) + delete[] bitmapData32; + cache_[entryHash] = std::unique_ptr(entry); + } + + draw_->BindTexture(0, entry->texture); + + // Okay, the texture is bound, let's draw. + 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 TextDrawerWin32::RecreateFonts() { + for (auto &iter : fontMap_) { + iter.second->dpiScale = dpiScale_; + iter.second->Create(); + } +} + +#else + +TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *thin3d) : draw_(thin3d), ctx_(NULL) { + fontScaleX_ = 1.0f; + fontScaleY_ = 1.0f; +} + +TextDrawerWin32::~TextDrawerWin32() { + ClearCache(); +} + +uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) { +#ifdef USING_QT_UI + // We will only use the default font + uint32_t fontHash = 0; //hash::Fletcher((const uint8_t *)fontName, strlen(fontName)); + fontHash ^= size; + fontHash ^= flags << 10; + + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHash; + return fontHash; + } + + QFont* font = new QFont(); + font->setPixelSize(size + 6); + fontMap_[fontHash] = font; + fontHash_ = fontHash; + return fontHash; +#else + ELOG("System fonts not supported on this platform"); + return 0; +#endif +} + +void TextDrawerWin32::SetFont(uint32_t fontHandle) { + +} + +void TextDrawerWin32::RecreateFonts() { + +} + +void TextDrawerWin32::MeasureString(const char *str, float *w, float *h) { + MeasureString(str, strlen(str), w, h); +} + +void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float *h) { +#ifdef USING_QT_UI + 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_; +#else + *w = 0; + *h = 0; +#endif +} + +void TextDrawerWin32::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) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + } + +#ifdef USING_QT_UI + QFont* font = fontMap_.find(fontHash_)->second; + QFontMetrics fm(*font); + QSize size = fm.size(0, QString::fromUtf8(toMeasure.c_str(), (int)toMeasure.size())); + *w = (float)size.width() * fontScaleX_; + *h = (float)size.height() * fontScaleY_; +#else + *w = 0; + *h = 0; +#endif +} + +void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { + using namespace Draw; + if (!strlen(str)) + return; + +#ifdef USING_QT_UI + uint32_t stringHash = hash::Fletcher((const uint8_t *)str, strlen(str)); + uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); + + target.Flush(true); + + TextStringEntry *entry; + + auto iter = cache_.find(entryHash); + if (iter != cache_.end()) { + entry = iter->second.get(); + entry->lastUsedFrame = frameCount_; + draw_->BindTexture(0, entry->texture); + } 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(); + + entry = new TextStringEntry(); + entry->bmWidth = entry->width = image.width(); + entry->bmHeight = entry->height = image.height(); + entry->lastUsedFrame = frameCount_; + + TextureDesc desc{}; + desc.type = TextureType::LINEAR2D; + desc.format = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; + desc.width = entry->bmWidth; + desc.height = entry->bmHeight; + desc.depth = 1; + desc.mipLevels = 1; + + 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); + } + float w = entry->bmWidth * fontScaleX_; + float h = entry->bmHeight * fontScaleY_; + 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); +#endif +} + +#endif + +void TextDrawerWin32::ClearCache() { + for (auto &iter : cache_) { + if (iter.second->texture) + iter.second->texture->Release(); + } + cache_.clear(); + 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. + float newDpiScale = CalculateDPIScale(); + if (newDpiScale != dpiScale_) { + dpiScale_ = newDpiScale; + ClearCache(); + RecreateFonts(); + } + + // Drop old strings. Use a prime number to reduce clashing with other rhythms + if (frameCount_ % 23 == 0) { + for (auto iter = cache_.begin(); iter != cache_.end();) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + if (iter->second->texture) + iter->second->texture->Release(); + cache_.erase(iter++); + } else { + iter++; + } + } + + for (auto iter = sizeCache_.begin(); iter != sizeCache_.end(); ) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + sizeCache_.erase(iter++); + } else { + iter++; + } + } + } +} \ No newline at end of file diff --git a/ext/native/gfx_es2/draw_text_win.h b/ext/native/gfx_es2/draw_text_win.h new file mode 100644 index 0000000000..6a9cbaa28f --- /dev/null +++ b/ext/native/gfx_es2/draw_text_win.h @@ -0,0 +1,44 @@ +#pragma once + +#include "ppsspp_config.h" + +#include +#include "gfx_es2/draw_text.h" + +#if defined(_WIN32) && !defined(USING_QT_UI) && !PPSSPP_PLATFORM(UWP) + +#define WIN32_LEAN_AND_MEAN +#include + +class TextDrawerContext; +// Internal struct but all details in .cpp file (pimpl to avoid pulling in excessive headers here) +class TextDrawerFontContext; + +class TextDrawerWin32 : public TextDrawer { +public: + TextDrawerWin32(Draw::DrawContext *draw); + ~TextDrawerWin32(); + + uint32_t SetFont(const char *fontName, int size, int flags) override; + void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. + 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; + // Use for housekeeping like throwing out old strings. + void OncePerFrame() override; + +protected: + void ClearCache() override; + void RecreateFonts() override; // On DPI change + + TextDrawerContext *ctx_; + std::map> fontMap_; + + uint32_t fontHash_; + // The key is the CityHash of the string xor the fontHash_. + std::map> cache_; + std::map> sizeCache_; +}; + +#endif \ No newline at end of file diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj index f60b1c2cb2..12cd0e4b3d 100644 --- a/ext/native/native.vcxproj +++ b/ext/native/native.vcxproj @@ -22,7 +22,8 @@ {C4DF647E-80EA-4111-A0A8-218B1B711E18} Win32Proj native - + + @@ -236,6 +237,8 @@ + + @@ -689,6 +692,8 @@ + + diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters index 71002e0a4a..b1718027c9 100644 --- a/ext/native/native.vcxproj.filters +++ b/ext/native/native.vcxproj.filters @@ -314,6 +314,12 @@ thin3d + + gfx + + + gfx + @@ -766,6 +772,12 @@ base + + gfx + + + gfx + From 6e98dbb574f4450c696d9509dcb8b57041fd7253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 4 Jun 2017 10:57:46 +0200 Subject: [PATCH 3/3] Split out the Qt font drawer in its own file. --- CMakeLists.txt | 4 + ext/native/gfx_es2/draw_text.cpp | 5 +- ext/native/gfx_es2/draw_text.h | 11 +- ext/native/gfx_es2/draw_text_qt.cpp | 205 +++++++++++++++++++++++++++ ext/native/gfx_es2/draw_text_qt.h | 35 +++++ ext/native/gfx_es2/draw_text_win.cpp | 153 +------------------- ext/native/gfx_es2/draw_text_win.h | 2 +- ext/native/ui/ui_context.cpp | 7 +- ext/native/ui/ui_context.h | 18 +-- 9 files changed, 268 insertions(+), 172 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98d4f79846..6021144a5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -863,6 +863,10 @@ add_library(native STATIC ext/native/gfx_es2/draw_buffer.h ext/native/gfx_es2/draw_text.cpp ext/native/gfx_es2/draw_text.h + ext/native/gfx_es2/draw_text_win.cpp + ext/native/gfx_es2/draw_text_win.h + ext/native/gfx_es2/draw_text_qt.cpp + ext/native/gfx_es2/draw_text_qt.h ext/native/gfx_es2/gpu_features.cpp ext/native/gfx_es2/gpu_features.h ext/native/gfx_es2/glsl_program.cpp diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index 62f878b389..1a49c2f0ab 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -7,6 +7,7 @@ #include "util/text/utf8.h" #include "gfx_es2/draw_text.h" #include "gfx_es2/draw_text_win.h" +#include "gfx_es2/draw_text_qt.h" TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) { // These probably shouldn't be state. @@ -39,11 +40,11 @@ float TextDrawer::CalculateDPIScale() { return scale; } -TextDrawer *CreateTextDrawer(Draw::DrawContext *draw) { +TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) { #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) return new TextDrawerWin32(draw); #elif defined(USING_QT_UI) - return new TextDrawerWin32(draw); + return new TextDrawerQt(draw); #else return nullptr; #endif diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index ff6ca26c53..186e43d900 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -49,7 +49,6 @@ enum { class TextDrawer { public: - TextDrawer(Draw::DrawContext *draw); virtual ~TextDrawer(); virtual uint32_t SetFont(const char *fontName, int size, int flags) = 0; @@ -64,7 +63,12 @@ public: float CalculateDPIScale(); + // Factory function that selects implementation. + static TextDrawer *Create(Draw::DrawContext *draw); + protected: + TextDrawer(Draw::DrawContext *draw); + Draw::DrawContext *draw_; virtual void ClearCache() = 0; virtual void RecreateFonts() = 0; // On DPI change @@ -86,7 +90,4 @@ protected: float MeasureWidth(const char *str, size_t bytes) override; TextDrawer *drawer_; -}; - - -TextDrawer *CreateTextDrawer(Draw::DrawContext *draw); \ No newline at end of file +}; \ No newline at end of file diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index e69de29bb2..ad33e8168e 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -0,0 +1,205 @@ +#include "base/display.h" +#include "base/logging.h" +#include "base/stringutil.h" +#include "thin3d/thin3d.h" +#include "util/hash/hash.h" +#include "util/text/wrap_text.h" +#include "util/text/utf8.h" +#include "gfx_es2/draw_text.h" +#include "gfx_es2/draw_text_qt.h" + +#if defined(USING_QT_UI) + +#include +#include +#include +#include + + +TextDrawerQt::TextDrawerQt(Draw::DrawContext *draw) : TextDrawer(draw) { +} + +TextDrawerQt::~TextDrawerQt() { + ClearCache(); +} + +uint32_t TextDrawerQt::SetFont(const char *fontName, int size, int flags) { + // We will only use the default font + uint32_t fontHash = 0; //hash::Fletcher((const uint8_t *)fontName, strlen(fontName)); + fontHash ^= size; + fontHash ^= flags << 10; + + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHash; + return fontHash; + } + + QFont* font = new QFont(); + font->setPixelSize(size + 6); + fontMap_[fontHash] = font; + fontHash_ = fontHash; + return fontHash; +} + +void TextDrawerQt::SetFont(uint32_t fontHandle) { + +} + +void TextDrawerQt::RecreateFonts() { + +} + +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_; +} + +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) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + } + + QFont* font = fontMap_.find(fontHash_)->second; + QFontMetrics fm(*font); + QSize size = fm.size(0, QString::fromUtf8(toMeasure.c_str(), (int)toMeasure.size())); + *w = (float)size.width() * fontScaleX_; + *h = (float)size.height() * fontScaleY_; +} + +void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { + using namespace Draw; + if (!strlen(str)) + return; + + uint32_t stringHash = hash::Fletcher((const uint8_t *)str, strlen(str)); + uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); + + target.Flush(true); + + TextStringEntry *entry; + + auto iter = cache_.find(entryHash); + if (iter != cache_.end()) { + entry = iter->second.get(); + entry->lastUsedFrame = frameCount_; + draw_->BindTexture(0, entry->texture); + } 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(); + + entry = new TextStringEntry(); + entry->bmWidth = entry->width = image.width(); + entry->bmHeight = entry->height = image.height(); + entry->lastUsedFrame = frameCount_; + + TextureDesc desc{}; + desc.type = TextureType::LINEAR2D; + desc.format = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; + desc.width = entry->bmWidth; + desc.height = entry->bmHeight; + desc.depth = 1; + desc.mipLevels = 1; + + 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); + } + float w = entry->bmWidth * fontScaleX_; + float h = entry->bmHeight * fontScaleY_; + 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) + iter.second->texture->Release(); + } + cache_.clear(); + sizeCache_.clear(); +} + +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. + float newDpiScale = CalculateDPIScale(); + if (newDpiScale != dpiScale_) { + dpiScale_ = newDpiScale; + ClearCache(); + RecreateFonts(); + } + + // Drop old strings. Use a prime number to reduce clashing with other rhythms + if (frameCount_ % 23 == 0) { + for (auto iter = cache_.begin(); iter != cache_.end();) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + if (iter->second->texture) + iter->second->texture->Release(); + cache_.erase(iter++); + } else { + iter++; + } + } + + for (auto iter = sizeCache_.begin(); iter != sizeCache_.end(); ) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + sizeCache_.erase(iter++); + } else { + iter++; + } + } + } +} + +#endif diff --git a/ext/native/gfx_es2/draw_text_qt.h b/ext/native/gfx_es2/draw_text_qt.h index 6f70f09bee..2a24b7cad5 100644 --- a/ext/native/gfx_es2/draw_text_qt.h +++ b/ext/native/gfx_es2/draw_text_qt.h @@ -1 +1,36 @@ #pragma once + +#include "ppsspp_config.h" + +#include +#include "gfx_es2/draw_text.h" + +#if defined(USING_QT_UI) + +class TextDrawerQt : public TextDrawer { +public: + TextDrawerQt(Draw::DrawContext *draw); + ~TextDrawerQt(); + + uint32_t SetFont(const char *fontName, int size, int flags) override; + void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. + 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; + // Use for housekeeping like throwing out old strings. + void OncePerFrame() override; + +protected: + void ClearCache() override; + void RecreateFonts() override; // On DPI change + + uint32_t fontHash_; + std::map fontMap_; + + // The key is the CityHash of the string xor the fontHash_. + std::map> cache_; + std::map> sizeCache_; +}; + +#endif \ No newline at end of file diff --git a/ext/native/gfx_es2/draw_text_win.cpp b/ext/native/gfx_es2/draw_text_win.cpp index 94c3cb1009..9df6ee09d0 100644 --- a/ext/native/gfx_es2/draw_text_win.cpp +++ b/ext/native/gfx_es2/draw_text_win.cpp @@ -324,155 +324,6 @@ void TextDrawerWin32::RecreateFonts() { } } -#else - -TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *thin3d) : draw_(thin3d), ctx_(NULL) { - fontScaleX_ = 1.0f; - fontScaleY_ = 1.0f; -} - -TextDrawerWin32::~TextDrawerWin32() { - ClearCache(); -} - -uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) { -#ifdef USING_QT_UI - // We will only use the default font - uint32_t fontHash = 0; //hash::Fletcher((const uint8_t *)fontName, strlen(fontName)); - fontHash ^= size; - fontHash ^= flags << 10; - - auto iter = fontMap_.find(fontHash); - if (iter != fontMap_.end()) { - fontHash_ = fontHash; - return fontHash; - } - - QFont* font = new QFont(); - font->setPixelSize(size + 6); - fontMap_[fontHash] = font; - fontHash_ = fontHash; - return fontHash; -#else - ELOG("System fonts not supported on this platform"); - return 0; -#endif -} - -void TextDrawerWin32::SetFont(uint32_t fontHandle) { - -} - -void TextDrawerWin32::RecreateFonts() { - -} - -void TextDrawerWin32::MeasureString(const char *str, float *w, float *h) { - MeasureString(str, strlen(str), w, h); -} - -void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float *h) { -#ifdef USING_QT_UI - 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_; -#else - *w = 0; - *h = 0; -#endif -} - -void TextDrawerWin32::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) { - bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); - } - -#ifdef USING_QT_UI - QFont* font = fontMap_.find(fontHash_)->second; - QFontMetrics fm(*font); - QSize size = fm.size(0, QString::fromUtf8(toMeasure.c_str(), (int)toMeasure.size())); - *w = (float)size.width() * fontScaleX_; - *h = (float)size.height() * fontScaleY_; -#else - *w = 0; - *h = 0; -#endif -} - -void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { - using namespace Draw; - if (!strlen(str)) - return; - -#ifdef USING_QT_UI - uint32_t stringHash = hash::Fletcher((const uint8_t *)str, strlen(str)); - uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); - - target.Flush(true); - - TextStringEntry *entry; - - auto iter = cache_.find(entryHash); - if (iter != cache_.end()) { - entry = iter->second.get(); - entry->lastUsedFrame = frameCount_; - draw_->BindTexture(0, entry->texture); - } 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(); - - entry = new TextStringEntry(); - entry->bmWidth = entry->width = image.width(); - entry->bmHeight = entry->height = image.height(); - entry->lastUsedFrame = frameCount_; - - TextureDesc desc{}; - desc.type = TextureType::LINEAR2D; - desc.format = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; - desc.width = entry->bmWidth; - desc.height = entry->bmHeight; - desc.depth = 1; - desc.mipLevels = 1; - - 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); - } - float w = entry->bmWidth * fontScaleX_; - float h = entry->bmHeight * fontScaleY_; - 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); -#endif -} - -#endif - void TextDrawerWin32::ClearCache() { for (auto &iter : cache_) { if (iter.second->texture) @@ -535,4 +386,6 @@ void TextDrawerWin32::OncePerFrame() { } } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/ext/native/gfx_es2/draw_text_win.h b/ext/native/gfx_es2/draw_text_win.h index 6a9cbaa28f..7461afaf63 100644 --- a/ext/native/gfx_es2/draw_text_win.h +++ b/ext/native/gfx_es2/draw_text_win.h @@ -10,7 +10,7 @@ #define WIN32_LEAN_AND_MEAN #include -class TextDrawerContext; +struct TextDrawerContext; // Internal struct but all details in .cpp file (pimpl to avoid pulling in excessive headers here) class TextDrawerFontContext; diff --git a/ext/native/ui/ui_context.cpp b/ext/native/ui/ui_context.cpp index e7f59df691..4c9f5fd19b 100644 --- a/ext/native/ui/ui_context.cpp +++ b/ext/native/ui/ui_context.cpp @@ -6,10 +6,7 @@ #include "gfx_es2/draw_buffer.h" #include "gfx_es2/draw_text.h" -UIContext::UIContext() - : ui_pipeline_(0), uitexture_(0), uidrawbuffer_(0), uidrawbufferTop_(0) { - fontScaleX_ = 1.0f; - fontScaleY_ = 1.0f; +UIContext::UIContext() { fontStyle_ = new UI::FontStyle(); bounds_ = Bounds(0, 0, dp_xres, dp_yres); } @@ -28,7 +25,7 @@ void UIContext::Init(Draw::DrawContext *thin3d, Draw::Pipeline *uipipe, Draw::Pi ui_pipeline_notex_ = uipipenotex; uidrawbuffer_ = uidrawbuffer; uidrawbufferTop_ = uidrawbufferTop; - textDrawer_ = CreateTextDrawer(thin3d); // May return nullptr + textDrawer_ = TextDrawer::Create(thin3d); // May return nullptr if no implementation is available for this platform. } void UIContext::FrameSetup(Draw::Texture *uiTexture) { diff --git a/ext/native/ui/ui_context.h b/ext/native/ui/ui_context.h index 5a248c3ba8..59468abde7 100644 --- a/ext/native/ui/ui_context.h +++ b/ext/native/ui/ui_context.h @@ -94,18 +94,18 @@ private: Draw::DrawContext *draw_; Bounds bounds_; - float fontScaleX_; - float fontScaleY_; - UI::FontStyle *fontStyle_; - TextDrawer *textDrawer_; + float fontScaleX_ = 1.0f; + float fontScaleY_ = 1.0f; + UI::FontStyle *fontStyle_ = nullptr; + TextDrawer *textDrawer_ = nullptr; Draw::SamplerState *sampler_; - Draw::Pipeline *ui_pipeline_; - Draw::Pipeline *ui_pipeline_notex_; - Draw::Texture *uitexture_; + Draw::Pipeline *ui_pipeline_ = nullptr; + Draw::Pipeline *ui_pipeline_notex_ = nullptr; + Draw::Texture *uitexture_ = nullptr; - DrawBuffer *uidrawbuffer_; - DrawBuffer *uidrawbufferTop_; + DrawBuffer *uidrawbuffer_ = nullptr; + DrawBuffer *uidrawbufferTop_ = nullptr; std::vector scissorStack_; std::vector transformStack_;