From 6652fe261f5af6e454b60ef30731ac2ceb3717ad Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 10 Mar 2020 17:53:30 -0700 Subject: [PATCH] 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 883cc7a587..2871be8140 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 0181e36dff..b310c28b2c 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 2032f7e14f..8962fa7f99 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 @@ public: 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 69096e071c..239f4556ce 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 bf6fe1bf07..a9a5a35927 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -60,6 +60,7 @@ public: 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 9da3cda741..3062c05225 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;