// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include "ext/xxhash.h" #include "Common/System/System.h" #include "Common/Data/Color/RGBAUtil.h" #include "Common/File/VFS/VFS.h" #include "Common/Data/Format/ZIMLoad.h" #include "Common/Data/Format/PNGLoad.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/Render/TextureAtlas.h" #include "Common/Render/Text/draw_text.h" #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Common/StringUtils.h" #include "Core/Config.h" #include "Common/BitScan.h" #include "Core/HDRemaster.h" #include "GPU/ge_constants.h" #include "GPU/GPUState.h" #include "GPU/GPUInterface.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/Util/PPGeDraw.h" #include "Core/HLE/sceKernel.h" #include "Core/HLE/sceKernelMemory.h" #include "Core/HLE/sceGe.h" #include "Core/MemMapHelpers.h" #include "Core/System.h" static Atlas g_ppge_atlas; static Draw::DrawContext *g_draw = nullptr; static u32 atlasPtr; static int atlasWidth; static int atlasHeight; static uint64_t atlasHash; static bool atlasRequiresReset; struct PPGeVertex { u16_le u, v; u32_le color; float_le x, y, z; }; struct PPGeRemasterVertex { float_le u, v; u32_le color; float_le x, y, z; }; static PSPPointer listArgs; static u32 listArgsSize = sizeof(PspGeListArgs); static u32 savedContextPtr; static u32 savedContextSize = 512 * 4; // Display list writer static u32 dlPtr; static u32 dlWritePtr; static u32 dlSize = 0x10000; // should be enough for a frame of gui... static u32 dataPtr; static u32 dataWritePtr; static u32 dataSize = 0x10000; // should be enough for a frame of gui... static PSPPointer palette; static u32 paletteSize = sizeof(u16) * 16; // Vertex collector static u32 vertexStart; static u32 vertexCount; // Used for formatting text struct AtlasCharVertex { float x; float y; const AtlasChar *c; }; struct AtlasTextMetrics { float x; float y; float maxWidth; float lineHeight; float scale; int numLines; }; typedef std::vector AtlasCharLine; typedef std::vector AtlasLineArray; 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 { 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; }; static std::map textDrawerImages; void PPGeSetDrawContext(Draw::DrawContext *draw) { g_draw = draw; } // Overwrite the current text lines buffer so it can be drawn later. void PPGePrepareText(const char *text, float x, float y, PPGeAlign align, float scale, float lineHeightScale, int WrapType = PPGE_LINE_NONE, int wrapWidth = 0); // 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); static void PPGeDecimateTextImages(int age = 97); 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); dlWritePtr += 4; _dbg_assert_(dlWritePtr <= dlPtr + dlSize); } static void WriteCmdAddrWithBase(u8 cmd, u32 addr) { WriteCmd(GE_CMD_BASE, (addr >> 8) & 0xFF0000); WriteCmd(cmd, addr & 0xFFFFFF); } /* static void WriteCmdFloat(u8 cmd, float f) { union { float fl; u32 u; } conv; conv.fl = f; WriteCmd(cmd, conv.u >> 8); }*/ static void BeginVertexData() { vertexCount = 0; vertexStart = dataWritePtr; } static void Vertex(float x, float y, float u, float v, int tw, int th, u32 color = 0xFFFFFFFF) { if (g_RemasterMode) { auto vtx = PSPPointer::Create(dataWritePtr); vtx->x = x; vtx->y = y; vtx->z = 0; vtx->u = u * tw; vtx->v = v * th; vtx->color = color; dataWritePtr += (u32)vtx.ElementSize(); } else { auto vtx = PSPPointer::Create(dataWritePtr); vtx->x = x; vtx->y = y; vtx->z = 0; vtx->u = u * tw; vtx->v = v * th; vtx->color = color; dataWritePtr += (u32)vtx.ElementSize(); } _dbg_assert_(dataWritePtr <= dataPtr + dataSize); vertexCount++; } static void EndVertexDataAndDraw(int prim) { _assert_msg_(vertexStart != 0, "Missing matching call to BeginVertexData()"); if (vertexCount != 0) { NotifyMemInfo(MemBlockFlags::WRITE, vertexStart, dataWritePtr - vertexStart, "PPGe Vertex"); WriteCmdAddrWithBase(GE_CMD_VADDR, vertexStart); WriteCmd(GE_CMD_PRIM, (prim << 16) | vertexCount); } vertexStart = 0; } bool PPGeIsFontTextureAddress(u32 addr) { return addr == atlasPtr; } static u32 __PPGeDoAlloc(u32 &size, bool fromTop, const char *name) { u32 ptr = kernelMemory.Alloc(size, fromTop, name); // Didn't get it, try again after decimating images. if (ptr == (u32)-1) { PPGeDecimateTextImages(4); PPGeImage::Decimate(4); ptr = kernelMemory.Alloc(size, fromTop, name); if (ptr == (u32)-1) { return 0; } } return ptr; } void __PPGeSetupListArgs() { if (listArgs.IsValid()) return; listArgs = __PPGeDoAlloc(listArgsSize, false, "PPGe List Args"); if (listArgs.IsValid()) { listArgs->size = 8; if (savedContextPtr == 0) savedContextPtr = __PPGeDoAlloc(savedContextSize, false, "PPGe Saved Context"); listArgs->context = savedContextPtr; } } void __PPGeInit() { // PPGe isn't really important for headless, and LoadZIM takes a long time. bool skipZIM = System_GetPropertyBool(SYSPROP_SKIP_UI); u8 *imageData[12]{}; int width[12]{}; int height[12]{}; int flags = 0; bool loadedZIM = !skipZIM && LoadZIM("ppge_atlas.zim", width, height, &flags, imageData); if (!skipZIM && !loadedZIM) { ERROR_LOG(SCEGE, "Failed to load ppge_atlas.zim.\n\nPlace it in the directory \"assets\" under your PPSSPP directory.\n\nPPGe stuff will not be drawn."); } if (loadedZIM) { size_t atlas_data_size; if (!g_ppge_atlas.IsMetadataLoaded()) { uint8_t *atlas_data = g_VFS.ReadFile("ppge_atlas.meta", &atlas_data_size); if (atlas_data) g_ppge_atlas.Load(atlas_data, atlas_data_size); delete[] atlas_data; } } u32 atlasSize = height[0] * width[0] / 2; // it's a 4-bit paletted texture in ram atlasWidth = width[0]; atlasHeight = height[0]; dlPtr = __PPGeDoAlloc(dlSize, false, "PPGe Display List"); dataPtr = __PPGeDoAlloc(dataSize, false, "PPGe Vertex Data"); __PPGeSetupListArgs(); atlasPtr = atlasSize == 0 ? 0 : __PPGeDoAlloc(atlasSize, false, "PPGe Atlas Texture"); palette = __PPGeDoAlloc(paletteSize, false, "PPGe Texture Palette"); // Generate 16-greyscale palette. All PPGe graphics are greyscale so we can use a tiny paletted texture. for (int i = 0; i < 16; i++) { int val = i; palette[i] = (val << 12) | 0xFFF; } NotifyMemInfo(MemBlockFlags::WRITE, palette.ptr, 16 * sizeof(u16_le), "PPGe Palette"); const u32_le *imagePtr = (u32_le *)imageData[0]; u8 *ramPtr = atlasPtr == 0 ? nullptr : (u8 *)Memory::GetPointerRange(atlasPtr, atlasSize); // Palettize to 4-bit, the easy way. for (int i = 0; i < width[0] * height[0] / 2; i++) { // Each pixel is 16 bits, so this loads two pixels. u32 c = imagePtr[i]; // It's white anyway, so we only look at one channel of each pixel. int a1 = (c & 0x0000000F) >> 0; int a2 = (c & 0x000F0000) >> 16; u8 cval = (a2 << 4) | a1; ramPtr[i] = cval; } if (atlasPtr != 0) { atlasHash = XXH3_64bits(ramPtr, atlasSize); NotifyMemInfo(MemBlockFlags::WRITE, atlasPtr, atlasSize, "PPGe Atlas"); } free(imageData[0]); // We can't create it here, because Android needs it on the right thread. // Avoid creating ever on headless just to be safe. textDrawerInited = PSP_CoreParameter().headLess; textDrawer = nullptr; textDrawerImages.clear(); atlasRequiresReset = false; INFO_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, 4); if (!s) return; Do(p, atlasPtr); Do(p, atlasWidth); Do(p, atlasHeight); Do(p, palette); // If the atlas the save state was created with differs from the current one, reload. uint64_t savedHash = atlasHash; if (s >= 4) { Do(p, savedHash); } else { // Memory was already updated by this point, so check directly. if (atlasPtr != 0) { savedHash = XXH3_64bits(Memory::GetPointerRange(atlasPtr, atlasWidth * atlasHeight / 2), atlasWidth * atlasHeight / 2); } else { savedHash ^= 1; } } atlasRequiresReset = savedHash != atlasHash; Do(p, savedContextPtr); Do(p, savedContextSize); if (s == 1) { listArgs = 0; } else { Do(p, listArgs); } if (s >= 3) { uint32_t sz = (uint32_t)textDrawerImages.size(); Do(p, 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{}; Do(p, textDrawerImages[key].ptr); } break; default: for (const auto &im : textDrawerImages) { Do(p, im.second.ptr); } break; } } else { textDrawerImages.clear(); } Do(p, dlPtr); Do(p, dlWritePtr); Do(p, dlSize); Do(p, dataPtr); Do(p, dataWritePtr); Do(p, dataSize); Do(p, vertexStart); Do(p, vertexCount); Do(p, char_lines); Do(p, char_lines_metrics); } void __PPGeShutdown() { if (atlasPtr) kernelMemory.Free(atlasPtr); if (dataPtr) kernelMemory.Free(dataPtr); if (dlPtr) kernelMemory.Free(dlPtr); if (listArgs.IsValid()) kernelMemory.Free(listArgs.ptr); if (savedContextPtr) kernelMemory.Free(savedContextPtr); if (palette) kernelMemory.Free(palette.ptr); atlasPtr = 0; dataPtr = 0; dlPtr = 0; savedContextPtr = 0; listArgs = 0; delete textDrawer; textDrawer = nullptr; for (auto im : textDrawerImages) kernelMemory.Free(im.second.ptr); textDrawerImages.clear(); } void PPGeBegin() { if (!dlPtr) return; // Reset write pointers to start of command and data buffers. dlWritePtr = dlPtr; dataWritePtr = dataPtr; // Set up the correct states for UI drawing WriteCmd(GE_CMD_OFFSETADDR, 0); WriteCmd(GE_CMD_ALPHABLENDENABLE, 1); WriteCmd(GE_CMD_BLENDMODE, 2 | (3 << 4)); WriteCmd(GE_CMD_ALPHATESTENABLE, 0); WriteCmd(GE_CMD_COLORTESTENABLE, 0); WriteCmd(GE_CMD_ZTESTENABLE, 0); WriteCmd(GE_CMD_LIGHTINGENABLE, 0); WriteCmd(GE_CMD_FOGENABLE, 0); WriteCmd(GE_CMD_STENCILTESTENABLE, 0); WriteCmd(GE_CMD_CULLFACEENABLE, 0); WriteCmd(GE_CMD_CLEARMODE, 0); // Normal mode WriteCmd(GE_CMD_MASKRGB, 0); WriteCmd(GE_CMD_MASKALPHA, 0); WriteCmd(GE_CMD_DITHERENABLE, 0); PPGeSetDefaultTexture(); PPGeScissor(0, 0, 480, 272); WriteCmd(GE_CMD_MINZ, 0); WriteCmd(GE_CMD_MAXZ, 0xFFFF); // Through mode, so we don't have to bother with matrices if (g_RemasterMode) { WriteCmd(GE_CMD_VERTEXTYPE, GE_VTYPE_TC_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_POS_FLOAT | GE_VTYPE_THROUGH); } else { WriteCmd(GE_CMD_VERTEXTYPE, GE_VTYPE_TC_16BIT | GE_VTYPE_COL_8888 | GE_VTYPE_POS_FLOAT | GE_VTYPE_THROUGH); } } void PPGeEnd() { if (!dlPtr) return; WriteCmd(GE_CMD_FINISH, 0); WriteCmd(GE_CMD_END, 0); // Might've come from an old savestate. __PPGeSetupListArgs(); if (dataWritePtr > dataPtr) { // We actually drew something gpu->EnableInterrupts(false); NotifyMemInfo(MemBlockFlags::WRITE, dlPtr, dlWritePtr - dlPtr, "PPGe ListCmds"); u32 list = sceGeListEnQueue(dlPtr, dlWritePtr, -1, listArgs.ptr); DEBUG_LOG(SCEGE, "PPGe enqueued display list %i", list); gpu->EnableInterrupts(true); } } void PPGeScissor(int x1, int y1, int x2, int y2) { _dbg_assert_(x1 >= 0 && x1 <= 480 && x2 >= 0 && x2 <= 480); _dbg_assert_(y1 >= 0 && y1 <= 272 && y2 >= 0 && y2 <= 272); WriteCmd(GE_CMD_SCISSOR1, (y1 << 10) | x1); WriteCmd(GE_CMD_SCISSOR2, ((y2 - 1) << 10) | (x2 - 1)); } void PPGeScissorReset() { PPGeScissor(0, 0, 480, 272); } static const AtlasChar *PPGeGetChar(const AtlasFont &atlasfont, unsigned int cval) { const AtlasChar *c = atlasfont.getChar(cval); if (c == NULL) { // Try to use a replacement character, these come from the below table. // http://unicode.org/cldr/charts/supplemental/character_fallback_substitutions.html switch (cval) { case 0x00A0: // NO-BREAK SPACE case 0x2000: // EN QUAD case 0x2001: // EM QUAD case 0x2002: // EN SPACE case 0x2003: // EM SPACE case 0x2004: // THREE-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE case 0x2007: // FIGURE SPACE case 0x2008: // PUNCTUATION SPACE case 0x2009: // THIN SPACE case 0x200A: // HAIR SPACE case 0x202F: // NARROW NO-BREAK SPACE case 0x205F: // MEDIUM MATHEMATICAL case 0x3000: // IDEOGRAPHIC SPACE c = atlasfont.getChar(0x0020); break; default: c = atlasfont.getChar(0xFFFD); break; } if (c == NULL) c = atlasfont.getChar('?'); } return c; } // Break a single text string into mutiple lines. static AtlasTextMetrics BreakLines(const char *text, const AtlasFont &atlasfont, float x, float y, PPGeAlign align, float scale, float lineHeightScale, int wrapType, float wrapWidth, bool dryRun) { y += atlasfont.ascend * scale; float sx = x, sy = y; // TODO: Can we wrap it smartly on the screen edge? if (wrapWidth <= 0) { wrapWidth = 480.f; } // used for replacing with ellipses float wrapCutoff = 8.0f; const AtlasChar *dot = PPGeGetChar(atlasfont, '.'); if (dot) { wrapCutoff = dot->wx * scale * 3.0f; } float threshold = sx + wrapWidth - wrapCutoff; //const float wrapGreyZone = 2.0f; // Grey zone for punctuations at line ends int numLines = 1; float maxw = 0; float lineHeight = atlasfont.height * lineHeightScale; for (UTF8 utf(text); !utf.end(); ) { float lineWidth = 0; bool skipRest = false; while (!utf.end()) { UTF8 utfWord(utf); float nextWidth = 0; float spaceWidth = 0; int numChars = 0; bool finished = false; while (!utfWord.end() && !finished) { UTF8 utfPrev = utfWord; u32 cval = utfWord.next(); const AtlasChar *ch = PPGeGetChar(atlasfont, cval); if (!ch) { continue; } switch (cval) { // TODO: This list of punctuation is very incomplete. case ',': case '.': case ':': case '!': case ')': case '?': case 0x3001: // IDEOGRAPHIC COMMA case 0x3002: // IDEOGRAPHIC FULL STOP case 0x06D4: // ARABIC FULL STOP case 0xFF01: // FULLWIDTH EXCLAMATION MARK case 0xFF09: // FULLWIDTH RIGHT PARENTHESIS case 0xFF1F: // FULLWIDTH QUESTION MARK // Count this character (punctuation is so clingy), but then we're done. ++numChars; nextWidth += ch->wx * scale; finished = true; break; case ' ': case 0x3000: // IDEOGRAPHIC SPACE spaceWidth += ch->wx * scale; finished = true; break; case '\t': case '\r': case '\n': // Ignore this character and we're done. finished = true; break; default: { // CJK characters can be wrapped more freely. bool isCJK = (cval >= 0x1100 && cval <= 0x11FF); // Hangul Jamo. isCJK = isCJK || (cval >= 0x2E80 && cval <= 0x2FFF); // Kangxi Radicals etc. #if 0 isCJK = isCJK || (cval >= 0x3040 && cval <= 0x31FF); // Hiragana, Katakana, Hangul Compatibility Jamo etc. isCJK = isCJK || (cval >= 0x3200 && cval <= 0x32FF); // CJK Enclosed isCJK = isCJK || (cval >= 0x3300 && cval <= 0x33FF); // CJK Compatibility isCJK = isCJK || (cval >= 0x3400 && cval <= 0x4DB5); // CJK Unified Ideographs Extension A #else isCJK = isCJK || (cval >= 0x3040 && cval <= 0x4DB5); // Above collapsed #endif isCJK = isCJK || (cval >= 0x4E00 && cval <= 0x9FBB); // CJK Unified Ideographs isCJK = isCJK || (cval >= 0xAC00 && cval <= 0xD7AF); // Hangul Syllables isCJK = isCJK || (cval >= 0xF900 && cval <= 0xFAD9); // CJK Compatibility Ideographs isCJK = isCJK || (cval >= 0x20000 && cval <= 0x2A6D6); // CJK Unified Ideographs Extension B isCJK = isCJK || (cval >= 0x2F800 && cval <= 0x2FA1D); // CJK Compatibility Supplement if (isCJK) { if (numChars > 0) { utfWord = utfPrev; finished = true; break; } } } ++numChars; nextWidth += ch->wx * scale; break; } } bool useEllipsis = false; if (wrapType > 0) { if (lineWidth + nextWidth > wrapWidth || skipRest) { if (wrapType & PPGE_LINE_WRAP_WORD) { // TODO: Should check if we have had at least one other word instead. if (lineWidth > 0) { ++numLines; break; } } if (wrapType & PPGE_LINE_USE_ELLIPSIS) { useEllipsis = true; if (skipRest) { numChars = 0; } else if (nextWidth < wrapCutoff) { // The word is too short, so just backspace! x = threshold; } nextWidth = 0; spaceWidth = 0; lineWidth = wrapWidth; } } } for (int i = 0; i < numChars; ++i) { u32 cval = utf.next(); const AtlasChar *c = PPGeGetChar(atlasfont, cval); if (c) { if (useEllipsis && x >= threshold && dot) { if (!dryRun) { AtlasCharVertex cv; // Replace the following part with an ellipsis. cv.x = x + dot->ox * scale; cv.y = y + dot->oy * scale; cv.c = dot; char_one_line.push_back(cv); cv.x += dot->wx * scale; char_one_line.push_back(cv); cv.x += dot->wx * scale; char_one_line.push_back(cv); } skipRest = true; break; } if (!dryRun) { AtlasCharVertex cv; cv.x = x + c->ox * scale; cv.y = y + c->oy * scale; cv.c = c; char_one_line.push_back(cv); } x += c->wx * scale; } } lineWidth += nextWidth; u32 cval = utf.end() ? 0 : utf.next(); if (spaceWidth > 0) { if (!dryRun) { // No need to check c. const AtlasChar *c = PPGeGetChar(atlasfont, cval); AtlasCharVertex cv; cv.x = x + c->ox * scale; cv.y = y + c->oy * scale; cv.c = c; char_one_line.push_back(cv); } x += spaceWidth; lineWidth += spaceWidth; if (wrapType > 0 && lineWidth > wrapWidth) { lineWidth = wrapWidth; } } else if (cval == '\n') { ++numLines; break; } utf = utfWord; } y += lineHeight; x = sx; if (lineWidth > maxw) { maxw = lineWidth; } if (!dryRun) { char_lines.push_back(char_one_line); char_one_line.clear(); } } const float w = maxw; const float h = (float)numLines * lineHeight; if (align & PPGeAlign::ANY) { if (!dryRun) { for (auto i = char_lines.begin(); i != char_lines.end(); ++i) { for (auto j = i->begin(); j != i->end(); ++j) { if (align & PPGeAlign::BOX_HCENTER) j->x -= w / 2.0f; else if (align & PPGeAlign::BOX_RIGHT) j->x -= w; if (align & PPGeAlign::BOX_VCENTER) j->y -= h / 2.0f; else if (align & PPGeAlign::BOX_BOTTOM) j->y -= h; } } } if (align & PPGeAlign::BOX_HCENTER) sx -= w / 2.0f; else if (align & PPGeAlign::BOX_RIGHT) sx -= w; if (align & PPGeAlign::BOX_VCENTER) sy -= h / 2.0f; else if (align & PPGeAlign::BOX_BOTTOM) sy -= h; } AtlasTextMetrics metrics = { sx, sy, w, lineHeight, scale, numLines }; return metrics; } static bool HasTextDrawer() { // We create this on first use so it's on the correct thread. if (textDrawerInited) { return textDrawer != nullptr; } // Should we pass a draw_? Yes! UWP requires it. textDrawer = TextDrawer::Create(g_draw); if (textDrawer) { textDrawer->SetFontScale(1.0f, 1.0f); textDrawer->SetForcedDPIScale(1.0f); textDrawer->SetFont(g_Config.sFont.c_str(), 18, 0); } textDrawerInited = true; return textDrawer != nullptr; } static std::string PPGeSanitizeText(const std::string &text) { return SanitizeUTF8(text); } void PPGeMeasureText(float *w, float *h, const char *text, float scale, int WrapType, int wrapWidth) { std::string s = PPGeSanitizeText(text); if (HasTextDrawer()) { std::string s2 = ReplaceAll(s, "&", "&&"); 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(s2.c_str(), s2.size(), b, &mw, &mh, dtalign); if (w) *w = mw; if (h) *h = mh; return; } if (!g_ppge_atlas.IsMetadataLoaded() || g_ppge_atlas.num_fonts < 1) { if (w) *w = 0; if (h) *h = 0; return; } const AtlasFont &atlasfont = g_ppge_atlas.fonts[0]; AtlasTextMetrics metrics = BreakLines(s.c_str(), atlasfont, 0, 0, PPGeAlign::BOX_TOP, scale, scale, WrapType, wrapWidth, true); if (w) *w = metrics.maxWidth; if (h) *h = metrics.lineHeight * metrics.numLines; } void PPGePrepareText(const char *text, float x, float y, PPGeAlign align, float scale, float lineHeightScale, int WrapType, int wrapWidth) { const AtlasFont &atlasfont = g_ppge_atlas.fonts[0]; if (!g_ppge_atlas.IsMetadataLoaded() || g_ppge_atlas.num_fonts < 1) { return; } char_lines_metrics = BreakLines(text, atlasfont, x, y, align, scale, lineHeightScale, WrapType, wrapWidth, false); } static void PPGeResetCurrentText() { char_one_line.clear(); char_lines.clear(); AtlasTextMetrics zeroBox = { 0 }; char_lines_metrics = zeroBox; } // Draws some text using the one font we have in the atlas. void PPGeDrawCurrentText(u32 color) { // If the atlas is larger than 512x512, need to use windows into it. bool useTextureWindow = g_Config.bSoftwareRendering && (atlasWidth > 512 || atlasHeight > 512); uint32_t texturePosX = 0; uint32_t texturePosY = 0; // Use half the available size just in case a character straddles a boundary. const float textureMaxPosX = !useTextureWindow ? 1.0f : atlasWidth / 256.0f; const float textureMaxPosY = !useTextureWindow ? 1.0f : atlasHeight / 256.0f; // These are the actual scale used. const float textureWindowW = !useTextureWindow ? atlasWidth : 512.0f; const float textureWindowH = !useTextureWindow ? atlasHeight : 512.0f; const float textureScaleX = !useTextureWindow ? 1.0f : atlasWidth / 512.0f; const float textureScaleY = !useTextureWindow ? 1.0f : atlasWidth / 512.0f; if (dlPtr) { float scale = char_lines_metrics.scale; if (useTextureWindow) { WriteCmd(GE_CMD_TEXWRAP, 0); WriteCmd(GE_CMD_TEXSIZE0, 9 | (9 << 8)); } BeginVertexData(); for (auto i = char_lines.begin(); i != char_lines.end(); ++i) { for (auto j = i->begin(); j != i->end(); ++j) { const AtlasChar &c = *j->c; int wantedPosX = (int)floorf(c.sx * textureMaxPosX); int wantedPosY = (int)floorf(c.sy * textureMaxPosY); if (useTextureWindow && (wantedPosX != texturePosX || wantedPosY != texturePosY)) { EndVertexDataAndDraw(GE_PRIM_RECTANGLES); uint32_t offset = atlasWidth * wantedPosY * 256 + wantedPosX * 256; WriteCmd(GE_CMD_TEXADDR0, (atlasPtr & 0xFFFFF0) + offset / 2); texturePosX = wantedPosX; texturePosY = wantedPosY; BeginVertexData(); } float sx = (c.sx - texturePosX / textureMaxPosX) * textureScaleX; float sy = (c.sy - texturePosY / textureMaxPosY) * textureScaleY; float ex = (c.ex - texturePosX / textureMaxPosX) * textureScaleX; float ey = (c.ey - texturePosY / textureMaxPosY) * textureScaleY; float cx1 = j->x; float cy1 = j->y; float cx2 = cx1 + c.pw * scale; float cy2 = cy1 + c.ph * scale; Vertex(cx1, cy1, sx, sy, textureWindowW, textureWindowH, color); Vertex(cx2, cy2, ex, ey, textureWindowW, textureWindowH, color); } } EndVertexDataAndDraw(GE_PRIM_RECTANGLES); } PPGeResetCurrentText(); if (useTextureWindow) { PPGeSetDefaultTexture(); } } // Return a value such that (1 << value) >= x inline int GetPow2(int x) { int ret = 31 - clz32_nonzero(x | 1); if ((1 << ret) < x) ret++; return ret; } static PPGeTextDrawerImage PPGeGetTextImage(const char *text, const PPGeStyle &style, float maxWidth, bool wrap) { int tdalign = 0; tdalign |= FLAG_ELLIPSIZE_TEXT; if (wrap) { tdalign |= FLAG_WRAP_TEXT; } PPGeTextDrawerCacheKey key{ text, tdalign, maxWidth / style.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(style.scale, style.scale); Bounds b(0, 0, maxWidth, 272.0f); std::string cleaned = ReplaceAll(text, "\r", ""); textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, cleaned.c_str(), b, tdalign); int bufwBytes = ((im.entry.bmWidth + 31) / 32) * 16; u32 sz = bufwBytes * (im.entry.bmHeight + 1); u32 origSz = sz; im.ptr = __PPGeDoAlloc(sz, true, "PPGeText"); if (bitmapData.size() & 1) bitmapData.resize(bitmapData.size() + 1); if (im.ptr) { int wBytes = (im.entry.bmWidth + 1) / 2; u8 *ramPtr = Memory::GetPointerWriteRange(im.ptr, sz); for (int y = 0; y < im.entry.bmHeight; ++y) { 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; textDrawerImages[key] = im; } return im; } static void PPGeDrawTextImage(const PPGeTextDrawerImage &im, float x, float y, const PPGeStyle &style) { if (!im.ptr) { return; } // Sometimes our texture can be larger than 512 wide, which means we need to tile it. int bufw = ((im.entry.bmWidth + 31) / 32) * 32; int tile1bmWidth = std::min(512, im.entry.bmWidth); int tile2bmWidth = tile1bmWidth == 512 ? im.entry.bmWidth - 512 : 0; bool hasTile2 = tile2bmWidth != 0; // Now figure the tile shift value. int tile1wp2 = GetPow2(tile1bmWidth); int tile2wp2 = hasTile2 ? GetPow2(tile2bmWidth) : 0; 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, tile1wp2 | (hp2 << 8)); WriteCmd(GE_CMD_TEXFLUSH, 0); float w = im.entry.width * style.scale; float h = im.entry.height * style.scale; if (style.align & PPGeAlign::BOX_HCENTER) x -= w / 2.0f; else if (style.align & PPGeAlign::BOX_RIGHT) x -= w; if (style.align & PPGeAlign::BOX_VCENTER) y -= h / 2.0f; else if (style.align & PPGeAlign::BOX_BOTTOM) y -= h; BeginVertexData(); float tile2x = 512 * style.scale; float tile1w = hasTile2 ? 512.0f * style.scale : w; float tile2w = hasTile2 ? w - tile1w : 0.0f; float tile1u1 = hasTile2 ? 1.0f : (float)im.entry.width / (1 << tile1wp2); float tile2u1 = hasTile2 ? (float)(im.entry.width - 512) / (1 << tile2wp2) : 0.0f; float v1 = (float)im.entry.height / (1 << hp2); if (style.hasShadow) { // Draw more shadows for a blurrier shadow. u32 shadowColor = alphaMul(style.shadowColor, 0.35f); for (float dy = 0.0f; dy <= 2.0f; dy += 1.0f) { for (float dx = 0.0f; dx <= 1.0f; dx += 0.5f) { if (dx == 0.0f && dy == 0.0f) continue; Vertex(x + dx, y + dy, 0, 0, 1 << tile1wp2, 1 << hp2, shadowColor); Vertex(x + dx + tile1w, y + dy + h, tile1u1, v1, 1 << tile1wp2, 1 << hp2, shadowColor); } } // Did we need another tile? if (hasTile2) { // Flush to change to tile2. EndVertexDataAndDraw(GE_PRIM_RECTANGLES); BeginVertexData(); WriteCmd(GE_CMD_TEXADDR0, (im.ptr & 0xFFFFF0) + 256); WriteCmd(GE_CMD_TEXSIZE0, tile2wp2 | (hp2 << 8)); for (float dy = 0.0f; dy <= 2.0f; dy += 1.0f) { for (float dx = 0.0f; dx <= 1.0f; dx += 0.5f) { if (dx == 0.0f && dy == 0.0f) continue; Vertex(x + tile2x + dx, y + dy, 0, 0, 1 << tile2wp2, 1 << hp2, shadowColor); Vertex(x + tile2x + dx + tile2w, y + dy + h, tile2u1, v1, 1 << tile2wp2, 1 << hp2, shadowColor); } } // Return to tile1 for the text. EndVertexDataAndDraw(GE_PRIM_RECTANGLES); BeginVertexData(); WriteCmd(GE_CMD_TEXADDR0, im.ptr & 0xFFFFF0); WriteCmd(GE_CMD_TEXSIZE0, tile1wp2 | (hp2 << 8)); } } Vertex(x, y, 0, 0, 1 << tile1wp2, 1 << hp2, style.color); Vertex(x + tile1w, y + h, tile1u1, v1, 1 << tile1wp2, 1 << hp2, style.color); if (hasTile2) { EndVertexDataAndDraw(GE_PRIM_RECTANGLES); BeginVertexData(); WriteCmd(GE_CMD_TEXADDR0, (im.ptr & 0xFFFFF0) + 256); WriteCmd(GE_CMD_TEXSIZE0, tile2wp2 | (hp2 << 8)); Vertex(x + tile2x, y, 0, 0, 1 << tile2wp2, 1 << hp2, style.color); Vertex(x + tile2x + tile2w, y + h, tile2u1, v1, 1 << tile2wp2, 1 << hp2, style.color); } EndVertexDataAndDraw(GE_PRIM_RECTANGLES); PPGeSetDefaultTexture(); } static void PPGeDecimateTextImages(int age) { // Do this always, in case the platform has no TextDrawer but save state did. for (auto it = textDrawerImages.begin(); it != textDrawerImages.end(); ) { if (gpuStats.numFlips - it->second.entry.lastUsedFrame >= age) { kernelMemory.Free(it->second.ptr); it = textDrawerImages.erase(it); } else { ++it; } } } void PPGeDrawText(const char *text, float x, float y, const PPGeStyle &style) { if (!text) { return; } std::string str = PPGeSanitizeText(text); if (str.empty()) { return; } if (HasTextDrawer()) { PPGeTextDrawerImage im = PPGeGetTextImage(ReplaceAll(str, "&", "&&").c_str(), style, 480.0f - x, false); if (im.ptr) { PPGeDrawTextImage(im, x, y, style); return; } } if (style.hasShadow) { // This doesn't have the nicer shadow because it's so many verts. PPGePrepareText(text, x + 1, y + 2, style.align, style.scale, style.scale, PPGE_LINE_USE_ELLIPSIS); PPGeDrawCurrentText(style.shadowColor); } PPGePrepareText(text, x, y, style.align, style.scale, style.scale, PPGE_LINE_USE_ELLIPSIS); PPGeDrawCurrentText(style.color); } static std::string_view StripTrailingWhite(std::string_view s) { size_t lastChar = s.find_last_not_of(" \t\r\n"); if (lastChar != s.npos) { return s.substr(0, lastChar + 1); } return s; } static std::string_view CropLinesToCount(std::string_view s, int numLines) { std::vector lines; SplitString(s, '\n', lines); if ((int)lines.size() <= numLines) { return s; } size_t len = 0; for (int i = 0; i < numLines; ++i) { len += lines[i].size() + 1; } return s.substr(0, len); } void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, float wrapHeight, const PPGeStyle &style) { std::string s = PPGeSanitizeText(text); if (wrapHeight != 0.0f) { s = StripTrailingWhite(s); } int zoom = (PSP_CoreParameter().pixelHeight + 479) / 480; zoom = std::min(zoom, PSP_CoreParameter().renderScaleFactor); float maxScaleDown = zoom == 1 ? 1.3f : 2.0f; if (HasTextDrawer()) { std::string s2 = ReplaceAll(s, "&", "&&"); float actualWidth, actualHeight; Bounds b(0, 0, wrapWidth <= 0 ? 480.0f - x : wrapWidth, wrapHeight); int tdalign = 0; textDrawer->SetFontScale(style.scale, style.scale); textDrawer->MeasureStringRect(s2.c_str(), s2.size(), b, &actualWidth, &actualHeight, tdalign | FLAG_WRAP_TEXT); // Check if we need to scale the text down to fit better. PPGeStyle adjustedStyle = style; 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. s2 = StripTrailingWhite(CropLinesToCount(s2, (int)maxLines)); s2.append("\n..."); } adjustedStyle.scale *= wrapHeight / actualHeight; } PPGeTextDrawerImage im = PPGeGetTextImage(s2.c_str(), adjustedStyle, wrapWidth <= 0 ? 480.0f - x : wrapWidth, true); if (im.ptr) { PPGeDrawTextImage(im, x, y, adjustedStyle); return; } } int sx = style.hasShadow ? 1 : 0; int sy = style.hasShadow ? 2 : 0; PPGePrepareText(s.c_str(), x + sx, y + sy, style.align, style.scale, style.scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); float scale = style.scale; float lineHeightScale = style.scale; float actualHeight = char_lines_metrics.lineHeight * char_lines_metrics.numLines; if (wrapHeight != 0.0f && actualHeight > wrapHeight) { if (actualHeight > wrapHeight * maxScaleDown) { float maxLines = floor(wrapHeight * maxScaleDown / char_lines_metrics.lineHeight); actualHeight = (maxLines + 1) * char_lines_metrics.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)); s.append("\n..."); } // Measure the text again after scaling down. PPGeResetCurrentText(); float reduced = style.scale * wrapHeight / actualHeight; // Try to keep the font as large as possible, so reduce the line height some. scale = reduced * 1.15f; lineHeightScale = reduced; PPGePrepareText(s.c_str(), x + sx, y + sy, style.align, scale, lineHeightScale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); } if (style.hasShadow) { // This doesn't have the nicer shadow because it's so many verts. PPGeDrawCurrentText(style.shadowColor); PPGePrepareText(s.c_str(), x, y, style.align, scale, lineHeightScale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth); } PPGeDrawCurrentText(style.color); } // Draws a "4-patch" for button-like things that can be resized void PPGeDraw4Patch(ImageID atlasImage, float x, float y, float w, float h, u32 color) { if (!dlPtr) return; const AtlasImage *img = g_ppge_atlas.getImage(atlasImage); if (!img) return; float borderx = img->w / 20; float bordery = img->h / 20; float u1 = img->u1, uhalf = (img->u1 + img->u2) / 2, u2 = img->u2; float v1 = img->v1, vhalf = (img->v1 + img->v2) / 2, v2 = img->v2; float xmid1 = x + borderx; float xmid2 = x + w - borderx; float ymid1 = y + bordery; float ymid2 = y + h - bordery; float x2 = x + w; float y2 = y + h; BeginVertexData(); // Top row Vertex(x, y, u1, v1, atlasWidth, atlasHeight, color); Vertex(xmid1, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid1, y, uhalf, v1, atlasWidth, atlasHeight, color); Vertex(xmid2, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid2, y, uhalf, v1, atlasWidth, atlasHeight, color); Vertex(x2, ymid1, u2, vhalf, atlasWidth, atlasHeight, color); // Middle row Vertex(x, ymid1, u1, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid1, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid1, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid2, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid2, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(x2, ymid2, u2, v2, atlasWidth, atlasHeight, color); // Bottom row Vertex(x, ymid2, u1, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid1, y2, uhalf, v2, atlasWidth, atlasHeight, color); Vertex(xmid1, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(xmid2, y2, uhalf, v2, atlasWidth, atlasHeight, color); Vertex(xmid2, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color); Vertex(x2, y2, u2, v2, atlasWidth, atlasHeight, color); EndVertexDataAndDraw(GE_PRIM_RECTANGLES); } void PPGeDrawRect(float x1, float y1, float x2, float y2, u32 color) { if (!dlPtr) return; WriteCmd(GE_CMD_TEXTUREMAPENABLE, 0); BeginVertexData(); Vertex(x1, y1, 0, 0, 0, 0, color); Vertex(x2, y2, 0, 0, 0, 0, color); EndVertexDataAndDraw(GE_PRIM_RECTANGLES); WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1); } // Just blits an image to the screen, multiplied with the color. void PPGeDrawImage(ImageID atlasImage, float x, float y, const PPGeStyle &style) { if (!dlPtr) return; const AtlasImage *img = g_ppge_atlas.getImage(atlasImage); if (!img) { return; } float w = img->w; float h = img->h; BeginVertexData(); if (style.hasShadow) { for (float dy = 0.0f; dy <= 2.0f; dy += 1.0f) { for (float dx = 0.0f; dx <= 1.0f; dx += 0.5f) { if (dx == 0.0f && dy == 0.0f) continue; Vertex(x + dx, y + dy, img->u1, img->v1, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f)); Vertex(x + dx + w, y + dy + h, img->u2, img->v2, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f)); } } } Vertex(x, y, img->u1, img->v1, atlasWidth, atlasHeight, style.color); Vertex(x + w, y + h, img->u2, img->v2, atlasWidth, atlasHeight, style.color); EndVertexDataAndDraw(GE_PRIM_RECTANGLES); } void PPGeDrawImage(ImageID atlasImage, float x, float y, float w, float h, const PPGeStyle &style) { if (!dlPtr) return; const AtlasImage *img = g_ppge_atlas.getImage(atlasImage); if (!img) { return; } BeginVertexData(); if (style.hasShadow) { for (float dy = 0.0f; dy <= 2.0f; dy += 1.0f) { for (float dx = 0.0f; dx <= 1.0f; dx += 0.5f) { if (dx == 0.0f && dy == 0.0f) continue; Vertex(x + dx, y + dy, img->u1, img->v1, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f)); Vertex(x + dx + w, y + dy + h, img->u2, img->v2, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f)); } } } Vertex(x, y, img->u1, img->v1, atlasWidth, atlasHeight, style.color); Vertex(x + w, y + h, img->u2, img->v2, atlasWidth, atlasHeight, style.color); EndVertexDataAndDraw(GE_PRIM_RECTANGLES); } void PPGeDrawImage(float x, float y, float w, float h, float u1, float v1, float u2, float v2, int tw, int th, const PPGeImageStyle &style) { if (!dlPtr) return; BeginVertexData(); Vertex(x, y, u1, v1, tw, th, style.color); Vertex(x + w, y + h, u2, v2, tw, th, style.color); EndVertexDataAndDraw(GE_PRIM_RECTANGLES); } void PPGeSetDefaultTexture() { WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1); int wp2 = GetPow2(atlasWidth); int hp2 = GetPow2(atlasHeight); WriteCmd(GE_CMD_CLUTADDR, palette.ptr & 0xFFFFF0); WriteCmd(GE_CMD_CLUTADDRUPPER, (palette.ptr & 0xFF000000) >> 8); WriteCmd(GE_CMD_CLUTFORMAT, 0x00FF02); WriteCmd(GE_CMD_LOADCLUT, 2); WriteCmd(GE_CMD_TEXSIZE0, wp2 | (hp2 << 8)); WriteCmd(GE_CMD_TEXMAPMODE, 0 | (1 << 8)); WriteCmd(GE_CMD_TEXMODE, 0); WriteCmd(GE_CMD_TEXFORMAT, GE_TFMT_CLUT4); // 4-bit CLUT WriteCmd(GE_CMD_TEXFILTER, (1 << 8) | 1); // mag = LINEAR min = LINEAR WriteCmd(GE_CMD_TEXWRAP, (1 << 8) | 1); // clamp texture wrapping WriteCmd(GE_CMD_TEXFUNC, (0 << 16) | (1 << 8) | 0); // RGBA texture reads, modulate, no color doubling WriteCmd(GE_CMD_TEXADDR0, atlasPtr & 0xFFFFF0); WriteCmd(GE_CMD_TEXBUFWIDTH0, atlasWidth | ((atlasPtr & 0xFF000000) >> 8)); WriteCmd(GE_CMD_TEXFLUSH, 0); } void PPGeSetTexture(u32 dataAddr, int width, int height) { WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1); int wp2 = GetPow2(width); int hp2 = GetPow2(height); WriteCmd(GE_CMD_TEXSIZE0, wp2 | (hp2 << 8)); WriteCmd(GE_CMD_TEXMAPMODE, 0 | (1 << 8)); WriteCmd(GE_CMD_TEXMODE, 0); WriteCmd(GE_CMD_TEXFORMAT, GE_TFMT_8888); // 4444 WriteCmd(GE_CMD_TEXFILTER, (1 << 8) | 1); // mag = LINEAR min = LINEAR WriteCmd(GE_CMD_TEXWRAP, (1 << 8) | 1); // clamp texture wrapping WriteCmd(GE_CMD_TEXFUNC, (0 << 16) | (1 << 8) | 0); // RGBA texture reads, modulate, no color doubling WriteCmd(GE_CMD_TEXADDR0, dataAddr & 0xFFFFF0); WriteCmd(GE_CMD_TEXBUFWIDTH0, width | ((dataAddr & 0xFF000000) >> 8)); WriteCmd(GE_CMD_TEXFLUSH, 0); } void PPGeDisableTexture() { WriteCmd(GE_CMD_TEXTUREMAPENABLE, 0); } std::vector PPGeImage::loadedTextures_; PPGeImage::PPGeImage(const std::string &pspFilename) : filename_(pspFilename) { } PPGeImage::PPGeImage(u32 pngPointer, size_t pngSize) : filename_(""), png_(pngPointer), size_(pngSize) { } PPGeImage::~PPGeImage() { Free(); } bool PPGeImage::Load() { loadFailed_ = false; Free(); // In case it fails to load. width_ = 0; height_ = 0; unsigned char *textureData; int success; if (filename_.empty()) { success = pngLoadPtr(Memory::GetPointerRange(png_, (u32)size_), size_, &width_, &height_, &textureData); } else { std::vector pngData; if (pspFileSystem.ReadEntireFile(filename_, pngData) < 0) { WARN_LOG(SCEGE, "PPGeImage cannot load file %s", filename_.c_str()); loadFailed_ = true; return false; } success = pngLoadPtr((const unsigned char *)&pngData[0], pngData.size(), &width_, &height_, &textureData); } if (!success) { WARN_LOG(SCEGE, "Bad PPGeImage - not a valid png"); loadFailed_ = true; return false; } u32 dataSize = width_ * height_ * 4; u32 texSize = dataSize + width_ * 4; texture_ = __PPGeDoAlloc(texSize, true, "Savedata Icon"); if (texture_ == 0) { free(textureData); WARN_LOG(SCEGE, "Bad PPGeImage - unable to allocate space for texture"); // Don't set loadFailed_ here, we'll try again if there's more memory later. return false; } Memory::Memcpy(texture_, textureData, dataSize, "PPGeTex"); Memory::Memset(texture_ + dataSize, 0, texSize - dataSize, "PPGeTexClear"); free(textureData); lastFrame_ = gpuStats.numFlips; loadedTextures_.push_back(this); return true; } bool PPGeImage::IsValid() { if (loadFailed_) return false; if (texture_ == 0) { Decimate(); return Load(); } return true; } void PPGeImage::Free() { if (texture_ != 0) { kernelMemory.Free(texture_); texture_ = 0; loadedTextures_.erase(std::remove(loadedTextures_.begin(), loadedTextures_.end(), this), loadedTextures_.end()); loadFailed_ = false; } } void PPGeImage::DoState(PointerWrap &p) { auto s = p.Section("PPGeImage", 1, 2); if (!s) return; Do(p, filename_); Do(p, png_); Do(p, size_); Do(p, texture_); Do(p, width_); Do(p, height_); Do(p, lastFrame_); if (s >= 2) { Do(p, loadFailed_); } else { loadFailed_ = false; } } void PPGeImage::CompatLoad(u32 texture, int width, int height) { // Won't be reloadable, so don't add to loadedTextures_. texture_ = texture; width_ = width; height_ = height; loadFailed_ = false; } void PPGeImage::Decimate(int age) { int tooOldFrame = gpuStats.numFlips - age; for (size_t i = 0; i < loadedTextures_.size(); ++i) { if (loadedTextures_[i]->lastFrame_ < tooOldFrame) { loadedTextures_[i]->Free(); // That altered loadedTextures_. --i; } } } void PPGeImage::SetTexture() { if (texture_ == 0 && !loadFailed_) { Decimate(); Load(); } if (texture_ != 0) { lastFrame_ = gpuStats.numFlips; PPGeSetTexture(texture_, width_, height_); } else { PPGeDisableTexture(); } } void PPGeNotifyFrame() { if (textDrawer) { textDrawer->OncePerFrame(); } PPGeDecimateTextImages(); PPGeImage::Decimate(); if (atlasRequiresReset) { __PPGeShutdown(); __PPGeInit(); } }