1 // Copyright (c) 2012- PPSSPP Project.
2
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
11
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18 #include <algorithm>
19
20 #include "ext/xxhash.h"
21 #include "Common/Data/Color/RGBAUtil.h"
22 #include "Common/File/VFS/VFS.h"
23 #include "Common/Data/Format/ZIMLoad.h"
24 #include "Common/Data/Format/PNGLoad.h"
25 #include "Common/Data/Encoding/Utf8.h"
26 #include "Common/Render/TextureAtlas.h"
27 #include "Common/Render/Text/draw_text.h"
28 #include "Common/Serialize/Serializer.h"
29 #include "Common/Serialize/SerializeFuncs.h"
30 #include "Common/StringUtils.h"
31 #include "Core/HDRemaster.h"
32 #include "Core/Host.h"
33 #include "GPU/ge_constants.h"
34 #include "GPU/GPUState.h"
35 #include "GPU/GPUInterface.h"
36 #include "Core/FileSystems/MetaFileSystem.h"
37 #include "Core/Util/PPGeDraw.h"
38 #include "Core/HLE/sceKernel.h"
39 #include "Core/HLE/sceKernelMemory.h"
40 #include "Core/HLE/sceGe.h"
41 #include "Core/MemMapHelpers.h"
42 #include "Core/System.h"
43
44 static Atlas g_ppge_atlas;
45 static Draw::DrawContext *g_draw = nullptr;
46
47 static u32 atlasPtr;
48 static int atlasWidth;
49 static int atlasHeight;
50 static uint64_t atlasHash;
51 static bool atlasRequiresReset;
52
53 struct PPGeVertex {
54 u16_le u, v;
55 u32_le color;
56 float_le x, y, z;
57 };
58
59 struct PPGeRemasterVertex {
60 float_le u, v;
61 u32_le color;
62 float_le x, y, z;
63 };
64
65 static PSPPointer<PspGeListArgs> listArgs;
66 static u32 listArgsSize = sizeof(PspGeListArgs);
67 static u32 savedContextPtr;
68 static u32 savedContextSize = 512 * 4;
69
70 // Display list writer
71 static u32 dlPtr;
72 static u32 dlWritePtr;
73 static u32 dlSize = 0x10000; // should be enough for a frame of gui...
74
75 static u32 dataPtr;
76 static u32 dataWritePtr;
77 static u32 dataSize = 0x10000; // should be enough for a frame of gui...
78
79 static PSPPointer<u16_le> palette;
80 static u32 paletteSize = sizeof(u16) * 16;
81
82 // Vertex collector
83 static u32 vertexStart;
84 static u32 vertexCount;
85
86 // Used for formatting text
87 struct AtlasCharVertex
88 {
89 float x;
90 float y;
91 const AtlasChar *c;
92 };
93
94 struct AtlasTextMetrics
95 {
96 float x;
97 float y;
98 float maxWidth;
99 float lineHeight;
100 float scale;
101 int numLines;
102
103 };
104
105 typedef std::vector<AtlasCharVertex> AtlasCharLine;
106 typedef std::vector<AtlasCharLine> AtlasLineArray;
107
108 static AtlasCharLine char_one_line;
109 static AtlasLineArray char_lines;
110 static AtlasTextMetrics char_lines_metrics;
111
112 static bool textDrawerInited = false;
113 static TextDrawer *textDrawer = nullptr;
114 struct PPGeTextDrawerCacheKey {
operator <PPGeTextDrawerCacheKey115 bool operator < (const PPGeTextDrawerCacheKey &other) const {
116 if (align != other.align)
117 return align < other.align;
118 if (wrapWidth != other.wrapWidth)
119 return wrapWidth < other.wrapWidth;
120 return text < other.text;
121 }
122 std::string text;
123 int align;
124 float wrapWidth;
125 };
126 struct PPGeTextDrawerImage {
127 TextStringEntry entry;
128 u32 ptr;
129 };
130 static std::map<PPGeTextDrawerCacheKey, PPGeTextDrawerImage> textDrawerImages;
131
PPGeSetDrawContext(Draw::DrawContext * draw)132 void PPGeSetDrawContext(Draw::DrawContext *draw) {
133 g_draw = draw;
134 }
135
136 // Overwrite the current text lines buffer so it can be drawn later.
137 void PPGePrepareText(const char *text, float x, float y, PPGeAlign align, float scale, float lineHeightScale,
138 int WrapType = PPGE_LINE_NONE, int wrapWidth = 0);
139
140 // These functions must be called between PPGeBegin and PPGeEnd.
141
142 // Draw currently buffered text using the state from PPGeGetTextBoundingBox() call.
143 // Clears the buffer and state when done.
144 void PPGeDrawCurrentText(u32 color = 0xFFFFFFFF);
145
146 static void PPGeDecimateTextImages(int age = 97);
147
148 void PPGeSetTexture(u32 dataAddr, int width, int height);
149
150 //only 0xFFFFFF of data is used
WriteCmd(u8 cmd,u32 data)151 static void WriteCmd(u8 cmd, u32 data) {
152 Memory::Write_U32((cmd << 24) | (data & 0xFFFFFF), dlWritePtr);
153 dlWritePtr += 4;
154 _dbg_assert_(dlWritePtr <= dlPtr + dlSize);
155 }
156
WriteCmdAddrWithBase(u8 cmd,u32 addr)157 static void WriteCmdAddrWithBase(u8 cmd, u32 addr) {
158 WriteCmd(GE_CMD_BASE, (addr >> 8) & 0xFF0000);
159 WriteCmd(cmd, addr & 0xFFFFFF);
160 }
161
162 /*
163 static void WriteCmdFloat(u8 cmd, float f) {
164 union {
165 float fl;
166 u32 u;
167 } conv;
168 conv.fl = f;
169 WriteCmd(cmd, conv.u >> 8);
170 }*/
171
BeginVertexData()172 static void BeginVertexData() {
173 vertexCount = 0;
174 vertexStart = dataWritePtr;
175 }
176
Vertex(float x,float y,float u,float v,int tw,int th,u32 color=0xFFFFFFFF)177 static void Vertex(float x, float y, float u, float v, int tw, int th, u32 color = 0xFFFFFFFF) {
178 if (g_RemasterMode) {
179 PPGeRemasterVertex vtx;
180 vtx.x = x; vtx.y = y; vtx.z = 0;
181 vtx.u = u * tw; vtx.v = v * th;
182 vtx.color = color;
183 Memory::WriteStruct(dataWritePtr, &vtx);
184 dataWritePtr += sizeof(vtx);
185 } else {
186 PPGeVertex vtx;
187 vtx.x = x; vtx.y = y; vtx.z = 0;
188 vtx.u = u * tw; vtx.v = v * th;
189 vtx.color = color;
190 Memory::WriteStruct(dataWritePtr, &vtx);
191 dataWritePtr += sizeof(vtx);
192 }
193 _dbg_assert_(dataWritePtr <= dataPtr + dataSize);
194 vertexCount++;
195 }
196
EndVertexDataAndDraw(int prim)197 static void EndVertexDataAndDraw(int prim) {
198 WriteCmdAddrWithBase(GE_CMD_VADDR, vertexStart);
199 WriteCmd(GE_CMD_PRIM, (prim << 16) | vertexCount);
200 }
201
PPGeIsFontTextureAddress(u32 addr)202 bool PPGeIsFontTextureAddress(u32 addr) {
203 return addr == atlasPtr;
204 }
205
__PPGeDoAlloc(u32 & size,bool fromTop,const char * name)206 static u32 __PPGeDoAlloc(u32 &size, bool fromTop, const char *name) {
207 u32 ptr = kernelMemory.Alloc(size, fromTop, name);
208 // Didn't get it, try again after decimating images.
209 if (ptr == (u32)-1) {
210 PPGeDecimateTextImages(4);
211 PPGeImage::Decimate(4);
212
213 ptr = kernelMemory.Alloc(size, fromTop, name);
214 if (ptr == (u32)-1) {
215 return 0;
216 }
217 }
218 return ptr;
219 }
220
__PPGeSetupListArgs()221 void __PPGeSetupListArgs()
222 {
223 if (listArgs.IsValid())
224 return;
225
226 listArgs = __PPGeDoAlloc(listArgsSize, false, "PPGe List Args");
227 if (listArgs.IsValid()) {
228 listArgs->size = 8;
229 if (savedContextPtr == 0)
230 savedContextPtr = __PPGeDoAlloc(savedContextSize, false, "PPGe Saved Context");
231 listArgs->context = savedContextPtr;
232 }
233 }
234
__PPGeInit()235 void __PPGeInit() {
236 // PPGe isn't really important for headless, and LoadZIM takes a long time.
237 bool skipZIM = host->ShouldSkipUI();
238
239 u8 *imageData[12]{};
240 int width[12]{};
241 int height[12]{};
242 int flags = 0;
243
244 bool loadedZIM = !skipZIM && LoadZIM("ppge_atlas.zim", width, height, &flags, imageData);
245 if (!skipZIM && !loadedZIM) {
246 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.");
247 }
248
249 if (loadedZIM) {
250 size_t atlas_data_size;
251 if (!g_ppge_atlas.IsMetadataLoaded()) {
252 uint8_t *atlas_data = VFSReadFile("ppge_atlas.meta", &atlas_data_size);
253 if (atlas_data)
254 g_ppge_atlas.Load(atlas_data, atlas_data_size);
255 delete[] atlas_data;
256 }
257 }
258
259 u32 atlasSize = height[0] * width[0] / 2; // it's a 4-bit paletted texture in ram
260 atlasWidth = width[0];
261 atlasHeight = height[0];
262 dlPtr = __PPGeDoAlloc(dlSize, false, "PPGe Display List");
263 dataPtr = __PPGeDoAlloc(dataSize, false, "PPGe Vertex Data");
264 __PPGeSetupListArgs();
265 atlasPtr = atlasSize == 0 ? 0 : __PPGeDoAlloc(atlasSize, false, "PPGe Atlas Texture");
266 palette = __PPGeDoAlloc(paletteSize, false, "PPGe Texture Palette");
267
268 // Generate 16-greyscale palette. All PPGe graphics are greyscale so we can use a tiny paletted texture.
269 for (int i = 0; i < 16; i++) {
270 int val = i;
271 palette[i] = (val << 12) | 0xFFF;
272 }
273
274 const u32_le *imagePtr = (u32_le *)imageData[0];
275 u8 *ramPtr = atlasPtr == 0 ? nullptr : (u8 *)Memory::GetPointer(atlasPtr);
276
277 // Palettize to 4-bit, the easy way.
278 for (int i = 0; i < width[0] * height[0] / 2; i++) {
279 // Each pixel is 16 bits, so this loads two pixels.
280 u32 c = imagePtr[i];
281 // It's white anyway, so we only look at one channel of each pixel.
282 int a1 = (c & 0x0000000F) >> 0;
283 int a2 = (c & 0x000F0000) >> 16;
284 u8 cval = (a2 << 4) | a1;
285 ramPtr[i] = cval;
286 }
287 atlasHash = XXH3_64bits(ramPtr, atlasWidth * atlasHeight / 2);
288
289 free(imageData[0]);
290
291 // We can't create it here, because Android needs it on the right thread.
292 // Avoid creating ever on headless just to be safe.
293 textDrawerInited = PSP_CoreParameter().headLess;
294 textDrawer = nullptr;
295 textDrawerImages.clear();
296
297 atlasRequiresReset = false;
298
299 INFO_LOG(SCEGE, "PPGe drawing library initialized. DL: %08x Data: %08x Atlas: %08x (%i) Args: %08x",
300 dlPtr, dataPtr, atlasPtr, atlasSize, listArgs.ptr);
301 }
302
__PPGeDoState(PointerWrap & p)303 void __PPGeDoState(PointerWrap &p)
304 {
305 auto s = p.Section("PPGeDraw", 1, 4);
306 if (!s)
307 return;
308
309 Do(p, atlasPtr);
310 Do(p, atlasWidth);
311 Do(p, atlasHeight);
312 Do(p, palette);
313
314 // If the atlas the save state was created with differs from the current one, reload.
315 uint64_t savedHash = atlasHash;
316 if (s >= 4) {
317 Do(p, savedHash);
318 } else {
319 // Memory was already updated by this point, so check directly.
320 if (atlasPtr != 0) {
321 savedHash = XXH3_64bits(Memory::GetPointer(atlasPtr), atlasWidth * atlasHeight / 2);
322 } else {
323 savedHash ^= 1;
324 }
325 }
326 atlasRequiresReset = savedHash != atlasHash;
327
328 Do(p, savedContextPtr);
329 Do(p, savedContextSize);
330
331 if (s == 1) {
332 listArgs = 0;
333 } else {
334 Do(p, listArgs);
335 }
336
337 if (s >= 3) {
338 uint32_t sz = (uint32_t)textDrawerImages.size();
339 Do(p, sz);
340
341 switch (p.mode) {
342 case PointerWrap::MODE_READ:
343 textDrawerImages.clear();
344 for (uint32_t i = 0; i < sz; ++i) {
345 // We only care about the pointers, so we can free them. We'll decimate right away.
346 PPGeTextDrawerCacheKey key{ StringFromFormat("__savestate__%d", i), -1, -1 };
347 textDrawerImages[key] = PPGeTextDrawerImage{};
348 Do(p, textDrawerImages[key].ptr);
349 }
350 break;
351 default:
352 for (const auto &im : textDrawerImages) {
353 Do(p, im.second.ptr);
354 }
355 break;
356 }
357 } else {
358 textDrawerImages.clear();
359 }
360
361 Do(p, dlPtr);
362 Do(p, dlWritePtr);
363 Do(p, dlSize);
364
365 Do(p, dataPtr);
366 Do(p, dataWritePtr);
367 Do(p, dataSize);
368
369 Do(p, vertexStart);
370 Do(p, vertexCount);
371
372 Do(p, char_lines);
373 Do(p, char_lines_metrics);
374 }
375
__PPGeShutdown()376 void __PPGeShutdown()
377 {
378 if (atlasPtr)
379 kernelMemory.Free(atlasPtr);
380 if (dataPtr)
381 kernelMemory.Free(dataPtr);
382 if (dlPtr)
383 kernelMemory.Free(dlPtr);
384 if (listArgs.IsValid())
385 kernelMemory.Free(listArgs.ptr);
386 if (savedContextPtr)
387 kernelMemory.Free(savedContextPtr);
388 if (palette)
389 kernelMemory.Free(palette.ptr);
390
391 atlasPtr = 0;
392 dataPtr = 0;
393 dlPtr = 0;
394 savedContextPtr = 0;
395 listArgs = 0;
396
397 delete textDrawer;
398 textDrawer = nullptr;
399
400 for (auto im : textDrawerImages)
401 kernelMemory.Free(im.second.ptr);
402 textDrawerImages.clear();
403 }
404
PPGeBegin()405 void PPGeBegin()
406 {
407 if (!dlPtr)
408 return;
409
410 // Reset write pointers to start of command and data buffers.
411 dlWritePtr = dlPtr;
412 dataWritePtr = dataPtr;
413
414 // Set up the correct states for UI drawing
415 WriteCmd(GE_CMD_OFFSETADDR, 0);
416 WriteCmd(GE_CMD_ALPHABLENDENABLE, 1);
417 WriteCmd(GE_CMD_BLENDMODE, 2 | (3 << 4));
418 WriteCmd(GE_CMD_ALPHATESTENABLE, 0);
419 WriteCmd(GE_CMD_COLORTESTENABLE, 0);
420 WriteCmd(GE_CMD_ZTESTENABLE, 0);
421 WriteCmd(GE_CMD_LIGHTINGENABLE, 0);
422 WriteCmd(GE_CMD_FOGENABLE, 0);
423 WriteCmd(GE_CMD_STENCILTESTENABLE, 0);
424 WriteCmd(GE_CMD_CULLFACEENABLE, 0);
425 WriteCmd(GE_CMD_CLEARMODE, 0); // Normal mode
426 WriteCmd(GE_CMD_MASKRGB, 0);
427 WriteCmd(GE_CMD_MASKALPHA, 0);
428
429 PPGeSetDefaultTexture();
430
431 PPGeScissor(0, 0, 480, 272);
432 WriteCmd(GE_CMD_MINZ, 0);
433 WriteCmd(GE_CMD_MAXZ, 0xFFFF);
434
435 // Through mode, so we don't have to bother with matrices
436 if (g_RemasterMode) {
437 WriteCmd(GE_CMD_VERTEXTYPE, GE_VTYPE_TC_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_POS_FLOAT | GE_VTYPE_THROUGH);
438 } else {
439 WriteCmd(GE_CMD_VERTEXTYPE, GE_VTYPE_TC_16BIT | GE_VTYPE_COL_8888 | GE_VTYPE_POS_FLOAT | GE_VTYPE_THROUGH);
440 }
441 }
442
PPGeEnd()443 void PPGeEnd()
444 {
445 if (!dlPtr)
446 return;
447
448 WriteCmd(GE_CMD_FINISH, 0);
449 WriteCmd(GE_CMD_END, 0);
450
451 // Might've come from an old savestate.
452 __PPGeSetupListArgs();
453
454 if (dataWritePtr > dataPtr) {
455 // We actually drew something
456 gpu->EnableInterrupts(false);
457 u32 list = sceGeListEnQueue(dlPtr, dlWritePtr, -1, listArgs.ptr);
458 DEBUG_LOG(SCEGE, "PPGe enqueued display list %i", list);
459 gpu->EnableInterrupts(true);
460 }
461 }
462
PPGeScissor(int x1,int y1,int x2,int y2)463 void PPGeScissor(int x1, int y1, int x2, int y2) {
464 _dbg_assert_(x1 >= 0 && x1 <= 480 && x2 >= 0 && x2 <= 480);
465 _dbg_assert_(y1 >= 0 && y1 <= 272 && y2 >= 0 && y2 <= 272);
466 WriteCmd(GE_CMD_SCISSOR1, (y1 << 10) | x1);
467 WriteCmd(GE_CMD_SCISSOR2, ((y2 - 1) << 10) | (x2 - 1));
468 }
469
PPGeScissorReset()470 void PPGeScissorReset() {
471 PPGeScissor(0, 0, 480, 272);
472 }
473
PPGeGetChar(const AtlasFont & atlasfont,unsigned int cval)474 static const AtlasChar *PPGeGetChar(const AtlasFont &atlasfont, unsigned int cval)
475 {
476 const AtlasChar *c = atlasfont.getChar(cval);
477 if (c == NULL) {
478 // Try to use a replacement character, these come from the below table.
479 // http://unicode.org/cldr/charts/supplemental/character_fallback_substitutions.html
480 switch (cval) {
481 case 0x00A0: // NO-BREAK SPACE
482 case 0x2000: // EN QUAD
483 case 0x2001: // EM QUAD
484 case 0x2002: // EN SPACE
485 case 0x2003: // EM SPACE
486 case 0x2004: // THREE-PER-EM SPACE
487 case 0x2005: // FOUR-PER-EM SPACE
488 case 0x2006: // SIX-PER-EM SPACE
489 case 0x2007: // FIGURE SPACE
490 case 0x2008: // PUNCTUATION SPACE
491 case 0x2009: // THIN SPACE
492 case 0x200A: // HAIR SPACE
493 case 0x202F: // NARROW NO-BREAK SPACE
494 case 0x205F: // MEDIUM MATHEMATICAL
495 case 0x3000: // IDEOGRAPHIC SPACE
496 c = atlasfont.getChar(0x0020);
497 break;
498
499 default:
500 c = atlasfont.getChar(0xFFFD);
501 break;
502 }
503 if (c == NULL)
504 c = atlasfont.getChar('?');
505 }
506 return c;
507 }
508
509 // Break a single text string into mutiple lines.
BreakLines(const char * text,const AtlasFont & atlasfont,float x,float y,PPGeAlign align,float scale,float lineHeightScale,int wrapType,float wrapWidth,bool dryRun)510 static AtlasTextMetrics BreakLines(const char *text, const AtlasFont &atlasfont, float x, float y,
511 PPGeAlign align, float scale, float lineHeightScale, int wrapType, float wrapWidth, bool dryRun)
512 {
513 y += atlasfont.ascend * scale;
514 float sx = x, sy = y;
515
516 // TODO: Can we wrap it smartly on the screen edge?
517 if (wrapWidth <= 0) {
518 wrapWidth = 480.f;
519 }
520
521 // used for replacing with ellipses
522 float wrapCutoff = 8.0f;
523 const AtlasChar *dot = PPGeGetChar(atlasfont, '.');
524 if (dot) {
525 wrapCutoff = dot->wx * scale * 3.0f;
526 }
527 float threshold = sx + wrapWidth - wrapCutoff;
528
529 //const float wrapGreyZone = 2.0f; // Grey zone for punctuations at line ends
530
531 int numLines = 1;
532 float maxw = 0;
533 float lineHeight = atlasfont.height * lineHeightScale;
534 for (UTF8 utf(text); !utf.end(); )
535 {
536 float lineWidth = 0;
537 bool skipRest = false;
538 while (!utf.end())
539 {
540 UTF8 utfWord(utf);
541 float nextWidth = 0;
542 float spaceWidth = 0;
543 int numChars = 0;
544 bool finished = false;
545 while (!utfWord.end() && !finished)
546 {
547 UTF8 utfPrev = utfWord;
548 u32 cval = utfWord.next();
549 const AtlasChar *ch = PPGeGetChar(atlasfont, cval);
550 if (!ch) {
551 continue;
552 }
553
554 switch (cval) {
555 // TODO: This list of punctuation is very incomplete.
556 case ',':
557 case '.':
558 case ':':
559 case '!':
560 case ')':
561 case '?':
562 case 0x3001: // IDEOGRAPHIC COMMA
563 case 0x3002: // IDEOGRAPHIC FULL STOP
564 case 0x06D4: // ARABIC FULL STOP
565 case 0xFF01: // FULLWIDTH EXCLAMATION MARK
566 case 0xFF09: // FULLWIDTH RIGHT PARENTHESIS
567 case 0xFF1F: // FULLWIDTH QUESTION MARK
568 // Count this character (punctuation is so clingy), but then we're done.
569 ++numChars;
570 nextWidth += ch->wx * scale;
571 finished = true;
572 break;
573
574 case ' ':
575 case 0x3000: // IDEOGRAPHIC SPACE
576 spaceWidth += ch->wx * scale;
577 finished = true;
578 break;
579
580 case '\t':
581 case '\r':
582 case '\n':
583 // Ignore this character and we're done.
584 finished = true;
585 break;
586
587 default:
588 {
589 // CJK characters can be wrapped more freely.
590 bool isCJK = (cval >= 0x1100 && cval <= 0x11FF); // Hangul Jamo.
591 isCJK = isCJK || (cval >= 0x2E80 && cval <= 0x2FFF); // Kangxi Radicals etc.
592 #if 0
593 isCJK = isCJK || (cval >= 0x3040 && cval <= 0x31FF); // Hiragana, Katakana, Hangul Compatibility Jamo etc.
594 isCJK = isCJK || (cval >= 0x3200 && cval <= 0x32FF); // CJK Enclosed
595 isCJK = isCJK || (cval >= 0x3300 && cval <= 0x33FF); // CJK Compatibility
596 isCJK = isCJK || (cval >= 0x3400 && cval <= 0x4DB5); // CJK Unified Ideographs Extension A
597 #else
598 isCJK = isCJK || (cval >= 0x3040 && cval <= 0x4DB5); // Above collapsed
599 #endif
600 isCJK = isCJK || (cval >= 0x4E00 && cval <= 0x9FBB); // CJK Unified Ideographs
601 isCJK = isCJK || (cval >= 0xAC00 && cval <= 0xD7AF); // Hangul Syllables
602 isCJK = isCJK || (cval >= 0xF900 && cval <= 0xFAD9); // CJK Compatibility Ideographs
603 isCJK = isCJK || (cval >= 0x20000 && cval <= 0x2A6D6); // CJK Unified Ideographs Extension B
604 isCJK = isCJK || (cval >= 0x2F800 && cval <= 0x2FA1D); // CJK Compatibility Supplement
605 if (isCJK) {
606 if (numChars > 0) {
607 utfWord = utfPrev;
608 finished = true;
609 break;
610 }
611 }
612 }
613 ++numChars;
614 nextWidth += ch->wx * scale;
615 break;
616 }
617 }
618
619 bool useEllipsis = false;
620 if (wrapType > 0)
621 {
622 if (lineWidth + nextWidth > wrapWidth || skipRest)
623 {
624 if (wrapType & PPGE_LINE_WRAP_WORD) {
625 // TODO: Should check if we have had at least one other word instead.
626 if (lineWidth > 0) {
627 ++numLines;
628 break;
629 }
630 }
631 if (wrapType & PPGE_LINE_USE_ELLIPSIS) {
632 useEllipsis = true;
633 if (skipRest) {
634 numChars = 0;
635 } else if (nextWidth < wrapCutoff) {
636 // The word is too short, so just backspace!
637 x = threshold;
638 }
639 nextWidth = 0;
640 spaceWidth = 0;
641 lineWidth = wrapWidth;
642 }
643 }
644 }
645 for (int i = 0; i < numChars; ++i)
646 {
647 u32 cval = utf.next();
648 const AtlasChar *c = PPGeGetChar(atlasfont, cval);
649 if (c)
650 {
651 if (useEllipsis && x >= threshold && dot)
652 {
653 if (!dryRun)
654 {
655 AtlasCharVertex cv;
656 // Replace the following part with an ellipsis.
657 cv.x = x + dot->ox * scale;
658 cv.y = y + dot->oy * scale;
659 cv.c = dot;
660 char_one_line.push_back(cv);
661 cv.x += dot->wx * scale;
662 char_one_line.push_back(cv);
663 cv.x += dot->wx * scale;
664 char_one_line.push_back(cv);
665 }
666 skipRest = true;
667 break;
668 }
669 if (!dryRun)
670 {
671 AtlasCharVertex cv;
672 cv.x = x + c->ox * scale;
673 cv.y = y + c->oy * scale;
674 cv.c = c;
675 char_one_line.push_back(cv);
676 }
677 x += c->wx * scale;
678 }
679 }
680 lineWidth += nextWidth;
681
682 u32 cval = utf.end() ? 0 : utf.next();
683 if (spaceWidth > 0)
684 {
685 if (!dryRun)
686 {
687 // No need to check c.
688 const AtlasChar *c = PPGeGetChar(atlasfont, cval);
689 AtlasCharVertex cv;
690 cv.x = x + c->ox * scale;
691 cv.y = y + c->oy * scale;
692 cv.c = c;
693 char_one_line.push_back(cv);
694 }
695 x += spaceWidth;
696 lineWidth += spaceWidth;
697 if (wrapType > 0 && lineWidth > wrapWidth) {
698 lineWidth = wrapWidth;
699 }
700 }
701 else if (cval == '\n') {
702 ++numLines;
703 break;
704 }
705 utf = utfWord;
706 }
707 y += lineHeight;
708 x = sx;
709 if (lineWidth > maxw) {
710 maxw = lineWidth;
711 }
712 if (!dryRun)
713 {
714 char_lines.push_back(char_one_line);
715 char_one_line.clear();
716 }
717 }
718
719 const float w = maxw;
720 const float h = (float)numLines * lineHeight;
721 if (align & PPGeAlign::ANY) {
722 if (!dryRun) {
723 for (auto i = char_lines.begin(); i != char_lines.end(); ++i) {
724 for (auto j = i->begin(); j != i->end(); ++j) {
725 if (align & PPGeAlign::BOX_HCENTER) j->x -= w / 2.0f;
726 else if (align & PPGeAlign::BOX_RIGHT) j->x -= w;
727
728 if (align & PPGeAlign::BOX_VCENTER) j->y -= h / 2.0f;
729 else if (align & PPGeAlign::BOX_BOTTOM) j->y -= h;
730 }
731 }
732 }
733 if (align & PPGeAlign::BOX_HCENTER) sx -= w / 2.0f;
734 else if (align & PPGeAlign::BOX_RIGHT) sx -= w;
735 if (align & PPGeAlign::BOX_VCENTER) sy -= h / 2.0f;
736 else if (align & PPGeAlign::BOX_BOTTOM) sy -= h;
737 }
738
739 AtlasTextMetrics metrics = { sx, sy, w, lineHeight, scale, numLines };
740 return metrics;
741 }
742
HasTextDrawer()743 static bool HasTextDrawer() {
744 // We create this on first use so it's on the correct thread.
745 if (textDrawerInited) {
746 return textDrawer != nullptr;
747 }
748
749 // Should we pass a draw_? Yes! UWP requires it.
750 textDrawer = TextDrawer::Create(g_draw);
751 if (textDrawer) {
752 textDrawer->SetFontScale(1.0f, 1.0f);
753 textDrawer->SetForcedDPIScale(1.0f);
754 textDrawer->SetFont(g_Config.sFont.c_str(), 18, 0);
755 }
756 textDrawerInited = true;
757 return textDrawer != nullptr;
758 }
759
PPGeMeasureText(float * w,float * h,const char * text,float scale,int WrapType,int wrapWidth)760 void PPGeMeasureText(float *w, float *h, const char *text, float scale, int WrapType, int wrapWidth) {
761 if (HasTextDrawer()) {
762 float mw, mh;
763 textDrawer->SetFontScale(scale, scale);
764 int dtalign = (WrapType & PPGE_LINE_WRAP_WORD) ? FLAG_WRAP_TEXT : 0;
765 if (WrapType & PPGE_LINE_USE_ELLIPSIS)
766 dtalign |= FLAG_ELLIPSIZE_TEXT;
767 Bounds b(0, 0, wrapWidth <= 0 ? 480.0f : wrapWidth, 272.0f);
768 textDrawer->MeasureStringRect(text, strlen(text), b, &mw, &mh, dtalign);
769
770 if (w)
771 *w = mw;
772 if (h)
773 *h = mh;
774 return;
775 }
776
777 if (!g_ppge_atlas.IsMetadataLoaded() || g_ppge_atlas.num_fonts < 1) {
778 if (w)
779 *w = 0;
780 if (h)
781 *h = 0;
782 return;
783 }
784
785 const AtlasFont &atlasfont = g_ppge_atlas.fonts[0];
786 AtlasTextMetrics metrics = BreakLines(text, atlasfont, 0, 0, PPGeAlign::BOX_TOP, scale, scale, WrapType, wrapWidth, true);
787 if (w) *w = metrics.maxWidth;
788 if (h) *h = metrics.lineHeight * metrics.numLines;
789 }
790
PPGePrepareText(const char * text,float x,float y,PPGeAlign align,float scale,float lineHeightScale,int WrapType,int wrapWidth)791 void PPGePrepareText(const char *text, float x, float y, PPGeAlign align, float scale, float lineHeightScale, int WrapType, int wrapWidth)
792 {
793 const AtlasFont &atlasfont = g_ppge_atlas.fonts[0];
794 if (!g_ppge_atlas.IsMetadataLoaded() || g_ppge_atlas.num_fonts < 1) {
795 return;
796 }
797 char_lines_metrics = BreakLines(text, atlasfont, x, y, align, scale, lineHeightScale, WrapType, wrapWidth, false);
798 }
799
PPGeResetCurrentText()800 static void PPGeResetCurrentText() {
801 char_one_line.clear();
802 char_lines.clear();
803 AtlasTextMetrics zeroBox = { 0 };
804 char_lines_metrics = zeroBox;
805 }
806
807 // Draws some text using the one font we have.
808 // Mostly rewritten.
PPGeDrawCurrentText(u32 color)809 void PPGeDrawCurrentText(u32 color)
810 {
811 if (dlPtr)
812 {
813 float scale = char_lines_metrics.scale;
814 BeginVertexData();
815 for (auto i = char_lines.begin(); i != char_lines.end(); ++i)
816 {
817 for (auto j = i->begin(); j != i->end(); ++j)
818 {
819 float cx1 = j->x;
820 float cy1 = j->y;
821 const AtlasChar &c = *j->c;
822 float cx2 = cx1 + c.pw * scale;
823 float cy2 = cy1 + c.ph * scale;
824 Vertex(cx1, cy1, c.sx, c.sy, atlasWidth, atlasHeight, color);
825 Vertex(cx2, cy2, c.ex, c.ey, atlasWidth, atlasHeight, color);
826 }
827 }
828 EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
829 }
830 PPGeResetCurrentText();
831 }
832
833 // Return a value such that (1 << value) >= x
GetPow2(int x)834 int GetPow2(int x) {
835 #ifdef __GNUC__
836 int ret = 31 - __builtin_clz(x | 1);
837 if ((1 << ret) < x)
838 #else
839 int ret = 0;
840 while ((1 << ret) < x)
841 #endif
842 ret++;
843 return ret;
844 }
845
PPGeGetTextImage(const char * text,const PPGeStyle & style,float maxWidth,bool wrap)846 static PPGeTextDrawerImage PPGeGetTextImage(const char *text, const PPGeStyle &style, float maxWidth, bool wrap) {
847 int tdalign = 0;
848 tdalign |= FLAG_ELLIPSIZE_TEXT;
849 if (wrap) {
850 tdalign |= FLAG_WRAP_TEXT;
851 }
852
853 PPGeTextDrawerCacheKey key{ text, tdalign, maxWidth / style.scale };
854 PPGeTextDrawerImage im{};
855
856 auto cacheItem = textDrawerImages.find(key);
857 if (cacheItem != textDrawerImages.end()) {
858 im = cacheItem->second;
859 cacheItem->second.entry.lastUsedFrame = gpuStats.numFlips;
860 } else {
861 std::vector<uint8_t> bitmapData;
862 textDrawer->SetFontScale(style.scale, style.scale);
863 Bounds b(0, 0, maxWidth, 272.0f);
864 std::string cleaned = ReplaceAll(text, "\r", "");
865 textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, cleaned.c_str(), b, tdalign);
866
867 int bufwBytes = ((im.entry.bmWidth + 31) / 32) * 16;
868 u32 sz = bufwBytes * (im.entry.bmHeight + 1);
869 u32 origSz = sz;
870 im.ptr = __PPGeDoAlloc(sz, true, "PPGeText");
871
872 if (bitmapData.size() & 1)
873 bitmapData.resize(bitmapData.size() + 1);
874
875 if (im.ptr) {
876 int wBytes = (im.entry.bmWidth + 1) / 2;
877 u8 *ramPtr = (u8 *)Memory::GetPointer(im.ptr);
878 for (int y = 0; y < im.entry.bmHeight; ++y) {
879 for (int x = 0; x < wBytes; ++x) {
880 uint8_t c1 = bitmapData[y * im.entry.bmWidth + x * 2];
881 uint8_t c2 = bitmapData[y * im.entry.bmWidth + x * 2 + 1];
882 // Convert this to 4-bit palette values.
883 ramPtr[y * bufwBytes + x] = (c2 & 0xF0) | (c1 >> 4);
884 }
885 if (bufwBytes != wBytes) {
886 memset(ramPtr + y * bufwBytes + wBytes, 0, bufwBytes - wBytes);
887 }
888 }
889 memset(ramPtr + im.entry.bmHeight * bufwBytes, 0, bufwBytes + sz - origSz);
890 }
891
892 im.entry.lastUsedFrame = gpuStats.numFlips;
893 textDrawerImages[key] = im;
894 }
895
896 return im;
897 }
898
PPGeDrawTextImage(PPGeTextDrawerImage im,float x,float y,const PPGeStyle & style)899 static void PPGeDrawTextImage(PPGeTextDrawerImage im, float x, float y, const PPGeStyle &style) {
900 if (!im.ptr) {
901 return;
902 }
903
904 int bufw = ((im.entry.bmWidth + 31) / 32) * 32;
905 int wp2 = GetPow2(im.entry.bmWidth);
906 int hp2 = GetPow2(im.entry.bmHeight);
907 WriteCmd(GE_CMD_TEXADDR0, im.ptr & 0xFFFFF0);
908 WriteCmd(GE_CMD_TEXBUFWIDTH0, bufw | ((im.ptr & 0xFF000000) >> 8));
909 WriteCmd(GE_CMD_TEXSIZE0, wp2 | (hp2 << 8));
910 WriteCmd(GE_CMD_TEXFLUSH, 0);
911
912 float w = im.entry.width * style.scale;
913 float h = im.entry.height * style.scale;
914
915 if (style.align & PPGeAlign::BOX_HCENTER)
916 x -= w / 2.0f;
917 else if (style.align & PPGeAlign::BOX_RIGHT)
918 x -= w;
919 if (style.align & PPGeAlign::BOX_VCENTER)
920 y -= h / 2.0f;
921 else if (style.align & PPGeAlign::BOX_BOTTOM)
922 y -= h;
923
924 BeginVertexData();
925 float u1 = (float)im.entry.width / (1 << wp2);
926 float v1 = (float)im.entry.height / (1 << hp2);
927 if (style.hasShadow) {
928 // Draw more shadows for a blurrier shadow.
929 for (float dy = 0.0f; dy <= 2.0f; dy += 1.0f) {
930 for (float dx = 0.0f; dx <= 1.0f; dx += 0.5f) {
931 if (dx == 0.0f && dy == 0.0f)
932 continue;
933 Vertex(x + dx, y + dy, 0, 0, 1 << wp2, 1 << hp2, alphaMul(style.shadowColor, 0.35f));
934 Vertex(x + dx + w, y + dy + h, u1, v1, 1 << wp2, 1 << hp2, alphaMul(style.shadowColor, 0.35f));
935 }
936 }
937 }
938 Vertex(x, y, 0, 0, 1 << wp2, 1 << hp2, style.color);
939 Vertex(x + w, y + h, u1, v1, 1 << wp2, 1 << hp2, style.color);
940 EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
941
942 PPGeSetDefaultTexture();
943 }
944
PPGeDecimateTextImages(int age)945 static void PPGeDecimateTextImages(int age) {
946 // Do this always, in case the platform has no TextDrawer but save state did.
947 for (auto it = textDrawerImages.begin(); it != textDrawerImages.end(); ) {
948 if (gpuStats.numFlips - it->second.entry.lastUsedFrame >= age) {
949 kernelMemory.Free(it->second.ptr);
950 it = textDrawerImages.erase(it);
951 } else {
952 ++it;
953 }
954 }
955 }
956
PPGeSanitizeText(const std::string & text)957 static std::string PPGeSanitizeText(const std::string &text) {
958 return SanitizeUTF8(text);
959 }
960
PPGeDrawText(const char * text,float x,float y,const PPGeStyle & style)961 void PPGeDrawText(const char *text, float x, float y, const PPGeStyle &style) {
962 if (!text) {
963 return;
964 }
965 std::string str = PPGeSanitizeText(text);
966 if (str.empty()) {
967 return;
968 }
969
970 if (HasTextDrawer()) {
971 PPGeTextDrawerImage im = PPGeGetTextImage(str.c_str(), style, 480.0f - x, false);
972 if (im.ptr) {
973 PPGeDrawTextImage(im, x, y, style);
974 return;
975 }
976 }
977
978 if (style.hasShadow) {
979 // This doesn't have the nicer shadow because it's so many verts.
980 PPGePrepareText(text, x + 1, y + 2, style.align, style.scale, style.scale, PPGE_LINE_USE_ELLIPSIS);
981 PPGeDrawCurrentText(style.shadowColor);
982 }
983
984 PPGePrepareText(text, x, y, style.align, style.scale, style.scale, PPGE_LINE_USE_ELLIPSIS);
985 PPGeDrawCurrentText(style.color);
986 }
987
StripTrailingWhite(const std::string & s)988 static std::string StripTrailingWhite(const std::string &s) {
989 size_t lastChar = s.find_last_not_of(" \t\r\n");
990 if (lastChar != s.npos) {
991 return s.substr(0, lastChar + 1);
992 }
993 return s;
994 }
995
CropLinesToCount(const std::string & s,int numLines)996 static std::string CropLinesToCount(const std::string &s, int numLines) {
997 std::vector<std::string> lines;
998 SplitString(s, '\n', lines);
999 if ((int)lines.size() <= numLines) {
1000 return s;
1001 }
1002
1003 size_t len = 0;
1004 for (int i = 0; i < numLines; ++i) {
1005 len += lines[i].length() + 1;
1006 }
1007
1008 return s.substr(0, len);
1009 }
1010
PPGeDrawTextWrapped(const char * text,float x,float y,float wrapWidth,float wrapHeight,const PPGeStyle & style)1011 void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, float wrapHeight, const PPGeStyle &style) {
1012 std::string s = PPGeSanitizeText(text);
1013 if (wrapHeight != 0.0f) {
1014 s = StripTrailingWhite(s);
1015 }
1016
1017 int zoom = (PSP_CoreParameter().pixelHeight + 479) / 480;
1018 zoom = std::min(zoom, PSP_CoreParameter().renderScaleFactor);
1019 float maxScaleDown = zoom == 1 ? 1.3f : 2.0f;
1020
1021 if (HasTextDrawer()) {
1022 float actualWidth, actualHeight;
1023 Bounds b(0, 0, wrapWidth <= 0 ? 480.0f - x : wrapWidth, wrapHeight);
1024 int tdalign = 0;
1025 textDrawer->SetFontScale(style.scale, style.scale);
1026 textDrawer->MeasureStringRect(s.c_str(), s.size(), b, &actualWidth, &actualHeight, tdalign | FLAG_WRAP_TEXT);
1027
1028 // Check if we need to scale the text down to fit better.
1029 PPGeStyle adjustedStyle = style;
1030 if (wrapHeight != 0.0f && actualHeight > wrapHeight) {
1031 // Cheap way to get the line height.
1032 float oneLine, twoLines;
1033 textDrawer->MeasureString("|", 1, &actualWidth, &oneLine);
1034 textDrawer->MeasureStringRect("|\n|", 3, Bounds(0, 0, 480, 272), &actualWidth, &twoLines);
1035
1036 float lineHeight = twoLines - oneLine;
1037 if (actualHeight > wrapHeight * maxScaleDown) {
1038 float maxLines = floor(wrapHeight * maxScaleDown / lineHeight);
1039 actualHeight = (maxLines + 1) * lineHeight;
1040 // Add an ellipsis if it's just too long to be readable.
1041 // On a PSP, it does this without scaling it down.
1042 s = StripTrailingWhite(CropLinesToCount(s, (int)maxLines)) + "\n...";
1043 }
1044
1045 adjustedStyle.scale *= wrapHeight / actualHeight;
1046 }
1047
1048 PPGeTextDrawerImage im = PPGeGetTextImage(s.c_str(), adjustedStyle, wrapWidth <= 0 ? 480.0f - x : wrapWidth, true);
1049 if (im.ptr) {
1050 PPGeDrawTextImage(im, x, y, adjustedStyle);
1051 return;
1052 }
1053 }
1054
1055 int sx = style.hasShadow ? 1 : 0;
1056 int sy = style.hasShadow ? 2 : 0;
1057 PPGePrepareText(s.c_str(), x + sx, y + sy, style.align, style.scale, style.scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth);
1058
1059 float scale = style.scale;
1060 float lineHeightScale = style.scale;
1061 float actualHeight = char_lines_metrics.lineHeight * char_lines_metrics.numLines;
1062 if (wrapHeight != 0.0f && actualHeight > wrapHeight) {
1063 if (actualHeight > wrapHeight * maxScaleDown) {
1064 float maxLines = floor(wrapHeight * maxScaleDown / char_lines_metrics.lineHeight);
1065 actualHeight = (maxLines + 1) * char_lines_metrics.lineHeight;
1066 // Add an ellipsis if it's just too long to be readable.
1067 // On a PSP, it does this without scaling it down.
1068 s = StripTrailingWhite(CropLinesToCount(s, (int)maxLines)) + "\n...";
1069 }
1070
1071 // Measure the text again after scaling down.
1072 PPGeResetCurrentText();
1073 float reduced = style.scale * wrapHeight / actualHeight;
1074 // Try to keep the font as large as possible, so reduce the line height some.
1075 scale = reduced * 1.15f;
1076 lineHeightScale = reduced;
1077 PPGePrepareText(s.c_str(), x + sx, y + sy, style.align, scale, lineHeightScale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth);
1078 }
1079 if (style.hasShadow) {
1080 // This doesn't have the nicer shadow because it's so many verts.
1081 PPGeDrawCurrentText(style.shadowColor);
1082 PPGePrepareText(s.c_str(), x, y, style.align, scale, lineHeightScale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth);
1083 }
1084 PPGeDrawCurrentText(style.color);
1085 }
1086
1087 // Draws a "4-patch" for button-like things that can be resized
PPGeDraw4Patch(ImageID atlasImage,float x,float y,float w,float h,u32 color)1088 void PPGeDraw4Patch(ImageID atlasImage, float x, float y, float w, float h, u32 color) {
1089 if (!dlPtr)
1090 return;
1091 const AtlasImage *img = g_ppge_atlas.getImage(atlasImage);
1092 if (!img)
1093 return;
1094 float borderx = img->w / 20;
1095 float bordery = img->h / 20;
1096 float u1 = img->u1, uhalf = (img->u1 + img->u2) / 2, u2 = img->u2;
1097 float v1 = img->v1, vhalf = (img->v1 + img->v2) / 2, v2 = img->v2;
1098 float xmid1 = x + borderx;
1099 float xmid2 = x + w - borderx;
1100 float ymid1 = y + bordery;
1101 float ymid2 = y + h - bordery;
1102 float x2 = x + w;
1103 float y2 = y + h;
1104 BeginVertexData();
1105 // Top row
1106 Vertex(x, y, u1, v1, atlasWidth, atlasHeight, color);
1107 Vertex(xmid1, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color);
1108 Vertex(xmid1, y, uhalf, v1, atlasWidth, atlasHeight, color);
1109 Vertex(xmid2, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color);
1110 Vertex(xmid2, y, uhalf, v1, atlasWidth, atlasHeight, color);
1111 Vertex(x2, ymid1, u2, vhalf, atlasWidth, atlasHeight, color);
1112 // Middle row
1113 Vertex(x, ymid1, u1, vhalf, atlasWidth, atlasHeight, color);
1114 Vertex(xmid1, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color);
1115 Vertex(xmid1, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color);
1116 Vertex(xmid2, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color);
1117 Vertex(xmid2, ymid1, uhalf, vhalf, atlasWidth, atlasHeight, color);
1118 Vertex(x2, ymid2, u2, v2, atlasWidth, atlasHeight, color);
1119 // Bottom row
1120 Vertex(x, ymid2, u1, vhalf, atlasWidth, atlasHeight, color);
1121 Vertex(xmid1, y2, uhalf, v2, atlasWidth, atlasHeight, color);
1122 Vertex(xmid1, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color);
1123 Vertex(xmid2, y2, uhalf, v2, atlasWidth, atlasHeight, color);
1124 Vertex(xmid2, ymid2, uhalf, vhalf, atlasWidth, atlasHeight, color);
1125 Vertex(x2, y2, u2, v2, atlasWidth, atlasHeight, color);
1126 EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
1127 }
1128
PPGeDrawRect(float x1,float y1,float x2,float y2,u32 color)1129 void PPGeDrawRect(float x1, float y1, float x2, float y2, u32 color) {
1130 if (!dlPtr)
1131 return;
1132
1133 WriteCmd(GE_CMD_TEXTUREMAPENABLE, 0);
1134
1135 BeginVertexData();
1136 Vertex(x1, y1, 0, 0, 0, 0, color);
1137 Vertex(x2, y2, 0, 0, 0, 0, color);
1138 EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
1139
1140 WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1);
1141 }
1142
1143 // Just blits an image to the screen, multiplied with the color.
PPGeDrawImage(ImageID atlasImage,float x,float y,const PPGeStyle & style)1144 void PPGeDrawImage(ImageID atlasImage, float x, float y, const PPGeStyle &style) {
1145 if (!dlPtr)
1146 return;
1147
1148 const AtlasImage *img = g_ppge_atlas.getImage(atlasImage);
1149 if (!img) {
1150 return;
1151 }
1152 float w = img->w;
1153 float h = img->h;
1154 BeginVertexData();
1155 if (style.hasShadow) {
1156 for (float dy = 0.0f; dy <= 2.0f; dy += 1.0f) {
1157 for (float dx = 0.0f; dx <= 1.0f; dx += 0.5f) {
1158 if (dx == 0.0f && dy == 0.0f)
1159 continue;
1160 Vertex(x + dx, y + dy, img->u1, img->v1, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f));
1161 Vertex(x + dx + w, y + dy + h, img->u2, img->v2, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f));
1162 }
1163 }
1164 }
1165 Vertex(x, y, img->u1, img->v1, atlasWidth, atlasHeight, style.color);
1166 Vertex(x + w, y + h, img->u2, img->v2, atlasWidth, atlasHeight, style.color);
1167 EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
1168 }
1169
PPGeDrawImage(ImageID atlasImage,float x,float y,float w,float h,const PPGeStyle & style)1170 void PPGeDrawImage(ImageID atlasImage, float x, float y, float w, float h, const PPGeStyle &style) {
1171 if (!dlPtr)
1172 return;
1173
1174 const AtlasImage *img = g_ppge_atlas.getImage(atlasImage);
1175 if (!img) {
1176 return;
1177 }
1178 BeginVertexData();
1179 if (style.hasShadow) {
1180 for (float dy = 0.0f; dy <= 2.0f; dy += 1.0f) {
1181 for (float dx = 0.0f; dx <= 1.0f; dx += 0.5f) {
1182 if (dx == 0.0f && dy == 0.0f)
1183 continue;
1184 Vertex(x + dx, y + dy, img->u1, img->v1, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f));
1185 Vertex(x + dx + w, y + dy + h, img->u2, img->v2, atlasWidth, atlasHeight, alphaMul(style.shadowColor, 0.35f));
1186 }
1187 }
1188 }
1189 Vertex(x, y, img->u1, img->v1, atlasWidth, atlasHeight, style.color);
1190 Vertex(x + w, y + h, img->u2, img->v2, atlasWidth, atlasHeight, style.color);
1191 EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
1192 }
1193
PPGeDrawImage(float x,float y,float w,float h,float u1,float v1,float u2,float v2,int tw,int th,const PPGeImageStyle & style)1194 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) {
1195 if (!dlPtr)
1196 return;
1197 BeginVertexData();
1198 Vertex(x, y, u1, v1, tw, th, style.color);
1199 Vertex(x + w, y + h, u2, v2, tw, th, style.color);
1200 EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
1201 }
1202
PPGeSetDefaultTexture()1203 void PPGeSetDefaultTexture()
1204 {
1205 WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1);
1206 int wp2 = GetPow2(atlasWidth);
1207 int hp2 = GetPow2(atlasHeight);
1208 WriteCmd(GE_CMD_CLUTADDR, palette.ptr & 0xFFFFF0);
1209 WriteCmd(GE_CMD_CLUTADDRUPPER, (palette.ptr & 0xFF000000) >> 8);
1210 WriteCmd(GE_CMD_CLUTFORMAT, 0x00FF02);
1211 WriteCmd(GE_CMD_LOADCLUT, 2);
1212 WriteCmd(GE_CMD_TEXSIZE0, wp2 | (hp2 << 8));
1213 WriteCmd(GE_CMD_TEXMAPMODE, 0 | (1 << 8));
1214 WriteCmd(GE_CMD_TEXMODE, 0);
1215 WriteCmd(GE_CMD_TEXFORMAT, GE_TFMT_CLUT4); // 4-bit CLUT
1216 WriteCmd(GE_CMD_TEXFILTER, (1 << 8) | 1); // mag = LINEAR min = LINEAR
1217 WriteCmd(GE_CMD_TEXWRAP, (1 << 8) | 1); // clamp texture wrapping
1218 WriteCmd(GE_CMD_TEXFUNC, (0 << 16) | (1 << 8) | 0); // RGBA texture reads, modulate, no color doubling
1219 WriteCmd(GE_CMD_TEXADDR0, atlasPtr & 0xFFFFF0);
1220 WriteCmd(GE_CMD_TEXBUFWIDTH0, atlasWidth | ((atlasPtr & 0xFF000000) >> 8));
1221 WriteCmd(GE_CMD_TEXFLUSH, 0);
1222 }
1223
PPGeSetTexture(u32 dataAddr,int width,int height)1224 void PPGeSetTexture(u32 dataAddr, int width, int height)
1225 {
1226 WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1);
1227 int wp2 = GetPow2(width);
1228 int hp2 = GetPow2(height);
1229 WriteCmd(GE_CMD_TEXSIZE0, wp2 | (hp2 << 8));
1230 WriteCmd(GE_CMD_TEXMAPMODE, 0 | (1 << 8));
1231 WriteCmd(GE_CMD_TEXMODE, 0);
1232 WriteCmd(GE_CMD_TEXFORMAT, GE_TFMT_8888); // 4444
1233 WriteCmd(GE_CMD_TEXFILTER, (1 << 8) | 1); // mag = LINEAR min = LINEAR
1234 WriteCmd(GE_CMD_TEXWRAP, (1 << 8) | 1); // clamp texture wrapping
1235 WriteCmd(GE_CMD_TEXFUNC, (0 << 16) | (1 << 8) | 0); // RGBA texture reads, modulate, no color doubling
1236 WriteCmd(GE_CMD_TEXADDR0, dataAddr & 0xFFFFF0);
1237 WriteCmd(GE_CMD_TEXBUFWIDTH0, width | ((dataAddr & 0xFF000000) >> 8));
1238 WriteCmd(GE_CMD_TEXFLUSH, 0);
1239 }
1240
PPGeDisableTexture()1241 void PPGeDisableTexture()
1242 {
1243 WriteCmd(GE_CMD_TEXTUREMAPENABLE, 0);
1244 }
1245
1246 std::vector<PPGeImage *> PPGeImage::loadedTextures_;
1247
PPGeImage(const std::string & pspFilename)1248 PPGeImage::PPGeImage(const std::string &pspFilename)
1249 : filename_(pspFilename), texture_(0) {
1250 }
1251
PPGeImage(u32 pngPointer,size_t pngSize)1252 PPGeImage::PPGeImage(u32 pngPointer, size_t pngSize)
1253 : filename_(""), png_(pngPointer), size_(pngSize), texture_(0) {
1254 }
1255
~PPGeImage()1256 PPGeImage::~PPGeImage() {
1257 Free();
1258 }
1259
Load()1260 bool PPGeImage::Load() {
1261 Free();
1262
1263 // In case it fails to load.
1264 width_ = 0;
1265 height_ = 0;
1266
1267 unsigned char *textureData;
1268 int success;
1269 if (filename_.empty()) {
1270 success = pngLoadPtr(Memory::GetPointer(png_), size_, &width_, &height_, &textureData);
1271 } else {
1272 std::vector<u8> pngData;
1273 if (pspFileSystem.ReadEntireFile(filename_, pngData) < 0) {
1274 WARN_LOG(SCEGE, "Bad PPGeImage - cannot load file");
1275 return false;
1276 }
1277
1278 success = pngLoadPtr((const unsigned char *)&pngData[0], pngData.size(), &width_, &height_, &textureData);
1279 }
1280 if (!success) {
1281 WARN_LOG(SCEGE, "Bad PPGeImage - not a valid png");
1282 return false;
1283 }
1284
1285 u32 dataSize = width_ * height_ * 4;
1286 u32 texSize = dataSize + width_ * 4;
1287 texture_ = __PPGeDoAlloc(texSize, true, "Savedata Icon");
1288 if (texture_ == 0) {
1289 free(textureData);
1290 WARN_LOG(SCEGE, "Bad PPGeImage - unable to allocate space for texture");
1291 return false;
1292 }
1293
1294 Memory::Memcpy(texture_, textureData, dataSize, "PPGeTex");
1295 Memory::Memset(texture_ + dataSize, 0, texSize - dataSize, "PPGeTexClear");
1296 free(textureData);
1297
1298 lastFrame_ = gpuStats.numFlips;
1299 loadedTextures_.push_back(this);
1300 return true;
1301 }
1302
Free()1303 void PPGeImage::Free() {
1304 if (texture_ != 0) {
1305 kernelMemory.Free(texture_);
1306 texture_ = 0;
1307 loadedTextures_.erase(std::remove(loadedTextures_.begin(), loadedTextures_.end(), this), loadedTextures_.end());
1308 }
1309 }
1310
DoState(PointerWrap & p)1311 void PPGeImage::DoState(PointerWrap &p) {
1312 auto s = p.Section("PPGeImage", 1);
1313 if (!s)
1314 return;
1315
1316 Do(p, filename_);
1317 Do(p, png_);
1318 Do(p, size_);
1319 Do(p, texture_);
1320 Do(p, width_);
1321 Do(p, height_);
1322 Do(p, lastFrame_);
1323 }
1324
CompatLoad(u32 texture,int width,int height)1325 void PPGeImage::CompatLoad(u32 texture, int width, int height) {
1326 // Won't be reloadable, so don't add to loadedTextures_.
1327 texture_ = texture;
1328 width_ = width;
1329 height_ = height;
1330 }
1331
Decimate(int age)1332 void PPGeImage::Decimate(int age) {
1333 int tooOldFrame = gpuStats.numFlips - age;
1334 for (size_t i = 0; i < loadedTextures_.size(); ++i) {
1335 if (loadedTextures_[i]->lastFrame_ < tooOldFrame) {
1336 loadedTextures_[i]->Free();
1337 // That altered loadedTextures_.
1338 --i;
1339 }
1340 }
1341 }
1342
SetTexture()1343 void PPGeImage::SetTexture() {
1344 if (texture_ == 0) {
1345 Decimate();
1346 Load();
1347 }
1348
1349 if (texture_ != 0) {
1350 lastFrame_ = gpuStats.numFlips;
1351 PPGeSetTexture(texture_, width_, height_);
1352 } else {
1353 PPGeDisableTexture();
1354 }
1355 }
1356
PPGeNotifyFrame()1357 void PPGeNotifyFrame() {
1358 if (textDrawer) {
1359 textDrawer->OncePerFrame();
1360 }
1361
1362 PPGeDecimateTextImages();
1363 PPGeImage::Decimate();
1364
1365 if (atlasRequiresReset) {
1366 __PPGeShutdown();
1367 __PPGeInit();
1368 }
1369 }
1370