2012-11-18 13:04:49 +01:00
// 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/.
2013-12-08 11:06:18 -08:00
# include <algorithm>
2013-12-29 23:28:31 +01:00
2020-10-04 23:24:14 +02:00
# include "Common/Render/TextureAtlas.h"
2020-10-05 00:05:28 +02:00
# include "Common/Render/Text/draw_text.h"
2013-12-29 23:28:31 +01:00
2020-10-04 20:48:47 +02:00
# 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"
2020-08-10 00:12:51 -07:00
# include "Common/Serialize/Serializer.h"
# include "Common/Serialize/SerializeFuncs.h"
2020-09-29 12:19:22 +02:00
# include "Common/StringUtils.h"
2015-12-27 18:24:53 -08:00
# include "Core/HDRemaster.h"
2013-12-29 23:44:35 +01:00
# include "Core/Host.h"
2013-04-17 16:37:15 +02:00
# include "GPU/ge_constants.h"
# include "GPU/GPUState.h"
# include "GPU/GPUInterface.h"
2013-12-29 23:28:31 +01:00
# include "Core/FileSystems/MetaFileSystem.h"
# include "Core/Util/PPGeDraw.h"
2013-04-17 16:37:15 +02:00
# include "Core/HLE/sceKernel.h"
# include "Core/HLE/sceKernelMemory.h"
# include "Core/HLE/sceGe.h"
2015-04-05 18:03:50 -07:00
# include "Core/MemMapHelpers.h"
2013-04-17 16:37:15 +02:00
# include "Core/System.h"
2012-11-18 13:04:49 +01:00
2020-02-29 21:51:14 +01:00
Atlas g_ppge_atlas ;
2020-12-20 12:11:02 +01:00
Draw : : DrawContext * g_draw = nullptr ;
2020-02-29 21:51:14 +01:00
2012-11-18 13:04:49 +01:00
static u32 atlasPtr ;
2012-12-13 22:06:45 +01:00
static int atlasWidth ;
static int atlasHeight ;
2012-11-18 13:04:49 +01:00
struct PPGeVertex {
2013-07-27 15:38:38 -07:00
u16_le u , v ;
u32_le color ;
float_le x , y , z ;
2012-11-18 13:04:49 +01:00
} ;
2015-12-27 18:24:53 -08:00
struct PPGeRemasterVertex {
float_le u , v ;
u32_le color ;
float_le x , y , z ;
} ;
2013-09-20 21:06:27 -07:00
static PSPPointer < PspGeListArgs > listArgs ;
static u32 listArgsSize = sizeof ( PspGeListArgs ) ;
2012-12-27 23:00:04 -08:00
static u32 savedContextPtr ;
static u32 savedContextSize = 512 * 4 ;
2012-11-18 13:04:49 +01:00
// Display list writer
2012-12-27 23:00:04 -08:00
static u32 dlPtr ;
static u32 dlWritePtr ;
static u32 dlSize = 0x10000 ; // should be enough for a frame of gui...
2012-11-18 13:04:49 +01:00
2012-12-27 23:00:04 -08:00
static u32 dataPtr ;
static u32 dataWritePtr ;
static u32 dataSize = 0x10000 ; // should be enough for a frame of gui...
2012-11-18 13:04:49 +01:00
2013-07-27 15:38:38 -07:00
static PSPPointer < u16_le > palette ;
static u32 paletteSize = sizeof ( u16 ) * 16 ;
2013-04-17 20:51:02 +02:00
2012-11-18 13:04:49 +01:00
// Vertex collector
2012-12-27 23:00:04 -08:00
static u32 vertexStart ;
static u32 vertexCount ;
2012-11-18 13:04:49 +01:00
2020-12-20 12:11:02 +01:00
// Used for formatting text
2013-06-10 13:48:35 +08:00
struct AtlasCharVertex
{
float x ;
float y ;
const AtlasChar * c ;
} ;
2013-06-10 20:37:30 +08:00
struct AtlasTextMetrics
{
float x ;
float y ;
float maxWidth ;
float lineHeight ;
float scale ;
int numLines ;
} ;
2013-06-10 13:48:35 +08:00
typedef std : : vector < AtlasCharVertex > AtlasCharLine ;
typedef std : : vector < AtlasCharLine > AtlasLineArray ;
2013-06-10 20:37:30 +08:00
static AtlasCharLine char_one_line ;
2013-06-10 13:48:35 +08:00
static AtlasLineArray char_lines ;
2013-06-10 20:37:30 +08:00
static AtlasTextMetrics char_lines_metrics ;
2013-06-10 13:48:35 +08:00
2020-03-10 21:40:47 -07:00
static bool textDrawerInited = false ;
2020-03-10 17:53:30 -07:00
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 ;
} ;
2021-02-06 14:23:35 -08:00
static std : : map < PPGeTextDrawerCacheKey , PPGeTextDrawerImage > textDrawerImages ;
2020-03-10 17:53:30 -07:00
2020-12-20 12:11:02 +01:00
void PPGeSetDrawContext ( Draw : : DrawContext * draw ) {
g_draw = draw ;
}
2020-03-10 10:06:14 -07:00
// Overwrite the current text lines buffer so it can be drawn later.
2020-05-23 19:59:56 -07:00
void PPGePrepareText ( const char * text , float x , float y , PPGeAlign align , float scale , float lineHeightScale ,
2020-03-10 10:06:14 -07:00
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 ) ;
2021-02-07 23:06:35 -08:00
static void PPGeDecimateTextImages ( int age = 97 ) ;
2020-03-10 10:06:14 -07:00
void PPGeSetTexture ( u32 dataAddr , int width , int height ) ;
2012-11-18 13:04:49 +01:00
//only 0xFFFFFF of data is used
static void WriteCmd ( u8 cmd , u32 data ) {
Memory : : Write_U32 ( ( cmd < < 24 ) | ( data & 0xFFFFFF ) , dlWritePtr ) ;
dlWritePtr + = 4 ;
2020-08-16 00:38:55 +02:00
_dbg_assert_ ( dlWritePtr < = dlPtr + dlSize ) ;
2012-11-18 13:04:49 +01:00
}
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 ;
}
2020-04-22 21:19:34 -07:00
static void Vertex ( float x , float y , float u , float v , int tw , int th , u32 color = 0xFFFFFFFF ) {
2015-12-27 18:24:53 -08:00
if ( g_RemasterMode ) {
PPGeRemasterVertex vtx ;
2020-04-22 21:19:34 -07:00
vtx . x = x ; vtx . y = y ; vtx . z = 0 ;
vtx . u = u * tw ; vtx . v = v * th ;
2015-12-27 18:24:53 -08:00
vtx . color = color ;
Memory : : WriteStruct ( dataWritePtr , & vtx ) ;
dataWritePtr + = sizeof ( vtx ) ;
} else {
PPGeVertex vtx ;
2020-04-22 21:19:34 -07:00
vtx . x = x ; vtx . y = y ; vtx . z = 0 ;
vtx . u = u * tw ; vtx . v = v * th ;
2015-12-27 18:24:53 -08:00
vtx . color = color ;
Memory : : WriteStruct ( dataWritePtr , & vtx ) ;
dataWritePtr + = sizeof ( vtx ) ;
}
2020-08-16 00:38:55 +02:00
_dbg_assert_ ( dataWritePtr < = dataPtr + dataSize ) ;
2012-11-18 13:04:49 +01:00
vertexCount + + ;
}
static void EndVertexDataAndDraw ( int prim ) {
WriteCmdAddrWithBase ( GE_CMD_VADDR , vertexStart ) ;
WriteCmd ( GE_CMD_PRIM , ( prim < < 16 ) | vertexCount ) ;
}
2012-12-30 23:22:30 -08:00
static u32 __PPGeDoAlloc ( u32 & size , bool fromTop , const char * name ) {
u32 ptr = kernelMemory . Alloc ( size , fromTop , name ) ;
2021-02-07 23:17:53 -08:00
// 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 ;
}
}
2012-12-30 23:22:30 -08:00
return ptr ;
}
2013-09-20 21:06:27 -07:00
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 ;
}
}
2020-03-21 21:26:09 -07:00
void __PPGeInit ( ) {
2013-09-17 23:12:54 -07:00
// PPGe isn't really important for headless, and LoadZIM takes a long time.
2021-01-02 09:36:13 -08:00
bool skipZIM = host - > ShouldSkipUI ( ) ;
2020-03-21 21:26:09 -07:00
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 ) {
2020-07-19 19:49:20 +02:00
ERROR_LOG ( SCEGE , " Failed to load ppge_atlas.zim. \n \n Place it in the directory \" assets \" under your PPSSPP directory. \n \n PPGe stuff will not be drawn. " ) ;
2012-11-18 13:04:49 +01:00
}
2020-03-21 21:26:09 -07:00
if ( loadedZIM ) {
size_t atlas_data_size ;
if ( ! g_ppge_atlas . IsMetadataLoaded ( ) ) {
2020-06-22 22:45:24 +02:00
uint8_t * atlas_data = VFSReadFile ( " ppge_atlas.meta " , & atlas_data_size ) ;
2020-03-22 07:12:16 -07:00
if ( atlas_data )
g_ppge_atlas . Load ( atlas_data , atlas_data_size ) ;
2020-03-21 21:26:09 -07:00
delete [ ] atlas_data ;
}
2020-03-01 11:34:33 +01:00
}
2020-03-21 21:26:09 -07:00
2015-01-17 12:08:39 -08:00
u32 atlasSize = height [ 0 ] * width [ 0 ] / 2 ; // it's a 4-bit paletted texture in ram
atlasWidth = width [ 0 ] ;
atlasHeight = height [ 0 ] ;
2012-12-30 23:22:30 -08:00
dlPtr = __PPGeDoAlloc ( dlSize , false , " PPGe Display List " ) ;
dataPtr = __PPGeDoAlloc ( dataSize , false , " PPGe Vertex Data " ) ;
2013-09-20 21:06:27 -07:00
__PPGeSetupListArgs ( ) ;
2020-03-21 21:26:09 -07:00
atlasPtr = atlasSize = = 0 ? 0 : __PPGeDoAlloc ( atlasSize , false , " PPGe Atlas Texture " ) ;
2013-07-27 15:38:38 -07:00
palette = __PPGeDoAlloc ( paletteSize , false , " PPGe Texture Palette " ) ;
2012-11-18 13:04:49 +01:00
2013-04-17 20:51:02 +02:00
// Generate 16-greyscale palette. All PPGe graphics are greyscale so we can use a tiny paletted texture.
for ( int i = 0 ; i < 16 ; i + + ) {
2013-04-18 15:01:36 +02:00
int val = i ;
palette [ i ] = ( val < < 12 ) | 0xFFF ;
2012-11-18 13:04:49 +01:00
}
2015-01-17 12:08:39 -08:00
const u32_le * imagePtr = ( u32_le * ) imageData [ 0 ] ;
2020-03-21 21:26:09 -07:00
u8 * ramPtr = atlasPtr = = 0 ? nullptr : ( u8 * ) Memory : : GetPointer ( atlasPtr ) ;
2013-04-17 20:51:02 +02:00
// Palettize to 4-bit, the easy way.
2015-01-17 12:08:39 -08:00
for ( int i = 0 ; i < width [ 0 ] * height [ 0 ] / 2 ; i + + ) {
2014-11-03 09:00:20 -08:00
// Each pixel is 16 bits, so this loads two pixels.
2014-11-02 17:44:24 -08:00
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 ;
2013-04-17 20:51:02 +02:00
u8 cval = ( a2 < < 4 ) | a1 ;
ramPtr [ i ] = cval ;
}
2020-06-22 22:45:24 +02:00
2015-01-17 12:08:39 -08:00
free ( imageData [ 0 ] ) ;
2012-11-18 13:04:49 +01:00
2020-03-10 21:40:47 -07:00
// We can't create it here, because Android needs it on the right thread.
2020-04-05 15:23:13 -07:00
// Avoid creating ever on headless just to be safe.
textDrawerInited = PSP_CoreParameter ( ) . headLess ;
2020-03-10 21:40:47 -07:00
textDrawer = nullptr ;
2020-03-10 17:53:30 -07:00
textDrawerImages . clear ( ) ;
2020-06-22 22:45:24 +02:00
INFO_LOG ( SCEGE , " PPGe drawing library initialized. DL: %08x Data: %08x Atlas: %08x (%i) Args: %08x " ,
2013-09-20 21:06:27 -07:00
dlPtr , dataPtr , atlasPtr , atlasSize , listArgs . ptr ) ;
2012-11-18 13:04:49 +01:00
}
2012-12-27 23:00:04 -08:00
void __PPGeDoState ( PointerWrap & p )
{
2020-03-10 17:53:30 -07:00
auto s = p . Section ( " PPGeDraw " , 1 , 3 ) ;
2013-09-14 20:23:03 -07:00
if ( ! s )
return ;
2020-08-09 21:20:42 -07:00
Do ( p , atlasPtr ) ;
Do ( p , atlasWidth ) ;
Do ( p , atlasHeight ) ;
Do ( p , palette ) ;
2012-12-27 23:00:04 -08:00
2020-08-09 21:20:42 -07:00
Do ( p , savedContextPtr ) ;
Do ( p , savedContextSize ) ;
2012-12-27 23:00:04 -08:00
2013-09-20 21:06:27 -07:00
if ( s = = 1 ) {
listArgs = 0 ;
} else {
2020-08-09 21:20:42 -07:00
Do ( p , listArgs ) ;
2013-09-20 21:06:27 -07:00
}
2020-03-10 17:53:30 -07:00
if ( s > = 3 ) {
uint32_t sz = ( uint32_t ) textDrawerImages . size ( ) ;
2020-08-09 21:20:42 -07:00
Do ( p , sz ) ;
2020-03-10 17:53:30 -07:00
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 { } ;
2020-08-09 21:20:42 -07:00
Do ( p , textDrawerImages [ key ] . ptr ) ;
2020-03-10 17:53:30 -07:00
}
break ;
default :
for ( const auto & im : textDrawerImages ) {
2020-08-09 21:20:42 -07:00
Do ( p , im . second . ptr ) ;
2020-03-10 17:53:30 -07:00
}
break ;
}
} else {
textDrawerImages . clear ( ) ;
}
2020-08-09 21:20:42 -07:00
Do ( p , dlPtr ) ;
Do ( p , dlWritePtr ) ;
Do ( p , dlSize ) ;
2012-12-27 23:00:04 -08:00
2020-08-09 21:20:42 -07:00
Do ( p , dataPtr ) ;
Do ( p , dataWritePtr ) ;
Do ( p , dataSize ) ;
2012-12-27 23:00:04 -08:00
2020-08-09 21:20:42 -07:00
Do ( p , vertexStart ) ;
Do ( p , vertexCount ) ;
2012-12-27 23:00:04 -08:00
2020-08-09 21:20:42 -07:00
Do ( p , char_lines ) ;
Do ( p , char_lines_metrics ) ;
2012-12-27 23:00:04 -08:00
}
2012-11-18 13:04:49 +01:00
void __PPGeShutdown ( )
{
2012-11-21 22:59:26 -08:00
if ( atlasPtr )
kernelMemory . Free ( atlasPtr ) ;
if ( dataPtr )
kernelMemory . Free ( dataPtr ) ;
if ( dlPtr )
kernelMemory . Free ( dlPtr ) ;
2013-09-20 21:06:27 -07:00
if ( listArgs . IsValid ( ) )
kernelMemory . Free ( listArgs . ptr ) ;
2012-11-21 22:59:26 -08:00
if ( savedContextPtr )
kernelMemory . Free ( savedContextPtr ) ;
2013-07-27 15:38:38 -07:00
if ( palette )
kernelMemory . Free ( palette . ptr ) ;
2012-11-21 22:59:26 -08:00
2012-11-18 13:04:49 +01:00
atlasPtr = 0 ;
dataPtr = 0 ;
dlPtr = 0 ;
savedContextPtr = 0 ;
2013-09-20 21:06:27 -07:00
listArgs = 0 ;
2020-03-10 17:53:30 -07:00
delete textDrawer ;
textDrawer = nullptr ;
2021-02-06 14:23:35 -08:00
for ( auto im : textDrawerImages )
kernelMemory . Free ( im . second . ptr ) ;
textDrawerImages . clear ( ) ;
2012-11-18 13:04:49 +01:00
}
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
2013-03-04 22:15:39 +01:00
WriteCmd ( GE_CMD_OFFSETADDR , 0 ) ;
2012-11-18 13:04:49 +01:00
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 ) ;
2013-03-04 22:15:39 +01:00
WriteCmd ( GE_CMD_LIGHTINGENABLE , 0 ) ;
2012-11-18 13:04:49 +01:00
WriteCmd ( GE_CMD_FOGENABLE , 0 ) ;
WriteCmd ( GE_CMD_STENCILTESTENABLE , 0 ) ;
WriteCmd ( GE_CMD_CULLFACEENABLE , 0 ) ;
WriteCmd ( GE_CMD_CLEARMODE , 0 ) ; // Normal mode
2013-03-04 22:15:39 +01:00
WriteCmd ( GE_CMD_MASKRGB , 0 ) ;
WriteCmd ( GE_CMD_MASKALPHA , 0 ) ;
2012-11-18 13:04:49 +01:00
2012-12-13 22:06:45 +01:00
PPGeSetDefaultTexture ( ) ;
2012-11-18 13:04:49 +01:00
2020-05-23 22:38:02 -07:00
PPGeScissor ( 0 , 0 , 480 , 272 ) ;
2012-11-18 13:04:49 +01:00
WriteCmd ( GE_CMD_MINZ , 0 ) ;
WriteCmd ( GE_CMD_MAXZ , 0xFFFF ) ;
// Through mode, so we don't have to bother with matrices
2015-12-27 18:24:53 -08:00
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 ) ;
}
2012-11-18 13:04:49 +01:00
}
void PPGeEnd ( )
{
if ( ! dlPtr )
return ;
WriteCmd ( GE_CMD_FINISH , 0 ) ;
WriteCmd ( GE_CMD_END , 0 ) ;
2013-09-20 21:06:27 -07:00
// Might've come from an old savestate.
__PPGeSetupListArgs ( ) ;
2012-11-18 13:04:49 +01:00
2013-09-20 21:06:27 -07:00
if ( dataWritePtr > dataPtr ) {
2012-11-18 13:04:49 +01:00
// We actually drew something
2013-09-20 21:06:27 -07:00
gpu - > EnableInterrupts ( false ) ;
u32 list = sceGeListEnQueue ( dlPtr , dlWritePtr , - 1 , listArgs . ptr ) ;
2013-09-07 22:02:55 +02:00
DEBUG_LOG ( SCEGE , " PPGe enqueued display list %i " , list ) ;
2012-11-18 13:04:49 +01:00
gpu - > EnableInterrupts ( true ) ;
}
}
2020-05-23 22:38:02 -07:00
void PPGeScissor ( int x1 , int y1 , int x2 , int y2 ) {
2020-08-16 00:38:55 +02:00
_dbg_assert_ ( x1 > = 0 & & x1 < = 480 & & x2 > = 0 & & x2 < = 480 ) ;
_dbg_assert_ ( y1 > = 0 & & y1 < = 272 & & y2 > = 0 & & y2 < = 272 ) ;
2020-05-23 22:38:02 -07:00
WriteCmd ( GE_CMD_SCISSOR1 , ( y1 < < 10 ) | x1 ) ;
WriteCmd ( GE_CMD_SCISSOR2 , ( ( y2 - 1 ) < < 10 ) | ( x2 - 1 ) ) ;
}
void PPGeScissorReset ( ) {
PPGeScissor ( 0 , 0 , 480 , 272 ) ;
}
2013-04-17 23:25:40 -07:00
static const AtlasChar * PPGeGetChar ( const AtlasFont & atlasfont , unsigned int cval )
{
const AtlasChar * c = atlasfont . getChar ( cval ) ;
2013-05-27 19:26:37 -07:00
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 ( ' ? ' ) ;
}
2013-04-17 23:25:40 -07:00
return c ;
}
2013-06-10 13:48:35 +08:00
// Break a single text string into mutiple lines.
2013-06-10 20:37:30 +08:00
static AtlasTextMetrics BreakLines ( const char * text , const AtlasFont & atlasfont , float x , float y ,
2020-05-23 19:59:56 -07:00
PPGeAlign align , float scale , float lineHeightScale , int wrapType , float wrapWidth , bool dryRun )
2013-06-10 13:48:35 +08:00
{
y + = atlasfont . ascend * scale ;
2013-06-10 20:37:30 +08:00
float sx = x , sy = y ;
// TODO: Can we wrap it smartly on the screen edge?
if ( wrapWidth < = 0 ) {
wrapWidth = 480.f ;
}
2013-06-10 13:48:35 +08:00
2013-06-15 19:32:32 +08:00
// used for replacing with ellipses
2013-06-10 13:48:35 +08:00
float wrapCutoff = 8.0f ;
const AtlasChar * dot = PPGeGetChar ( atlasfont , ' . ' ) ;
if ( dot ) {
wrapCutoff = dot - > wx * scale * 3.0f ;
}
2013-06-15 19:28:05 +08:00
float threshold = sx + wrapWidth - wrapCutoff ;
2013-06-10 13:48:35 +08:00
//const float wrapGreyZone = 2.0f; // Grey zone for punctuations at line ends
2013-06-10 20:37:30 +08:00
int numLines = 1 ;
2013-06-10 13:48:35 +08:00
float maxw = 0 ;
2019-02-16 14:52:01 -08:00
float lineHeight = atlasfont . height * lineHeightScale ;
2013-06-10 13:48:35 +08:00
for ( UTF8 utf ( text ) ; ! utf . end ( ) ; )
{
float lineWidth = 0 ;
2013-06-15 19:28:05 +08:00
bool skipRest = false ;
2013-06-10 13:48:35 +08:00
while ( ! utf . end ( ) )
{
2013-06-15 16:45:52 +08:00
UTF8 utfWord ( utf ) ;
2013-06-15 19:32:32 +08:00
float nextWidth = 0 ;
float spaceWidth = 0 ;
int numChars = 0 ;
bool finished = false ;
2013-06-15 16:45:52 +08:00
while ( ! utfWord . end ( ) & & ! finished )
2013-06-15 19:32:32 +08:00
{
2013-06-15 16:45:52 +08:00
UTF8 utfPrev = utfWord ;
u32 cval = utfWord . next ( ) ;
2013-06-15 19:32:32 +08:00
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 :
2013-06-15 16:45:52 +08:00
{
// 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 ;
2019-02-27 13:42:00 +01:00
}
2013-06-15 16:45:52 +08:00
}
}
2013-06-15 19:32:32 +08:00
+ + numChars ;
nextWidth + = ch - > wx * scale ;
break ;
}
2013-06-10 13:48:35 +08:00
}
2013-06-15 19:32:32 +08:00
2013-06-15 19:28:05 +08:00
bool useEllipsis = false ;
2013-06-15 19:32:32 +08:00
if ( wrapType > 0 )
2013-06-10 13:48:35 +08:00
{
2013-06-15 19:28:05 +08:00
if ( lineWidth + nextWidth > wrapWidth | | skipRest )
2013-06-10 13:48:35 +08:00
{
2013-06-15 19:32:32 +08:00
if ( wrapType & PPGE_LINE_WRAP_WORD ) {
// TODO: Should check if we have had at least one other word instead.
if ( lineWidth > 0 ) {
+ + numLines ;
break ;
2013-06-10 13:48:35 +08:00
}
2013-06-15 19:32:32 +08:00
}
if ( wrapType & PPGE_LINE_USE_ELLIPSIS ) {
2013-06-15 19:28:05 +08:00
useEllipsis = true ;
if ( skipRest ) {
numChars = 0 ;
} else if ( nextWidth < wrapCutoff ) {
// The word is too short, so just backspace!
x = threshold ;
2013-06-10 13:48:35 +08:00
}
2013-06-15 19:28:05 +08:00
nextWidth = 0 ;
spaceWidth = 0 ;
lineWidth = wrapWidth ;
2013-06-10 13:48:35 +08:00
}
}
2013-06-15 19:32:32 +08:00
}
for ( int i = 0 ; i < numChars ; + + i )
{
u32 cval = utf . next ( ) ;
const AtlasChar * c = PPGeGetChar ( atlasfont , cval ) ;
if ( c )
{
2013-06-15 19:28:05 +08:00
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 ;
}
2013-06-15 19:32:32 +08:00
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 ;
2019-02-27 13:42:00 +01:00
u32 cval = utf . end ( ) ? 0 : utf . next ( ) ;
2013-06-15 19:32:32 +08:00
if ( spaceWidth > 0 )
{
2013-06-10 20:37:30 +08:00
if ( ! dryRun )
{
2013-06-15 19:32:32 +08:00
// No need to check c.
const AtlasChar * c = PPGeGetChar ( atlasfont , cval ) ;
2013-06-10 20:37:30 +08:00
AtlasCharVertex cv ;
cv . x = x + c - > ox * scale ;
cv . y = y + c - > oy * scale ;
cv . c = c ;
char_one_line . push_back ( cv ) ;
}
2013-06-15 19:32:32 +08:00
x + = spaceWidth ;
lineWidth + = spaceWidth ;
if ( wrapType > 0 & & lineWidth > wrapWidth ) {
lineWidth = wrapWidth ;
}
}
else if ( cval = = ' \n ' ) {
+ + numLines ;
break ;
2013-06-10 13:48:35 +08:00
}
2013-06-15 16:45:52 +08:00
utf = utfWord ;
2013-06-10 13:48:35 +08:00
}
2013-06-10 20:37:30 +08:00
y + = lineHeight ;
2013-06-10 13:48:35 +08:00
x = sx ;
2013-06-10 20:37:30 +08:00
if ( lineWidth > maxw ) {
2013-06-10 13:48:35 +08:00
maxw = lineWidth ;
2013-06-10 20:37:30 +08:00
}
if ( ! dryRun )
{
char_lines . push_back ( char_one_line ) ;
char_one_line . clear ( ) ;
}
2013-06-10 13:48:35 +08:00
}
2013-06-10 20:37:30 +08:00
const float w = maxw ;
const float h = ( float ) numLines * lineHeight ;
2020-05-23 19:59:56 -07:00
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 ;
2013-06-10 20:37:30 +08:00
}
2013-06-10 13:48:35 +08:00
}
}
2020-05-23 19:59:56 -07:00
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 ;
2013-06-10 13:48:35 +08:00
}
2013-06-10 20:37:30 +08:00
AtlasTextMetrics metrics = { sx , sy , w , lineHeight , scale , numLines } ;
return metrics ;
2013-06-10 13:48:35 +08:00
}
2020-03-10 21:40:47 -07:00
static bool HasTextDrawer ( ) {
// We create this on first use so it's on the correct thread.
if ( textDrawerInited ) {
return textDrawer ! = nullptr ;
}
2020-12-20 12:11:02 +01:00
// TODO: Should we pass a draw_? Yes! UWP requires it.
textDrawer = TextDrawer : : Create ( g_draw ) ;
2020-03-10 21:40:47 -07:00
if ( textDrawer ) {
textDrawer - > SetFontScale ( 1.0f , 1.0f ) ;
textDrawer - > SetForcedDPIScale ( 1.0f ) ;
2020-03-28 14:37:35 +01:00
textDrawer - > SetFont ( g_Config . sFont . c_str ( ) , 18 , 0 ) ;
2020-03-10 21:40:47 -07:00
}
textDrawerInited = true ;
return textDrawer ! = nullptr ;
}
2020-03-23 18:23:35 -07:00
void PPGeMeasureText ( float * w , float * h , const char * text , float scale , int WrapType , int wrapWidth ) {
2020-03-10 21:40:47 -07:00
if ( HasTextDrawer ( ) ) {
2020-03-10 17:53:30 -07:00
float mw , mh ;
textDrawer - > SetFontScale ( scale , scale ) ;
int dtalign = ( WrapType & PPGE_LINE_WRAP_WORD ) ? FLAG_WRAP_TEXT : 0 ;
2020-03-10 19:06:30 -07:00
if ( WrapType & PPGE_LINE_USE_ELLIPSIS )
dtalign | = FLAG_ELLIPSIZE_TEXT ;
2020-03-10 17:53:30 -07:00
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 ;
return ;
}
2020-03-21 21:26:09 -07:00
if ( ! g_ppge_atlas . IsMetadataLoaded ( ) | | g_ppge_atlas . num_fonts < 1 ) {
if ( w )
* w = 0 ;
if ( h )
* h = 0 ;
return ;
}
2020-02-29 21:51:14 +01:00
const AtlasFont & atlasfont = g_ppge_atlas . fonts [ 0 ] ;
2020-05-23 19:59:56 -07:00
AtlasTextMetrics metrics = BreakLines ( text , atlasfont , 0 , 0 , PPGeAlign : : BOX_TOP , scale , scale , WrapType , wrapWidth , true ) ;
2013-06-10 20:37:30 +08:00
if ( w ) * w = metrics . maxWidth ;
2020-03-23 18:23:35 -07:00
if ( h ) * h = metrics . lineHeight * metrics . numLines ;
2013-06-10 20:37:30 +08:00
}
2020-05-23 19:59:56 -07:00
void PPGePrepareText ( const char * text , float x , float y , PPGeAlign align , float scale , float lineHeightScale , int WrapType , int wrapWidth )
2013-06-10 20:37:30 +08:00
{
2020-02-29 21:51:14 +01:00
const AtlasFont & atlasfont = g_ppge_atlas . fonts [ 0 ] ;
2020-03-21 21:26:09 -07:00
if ( ! g_ppge_atlas . IsMetadataLoaded ( ) | | g_ppge_atlas . num_fonts < 1 ) {
return ;
}
2019-02-16 14:52:01 -08:00
char_lines_metrics = BreakLines ( text , atlasfont , x , y , align , scale , lineHeightScale , WrapType , wrapWidth , false ) ;
2013-06-10 20:37:30 +08:00
}
2019-02-16 14:52:01 -08:00
static void PPGeResetCurrentText ( ) {
char_one_line . clear ( ) ;
char_lines . clear ( ) ;
AtlasTextMetrics zeroBox = { 0 } ;
char_lines_metrics = zeroBox ;
}
2013-06-10 13:48:35 +08:00
// Draws some text using the one font we have.
// Mostly rewritten.
2013-06-10 20:37:30 +08:00
void PPGeDrawCurrentText ( u32 color )
{
if ( dlPtr )
2013-06-10 13:48:35 +08:00
{
2013-06-10 20:37:30 +08:00
float scale = char_lines_metrics . scale ;
BeginVertexData ( ) ;
2013-06-12 10:51:30 +10:00
for ( auto i = char_lines . begin ( ) ; i ! = char_lines . end ( ) ; + + i )
2013-06-10 13:48:35 +08:00
{
2013-06-12 10:51:30 +10:00
for ( auto j = i - > begin ( ) ; j ! = i - > end ( ) ; + + j )
2013-06-10 20:37:30 +08:00
{
float cx1 = j - > x ;
float cy1 = j - > y ;
const AtlasChar & c = * j - > c ;
float cx2 = cx1 + c . pw * scale ;
float cy2 = cy1 + c . ph * scale ;
Vertex ( cx1 , cy1 , c . sx , c . sy , atlasWidth , atlasHeight , color ) ;
Vertex ( cx2 , cy2 , c . ex , c . ey , atlasWidth , atlasHeight , color ) ;
}
2013-06-10 13:48:35 +08:00
}
2013-06-10 20:37:30 +08:00
EndVertexDataAndDraw ( GE_PRIM_RECTANGLES ) ;
2013-06-10 13:48:35 +08:00
}
2019-02-16 14:52:01 -08:00
PPGeResetCurrentText ( ) ;
2013-06-10 13:48:35 +08:00
}
2013-05-19 23:36:23 -07:00
2020-03-10 17:53:30 -07:00
// 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 ;
}
2020-05-23 21:21:16 -07:00
static PPGeTextDrawerImage PPGeGetTextImage ( const char * text , const PPGeStyle & style , float maxWidth , bool wrap ) {
2020-05-23 19:59:56 -07:00
int tdalign = 0 ;
2020-03-10 19:06:30 -07:00
tdalign | = FLAG_ELLIPSIZE_TEXT ;
2020-03-10 17:53:30 -07:00
if ( wrap ) {
tdalign | = FLAG_WRAP_TEXT ;
}
2020-05-23 21:21:16 -07:00
PPGeTextDrawerCacheKey key { text , tdalign , maxWidth / style . scale } ;
2020-06-28 21:35:19 +02:00
PPGeTextDrawerImage im { } ;
2020-03-10 17:53:30 -07:00
auto cacheItem = textDrawerImages . find ( key ) ;
if ( cacheItem ! = textDrawerImages . end ( ) ) {
im = cacheItem - > second ;
cacheItem - > second . entry . lastUsedFrame = gpuStats . numFlips ;
} else {
std : : vector < uint8_t > bitmapData ;
2020-05-23 21:21:16 -07:00
textDrawer - > SetFontScale ( style . scale , style . scale ) ;
2020-03-10 17:53:30 -07:00
Bounds b ( 0 , 0 , maxWidth , 272.0f ) ;
2020-03-11 06:37:18 -07:00
std : : string cleaned = ReplaceAll ( text , " \r " , " " ) ;
textDrawer - > DrawStringBitmapRect ( bitmapData , im . entry , Draw : : DataFormat : : R8_UNORM , cleaned . c_str ( ) , b , tdalign ) ;
2020-03-10 17:53:30 -07:00
int bufwBytes = ( ( im . entry . bmWidth + 31 ) / 32 ) * 16 ;
2020-03-11 06:48:11 -07:00
u32 sz = bufwBytes * ( im . entry . bmHeight + 1 ) ;
2020-03-10 17:53:30 -07:00
u32 origSz = sz ;
im . ptr = __PPGeDoAlloc ( sz , true , " PPGeText " ) ;
if ( bitmapData . size ( ) & 1 )
bitmapData . resize ( bitmapData . size ( ) + 1 ) ;
if ( im . ptr ) {
2020-03-11 06:48:11 -07:00
int wBytes = ( im . entry . bmWidth + 1 ) / 2 ;
2020-03-10 17:53:30 -07:00
u8 * ramPtr = ( u8 * ) Memory : : GetPointer ( im . ptr ) ;
for ( int y = 0 ; y < im . entry . bmHeight ; + + y ) {
2020-03-11 06:48:11 -07:00
for ( int x = 0 ; x < wBytes ; + + x ) {
2020-03-10 17:53:30 -07:00
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 ) ;
}
2020-03-11 06:48:11 -07:00
if ( bufwBytes ! = wBytes ) {
memset ( ramPtr + y * bufwBytes + wBytes , 0 , bufwBytes - wBytes ) ;
}
2020-03-10 17:53:30 -07:00
}
2020-03-11 06:48:11 -07:00
memset ( ramPtr + im . entry . bmHeight * bufwBytes , 0 , bufwBytes + sz - origSz ) ;
2020-03-10 17:53:30 -07:00
}
im . entry . lastUsedFrame = gpuStats . numFlips ;
textDrawerImages [ key ] = im ;
}
return im ;
}
2020-05-23 21:21:16 -07:00
static void PPGeDrawTextImage ( PPGeTextDrawerImage im , float x , float y , const PPGeStyle & style ) {
2020-06-28 21:35:19 +02:00
if ( ! im . ptr ) {
return ;
}
2020-03-10 17:53:30 -07:00
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 ) ;
2020-05-23 21:21:16 -07:00
float w = im . entry . width * style . scale ;
float h = im . entry . height * style . scale ;
2020-03-10 17:53:30 -07:00
2020-05-23 21:21:16 -07:00
if ( style . align & PPGeAlign : : BOX_HCENTER )
2020-03-10 17:53:30 -07:00
x - = w / 2.0f ;
2020-05-23 21:21:16 -07:00
else if ( style . align & PPGeAlign : : BOX_RIGHT )
2020-03-10 17:53:30 -07:00
x - = w ;
2020-05-23 21:21:16 -07:00
if ( style . align & PPGeAlign : : BOX_VCENTER )
2020-03-10 17:53:30 -07:00
y - = h / 2.0f ;
2020-05-23 21:21:16 -07:00
else if ( style . align & PPGeAlign : : BOX_BOTTOM )
2020-03-10 17:53:30 -07:00
y - = h ;
BeginVertexData ( ) ;
float u1 = ( float ) im . entry . width / ( 1 < < wp2 ) ;
float v1 = ( float ) im . entry . height / ( 1 < < hp2 ) ;
2020-05-23 21:21:16 -07:00
if ( style . hasShadow ) {
2020-05-23 21:42:30 -07:00
// Draw more shadows for a blurrier shadow.
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 < < wp2 , 1 < < hp2 , alphaMul ( style . shadowColor , 0.35f ) ) ;
Vertex ( x + dx + w , y + dy + h , u1 , v1 , 1 < < wp2 , 1 < < hp2 , alphaMul ( style . shadowColor , 0.35f ) ) ;
}
}
2020-05-23 21:21:16 -07:00
}
Vertex ( x , y , 0 , 0 , 1 < < wp2 , 1 < < hp2 , style . color ) ;
Vertex ( x + w , y + h , u1 , v1 , 1 < < wp2 , 1 < < hp2 , style . color ) ;
2020-03-10 17:53:30 -07:00
EndVertexDataAndDraw ( GE_PRIM_RECTANGLES ) ;
PPGeSetDefaultTexture ( ) ;
}
2021-02-07 23:06:35 -08:00
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 ;
}
}
}
2020-05-23 21:21:16 -07:00
void PPGeDrawText ( const char * text , float x , float y , const PPGeStyle & style ) {
2020-06-28 21:35:19 +02:00
if ( ! text | | ! strlen ( text ) ) {
return ;
}
2020-03-10 21:40:47 -07:00
if ( HasTextDrawer ( ) ) {
2020-05-23 21:21:16 -07:00
PPGeTextDrawerImage im = PPGeGetTextImage ( text , style , 480.0f - x , false ) ;
2021-02-06 14:10:18 -08:00
if ( im . ptr ) {
PPGeDrawTextImage ( im , x , y , style ) ;
return ;
}
2020-03-10 17:53:30 -07:00
}
2020-05-23 21:21:16 -07:00
if ( style . hasShadow ) {
2020-05-23 21:42:30 -07:00
// This doesn't have the nicer shadow because it's so many verts.
2020-05-23 21:21:16 -07:00
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 ) ;
2013-06-10 20:37:30 +08:00
}
2019-02-16 14:52:01 -08:00
static std : : string StripTrailingWhite ( const std : : string & 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 CropLinesToCount ( const std : : string & s , int numLines ) {
std : : vector < std : : string > 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 ] . length ( ) + 1 ;
}
return s . substr ( 0 , len ) ;
}
2020-05-23 21:21:16 -07:00
void PPGeDrawTextWrapped ( const char * text , float x , float y , float wrapWidth , float wrapHeight , const PPGeStyle & style ) {
2019-02-16 14:52:01 -08:00
std : : string s = text ;
2019-02-20 17:04:30 -08:00
if ( wrapHeight ! = 0.0f ) {
2019-02-16 14:52:01 -08:00
s = StripTrailingWhite ( s ) ;
}
int zoom = ( PSP_CoreParameter ( ) . pixelHeight + 479 ) / 480 ;
2021-01-23 09:04:58 -08:00
zoom = std : : min ( zoom , PSP_CoreParameter ( ) . renderScaleFactor ) ;
2019-02-16 14:52:01 -08:00
float maxScaleDown = zoom = = 1 ? 1.3f : 2.0f ;
2020-03-10 17:53:30 -07:00
2020-03-10 21:40:47 -07:00
if ( HasTextDrawer ( ) ) {
2020-03-10 17:53:30 -07:00
float actualWidth , actualHeight ;
Bounds b ( 0 , 0 , wrapWidth < = 0 ? 480.0f - x : wrapWidth , wrapHeight ) ;
2020-05-23 19:59:56 -07:00
int tdalign = 0 ;
2020-05-23 21:21:16 -07:00
textDrawer - > SetFontScale ( style . scale , style . scale ) ;
2020-03-10 17:53:30 -07:00
textDrawer - > MeasureStringRect ( s . c_str ( ) , s . size ( ) , b , & actualWidth , & actualHeight , tdalign | FLAG_WRAP_TEXT ) ;
2020-05-23 21:21:16 -07:00
// Check if we need to scale the text down to fit better.
PPGeStyle adjustedStyle = style ;
2020-03-10 17:53:30 -07:00
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 ... " ;
}
2020-05-23 21:21:16 -07:00
adjustedStyle . scale * = wrapHeight / actualHeight ;
2020-03-10 17:53:30 -07:00
}
2020-05-23 21:21:16 -07:00
PPGeTextDrawerImage im = PPGeGetTextImage ( s . c_str ( ) , adjustedStyle , wrapWidth < = 0 ? 480.0f - x : wrapWidth , true ) ;
2021-02-06 14:10:18 -08:00
if ( im . ptr ) {
PPGeDrawTextImage ( im , x , y , adjustedStyle ) ;
return ;
}
2020-03-10 17:53:30 -07:00
}
2020-05-23 21:21:16 -07:00
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 ) ;
2020-03-10 17:53:30 -07:00
2020-05-23 21:21:16 -07:00
float scale = style . scale ;
float lineHeightScale = style . scale ;
2019-02-16 14:52:01 -08:00
float actualHeight = char_lines_metrics . lineHeight * char_lines_metrics . numLines ;
2019-02-20 17:04:30 -08:00
if ( wrapHeight ! = 0.0f & & actualHeight > wrapHeight ) {
2019-02-16 14:52:01 -08:00
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 ) ) + " \n ... " ;
}
// Measure the text again after scaling down.
PPGeResetCurrentText ( ) ;
2020-05-23 21:21:16 -07:00
float reduced = style . scale * wrapHeight / actualHeight ;
2019-02-16 14:52:01 -08:00
// Try to keep the font as large as possible, so reduce the line height some.
2020-05-23 21:21:16 -07:00
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 ) ;
2019-02-16 14:52:01 -08:00
}
2020-05-23 21:21:16 -07:00
if ( style . hasShadow ) {
2020-05-23 21:42:30 -07:00
// This doesn't have the nicer shadow because it's so many verts.
2020-05-23 21:21:16 -07:00
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 ) ;
2013-05-19 23:20:50 -07:00
}
2012-11-18 13:04:49 +01:00
// Draws a "4-patch" for button-like things that can be resized
2020-02-29 21:51:14 +01:00
void PPGeDraw4Patch ( ImageID atlasImage , float x , float y , float w , float h , u32 color ) {
2012-11-18 13:04:49 +01:00
if ( ! dlPtr )
return ;
2020-02-29 21:51:14 +01:00
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 ;
2012-11-18 13:04:49 +01:00
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
2012-12-13 22:06:45 +01:00
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 ) ;
2012-11-18 13:04:49 +01:00
// Middle row
2012-12-13 22:06:45 +01:00
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 ) ;
2012-11-18 13:04:49 +01:00
// Bottom row
2012-12-13 22:06:45 +01:00
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 ) ;
2012-11-18 13:04:49 +01:00
EndVertexDataAndDraw ( GE_PRIM_RECTANGLES ) ;
}
2020-02-29 21:51:14 +01:00
void PPGeDrawRect ( float x1 , float y1 , float x2 , float y2 , u32 color ) {
2013-01-06 23:23:36 +01:00
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 ) ;
}
2012-11-18 13:04:49 +01:00
// Just blits an image to the screen, multiplied with the color.
2020-05-23 21:21:16 -07:00
void PPGeDrawImage ( ImageID atlasImage , float x , float y , const PPGeStyle & style ) {
2012-11-18 13:04:49 +01:00
if ( ! dlPtr )
return ;
2020-02-29 21:51:14 +01:00
const AtlasImage * img = g_ppge_atlas . getImage ( atlasImage ) ;
if ( ! img ) {
return ;
}
float w = img - > w ;
float h = img - > h ;
2012-11-18 13:04:49 +01:00
BeginVertexData ( ) ;
2020-05-23 21:21:16 -07:00
if ( style . hasShadow ) {
2020-05-23 21:42:30 -07:00
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 ) ) ;
}
}
2020-05-23 21:21:16 -07:00
}
Vertex ( x , y , img - > u1 , img - > v1 , atlasWidth , atlasHeight , style . color ) ;
Vertex ( x + w , y + h , img - > u2 , img - > v2 , atlasWidth , atlasHeight , style . color ) ;
2012-11-18 13:04:49 +01:00
EndVertexDataAndDraw ( GE_PRIM_RECTANGLES ) ;
}
2020-05-23 21:21:16 -07:00
void PPGeDrawImage ( ImageID atlasImage , float x , float y , float w , float h , const PPGeStyle & style ) {
2012-12-10 13:08:54 +01:00
if ( ! dlPtr )
return ;
2020-02-29 21:51:14 +01:00
const AtlasImage * img = g_ppge_atlas . getImage ( atlasImage ) ;
2020-03-21 21:26:09 -07:00
if ( ! img ) {
return ;
}
2012-12-10 13:08:54 +01:00
BeginVertexData ( ) ;
2020-05-23 21:21:16 -07:00
if ( style . hasShadow ) {
2020-05-23 21:42:30 -07:00
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 ) ) ;
}
}
2020-05-23 21:21:16 -07:00
}
Vertex ( x , y , img - > u1 , img - > v1 , atlasWidth , atlasHeight , style . color ) ;
Vertex ( x + w , y + h , img - > u2 , img - > v2 , atlasWidth , atlasHeight , style . color ) ;
2012-12-10 13:08:54 +01:00
EndVertexDataAndDraw ( GE_PRIM_RECTANGLES ) ;
}
2021-01-03 14:42:29 -08:00
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 ) {
2012-12-13 22:06:45 +01:00
if ( ! dlPtr )
return ;
BeginVertexData ( ) ;
2021-01-03 14:42:29 -08:00
Vertex ( x , y , u1 , v1 , tw , th , style . color ) ;
Vertex ( x + w , y + h , u2 , v2 , tw , th , style . color ) ;
2012-12-13 22:06:45 +01:00
EndVertexDataAndDraw ( GE_PRIM_RECTANGLES ) ;
}
void PPGeSetDefaultTexture ( )
{
WriteCmd ( GE_CMD_TEXTUREMAPENABLE , 1 ) ;
int wp2 = GetPow2 ( atlasWidth ) ;
int hp2 = GetPow2 ( atlasHeight ) ;
2013-07-27 15:38:38 -07:00
WriteCmd ( GE_CMD_CLUTADDR , palette . ptr & 0xFFFFF0 ) ;
WriteCmd ( GE_CMD_CLUTADDRUPPER , ( palette . ptr & 0xFF000000 ) > > 8 ) ;
2013-04-18 15:01:36 +02:00
WriteCmd ( GE_CMD_CLUTFORMAT , 0x00FF02 ) ;
2013-04-17 20:51:02 +02:00
WriteCmd ( GE_CMD_LOADCLUT , 2 ) ;
WriteCmd ( GE_CMD_TEXSIZE0 , wp2 | ( hp2 < < 8 ) ) ;
2012-12-13 22:06:45 +01:00
WriteCmd ( GE_CMD_TEXMAPMODE , 0 | ( 1 < < 8 ) ) ;
WriteCmd ( GE_CMD_TEXMODE , 0 ) ;
2013-04-17 20:51:02 +02:00
WriteCmd ( GE_CMD_TEXFORMAT , GE_TFMT_CLUT4 ) ; // 4-bit CLUT
2012-12-13 22:06:45 +01:00
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 ) ;
2013-04-17 20:51:02 +02:00
WriteCmd ( GE_CMD_TEXSIZE0 , wp2 | ( hp2 < < 8 ) ) ;
2012-12-13 22:06:45 +01:00
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 ) ;
}
2013-12-08 11:06:18 -08:00
std : : vector < PPGeImage * > PPGeImage : : loadedTextures_ ;
PPGeImage : : PPGeImage ( const std : : string & pspFilename )
: filename_ ( pspFilename ) , texture_ ( 0 ) {
}
PPGeImage : : PPGeImage ( u32 pngPointer , size_t pngSize )
2013-12-17 23:36:51 -08:00
: filename_ ( " " ) , png_ ( pngPointer ) , size_ ( pngSize ) , texture_ ( 0 ) {
2013-12-08 11:06:18 -08:00
}
PPGeImage : : ~ PPGeImage ( ) {
Free ( ) ;
}
bool PPGeImage : : Load ( ) {
Free ( ) ;
// In case it fails to load.
width_ = 0 ;
height_ = 0 ;
unsigned char * textureData ;
int success ;
if ( filename_ . empty ( ) ) {
2020-10-04 20:48:47 +02:00
success = pngLoadPtr ( Memory : : GetPointer ( png_ ) , size_ , & width_ , & height_ , & textureData ) ;
2013-12-08 11:06:18 -08:00
} else {
2013-12-08 12:02:37 -08:00
std : : vector < u8 > pngData ;
2013-12-08 11:06:18 -08:00
if ( pspFileSystem . ReadEntireFile ( filename_ , pngData ) < 0 ) {
WARN_LOG ( SCEGE , " Bad PPGeImage - cannot load file " ) ;
return false ;
}
2020-10-04 20:48:47 +02:00
success = pngLoadPtr ( ( const unsigned char * ) & pngData [ 0 ] , pngData . size ( ) , & width_ , & height_ , & textureData ) ;
2013-12-08 11:06:18 -08:00
}
if ( ! success ) {
WARN_LOG ( SCEGE , " Bad PPGeImage - not a valid png " ) ;
return false ;
}
2021-01-03 14:42:29 -08:00
u32 dataSize = width_ * height_ * 4 ;
u32 texSize = dataSize + width_ * 4 ;
2013-12-08 11:06:18 -08:00
texture_ = __PPGeDoAlloc ( texSize , true , " Savedata Icon " ) ;
if ( texture_ = = 0 ) {
2013-12-17 08:07:30 -08:00
free ( textureData ) ;
2013-12-08 11:06:18 -08:00
WARN_LOG ( SCEGE , " Bad PPGeImage - unable to allocate space for texture " ) ;
return false ;
}
2021-01-03 14:42:29 -08:00
Memory : : Memcpy ( texture_ , textureData , dataSize ) ;
Memory : : Memset ( texture_ + dataSize , 0 , texSize - dataSize ) ;
2013-12-08 11:06:18 -08:00
free ( textureData ) ;
lastFrame_ = gpuStats . numFlips ;
loadedTextures_ . push_back ( this ) ;
return true ;
}
void PPGeImage : : Free ( ) {
if ( texture_ ! = 0 ) {
kernelMemory . Free ( texture_ ) ;
texture_ = 0 ;
loadedTextures_ . erase ( std : : remove ( loadedTextures_ . begin ( ) , loadedTextures_ . end ( ) , this ) , loadedTextures_ . end ( ) ) ;
}
}
void PPGeImage : : DoState ( PointerWrap & p ) {
auto s = p . Section ( " PPGeImage " , 1 ) ;
if ( ! s )
return ;
2020-08-09 21:20:42 -07:00
Do ( p , filename_ ) ;
Do ( p , png_ ) ;
Do ( p , size_ ) ;
Do ( p , texture_ ) ;
Do ( p , width_ ) ;
Do ( p , height_ ) ;
Do ( p , lastFrame_ ) ;
2013-12-08 11:06:18 -08:00
}
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 ;
}
2021-02-07 23:17:53 -08:00
void PPGeImage : : Decimate ( int age ) {
int tooOldFrame = gpuStats . numFlips - age ;
2013-12-08 11:06:18 -08:00
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 ) {
Decimate ( ) ;
Load ( ) ;
}
if ( texture_ ! = 0 ) {
lastFrame_ = gpuStats . numFlips ;
PPGeSetTexture ( texture_ , width_ , height_ ) ;
} else {
PPGeDisableTexture ( ) ;
}
}
2020-03-10 17:53:30 -07:00
void PPGeNotifyFrame ( ) {
if ( textDrawer ) {
textDrawer - > OncePerFrame ( ) ;
}
2021-02-07 23:06:35 -08:00
PPGeDecimateTextImages ( ) ;
2020-03-10 17:53:30 -07:00
PPGeImage : : Decimate ( ) ;
}