2012-11-01 16:19:01 +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
2012-11-04 23:58:25 +01:00
// the Free Software Foundation, version 2.0 or later versions.
2012-11-01 16:19:01 +01:00
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
# include <map>
2013-02-10 13:19:01 -08:00
# include <algorithm>
2012-11-01 16:19:01 +01:00
2013-02-21 21:37:19 +01:00
# include "Core/MemMap.h"
2013-03-24 09:42:49 -07:00
# include "Core/Reporting.h"
2013-02-21 21:37:19 +01:00
# include "GPU/ge_constants.h"
# include "GPU/GPUState.h"
# include "GPU/GLES/TextureCache.h"
# include "GPU/GLES/Framebuffer.h"
2013-09-15 21:27:13 -07:00
# include "GPU/Common/TextureDecoder.h"
2013-02-21 21:37:19 +01:00
# include "Core/Config.h"
2012-11-01 16:19:01 +01:00
2013-08-16 23:02:29 -07:00
# include "ext/xxhash.h"
2013-09-15 12:46:14 +02:00
# include "math/math_util.h"
2013-09-29 00:25:13 +05:00
# include "native/gfx_es2/gl_state.h"
2013-04-28 02:38:59 -07:00
2013-05-05 19:58:59 -07:00
# ifdef _M_SSE
# include <xmmintrin.h>
# endif
2013-02-15 00:30:02 +01:00
// If a texture hasn't been seen for this many frames, get rid of it.
2012-11-22 23:07:15 +01:00
# define TEXTURE_KILL_AGE 200
2013-05-05 00:00:22 -07:00
# define TEXTURE_KILL_AGE_LOWMEM 60
// Not used in lowmem mode.
2013-05-04 20:33:31 -07:00
# define TEXTURE_SECOND_KILL_AGE 100
2013-08-01 00:13:58 +02:00
// Try to be prime to other decimation intervals.
# define TEXCACHE_DECIMATION_INTERVAL 13
2013-11-03 01:08:48 -08:00
# ifndef GL_UNPACK_ROW_LENGTH
# define GL_UNPACK_ROW_LENGTH 0x0CF2
# endif
2013-11-09 12:52:58 -08:00
// TODO: This helps when you have plenty of VRAM, sometimes quite a bit.
// But on Android, it sometimes causes out of memory that isn't recovered from.
# if !defined(USING_GLES2) && !defined(_XBOX)
# define USE_SECONDARY_CACHE 1
# else
# define USE_SECONDARY_CACHE 0
# endif
2013-06-30 03:35:28 +08:00
extern int g_iNumVideos ;
2013-05-26 12:45:53 -07:00
TextureCache : : TextureCache ( ) : clearCacheNextFrame_ ( false ) , lowMemoryMode_ ( false ) , clutBuf_ ( NULL ) {
2013-02-12 19:05:30 +01:00
lastBoundTexture = - 1 ;
2013-08-01 00:13:58 +02:00
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL ;
2013-01-11 02:00:51 +01:00
// This is 5MB of temporary storage. Might be possible to shrink it.
2013-04-09 01:52:19 -07:00
tmpTexBuf32 . resize ( 1024 * 512 ) ; // 2MB
tmpTexBuf16 . resize ( 1024 * 512 ) ; // 1MB
tmpTexBufRearrange . resize ( 1024 * 512 ) ; // 2MB
2013-10-31 01:26:54 -07:00
clutBufConverted_ = ( u32 * ) AllocateAlignedMemory ( 4096 * sizeof ( u32 ) , 16 ) ; // 16KB
clutBufRaw_ = ( u32 * ) AllocateAlignedMemory ( 4096 * sizeof ( u32 ) , 16 ) ; // 16KB
2013-02-11 20:48:07 +08:00
glGetFloatv ( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT , & maxAnisotropyLevel ) ;
2013-11-02 02:09:54 -07:00
SetupQuickTexHash ( ) ;
2012-11-28 16:12:29 +01:00
}
2013-01-30 20:40:26 +01:00
TextureCache : : ~ TextureCache ( ) {
2013-10-31 01:26:54 -07:00
FreeAlignedMemory ( clutBufConverted_ ) ;
FreeAlignedMemory ( clutBufRaw_ ) ;
2012-11-28 16:12:29 +01:00
}
2013-01-30 20:40:26 +01:00
void TextureCache : : Clear ( bool delete_them ) {
2013-01-26 21:38:27 +01:00
glBindTexture ( GL_TEXTURE_2D , 0 ) ;
2013-06-22 10:15:30 -07:00
lastBoundTexture = - 1 ;
2013-01-06 12:11:47 +01:00
if ( delete_them ) {
for ( TexCache : : iterator iter = cache . begin ( ) ; iter ! = cache . end ( ) ; + + iter ) {
2012-11-01 16:19:01 +01:00
DEBUG_LOG ( G3D , " Deleting texture %i " , iter - > second . texture ) ;
glDeleteTextures ( 1 , & iter - > second . texture ) ;
}
2013-05-04 20:33:31 -07:00
for ( TexCache : : iterator iter = secondCache . begin ( ) ; iter ! = secondCache . end ( ) ; + + iter ) {
DEBUG_LOG ( G3D , " Deleting texture %i " , iter - > second . texture ) ;
glDeleteTextures ( 1 , & iter - > second . texture ) ;
}
2012-11-01 16:19:01 +01:00
}
2013-05-04 20:33:31 -07:00
if ( cache . size ( ) + secondCache . size ( ) ) {
INFO_LOG ( G3D , " Texture cached cleared from %i textures " , ( int ) ( cache . size ( ) + secondCache . size ( ) ) ) ;
2012-11-01 16:19:01 +01:00
cache . clear ( ) ;
2013-05-04 20:33:31 -07:00
secondCache . clear ( ) ;
2012-11-01 16:19:01 +01:00
}
}
2012-11-22 23:07:15 +01:00
// Removes old textures.
2013-01-30 20:40:26 +01:00
void TextureCache : : Decimate ( ) {
2013-08-01 00:13:58 +02:00
if ( - - decimationCounter_ < = 0 ) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL ;
} else {
return ;
}
2013-01-26 21:38:27 +01:00
glBindTexture ( GL_TEXTURE_2D , 0 ) ;
2013-06-22 10:15:30 -07:00
lastBoundTexture = - 1 ;
2013-05-05 00:00:22 -07:00
int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE ;
2013-02-25 00:59:23 -08:00
for ( TexCache : : iterator iter = cache . begin ( ) ; iter ! = cache . end ( ) ; ) {
2013-08-07 22:32:04 +02:00
if ( iter - > second . lastFrame + TEXTURE_KILL_AGE < gpuStats . numFlips ) {
2012-11-22 23:07:15 +01:00
glDeleteTextures ( 1 , & iter - > second . texture ) ;
2012-11-24 10:43:16 -08:00
cache . erase ( iter + + ) ;
2012-11-22 23:07:15 +01:00
}
2012-11-24 10:43:16 -08:00
else
+ + iter ;
2012-11-22 23:07:15 +01:00
}
2013-11-09 12:52:58 -08:00
# if USE_SECONDARY_CACHE
2013-05-04 20:33:31 -07:00
for ( TexCache : : iterator iter = secondCache . begin ( ) ; iter ! = secondCache . end ( ) ; ) {
2013-08-07 22:32:04 +02:00
if ( lowMemoryMode_ | | iter - > second . lastFrame + TEXTURE_KILL_AGE < gpuStats . numFlips ) {
2013-05-04 20:33:31 -07:00
glDeleteTextures ( 1 , & iter - > second . texture ) ;
secondCache . erase ( iter + + ) ;
}
else
+ + iter ;
}
2013-11-09 12:52:58 -08:00
# endif
2012-11-22 23:07:15 +01:00
}
2013-05-05 22:52:01 -07:00
void TextureCache : : Invalidate ( u32 addr , int size , GPUInvalidationType type ) {
2013-08-22 23:23:48 -07:00
addr & = 0x0FFFFFFF ;
2012-12-21 23:43:48 +01:00
u32 addr_end = addr + size ;
2013-06-30 20:52:15 -07:00
// They could invalidate inside the texture, let's just give a bit of leeway.
const int LARGEST_TEXTURE_SIZE = 512 * 512 * 4 ;
u64 startKey = addr - LARGEST_TEXTURE_SIZE ;
u64 endKey = addr + size + LARGEST_TEXTURE_SIZE ;
for ( TexCache : : iterator iter = cache . lower_bound ( startKey ) , end = cache . upper_bound ( endKey ) ; iter ! = end ; + + iter ) {
2013-01-22 19:18:48 +01:00
u32 texAddr = iter - > second . addr ;
u32 texEnd = iter - > second . addr + iter - > second . sizeInRAM ;
2013-05-11 15:52:41 -07:00
2013-06-05 00:43:42 +08:00
if ( texAddr < addr_end & & addr < texEnd ) {
2013-05-08 07:52:54 -07:00
if ( ( iter - > second . status & TexCacheEntry : : STATUS_MASK ) = = TexCacheEntry : : STATUS_RELIABLE ) {
// Clear status -> STATUS_HASHING.
iter - > second . status & = ~ TexCacheEntry : : STATUS_MASK ;
2013-02-10 14:11:53 -08:00
}
2013-05-05 22:52:01 -07:00
if ( type ! = GPU_INVALIDATE_ALL ) {
2013-01-06 12:27:01 +01:00
gpuStats . numTextureInvalidations + + ;
2013-05-05 22:52:01 -07:00
// Start it over from 0 (unless it's safe.)
iter - > second . numFrames = type = = GPU_INVALIDATE_SAFE ? 256 : 0 ;
2013-02-10 23:29:44 -08:00
iter - > second . framesUntilNextFullHash = 0 ;
2013-10-20 20:32:02 -07:00
} else if ( ! iter - > second . framebuffer ) {
2013-01-05 17:04:56 -08:00
iter - > second . invalidHint + + ;
2013-01-02 23:21:02 -08:00
}
2012-12-21 23:43:48 +01:00
}
}
}
2013-06-05 00:43:42 +08:00
void TextureCache : : InvalidateAll ( GPUInvalidationType /*unused*/ ) {
for ( TexCache : : iterator iter = cache . begin ( ) , end = cache . end ( ) ; iter ! = end ; + + iter ) {
if ( ( iter - > second . status & TexCacheEntry : : STATUS_MASK ) = = TexCacheEntry : : STATUS_RELIABLE ) {
// Clear status -> STATUS_HASHING.
iter - > second . status & = ~ TexCacheEntry : : STATUS_MASK ;
}
2013-10-20 20:32:02 -07:00
if ( ! iter - > second . framebuffer ) {
iter - > second . invalidHint + + ;
}
2013-06-05 00:43:42 +08:00
}
2012-11-22 23:07:15 +01:00
}
2013-04-30 17:20:28 +02:00
void TextureCache : : ClearNextFrame ( ) {
clearCacheNextFrame_ = true ;
}
2013-07-31 23:29:35 -07:00
template < typename T >
inline void AttachFramebufferValid ( T & entry , VirtualFramebuffer * framebuffer ) {
2013-09-01 11:55:03 -07:00
const bool hasInvalidFramebuffer = entry - > framebuffer = = 0 | | entry - > invalidHint = = - 1 ;
2013-09-05 01:17:37 -07:00
const bool hasOlderFramebuffer = entry - > framebuffer ! = 0 & & entry - > framebuffer - > last_frame_render < framebuffer - > last_frame_render ;
2013-09-01 11:55:03 -07:00
if ( hasInvalidFramebuffer | | hasOlderFramebuffer ) {
entry - > framebuffer = framebuffer ;
entry - > invalidHint = 0 ;
2013-09-22 00:18:46 -07:00
host - > GPUNotifyTextureAttachment ( entry - > addr ) ;
2013-09-01 11:55:03 -07:00
}
2013-07-31 23:29:35 -07:00
}
template < typename T >
inline void AttachFramebufferInvalid ( T & entry , VirtualFramebuffer * framebuffer ) {
if ( entry - > framebuffer = = 0 | | entry - > framebuffer = = framebuffer ) {
entry - > framebuffer = framebuffer ;
entry - > invalidHint = - 1 ;
2013-09-22 00:18:46 -07:00
host - > GPUNotifyTextureAttachment ( entry - > addr ) ;
2013-07-31 23:29:35 -07:00
}
}
2013-07-31 23:19:15 -07:00
inline void TextureCache : : AttachFramebuffer ( TexCacheEntry * entry , u32 address , VirtualFramebuffer * framebuffer , bool exactMatch ) {
// If they match exactly, it's non-CLUT and from the top left.
if ( exactMatch ) {
2013-09-16 20:25:29 +08:00
// Apply to non-buffered and buffered mode only.
2013-09-21 09:56:32 +08:00
if ( ! ( g_Config . iRenderingMode = = FB_NON_BUFFERED_MODE | | g_Config . iRenderingMode = = FB_BUFFERED_MODE ) )
2013-09-16 20:25:29 +08:00
return ;
2013-09-07 13:31:14 -07:00
DEBUG_LOG ( G3D , " Render to texture detected at %08x! " , address ) ;
2013-09-16 20:25:29 +08:00
if ( ! entry - > framebuffer | | entry - > invalidHint = = - 1 ) {
2013-07-31 23:19:15 -07:00
if ( entry - > format ! = framebuffer - > format ) {
2013-09-21 10:13:30 +08:00
WARN_LOG_REPORT_ONCE ( diffFormat1 , G3D , " Render to texture with different formats %d != %d " , entry - > format , framebuffer - > format ) ;
2013-07-31 23:29:35 -07:00
// If it already has one, let's hope that one is correct.
2013-08-19 21:39:56 +08:00
AttachFramebufferInvalid ( entry , framebuffer ) ;
2013-07-31 23:29:35 -07:00
} else {
AttachFramebufferValid ( entry , framebuffer ) ;
2013-07-31 23:19:15 -07:00
}
// TODO: Delete the original non-fbo texture too.
}
2013-09-16 20:25:29 +08:00
} else {
2013-09-21 09:56:32 +08:00
// Apply to buffered mode only.
2013-09-16 20:25:29 +08:00
if ( ! ( g_Config . iRenderingMode = = FB_BUFFERED_MODE ) )
return ;
2013-07-31 23:19:15 -07:00
// 3rd Birthday (and possibly other games) render to a 16 bit clut texture.
const bool compatFormat = framebuffer - > format = = entry - > format
| | ( framebuffer - > format = = GE_FORMAT_8888 & & entry - > format = = GE_TFMT_CLUT32 )
| | ( framebuffer - > format ! = GE_FORMAT_8888 & & entry - > format = = GE_TFMT_CLUT16 ) ;
// Is it at least the right stride?
if ( framebuffer - > fb_stride = = entry - > bufw & & compatFormat ) {
if ( framebuffer - > format ! = entry - > format ) {
2013-09-21 10:13:30 +08:00
WARN_LOG_REPORT_ONCE ( diffFormat2 , G3D , " Render to texture with different formats %d != %d at %08x " , entry - > format , framebuffer - > format , address ) ;
2013-07-31 23:19:15 -07:00
// TODO: Use an FBO to translate the palette?
2013-08-07 22:47:41 +08:00
AttachFramebufferValid ( entry , framebuffer ) ;
2013-07-31 23:19:15 -07:00
} else if ( ( entry - > addr - address ) / entry - > bufw < framebuffer - > height ) {
2013-09-07 13:31:14 -07:00
WARN_LOG_REPORT_ONCE ( subarea , G3D , " Render to area containing texture at %08x " , address ) ;
2013-07-31 23:19:15 -07:00
// TODO: Keep track of the y offset.
2013-09-08 19:44:36 +08:00
// If "AttachFramebufferValid" , God of War Ghost of Sparta/Chains of Olympus will be missing special effect.
2013-09-07 18:48:10 +08:00
AttachFramebufferInvalid ( entry , framebuffer ) ;
2013-07-31 23:19:15 -07:00
}
}
}
}
inline void TextureCache : : DetachFramebuffer ( TexCacheEntry * entry , u32 address , VirtualFramebuffer * framebuffer ) {
if ( entry - > framebuffer = = framebuffer ) {
entry - > framebuffer = 0 ;
2013-09-22 00:18:46 -07:00
host - > GPUNotifyTextureAttachment ( entry - > addr ) ;
2013-07-31 23:19:15 -07:00
}
2013-02-01 00:18:23 +01:00
}
2013-07-31 23:19:15 -07:00
void TextureCache : : NotifyFramebuffer ( u32 address , VirtualFramebuffer * framebuffer , FramebufferNotification msg ) {
2013-07-27 22:20:02 -07:00
// This is a rough heuristic, because sometimes our framebuffers are too tall.
static const u32 MAX_SUBAREA_Y_OFFSET = 32 ;
2013-02-09 21:18:46 +01:00
// Must be in VRAM so | 0x04000000 it is.
2013-07-27 20:21:32 -07:00
const u64 cacheKey = ( u64 ) ( address | 0x04000000 ) < < 32 ;
// If it has a clut, those are the low 32 bits, so it'll be inside this range.
// Also, if it's a subsample of the buffer, it'll also be within the FBO.
2013-07-27 22:20:02 -07:00
const u64 cacheKeyEnd = cacheKey + ( ( u64 ) ( framebuffer - > fb_stride * MAX_SUBAREA_Y_OFFSET ) < < 32 ) ;
2013-07-27 20:21:32 -07:00
2013-07-31 23:19:15 -07:00
switch ( msg ) {
case NOTIFY_FB_CREATED :
case NOTIFY_FB_UPDATED :
2013-09-01 11:44:49 -07:00
// Ensure it's in the framebuffer cache.
if ( std : : find ( fbCache_ . begin ( ) , fbCache_ . end ( ) , framebuffer ) = = fbCache_ . end ( ) ) {
fbCache_ . push_back ( framebuffer ) ;
}
2013-07-31 23:19:15 -07:00
for ( auto it = cache . lower_bound ( cacheKey ) , end = cache . upper_bound ( cacheKeyEnd ) ; it ! = end ; + + it ) {
2013-08-21 00:34:47 -07:00
AttachFramebuffer ( & it - > second , address | 0x04000000 , framebuffer , it - > first = = cacheKey ) ;
2013-07-20 00:31:39 +02:00
}
2013-07-31 23:19:15 -07:00
break ;
2013-02-01 00:18:23 +01:00
2013-07-31 23:19:15 -07:00
case NOTIFY_FB_DESTROYED :
2013-09-01 11:44:49 -07:00
fbCache_ . erase ( std : : remove ( fbCache_ . begin ( ) , fbCache_ . end ( ) , framebuffer ) , fbCache_ . end ( ) ) ;
2013-07-31 23:19:15 -07:00
for ( auto it = cache . lower_bound ( cacheKey ) , end = cache . upper_bound ( cacheKeyEnd ) ; it ! = end ; + + it ) {
2013-08-21 00:34:47 -07:00
DetachFramebuffer ( & it - > second , address | 0x04000000 , framebuffer ) ;
2013-04-27 20:06:31 +02:00
}
2013-07-31 23:19:15 -07:00
break ;
2013-04-27 20:06:31 +02:00
}
2013-02-01 00:18:23 +01:00
}
2013-04-18 14:59:00 +02:00
void * TextureCache : : UnswizzleFromMem ( u32 texaddr , u32 bufw , u32 bytesPerPixel , u32 level ) {
2013-05-06 23:49:30 -07:00
const u32 rowWidth = ( bytesPerPixel > 0 ) ? ( bufw * bytesPerPixel ) : ( bufw / 2 ) ;
2013-04-13 18:59:44 -07:00
const u32 pitch = rowWidth / 4 ;
const int bxc = rowWidth / 16 ;
2013-07-31 01:52:29 +08:00
int byc = ( gstate . getTextureHeight ( level ) + 7 ) / 8 ;
2012-11-08 23:26:30 +01:00
if ( byc = = 0 )
byc = 1 ;
u32 ydest = 0 ;
2013-04-13 18:59:44 -07:00
if ( rowWidth > = 16 ) {
const u32 * src = ( u32 * ) Memory : : GetPointer ( texaddr ) ;
2013-08-12 23:32:38 -07:00
u32 * ydestp = tmpTexBuf32 . data ( ) ;
2013-04-13 18:59:44 -07:00
for ( int by = 0 ; by < byc ; by + + ) {
2013-08-12 23:32:38 -07:00
u32 * xdest = ydestp ;
2013-01-06 12:11:47 +01:00
for ( int bx = 0 ; bx < bxc ; bx + + ) {
2013-04-13 18:59:44 -07:00
u32 * dest = xdest ;
2013-01-06 12:11:47 +01:00
for ( int n = 0 ; n < 8 ; n + + ) {
2013-04-13 18:59:44 -07:00
memcpy ( dest , src , 16 ) ;
2012-11-08 23:26:30 +01:00
dest + = pitch ;
2013-04-13 18:59:44 -07:00
src + = 4 ;
2012-11-08 23:26:30 +01:00
}
xdest + = 4 ;
}
2013-08-12 23:32:38 -07:00
ydestp + = ( rowWidth * 8 ) / 4 ;
2013-04-13 18:59:44 -07:00
}
} else if ( rowWidth = = 8 ) {
const u32 * src = ( u32 * ) Memory : : GetPointer ( texaddr ) ;
for ( int by = 0 ; by < byc ; by + + ) {
2013-01-06 12:11:47 +01:00
for ( int n = 0 ; n < 8 ; n + + , ydest + = 2 ) {
2013-04-13 18:59:44 -07:00
tmpTexBuf32 [ ydest + 0 ] = * src + + ;
tmpTexBuf32 [ ydest + 1 ] = * src + + ;
src + = 2 ; // skip two u32
2012-11-08 23:26:30 +01:00
}
2013-04-13 18:59:44 -07:00
}
} else if ( rowWidth = = 4 ) {
const u32 * src = ( u32 * ) Memory : : GetPointer ( texaddr ) ;
for ( int by = 0 ; by < byc ; by + + ) {
2013-01-06 12:11:47 +01:00
for ( int n = 0 ; n < 8 ; n + + , ydest + + ) {
2013-04-13 18:59:44 -07:00
tmpTexBuf32 [ ydest ] = * src + + ;
src + = 3 ;
2012-11-08 23:26:30 +01:00
}
2013-04-13 18:59:44 -07:00
}
} else if ( rowWidth = = 2 ) {
const u16 * src = ( u16 * ) Memory : : GetPointer ( texaddr ) ;
for ( int by = 0 ; by < byc ; by + + ) {
2013-01-06 12:11:47 +01:00
for ( int n = 0 ; n < 4 ; n + + , ydest + + ) {
2013-04-13 18:59:44 -07:00
u16 n1 = src [ 0 ] ;
u16 n2 = src [ 8 ] ;
2012-11-08 23:26:30 +01:00
tmpTexBuf32 [ ydest ] = ( u32 ) n1 | ( ( u32 ) n2 < < 16 ) ;
2013-04-13 18:59:44 -07:00
src + = 16 ;
2012-11-08 23:26:30 +01:00
}
}
2013-04-13 18:59:44 -07:00
} else if ( rowWidth = = 1 ) {
const u8 * src = ( u8 * ) Memory : : GetPointer ( texaddr ) ;
for ( int by = 0 ; by < byc ; by + + ) {
2013-01-06 12:11:47 +01:00
for ( int n = 0 ; n < 2 ; n + + , ydest + + ) {
2013-04-13 18:59:44 -07:00
u8 n1 = src [ 0 ] ;
u8 n2 = src [ 16 ] ;
u8 n3 = src [ 32 ] ;
u8 n4 = src [ 48 ] ;
2012-11-08 23:26:30 +01:00
tmpTexBuf32 [ ydest ] = ( u32 ) n1 | ( ( u32 ) n2 < < 8 ) | ( ( u32 ) n3 < < 16 ) | ( ( u32 ) n4 < < 24 ) ;
2013-04-13 18:59:44 -07:00
src + = 64 ;
2012-11-08 23:26:30 +01:00
}
}
}
2013-04-09 01:52:19 -07:00
return tmpTexBuf32 . data ( ) ;
2012-11-08 23:26:30 +01:00
}
2013-09-15 19:50:43 -07:00
void * TextureCache : : ReadIndexedTex ( int level , u32 texaddr , int bytesPerIndex , GLuint dstFmt , int bufw ) {
2013-07-31 01:52:29 +08:00
int w = gstate . getTextureWidth ( level ) ;
int h = gstate . getTextureHeight ( level ) ;
2013-04-28 13:14:25 -07:00
int length = bufw * h ;
2012-11-08 23:26:30 +01:00
void * buf = NULL ;
2013-07-21 19:34:55 -07:00
switch ( gstate . getClutPaletteFormat ( ) ) {
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_BGR5650 :
case GE_CMODE_16BIT_ABGR5551 :
case GE_CMODE_16BIT_ABGR4444 :
2012-11-01 16:19:01 +01:00
{
2013-04-28 13:14:25 -07:00
tmpTexBuf16 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
2013-05-11 15:45:17 -07:00
const u16 * clut = GetCurrentClut < u16 > ( ) ;
2013-08-24 10:51:17 -07:00
if ( ! gstate . isTextureSwizzled ( ) ) {
2013-01-06 12:11:47 +01:00
switch ( bytesPerIndex ) {
2012-11-08 23:26:30 +01:00
case 1 :
2013-05-11 15:45:17 -07:00
DeIndexTexture < u8 > ( tmpTexBuf16 . data ( ) , texaddr , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
case 2 :
2013-05-11 15:45:17 -07:00
DeIndexTexture < u16 > ( tmpTexBuf16 . data ( ) , texaddr , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
case 4 :
2013-05-11 15:45:17 -07:00
DeIndexTexture < u32 > ( tmpTexBuf16 . data ( ) , texaddr , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
}
2013-01-06 12:11:47 +01:00
} else {
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
2013-04-18 14:59:00 +02:00
UnswizzleFromMem ( texaddr , bufw , bytesPerIndex , level ) ;
2013-01-06 12:11:47 +01:00
switch ( bytesPerIndex ) {
2012-11-08 23:26:30 +01:00
case 1 :
2013-05-11 15:45:17 -07:00
DeIndexTexture ( tmpTexBuf16 . data ( ) , ( u8 * ) tmpTexBuf32 . data ( ) , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
case 2 :
2013-05-11 15:45:17 -07:00
DeIndexTexture ( tmpTexBuf16 . data ( ) , ( u16 * ) tmpTexBuf32 . data ( ) , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
case 4 :
2013-05-11 15:45:17 -07:00
DeIndexTexture ( tmpTexBuf16 . data ( ) , ( u32 * ) tmpTexBuf32 . data ( ) , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
}
}
2013-04-09 01:52:19 -07:00
buf = tmpTexBuf16 . data ( ) ;
2012-11-08 23:26:30 +01:00
}
break ;
case GE_CMODE_32BIT_ABGR8888 :
{
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
2013-05-11 15:45:17 -07:00
const u32 * clut = GetCurrentClut < u32 > ( ) ;
2013-08-24 10:51:17 -07:00
if ( ! gstate . isTextureSwizzled ( ) ) {
2013-01-06 12:11:47 +01:00
switch ( bytesPerIndex ) {
2012-11-08 23:26:30 +01:00
case 1 :
2013-05-11 15:45:17 -07:00
DeIndexTexture < u8 > ( tmpTexBuf32 . data ( ) , texaddr , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
case 2 :
2013-05-11 15:45:17 -07:00
DeIndexTexture < u16 > ( tmpTexBuf32 . data ( ) , texaddr , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
case 4 :
2013-05-11 15:45:17 -07:00
DeIndexTexture < u32 > ( tmpTexBuf32 . data ( ) , texaddr , length , clut ) ;
2012-11-08 23:26:30 +01:00
break ;
}
2013-04-17 01:07:00 -07:00
buf = tmpTexBuf32 . data ( ) ;
2013-01-06 12:11:47 +01:00
} else {
2013-04-18 14:59:00 +02:00
UnswizzleFromMem ( texaddr , bufw , bytesPerIndex , level ) ;
2013-04-17 01:07:00 -07:00
// Since we had to unswizzle to tmpTexBuf32, let's output to tmpTexBuf16.
2013-04-28 13:14:25 -07:00
tmpTexBuf16 . resize ( std : : max ( bufw , w ) * h * 2 ) ;
2013-04-17 01:07:00 -07:00
u32 * dest32 = ( u32 * ) tmpTexBuf16 . data ( ) ;
2013-01-06 12:11:47 +01:00
switch ( bytesPerIndex ) {
2012-11-08 23:26:30 +01:00
case 1 :
2013-05-11 15:45:17 -07:00
DeIndexTexture ( dest32 , ( u8 * ) tmpTexBuf32 . data ( ) , length , clut ) ;
2013-04-17 01:07:00 -07:00
buf = dest32 ;
2012-11-08 23:26:30 +01:00
break ;
case 2 :
2013-05-11 15:45:17 -07:00
DeIndexTexture ( dest32 , ( u16 * ) tmpTexBuf32 . data ( ) , length , clut ) ;
2013-04-17 01:07:00 -07:00
buf = dest32 ;
2012-11-08 23:26:30 +01:00
break ;
case 4 :
2013-05-11 15:45:17 -07:00
// TODO: If a game actually uses this mode, check if using dest32 or tmpTexBuf32 is faster.
DeIndexTexture ( tmpTexBuf32 . data ( ) , tmpTexBuf32 . data ( ) , length , clut ) ;
2013-04-17 01:07:00 -07:00
buf = tmpTexBuf32 . data ( ) ;
2012-11-08 23:26:30 +01:00
break ;
}
2012-11-01 16:19:01 +01:00
}
2012-11-08 23:26:30 +01:00
}
break ;
2012-11-01 16:19:01 +01:00
2012-11-08 23:26:30 +01:00
default :
2013-09-07 13:43:07 -07:00
ERROR_LOG_REPORT ( G3D , " Unhandled clut texture mode %d!!! " , ( gstate . clutformat & 3 ) ) ;
2012-11-08 23:26:30 +01:00
break ;
2012-11-01 16:19:01 +01:00
}
2012-11-08 23:26:30 +01:00
return buf ;
}
2013-01-06 12:11:47 +01:00
GLenum getClutDestFormat ( GEPaletteFormat format ) {
switch ( format ) {
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_ABGR4444 :
2012-11-09 01:24:19 +01:00
return GL_UNSIGNED_SHORT_4_4_4_4 ;
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_ABGR5551 :
2012-11-09 01:24:19 +01:00
return GL_UNSIGNED_SHORT_5_5_5_1 ;
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_BGR5650 :
2012-11-09 01:24:19 +01:00
return GL_UNSIGNED_SHORT_5_6_5 ;
2012-11-08 23:26:30 +01:00
case GE_CMODE_32BIT_ABGR8888 :
return GL_UNSIGNED_BYTE ;
2012-11-01 16:19:01 +01:00
}
2012-11-08 23:26:30 +01:00
return 0 ;
2012-11-01 16:19:01 +01:00
}
2013-02-08 00:04:34 +01:00
static const u8 texByteAlignMap [ ] = { 2 , 2 , 2 , 4 } ;
2012-11-08 23:26:30 +01:00
2013-02-08 00:04:34 +01:00
static const GLuint MinFiltGL [ 8 ] = {
2013-01-06 17:44:14 +01:00
GL_NEAREST ,
GL_LINEAR ,
GL_NEAREST ,
GL_LINEAR ,
GL_NEAREST_MIPMAP_NEAREST ,
GL_LINEAR_MIPMAP_NEAREST ,
GL_NEAREST_MIPMAP_LINEAR ,
GL_LINEAR_MIPMAP_LINEAR ,
} ;
2013-02-08 00:04:34 +01:00
static const GLuint MagFiltGL [ 2 ] = {
2013-01-06 17:44:14 +01:00
GL_NEAREST ,
GL_LINEAR
} ;
2012-11-01 16:19:01 +01:00
// This should not have to be done per texture! OpenGL is silly yo
// TODO: Dirty-check this against the current texture.
2013-01-30 20:40:26 +01:00
void TextureCache : : UpdateSamplingParams ( TexCacheEntry & entry , bool force ) {
2012-11-01 16:19:01 +01:00
int minFilt = gstate . texfilter & 0x7 ;
2013-01-06 12:11:47 +01:00
int magFilt = ( gstate . texfilter > > 8 ) & 1 ;
2013-08-24 10:20:07 -07:00
bool sClamp = gstate . isTexCoordClampedS ( ) ;
bool tClamp = gstate . isTexCoordClampedT ( ) ;
2013-01-06 17:44:14 +01:00
2013-07-19 21:32:46 +08:00
bool noMip = ( gstate . texlevel & 0xFFFFFF ) = = 0x000001 | | ( gstate . texlevel & 0xFFFFFF ) = = 0x100001 ; // Fix texlevel at 0
2013-06-20 21:34:35 +02:00
2013-02-08 00:04:34 +01:00
if ( entry . maxLevel = = 0 ) {
// Enforce no mip filtering, for safety.
minFilt & = 1 ; // no mipmaps yet
} else {
// TODO: Is this a signed value? Which direction?
float lodBias = 0.0 ; // -(float)((gstate.texlevel >> 16) & 0xFF) / 16.0f;
if ( force | | entry . lodBias ! = lodBias ) {
2013-06-19 12:42:04 +02:00
# ifndef USING_GLES2
2013-02-08 00:04:34 +01:00
glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_LOD_BIAS , lodBias ) ;
2013-06-19 12:42:04 +02:00
# endif
2013-02-08 00:04:34 +01:00
entry . lodBias = lodBias ;
}
}
2012-11-01 16:19:01 +01:00
2013-11-29 18:07:50 +01:00
if ( ( g_Config . iTexFiltering = = LINEAR & & ! gstate . isColorTestEnabled ( ) & & ! gstate . isAlphaTestEnabled ( ) ) | | ( g_Config . iTexFiltering = = LINEARFMV & & g_iNumVideos ) ) {
2013-01-06 17:44:14 +01:00
magFilt | = 1 ;
minFilt | = 1 ;
}
2013-10-14 17:07:45 +08:00
// Force Nearest when color test enabled and rendering resolution greater than 480x272
2013-10-15 11:59:32 +02:00
if ( g_Config . iTexFiltering = = NEAREST | | ( gstate . isColorTestEnabled ( ) & & g_Config . iInternalResolution ! = 1 & & gstate . isModeThrough ( ) ) ) {
2013-06-23 22:39:32 +02:00
magFilt & = ~ 1 ;
minFilt & = ~ 1 ;
2013-06-21 06:52:10 +02:00
}
2013-07-19 21:32:46 +08:00
if ( ! g_Config . bMipMap | | noMip ) {
2013-04-03 16:43:09 +08:00
magFilt & = 1 ;
minFilt & = 1 ;
}
2013-06-17 20:45:08 +02:00
2013-01-06 17:44:14 +01:00
if ( force | | entry . minFilt ! = minFilt ) {
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , MinFiltGL [ minFilt ] ) ;
entry . minFilt = minFilt ;
}
if ( force | | entry . magFilt ! = magFilt ) {
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , MagFiltGL [ magFilt ] ) ;
entry . magFilt = magFilt ;
}
2013-10-08 15:00:48 +02:00
// Workaround for a clamping bug in pre-HD ATI/AMD drivers
2013-09-29 00:25:13 +05:00
if ( gl_extensions . ATIClampBug & & entry . framebuffer )
return ;
2013-10-08 15:00:48 +02:00
2013-09-29 10:30:25 +08:00
if ( force | | entry . sClamp ! = sClamp ) {
2013-01-06 17:44:14 +01:00
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT ) ;
entry . sClamp = sClamp ;
2013-09-29 10:30:25 +08:00
}
if ( force | | entry . tClamp ! = tClamp ) {
2013-01-06 17:44:14 +01:00
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT ) ;
entry . tClamp = tClamp ;
2012-12-28 23:23:42 +08:00
}
2012-11-01 16:19:01 +01:00
}
2013-05-26 13:31:58 -07:00
static void ConvertColors ( void * dstBuf , const void * srcBuf , GLuint dstFmt , int numPixels ) {
const u32 * src = ( const u32 * ) srcBuf ;
u32 * dst = ( u32 * ) dstBuf ;
2013-10-31 00:34:18 -07:00
// TODO: NEON.
2012-11-09 01:24:19 +01:00
switch ( dstFmt ) {
case GL_UNSIGNED_SHORT_4_4_4_4 :
{
2013-10-31 00:34:18 -07:00
# ifdef _M_SSE
2013-10-31 23:27:28 -07:00
const __m128i maskB = _mm_set1_epi16 ( 0x00F0 ) ;
const __m128i maskG = _mm_set1_epi16 ( 0x0F00 ) ;
2013-10-31 00:34:18 -07:00
__m128i * srcp = ( __m128i * ) src ;
__m128i * dstp = ( __m128i * ) dst ;
const int sseChunks = numPixels / 8 ;
for ( int i = 0 ; i < sseChunks ; + + i ) {
__m128i c = _mm_load_si128 ( & srcp [ i ] ) ;
2013-10-31 23:27:28 -07:00
__m128i v = _mm_srli_epi16 ( c , 12 ) ;
v = _mm_or_si128 ( v , _mm_and_si128 ( _mm_srli_epi16 ( c , 4 ) , maskB ) ) ;
v = _mm_or_si128 ( v , _mm_and_si128 ( _mm_slli_epi16 ( c , 4 ) , maskG ) ) ;
v = _mm_or_si128 ( v , _mm_slli_epi16 ( c , 12 ) ) ;
2013-10-31 00:34:18 -07:00
_mm_store_si128 ( & dstp [ i ] , v ) ;
}
// The remainder is done in chunks of 2, SSE was chunks of 8.
int i = sseChunks * 8 / 2 ;
# else
int i = 0 ;
# endif
for ( ; i < ( numPixels + 1 ) / 2 ; i + + ) {
2013-05-26 13:31:58 -07:00
u32 c = src [ i ] ;
dst [ i ] = ( ( c > > 12 ) & 0x000F000F ) |
2013-05-03 00:12:42 -07:00
( ( c > > 4 ) & 0x00F000F0 ) |
( ( c < < 4 ) & 0x0F000F00 ) |
( ( c < < 12 ) & 0xF000F000 ) ;
2012-11-09 01:24:19 +01:00
}
}
break ;
2013-10-31 00:34:18 -07:00
// Final Fantasy 2 uses this heavily in animated textures.
2012-11-09 01:24:19 +01:00
case GL_UNSIGNED_SHORT_5_5_5_1 :
{
2013-10-31 00:34:18 -07:00
# ifdef _M_SSE
2013-10-31 23:27:28 -07:00
const __m128i maskB = _mm_set1_epi16 ( 0x003E ) ;
const __m128i maskG = _mm_set1_epi16 ( 0x07C0 ) ;
2013-10-31 00:34:18 -07:00
__m128i * srcp = ( __m128i * ) src ;
__m128i * dstp = ( __m128i * ) dst ;
const int sseChunks = numPixels / 8 ;
for ( int i = 0 ; i < sseChunks ; + + i ) {
__m128i c = _mm_load_si128 ( & srcp [ i ] ) ;
2013-10-31 23:27:28 -07:00
__m128i v = _mm_srli_epi16 ( c , 15 ) ;
v = _mm_or_si128 ( v , _mm_and_si128 ( _mm_srli_epi16 ( c , 9 ) , maskB ) ) ;
v = _mm_or_si128 ( v , _mm_and_si128 ( _mm_slli_epi16 ( c , 1 ) , maskG ) ) ;
v = _mm_or_si128 ( v , _mm_slli_epi16 ( c , 11 ) ) ;
2013-10-31 00:34:18 -07:00
_mm_store_si128 ( & dstp [ i ] , v ) ;
}
// The remainder is done in chunks of 2, SSE was chunks of 8.
int i = sseChunks * 8 / 2 ;
# else
int i = 0 ;
# endif
for ( ; i < ( numPixels + 1 ) / 2 ; i + + ) {
2013-05-26 13:31:58 -07:00
u32 c = src [ i ] ;
dst [ i ] = ( ( c > > 15 ) & 0x00010001 ) |
2013-05-03 00:12:42 -07:00
( ( c > > 9 ) & 0x003E003E ) |
( ( c < < 1 ) & 0x07C007C0 ) |
( ( c < < 11 ) & 0xF800F800 ) ;
2012-11-09 01:24:19 +01:00
}
}
break ;
case GL_UNSIGNED_SHORT_5_6_5 :
{
2013-10-31 00:34:18 -07:00
# ifdef _M_SSE
2013-10-31 23:27:28 -07:00
const __m128i maskG = _mm_set1_epi16 ( 0x07E0 ) ;
2013-10-31 00:34:18 -07:00
__m128i * srcp = ( __m128i * ) src ;
__m128i * dstp = ( __m128i * ) dst ;
const int sseChunks = numPixels / 8 ;
for ( int i = 0 ; i < sseChunks ; + + i ) {
__m128i c = _mm_load_si128 ( & srcp [ i ] ) ;
2013-10-31 23:27:28 -07:00
__m128i v = _mm_srli_epi16 ( c , 11 ) ;
2013-10-31 00:34:18 -07:00
v = _mm_or_si128 ( v , _mm_and_si128 ( c , maskG ) ) ;
2013-10-31 23:27:28 -07:00
v = _mm_or_si128 ( v , _mm_slli_epi16 ( c , 11 ) ) ;
2013-10-31 00:34:18 -07:00
_mm_store_si128 ( & dstp [ i ] , v ) ;
}
// The remainder is done in chunks of 2, SSE was chunks of 8.
int i = sseChunks * 8 / 2 ;
# else
int i = 0 ;
# endif
for ( ; i < ( numPixels + 1 ) / 2 ; i + + ) {
2013-05-26 13:31:58 -07:00
u32 c = src [ i ] ;
dst [ i ] = ( ( c > > 11 ) & 0x001F001F ) |
2013-05-03 00:12:42 -07:00
( ( c > > 0 ) & 0x07E007E0 ) |
( ( c < < 11 ) & 0xF800F800 ) ;
2012-11-09 01:24:19 +01:00
}
}
break ;
default :
{
2012-11-29 15:06:54 +01:00
// No need to convert RGBA8888, right order already
2013-06-22 10:16:38 -07:00
if ( dst ! = src )
memcpy ( dst , src , numPixels * sizeof ( u32 ) ) ;
2012-11-09 01:24:19 +01:00
}
break ;
}
}
2013-01-30 20:40:26 +01:00
void TextureCache : : StartFrame ( ) {
2013-01-10 23:49:33 +01:00
lastBoundTexture = - 1 ;
2013-04-30 17:20:28 +02:00
if ( clearCacheNextFrame_ ) {
Clear ( true ) ;
clearCacheNextFrame_ = false ;
} else {
Decimate ( ) ;
}
2013-01-10 23:49:33 +01:00
}
2013-02-10 13:37:05 -08:00
static inline u32 MiniHash ( const u32 * ptr ) {
return ptr [ 0 ] ;
}
2013-05-26 13:16:57 -07:00
static inline u32 QuickClutHash ( const u8 * clut , u32 bytes ) {
// CLUTs always come in multiples of 32 bytes, can't load them any other way.
_dbg_assert_msg_ ( G3D , ( bytes & 31 ) = = 0 , " CLUT should always have a multiple of 32 bytes. " ) ;
const u32 prime = 2246822519U ;
u32 hash = 0 ;
# ifdef _M_SSE
if ( ( ( ( u32 ) ( intptr_t ) clut ) & 0xf ) = = 0 ) {
__m128i cursor = _mm_set1_epi32 ( 0 ) ;
const __m128i mult = _mm_set1_epi32 ( prime ) ;
const __m128i * p = ( const __m128i * ) clut ;
for ( u32 i = 0 ; i < bytes / 16 ; + + i ) {
2013-05-27 09:54:16 +01:00
cursor = _mm_add_epi32 ( cursor , _mm_mul_epu32 ( _mm_load_si128 ( & p [ i ] ) , mult ) ) ;
2013-05-26 13:16:57 -07:00
}
// Add the four parts into the low i32.
cursor = _mm_add_epi32 ( cursor , _mm_srli_si128 ( cursor , 8 ) ) ;
cursor = _mm_add_epi32 ( cursor , _mm_srli_si128 ( cursor , 4 ) ) ;
hash = _mm_cvtsi128_si32 ( cursor ) ;
} else {
# else
// TODO: ARM NEON implementation (using CPUDetect to be sure it has NEON.)
{
# endif
for ( const u32 * p = ( u32 * ) clut , * end = ( u32 * ) ( clut + bytes ) ; p < end ; ) {
hash + = * p + + * prime ;
}
}
return hash ;
}
2013-07-21 19:34:55 -07:00
static inline u32 QuickTexHash ( u32 addr , int bufw , int w , int h , GETextureFormat format ) {
2013-09-15 21:27:13 -07:00
const u32 sizeInRAM = ( textureBitsPerPixel [ format ] * bufw * h ) / 8 ;
2013-02-10 13:37:05 -08:00
const u32 * checkp = ( const u32 * ) Memory : : GetPointer ( addr ) ;
2013-05-04 21:30:18 -07:00
2013-11-02 02:09:54 -07:00
return DoQuickTexHash ( checkp , sizeInRAM ) ;
2013-02-21 22:37:45 -08:00
}
2013-05-26 13:44:47 -07:00
inline bool TextureCache : : TexCacheEntry : : Matches ( u16 dim2 , u8 format2 , int maxLevel2 ) {
return dim = = dim2 & & format = = format2 & & maxLevel = = maxLevel2 ;
2013-05-04 20:33:31 -07:00
}
2013-05-12 10:57:41 -07:00
void TextureCache : : LoadClut ( ) {
2013-08-24 11:10:56 -07:00
u32 clutAddr = gstate . getClutAddress ( ) ;
2013-05-11 15:45:17 -07:00
if ( Memory : : IsValidAddress ( clutAddr ) ) {
2013-11-13 12:08:46 +10:00
# ifdef _M_SSE
2013-11-12 15:29:57 +01:00
int numBlocks = gstate . getClutLoadBlocks ( ) ;
clutTotalBytes_ = numBlocks * 32 ;
const __m128i * source = ( const __m128i * ) Memory : : GetPointerUnchecked ( clutAddr ) ;
__m128i * dest = ( __m128i * ) clutBufRaw_ ;
for ( int i = 0 ; i < numBlocks ; i + + , source + = 2 , dest + = 2 ) {
__m128i data1 = _mm_loadu_si128 ( source ) ;
__m128i data2 = _mm_loadu_si128 ( source + 1 ) ;
_mm_store_si128 ( dest , data1 ) ;
_mm_store_si128 ( dest + 1 , data2 ) ;
}
# else
clutTotalBytes_ = gstate . getClutLoadBytes ( ) ;
2013-08-11 01:46:21 +02:00
Memory : : MemcpyUnchecked ( clutBufRaw_ , clutAddr , clutTotalBytes_ ) ;
2013-11-12 15:29:57 +01:00
# endif
2013-05-11 15:45:17 -07:00
} else {
2013-11-12 15:29:57 +01:00
clutTotalBytes_ = gstate . getClutLoadBytes ( ) ;
2013-05-26 12:45:53 -07:00
memset ( clutBufRaw_ , 0xFF , clutTotalBytes_ ) ;
2013-05-11 15:45:17 -07:00
}
2013-05-26 12:45:53 -07:00
// Reload the clut next time.
2013-06-15 23:04:43 -07:00
clutLastFormat_ = 0xFFFFFFFF ;
2013-05-12 10:57:41 -07:00
}
void TextureCache : : UpdateCurrentClut ( ) {
2013-07-21 18:44:04 -07:00
const GEPaletteFormat clutFormat = gstate . getClutPaletteFormat ( ) ;
2013-07-21 19:34:55 -07:00
const u32 clutBase = gstate . getClutIndexStartPos ( ) ;
2013-06-15 23:04:43 -07:00
const u32 clutBaseBytes = clutBase * ( clutFormat = = GE_CMODE_32BIT_ABGR8888 ? sizeof ( u32 ) : sizeof ( u16 ) ) ;
// Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier.
// If not, we're going to hash random data, which hopefully doesn't cause a performance issue.
const u32 clutExtendedBytes = clutTotalBytes_ + clutBaseBytes ;
2013-08-16 23:02:29 -07:00
clutHash_ = XXH32 ( ( const char * ) clutBufRaw_ , clutExtendedBytes , 0xC0108888 ) ;
2013-05-26 12:45:53 -07:00
// Avoid a copy when we don't need to convert colors.
if ( clutFormat ! = GE_CMODE_32BIT_ABGR8888 ) {
2013-06-15 23:04:43 -07:00
ConvertColors ( clutBufConverted_ , clutBufRaw_ , getClutDestFormat ( clutFormat ) , clutExtendedBytes / sizeof ( u16 ) ) ;
2013-05-26 12:45:53 -07:00
clutBuf_ = clutBufConverted_ ;
} else {
clutBuf_ = clutBufRaw_ ;
}
2013-05-12 10:57:41 -07:00
// Special optimization: fonts typically draw clut4 with just alpha values in a single color.
clutAlphaLinear_ = false ;
clutAlphaLinearColor_ = 0 ;
2013-07-21 19:34:55 -07:00
if ( gstate . getClutPaletteFormat ( ) = = GE_CMODE_16BIT_ABGR4444 & & gstate . isClutIndexSimple ( ) ) {
2013-05-12 10:57:41 -07:00
const u16 * clut = GetCurrentClut < u16 > ( ) ;
clutAlphaLinear_ = true ;
clutAlphaLinearColor_ = clut [ 15 ] & 0xFFF0 ;
for ( int i = 0 ; i < 16 ; + + i ) {
if ( ( clut [ i ] & 0xf ) ! = i ) {
clutAlphaLinear_ = false ;
break ;
}
// Alpha 0 doesn't matter.
if ( i ! = 0 & & ( clut [ i ] & 0xFFF0 ) ! = clutAlphaLinearColor_ ) {
clutAlphaLinear_ = false ;
break ;
}
}
}
2013-05-26 12:45:53 -07:00
2013-06-15 23:04:43 -07:00
clutLastFormat_ = gstate . clutformat ;
2013-05-11 15:45:17 -07:00
}
template < typename T >
inline const T * TextureCache : : GetCurrentClut ( ) {
return ( const T * ) clutBuf_ ;
}
inline u32 TextureCache : : GetCurrentClutHash ( ) {
return clutHash_ ;
}
2013-06-22 10:35:34 -07:00
// #define DEBUG_TEXTURES
# ifdef DEBUG_TEXTURES
bool SetDebugTexture ( ) {
static const int highlightFrames = 30 ;
static int numTextures = 0 ;
static int lastFrames = 0 ;
static int mostTextures = 1 ;
2013-08-07 22:32:04 +02:00
if ( lastFrames ! = gpuStats . numFlips ) {
2013-06-22 10:35:34 -07:00
mostTextures = std : : max ( mostTextures , numTextures ) ;
numTextures = 0 ;
2013-08-07 22:32:04 +02:00
lastFrames = gpuStats . numFlips ;
2013-06-22 10:35:34 -07:00
}
static GLuint solidTexture = 0 ;
bool changed = false ;
2013-08-07 22:32:04 +02:00
if ( ( ( gpuStats . numFlips / highlightFrames ) % mostTextures ) = = numTextures ) {
if ( gpuStats . numFlips % highlightFrames = = 0 ) {
2013-09-07 13:31:14 -07:00
NOTICE_LOG ( G3D , " Highlighting texture # %d / %d " , numTextures , mostTextures ) ;
2013-06-22 10:35:34 -07:00
}
static const u32 solidTextureData [ ] = { 0x99AA99FF } ;
if ( solidTexture = = 0 ) {
glGenTextures ( 1 , & solidTexture ) ;
glBindTexture ( GL_TEXTURE_2D , solidTexture ) ;
glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ) ;
glPixelStorei ( GL_PACK_ALIGNMENT , 1 ) ;
glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA , 1 , 1 , 0 , GL_RGBA , GL_UNSIGNED_BYTE , solidTextureData ) ;
} else {
glBindTexture ( GL_TEXTURE_2D , solidTexture ) ;
}
changed = true ;
}
+ + numTextures ;
return changed ;
}
# endif
2013-09-01 11:40:35 -07:00
void TextureCache : : SetTextureFramebuffer ( TexCacheEntry * entry )
{
entry - > framebuffer - > usageFlags | = FB_USAGE_TEXTURE ;
bool useBufferedRendering = g_Config . iRenderingMode ! = FB_NON_BUFFERED_MODE ;
if ( useBufferedRendering ) {
// For now, let's not bind FBOs that we know are off (invalidHint will be -1.)
// But let's still not use random memory.
2013-09-14 21:03:24 +08:00
if ( entry - > framebuffer - > fbo ) {
2013-09-01 11:40:35 -07:00
fbo_bind_color_as_texture ( entry - > framebuffer - > fbo , 0 ) ;
2013-09-01 11:41:11 -07:00
// Keep the framebuffer alive.
// TODO: Dangerous if it sets a new one?
entry - > framebuffer - > last_frame_used = gpuStats . numFlips ;
2013-09-01 11:40:35 -07:00
} else {
glBindTexture ( GL_TEXTURE_2D , 0 ) ;
gstate_c . skipDrawReason | = SKIPDRAW_BAD_FB_TEXTURE ;
}
UpdateSamplingParams ( * entry , false ) ;
gstate_c . curTextureWidth = entry - > framebuffer - > width ;
gstate_c . curTextureHeight = entry - > framebuffer - > height ;
gstate_c . flipTexture = true ;
gstate_c . textureFullAlpha = entry - > framebuffer - > format = = GE_FORMAT_565 ;
} else {
if ( entry - > framebuffer - > fbo )
entry - > framebuffer - > fbo = 0 ;
glBindTexture ( GL_TEXTURE_2D , 0 ) ;
}
}
2013-09-27 21:53:42 -07:00
void TextureCache : : SetTexture ( bool force ) {
2013-06-22 10:35:34 -07:00
# ifdef DEBUG_TEXTURES
if ( SetDebugTexture ( ) ) {
// A different texture was bound, let's rebind next time.
lastBoundTexture = - 1 ;
return ;
}
# endif
2013-09-27 21:53:42 -07:00
if ( force ) {
lastBoundTexture = - 1 ;
}
2013-09-15 21:39:28 -07:00
u32 texaddr = gstate . getTextureAddress ( 0 ) ;
2013-01-06 12:11:47 +01:00
if ( ! Memory : : IsValidAddress ( texaddr ) ) {
// Bind a null texture and return.
glBindTexture ( GL_TEXTURE_2D , 0 ) ;
2013-06-22 10:15:30 -07:00
lastBoundTexture = - 1 ;
2013-01-06 12:11:47 +01:00
return ;
}
2013-07-21 19:34:55 -07:00
GETextureFormat format = gstate . getTextureFormat ( ) ;
2013-02-01 00:02:50 +01:00
if ( format > = 11 ) {
2013-03-26 00:54:00 -07:00
ERROR_LOG_REPORT ( G3D , " Unknown texture format %i " , format ) ;
2013-07-21 19:34:55 -07:00
// TODO: Better assumption?
format = GE_TFMT_5650 ;
2013-02-01 00:02:50 +01:00
}
2013-07-21 19:34:55 -07:00
bool hasClut = gstate . isTextureFormatIndexed ( ) ;
2013-02-01 00:02:50 +01:00
2013-06-30 20:52:15 -07:00
u64 cachekey = ( u64 ) texaddr < < 32 ;
2013-07-07 18:37:00 -07:00
u32 cluthash ;
2013-02-12 21:00:51 +01:00
if ( hasClut ) {
2013-06-15 23:04:43 -07:00
if ( clutLastFormat_ ! = gstate . clutformat ) {
2013-05-12 10:57:41 -07:00
// We update here because the clut format can be specified after the load.
UpdateCurrentClut ( ) ;
}
2013-05-20 00:53:39 -07:00
cluthash = GetCurrentClutHash ( ) ^ gstate . clutformat ;
2013-06-30 20:52:15 -07:00
cachekey | = cluthash ;
2013-02-18 08:34:51 -08:00
} else {
2013-05-04 20:33:31 -07:00
cluthash = 0 ;
2013-02-12 21:00:51 +01:00
}
2013-10-08 15:00:48 +02:00
2013-09-15 21:27:13 -07:00
int bufw = GetTextureBufw ( 0 , texaddr , format ) ;
2013-07-30 23:23:02 +08:00
int w = gstate . getTextureWidth ( 0 ) ;
int h = gstate . getTextureHeight ( 0 ) ;
2013-02-09 19:24:48 +01:00
int maxLevel = ( ( gstate . texmode > > 16 ) & 0x7 ) ;
2013-02-01 00:02:50 +01:00
2013-02-09 21:18:46 +01:00
u32 texhash = MiniHash ( ( const u32 * ) Memory : : GetPointer ( texaddr ) ) ;
2013-05-11 14:58:00 -07:00
u32 fullhash = 0 ;
2013-02-02 12:37:41 +01:00
2012-11-24 10:58:10 -08:00
TexCache : : iterator iter = cache . find ( cachekey ) ;
2013-02-10 14:11:53 -08:00
TexCacheEntry * entry = NULL ;
2013-02-15 00:30:02 +01:00
gstate_c . flipTexture = false ;
2013-03-03 13:00:21 +01:00
gstate_c . skipDrawReason & = ~ SKIPDRAW_BAD_FB_TEXTURE ;
2013-08-27 15:11:55 +08:00
bool useBufferedRendering = g_Config . iRenderingMode ! = FB_NON_BUFFERED_MODE ;
2013-06-11 20:21:19 +02:00
bool replaceImages = false ;
2013-10-08 15:00:48 +02:00
2013-01-06 12:11:47 +01:00
if ( iter ! = cache . end ( ) ) {
2013-02-10 14:11:53 -08:00
entry = & iter - > second ;
2013-09-06 23:40:23 -07:00
// Validate the texture still matches the cache entry.
2013-09-08 16:08:31 +08:00
u16 dim = gstate . getTextureDimension ( 0 ) ;
2013-09-06 23:40:23 -07:00
bool match = entry - > Matches ( dim , format , maxLevel ) ;
2013-09-22 00:18:46 -07:00
# ifndef USING_GLES2
match & = host - > GPUAllowTextureCache ( texaddr ) ;
# endif
2013-09-06 23:40:23 -07:00
2013-02-09 21:18:46 +01:00
// Check for FBO - slow!
2013-10-20 20:32:02 -07:00
if ( entry - > framebuffer ) {
if ( match ) {
SetTextureFramebuffer ( entry ) ;
lastBoundTexture = - 1 ;
entry - > lastFrame = gpuStats . numFlips ;
return ;
} else {
// Make sure we re-evaluate framebuffers.
DetachFramebuffer ( entry , texaddr , entry - > framebuffer ) ;
2013-10-21 08:09:08 -07:00
match = false ;
2013-10-20 20:32:02 -07:00
}
2013-07-04 00:54:44 +08:00
}
2013-03-03 13:00:21 +01:00
2013-05-08 07:52:54 -07:00
bool rehash = ( entry - > status & TexCacheEntry : : STATUS_MASK ) = = TexCacheEntry : : STATUS_UNRELIABLE ;
2013-05-04 20:33:31 -07:00
bool doDelete = true ;
2013-02-10 13:19:01 -08:00
if ( match ) {
2013-08-07 22:32:04 +02:00
if ( entry - > lastFrame ! = gpuStats . numFlips ) {
2013-09-29 14:53:14 -07:00
u32 diff = gpuStats . numFlips - entry - > lastFrame ;
2013-02-10 14:11:53 -08:00
entry - > numFrames + + ;
2013-09-29 14:53:14 -07:00
if ( entry - > framesUntilNextFullHash < diff ) {
2013-09-29 15:26:56 -07:00
// Exponential backoff up to 512 frames. Textures are often reused.
2013-09-29 14:53:14 -07:00
if ( entry - > numFrames > 32 ) {
// Also, try to add some "randomness" to avoid rehashing several textures the same frame.
2013-09-29 15:26:56 -07:00
entry - > framesUntilNextFullHash = std : : min ( 512 , entry - > numFrames ) + ( entry - > texture & 15 ) ;
2013-09-29 14:53:14 -07:00
} else {
entry - > framesUntilNextFullHash = entry - > numFrames ;
}
rehash = true ;
} else {
entry - > framesUntilNextFullHash - = diff ;
}
2013-02-10 13:19:01 -08:00
}
2013-04-28 11:20:56 -07:00
// If it's not huge or has been invalidated many times, recheck the whole texture.
if ( entry - > invalidHint > 180 | | ( entry - > invalidHint > 15 & & dim < = 0x909 ) ) {
entry - > invalidHint = 0 ;
rehash = true ;
}
2013-01-02 23:21:02 -08:00
2013-05-26 13:44:47 -07:00
bool hashFail = false ;
if ( texhash ! = entry - > hash ) {
fullhash = QuickTexHash ( texaddr , bufw , w , h , format ) ;
hashFail = true ;
rehash = false ;
}
2013-05-08 07:52:54 -07:00
if ( rehash & & ( entry - > status & TexCacheEntry : : STATUS_MASK ) ! = TexCacheEntry : : STATUS_RELIABLE ) {
2013-05-11 14:58:00 -07:00
fullhash = QuickTexHash ( texaddr , bufw , w , h , format ) ;
if ( fullhash ! = entry - > fullhash ) {
2013-05-26 13:44:47 -07:00
hashFail = true ;
} else if ( ( entry - > status & TexCacheEntry : : STATUS_MASK ) = = TexCacheEntry : : STATUS_UNRELIABLE & & entry - > numFrames > TexCacheEntry : : FRAMES_REGAIN_TRUST ) {
// Reset to STATUS_HASHING.
entry - > status & = ~ TexCacheEntry : : STATUS_MASK ;
}
}
2013-05-04 20:33:31 -07:00
2013-05-26 13:44:47 -07:00
if ( hashFail ) {
match = false ;
entry - > status | = TexCacheEntry : : STATUS_UNRELIABLE ;
entry - > numFrames = 0 ;
// Don't give up just yet. Let's try the secondary cache if it's been invalidated before.
// If it's failed a bunch of times, then the second cache is just wasting time and VRAM.
2013-11-09 12:52:58 -08:00
# if USE_SECONDARY_CACHE
2013-05-26 13:44:47 -07:00
if ( entry - > numInvalidated > 2 & & entry - > numInvalidated < 128 & & ! lowMemoryMode_ ) {
u64 secondKey = fullhash | ( u64 ) cluthash < < 32 ;
TexCache : : iterator secondIter = secondCache . find ( secondKey ) ;
if ( secondIter ! = secondCache . end ( ) ) {
TexCacheEntry * secondEntry = & secondIter - > second ;
if ( secondEntry - > Matches ( dim , format , maxLevel ) ) {
// Reset the numInvalidated value lower, we got a match.
if ( entry - > numInvalidated > 8 ) {
- - entry - > numInvalidated ;
}
entry = secondEntry ;
match = true ;
2013-05-04 20:33:31 -07:00
}
} else {
2013-05-26 13:44:47 -07:00
secondKey = entry - > fullhash | ( u64 ) entry - > cluthash < < 32 ;
secondCache [ secondKey ] = * entry ;
doDelete = false ;
2013-05-04 20:33:31 -07:00
}
2013-04-28 11:20:56 -07:00
}
2013-11-09 12:52:58 -08:00
# endif
2013-01-02 23:21:02 -08:00
}
}
2012-11-18 23:35:02 +01:00
if ( match ) {
2013-02-10 14:11:53 -08:00
// TODO: Mark the entry reliable if it's been safe for long enough?
2012-11-01 16:19:01 +01:00
//got one!
2013-08-07 22:32:04 +02:00
entry - > lastFrame = gpuStats . numFlips ;
2013-02-10 14:11:53 -08:00
if ( entry - > texture ! = lastBoundTexture ) {
glBindTexture ( GL_TEXTURE_2D , entry - > texture ) ;
lastBoundTexture = entry - > texture ;
2013-06-11 02:19:23 +08:00
gstate_c . textureFullAlpha = ( entry - > status & TexCacheEntry : : STATUS_ALPHA_MASK ) = = TexCacheEntry : : STATUS_ALPHA_FULL ;
2013-01-10 23:49:33 +01:00
}
2013-02-10 14:11:53 -08:00
UpdateSamplingParams ( * entry , false ) ;
2013-09-07 13:43:07 -07:00
VERBOSE_LOG ( G3D , " Texture at %08x Found in Cache, applying " , texaddr ) ;
2012-11-01 16:19:01 +01:00
return ; //Done!
2012-11-18 23:35:02 +01:00
} else {
2013-05-04 20:33:31 -07:00
entry - > numInvalidated + + ;
gpuStats . numTextureInvalidations + + ;
2013-09-07 13:43:07 -07:00
DEBUG_LOG ( G3D , " Texture different or overwritten, reloading at %08x " , texaddr ) ;
2013-05-04 20:33:31 -07:00
if ( doDelete ) {
2013-09-08 16:08:31 +08:00
if ( entry - > maxLevel = = maxLevel & & entry - > dim = = gstate . getTextureDimension ( 0 ) & & entry - > format = = format & & g_Config . iTexScalingLevel < = 1 ) {
2013-06-11 20:21:19 +02:00
// Actually, if size and number of levels match, let's try to avoid deleting and recreating.
// Instead, let's use glTexSubImage to replace the images.
2013-06-14 00:46:49 -07:00
replaceImages = true ;
2013-06-11 20:21:19 +02:00
} else {
2013-06-13 23:04:11 -07:00
if ( entry - > texture = = lastBoundTexture ) {
lastBoundTexture = - 1 ;
}
2013-06-11 20:21:19 +02:00
glDeleteTextures ( 1 , & entry - > texture ) ;
2013-06-13 23:04:11 -07:00
}
2013-05-04 20:33:31 -07:00
}
2013-02-10 14:25:44 -08:00
if ( entry - > status = = TexCacheEntry : : STATUS_RELIABLE ) {
entry - > status = TexCacheEntry : : STATUS_HASHING ;
}
2012-11-01 16:19:01 +01:00
}
2013-01-06 12:11:47 +01:00
} else {
2013-09-07 13:43:07 -07:00
VERBOSE_LOG ( G3D , " No texture in cache, decoding... " ) ;
2013-02-10 14:11:53 -08:00
TexCacheEntry entryNew = { 0 } ;
cache [ cachekey ] = entryNew ;
2013-02-10 14:25:44 -08:00
2013-02-10 14:11:53 -08:00
entry = & cache [ cachekey ] ;
2013-02-10 14:25:44 -08:00
entry - > status = TexCacheEntry : : STATUS_HASHING ;
2012-11-01 16:19:01 +01:00
}
2013-05-08 00:35:17 -07:00
if ( ( bufw = = 0 | | ( gstate . texbufwidth [ 0 ] & 0xf800 ) ! = 0 ) & & texaddr > = PSP_GetUserMemoryBase ( ) ) {
2013-09-07 13:31:14 -07:00
ERROR_LOG_REPORT ( G3D , " Texture with unexpected bufw (full=%d) " , gstate . texbufwidth [ 0 ] & 0xffff ) ;
2013-05-06 23:49:30 -07:00
}
2013-02-12 21:00:51 +01:00
2013-04-28 11:20:56 -07:00
// We have to decode it, let's setup the cache entry first.
2013-02-10 14:11:53 -08:00
entry - > addr = texaddr ;
entry - > hash = texhash ;
entry - > format = format ;
2013-08-07 22:32:04 +02:00
entry - > lastFrame = gpuStats . numFlips ;
2013-02-21 21:37:19 +01:00
entry - > framebuffer = 0 ;
2013-02-10 14:11:53 -08:00
entry - > maxLevel = maxLevel ;
entry - > lodBias = 0.0f ;
2013-10-08 15:00:48 +02:00
2013-09-08 16:08:31 +08:00
entry - > dim = gstate . getTextureDimension ( 0 ) ;
2013-07-27 20:20:39 -07:00
entry - > bufw = bufw ;
2012-11-01 16:19:01 +01:00
2013-01-22 19:18:48 +01:00
// This would overestimate the size in many case so we underestimate instead
// to avoid excessive clearing caused by cache invalidations.
2013-09-15 21:27:13 -07:00
entry - > sizeInRAM = ( textureBitsPerPixel [ format ] * bufw * h / 2 ) / 8 ;
2013-01-22 19:18:48 +01:00
2013-05-11 14:58:00 -07:00
entry - > fullhash = fullhash = = 0 ? QuickTexHash ( texaddr , bufw , w , h , format ) : fullhash ;
2013-05-20 00:55:53 -07:00
entry - > cluthash = cluthash ;
2013-01-02 23:21:02 -08:00
2013-05-08 07:52:54 -07:00
entry - > status & = ~ TexCacheEntry : : STATUS_ALPHA_MASK ;
2013-02-08 00:04:34 +01:00
gstate_c . curTextureWidth = w ;
gstate_c . curTextureHeight = h ;
2013-10-26 08:37:52 -07:00
// Always generate a texture name, we might need it if the texture is replaced later.
if ( ! replaceImages ) {
glGenTextures ( 1 , & entry - > texture ) ;
}
2013-09-01 11:44:49 -07:00
// Before we go reading the texture from memory, let's check for render-to-texture.
for ( size_t i = 0 , n = fbCache_ . size ( ) ; i < n ; + + i ) {
auto framebuffer = fbCache_ [ i ] ;
// This is a rough heuristic, because sometimes our framebuffers are too tall.
static const u32 MAX_SUBAREA_Y_OFFSET = 32 ;
// Must be in VRAM so | 0x04000000 it is.
const u64 cacheKeyStart = ( u64 ) ( framebuffer - > fb_address | 0x04000000 ) < < 32 ;
// If it has a clut, those are the low 32 bits, so it'll be inside this range.
// Also, if it's a subsample of the buffer, it'll also be within the FBO.
const u64 cacheKeyEnd = cacheKeyStart + ( ( u64 ) ( framebuffer - > fb_stride * MAX_SUBAREA_Y_OFFSET ) < < 32 ) ;
if ( cachekey > = cacheKeyStart & & cachekey < cacheKeyEnd ) {
2013-09-09 06:20:54 +08:00
AttachFramebuffer ( entry , framebuffer - > fb_address | 0x04000000 , framebuffer , cachekey = = cacheKeyStart ) ;
2013-09-01 11:44:49 -07:00
}
2013-09-01 14:44:37 -07:00
}
2013-09-01 11:44:49 -07:00
2013-09-01 14:44:37 -07:00
// If we ended up with a framebuffer, attach it - no texture decoding needed.
if ( entry - > framebuffer ) {
SetTextureFramebuffer ( entry ) ;
lastBoundTexture = - 1 ;
entry - > lastFrame = gpuStats . numFlips ;
return ;
2013-09-01 11:44:49 -07:00
}
2013-02-10 14:11:53 -08:00
glBindTexture ( GL_TEXTURE_2D , entry - > texture ) ;
2013-02-12 20:12:08 +01:00
lastBoundTexture = entry - > texture ;
2013-10-08 15:00:48 +02:00
2013-02-12 21:00:51 +01:00
// Adjust maxLevel to actually present levels..
for ( int i = 0 ; i < = maxLevel ; i + + ) {
// If encountering levels pointing to nothing, adjust max level.
2013-09-15 21:39:28 -07:00
u32 levelTexaddr = gstate . getTextureAddress ( i ) ;
2013-02-12 21:00:51 +01:00
if ( ! Memory : : IsValidAddress ( levelTexaddr ) ) {
maxLevel = i - 1 ;
break ;
}
}
2013-09-01 08:28:09 +08:00
2013-10-08 15:00:48 +02:00
// In addition, simply don't load more than level 0 if g_Config.bMipMap is false.
if ( ! g_Config . bMipMap ) {
maxLevel = 0 ;
}
// If GLES3 is available, we can preallocate the storage, which makes texture loading more efficient.
GLenum dstFmt = GetDestFormat ( format , gstate . getClutPaletteFormat ( ) ) ;
2013-09-01 08:28:09 +08:00
2013-10-09 09:43:53 +02:00
#if 0 // Needs more testing
2013-10-08 15:00:48 +02:00
# ifdef MAY_HAVE_GLES3
if ( gl_extensions . GLES3 ) {
// glTexStorage2D requires the use of sized formats.
GLenum storageFmt = GL_RGBA8 ;
switch ( dstFmt ) {
case GL_UNSIGNED_BYTE : storageFmt = GL_RGBA8 ; break ;
case GL_UNSIGNED_SHORT_5_6_5 : storageFmt = GL_RGB565 ; break ;
case GL_UNSIGNED_SHORT_4_4_4_4 : storageFmt = GL_RGBA4 ; break ;
case GL_UNSIGNED_SHORT_5_5_5_1 : storageFmt = GL_RGB5_A1 ; break ;
default :
ERROR_LOG ( G3D , " Unknown dstfmt %i " , ( int ) dstFmt ) ;
break ;
}
glTexStorage2D ( GL_TEXTURE_2D , maxLevel + 1 , storageFmt , w , h ) ;
// Make sure we don't use glTexImage2D after glTexStorage2D.
replaceImages = true ;
}
2013-10-09 09:43:53 +02:00
# endif
2013-10-08 15:00:48 +02:00
# endif
2013-04-03 07:33:14 +08:00
2013-10-08 15:00:48 +02:00
// GLES2 doesn't have support for a "Max lod" which is critical as PSP games often
// don't specify mips all the way down. As a result, we either need to manually generate
// the bottom few levels or rely on OpenGL's autogen mipmaps instead, which might not
// be as good quality as the game's own (might even be better in some cases though).
// For now, I choose to use autogen mips on GLES2 and the game's own on other platforms.
// As is usual, GLES3 will solve this problem nicely but wide distribution of that is
// years away.
//
// Actually, seems we reverted to autogen mipmaps on all platforms.
LoadTextureLevel ( * entry , 0 , replaceImages , dstFmt ) ;
if ( maxLevel > 0 ) {
glGenerateMipmap ( GL_TEXTURE_2D ) ;
2013-09-01 08:28:09 +08:00
/*
2013-04-03 07:33:14 +08:00
for ( int i = 0 ; i < = maxLevel ; i + + ) {
2013-06-11 20:21:19 +02:00
LoadTextureLevel ( * entry , i , replaceImages ) ;
2013-04-03 07:33:14 +08:00
}
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , maxLevel ) ;
glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LOD , ( float ) maxLevel ) ;
2013-09-01 08:28:09 +08:00
*/
2013-04-03 07:33:14 +08:00
} else {
2013-10-08 15:00:48 +02:00
// TODO: This is supported on GLES3
# if !defined(USING_GLES2)
2013-04-03 07:33:14 +08:00
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , 0 ) ;
2013-04-03 00:57:08 -07:00
# endif
2013-04-03 07:33:14 +08:00
}
2013-07-17 22:27:05 +02:00
int aniso = 1 < < g_Config . iAnisotropyLevel ;
float anisotropyLevel = ( float ) aniso > maxAnisotropyLevel ? maxAnisotropyLevel : ( float ) aniso ;
2013-02-11 19:03:11 +01:00
glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAX_ANISOTROPY_EXT , anisotropyLevel ) ;
2012-11-09 00:51:04 +01:00
2013-02-10 14:11:53 -08:00
UpdateSamplingParams ( * entry , true ) ;
2013-02-08 00:04:34 +01:00
//glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ) ;
//glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei ( GL_PACK_ALIGNMENT , 1 ) ;
2013-05-08 07:52:54 -07:00
2013-06-11 02:19:23 +08:00
gstate_c . textureFullAlpha = ( entry - > status & TexCacheEntry : : STATUS_ALPHA_MASK ) = = TexCacheEntry : : STATUS_ALPHA_FULL ;
2013-02-08 00:04:34 +01:00
}
2013-10-08 15:00:48 +02:00
GLenum TextureCache : : GetDestFormat ( GETextureFormat format , GEPaletteFormat clutFormat ) const {
switch ( format ) {
case GE_TFMT_CLUT4 :
case GE_TFMT_CLUT8 :
case GE_TFMT_CLUT16 :
case GE_TFMT_CLUT32 :
return getClutDestFormat ( clutFormat ) ;
case GE_TFMT_4444 :
return GL_UNSIGNED_SHORT_4_4_4_4 ;
case GE_TFMT_5551 :
return GL_UNSIGNED_SHORT_5_5_5_1 ;
case GE_TFMT_5650 :
return GL_UNSIGNED_SHORT_5_6_5 ;
case GE_TFMT_8888 :
case GE_TFMT_DXT1 :
case GE_TFMT_DXT3 :
case GE_TFMT_DXT5 :
default :
return GL_UNSIGNED_BYTE ;
}
}
2013-10-31 02:39:46 +01:00
void * TextureCache : : DecodeTextureLevel ( GETextureFormat format , GEPaletteFormat clutformat , int level , u32 & texByteAlign , GLenum dstFmt , int * bufwout ) {
2012-11-08 23:26:30 +01:00
void * finalBuf = NULL ;
2012-11-01 16:19:01 +01:00
2013-09-15 21:39:28 -07:00
u32 texaddr = gstate . getTextureAddress ( level ) ;
2013-02-08 00:04:34 +01:00
2013-09-15 21:27:13 -07:00
int bufw = GetTextureBufw ( level , texaddr , format ) ;
2013-10-31 02:39:46 +01:00
if ( bufwout )
* bufwout = bufw ;
2013-07-31 01:52:29 +08:00
int w = gstate . getTextureWidth ( level ) ;
int h = gstate . getTextureHeight ( level ) ;
2013-02-08 00:04:34 +01:00
const u8 * texptr = Memory : : GetPointer ( texaddr ) ;
2013-08-18 22:43:26 -07:00
switch ( format ) {
2012-11-08 23:26:30 +01:00
case GE_TFMT_CLUT4 :
2013-05-12 09:04:50 -07:00
{
const bool mipmapShareClut = ( gstate . texmode & 0x100 ) = = 0 ;
const int clutSharingOffset = mipmapShareClut ? 0 : level * 16 ;
2013-04-28 11:20:56 -07:00
switch ( clutformat ) {
2012-11-08 23:26:30 +01:00
case GE_CMODE_16BIT_BGR5650 :
case GE_CMODE_16BIT_ABGR5551 :
case GE_CMODE_16BIT_ABGR4444 :
2012-11-01 16:19:01 +01:00
{
2013-04-28 13:14:25 -07:00
tmpTexBuf16 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
2013-05-12 09:04:50 -07:00
const u16 * clut = GetCurrentClut < u16 > ( ) + clutSharingOffset ;
2012-11-08 23:26:30 +01:00
texByteAlign = 2 ;
2013-08-24 10:51:17 -07:00
if ( ! gstate . isTextureSwizzled ( ) ) {
2013-05-12 09:15:31 -07:00
if ( clutAlphaLinear_ & & mipmapShareClut ) {
DeIndexTexture4Optimal ( tmpTexBuf16 . data ( ) , texaddr , bufw * h , clutAlphaLinearColor_ ) ;
2013-05-05 22:34:35 -07:00
} else {
2013-05-12 09:04:50 -07:00
DeIndexTexture4 ( tmpTexBuf16 . data ( ) , texaddr , bufw * h , clut ) ;
2013-05-05 22:34:35 -07:00
}
2013-01-06 12:11:47 +01:00
} else {
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
2013-04-18 14:59:00 +02:00
UnswizzleFromMem ( texaddr , bufw , 0 , level ) ;
2013-05-12 09:15:31 -07:00
if ( clutAlphaLinear_ & & mipmapShareClut ) {
DeIndexTexture4Optimal ( tmpTexBuf16 . data ( ) , ( u8 * ) tmpTexBuf32 . data ( ) , bufw * h , clutAlphaLinearColor_ ) ;
2013-05-05 22:34:35 -07:00
} else {
2013-05-12 09:04:50 -07:00
DeIndexTexture4 ( tmpTexBuf16 . data ( ) , ( u8 * ) tmpTexBuf32 . data ( ) , bufw * h , clut ) ;
2013-05-05 22:34:35 -07:00
}
2012-11-01 16:19:01 +01:00
}
2013-04-09 01:52:19 -07:00
finalBuf = tmpTexBuf16 . data ( ) ;
2012-11-08 23:26:30 +01:00
}
2012-11-01 16:19:01 +01:00
break ;
2012-11-08 23:26:30 +01:00
case GE_CMODE_32BIT_ABGR8888 :
{
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
2013-05-12 09:04:50 -07:00
const u32 * clut = GetCurrentClut < u32 > ( ) + clutSharingOffset ;
2013-08-24 10:51:17 -07:00
if ( ! gstate . isTextureSwizzled ( ) ) {
2013-05-12 09:04:50 -07:00
DeIndexTexture4 ( tmpTexBuf32 . data ( ) , texaddr , bufw * h , clut ) ;
2013-04-28 01:53:24 -07:00
finalBuf = tmpTexBuf32 . data ( ) ;
2013-01-06 12:11:47 +01:00
} else {
2013-04-18 14:59:00 +02:00
UnswizzleFromMem ( texaddr , bufw , 0 , level ) ;
2013-04-28 01:53:24 -07:00
// Let's reuse tmpTexBuf16, just need double the space.
2013-04-28 13:14:25 -07:00
tmpTexBuf16 . resize ( std : : max ( bufw , w ) * h * 2 ) ;
2013-05-12 09:04:50 -07:00
DeIndexTexture4 ( ( u32 * ) tmpTexBuf16 . data ( ) , ( u8 * ) tmpTexBuf32 . data ( ) , bufw * h , clut ) ;
2013-04-28 01:53:24 -07:00
finalBuf = tmpTexBuf16 . data ( ) ;
2012-11-01 16:19:01 +01:00
}
2012-11-08 23:26:30 +01:00
}
2012-11-01 16:19:01 +01:00
break ;
2012-11-08 23:26:30 +01:00
default :
2013-09-07 13:43:07 -07:00
ERROR_LOG_REPORT ( G3D , " Unknown CLUT4 texture mode %d " , gstate . getClutPaletteFormat ( ) ) ;
2013-04-28 11:20:56 -07:00
return NULL ;
2012-11-01 16:19:01 +01:00
}
2013-05-12 09:04:50 -07:00
}
2012-11-08 23:26:30 +01:00
break ;
2012-11-01 16:19:01 +01:00
case GE_TFMT_CLUT8 :
2013-07-21 19:34:55 -07:00
texByteAlign = texByteAlignMap [ gstate . getClutPaletteFormat ( ) ] ;
2013-09-15 19:50:43 -07:00
finalBuf = ReadIndexedTex ( level , texaddr , 1 , dstFmt , bufw ) ;
2012-11-08 23:26:30 +01:00
break ;
case GE_TFMT_CLUT16 :
2013-07-21 19:34:55 -07:00
texByteAlign = texByteAlignMap [ gstate . getClutPaletteFormat ( ) ] ;
2013-09-15 19:50:43 -07:00
finalBuf = ReadIndexedTex ( level , texaddr , 2 , dstFmt , bufw ) ;
2012-11-08 23:26:30 +01:00
break ;
case GE_TFMT_CLUT32 :
2013-07-21 19:34:55 -07:00
texByteAlign = texByteAlignMap [ gstate . getClutPaletteFormat ( ) ] ;
2013-09-15 19:50:43 -07:00
finalBuf = ReadIndexedTex ( level , texaddr , 4 , dstFmt , bufw ) ;
2012-11-08 23:26:30 +01:00
break ;
case GE_TFMT_4444 :
case GE_TFMT_5551 :
case GE_TFMT_5650 :
texByteAlign = 2 ;
2013-08-24 10:51:17 -07:00
if ( ! gstate . isTextureSwizzled ( ) ) {
2012-11-28 16:20:38 +01:00
int len = std : : max ( bufw , w ) * h ;
2013-04-09 01:52:19 -07:00
tmpTexBuf16 . resize ( len ) ;
tmpTexBufRearrange . resize ( len ) ;
finalBuf = tmpTexBuf16 . data ( ) ;
2013-06-30 20:36:27 -07:00
ConvertColors ( finalBuf , Memory : : GetPointer ( texaddr ) , dstFmt , bufw * h ) ;
} else {
2013-04-28 10:49:06 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
2013-04-18 14:59:00 +02:00
finalBuf = UnswizzleFromMem ( texaddr , bufw , 2 , level ) ;
2013-06-30 20:36:27 -07:00
ConvertColors ( finalBuf , finalBuf , dstFmt , bufw * h ) ;
2013-04-28 10:49:06 -07:00
}
2012-11-08 23:26:30 +01:00
break ;
2012-11-01 16:19:01 +01:00
case GE_TFMT_8888 :
2013-08-24 10:51:17 -07:00
if ( ! gstate . isTextureSwizzled ( ) ) {
2013-05-03 01:02:00 -07:00
// Special case: if we don't need to deal with packing, we don't need to copy.
2013-11-02 15:23:35 +01:00
if ( ( g_Config . iTexScalingLevel = = 1 & & gl_extensions . EXT_unpack_subimage ) | | w = = bufw ) {
2013-05-03 01:02:00 -07:00
finalBuf = Memory : : GetPointer ( texaddr ) ;
} else {
int len = bufw * h ;
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
Memory : : Memcpy ( tmpTexBuf32 . data ( ) , texaddr , len * sizeof ( u32 ) ) ;
finalBuf = tmpTexBuf32 . data ( ) ;
}
2012-11-01 16:19:01 +01:00
}
2013-04-28 10:49:06 -07:00
else {
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
2013-04-18 14:59:00 +02:00
finalBuf = UnswizzleFromMem ( texaddr , bufw , 4 , level ) ;
2013-04-28 10:49:06 -07:00
}
2012-11-08 23:26:30 +01:00
break ;
2012-11-01 16:19:01 +01:00
case GE_TFMT_DXT1 :
{
2013-04-28 10:49:06 -07:00
int minw = std : : min ( bufw , w ) ;
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
2013-04-09 01:52:19 -07:00
u32 * dst = tmpTexBuf32 . data ( ) ;
2012-11-01 16:19:01 +01:00
DXT1Block * src = ( DXT1Block * ) texptr ;
2013-01-06 12:11:47 +01:00
for ( int y = 0 ; y < h ; y + = 4 ) {
2012-11-28 16:12:29 +01:00
u32 blockIndex = ( y / 4 ) * ( bufw / 4 ) ;
2013-04-28 10:49:06 -07:00
for ( int x = 0 ; x < minw ; x + = 4 ) {
2013-09-25 23:07:09 -07:00
DecodeDXT1Block ( dst + bufw * y + x , src + blockIndex , bufw ) ;
2012-11-28 16:12:29 +01:00
blockIndex + + ;
2012-11-01 16:19:01 +01:00
}
}
2013-04-09 01:52:19 -07:00
finalBuf = tmpTexBuf32 . data ( ) ;
2012-11-28 16:12:29 +01:00
w = ( w + 3 ) & ~ 3 ;
2012-11-01 16:19:01 +01:00
}
break ;
case GE_TFMT_DXT3 :
2012-11-28 16:12:29 +01:00
{
2013-04-28 10:49:06 -07:00
int minw = std : : min ( bufw , w ) ;
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
2013-04-09 01:52:19 -07:00
u32 * dst = tmpTexBuf32 . data ( ) ;
2012-11-28 16:12:29 +01:00
DXT3Block * src = ( DXT3Block * ) texptr ;
2013-01-06 12:11:47 +01:00
for ( int y = 0 ; y < h ; y + = 4 ) {
2012-11-28 16:20:38 +01:00
u32 blockIndex = ( y / 4 ) * ( bufw / 4 ) ;
2013-04-28 10:49:06 -07:00
for ( int x = 0 ; x < minw ; x + = 4 ) {
2013-09-25 23:07:09 -07:00
DecodeDXT3Block ( dst + bufw * y + x , src + blockIndex , bufw ) ;
2012-11-28 16:12:29 +01:00
blockIndex + + ;
}
}
w = ( w + 3 ) & ~ 3 ;
2013-04-09 01:52:19 -07:00
finalBuf = tmpTexBuf32 . data ( ) ;
2012-11-28 16:12:29 +01:00
}
break ;
2013-07-22 19:22:21 +02:00
case GE_TFMT_DXT5 :
2012-11-28 16:12:29 +01:00
{
2013-04-28 10:49:06 -07:00
int minw = std : : min ( bufw , w ) ;
2013-04-28 13:14:25 -07:00
tmpTexBuf32 . resize ( std : : max ( bufw , w ) * h ) ;
tmpTexBufRearrange . resize ( std : : max ( bufw , w ) * h ) ;
2013-04-09 01:52:19 -07:00
u32 * dst = tmpTexBuf32 . data ( ) ;
2012-11-28 16:12:29 +01:00
DXT5Block * src = ( DXT5Block * ) texptr ;
2013-04-28 10:49:06 -07:00
2013-01-06 12:11:47 +01:00
for ( int y = 0 ; y < h ; y + = 4 ) {
2012-11-28 16:12:29 +01:00
u32 blockIndex = ( y / 4 ) * ( bufw / 4 ) ;
2013-04-28 10:49:06 -07:00
for ( int x = 0 ; x < minw ; x + = 4 ) {
2013-09-25 23:07:09 -07:00
DecodeDXT5Block ( dst + bufw * y + x , src + blockIndex , bufw ) ;
2012-11-28 16:12:29 +01:00
blockIndex + + ;
}
}
w = ( w + 3 ) & ~ 3 ;
2013-04-09 01:52:19 -07:00
finalBuf = tmpTexBuf32 . data ( ) ;
2012-11-28 16:12:29 +01:00
}
2012-11-08 23:26:30 +01:00
break ;
2012-11-01 16:19:01 +01:00
default :
2013-04-28 11:20:56 -07:00
ERROR_LOG_REPORT ( G3D , " Unknown Texture Format %d!!! " , format ) ;
return NULL ;
2012-11-01 16:19:01 +01:00
}
2012-11-28 16:12:29 +01:00
if ( ! finalBuf ) {
2013-04-28 10:49:06 -07:00
ERROR_LOG_REPORT ( G3D , " NO finalbuf! Will crash! " ) ;
2012-11-28 16:12:29 +01:00
}
2013-11-02 15:23:35 +01:00
if ( ( g_Config . iTexScalingLevel ! = 1 | | ! gl_extensions . EXT_unpack_subimage ) & & w ! = bufw ) {
2012-11-09 00:51:04 +01:00
int pixelSize ;
switch ( dstFmt ) {
2012-11-09 01:24:19 +01:00
case GL_UNSIGNED_SHORT_4_4_4_4 :
case GL_UNSIGNED_SHORT_5_5_5_1 :
case GL_UNSIGNED_SHORT_5_6_5 :
2012-11-09 00:51:04 +01:00
pixelSize = 2 ;
2012-11-09 01:24:19 +01:00
break ;
2012-11-09 00:51:04 +01:00
default :
pixelSize = 4 ;
2012-11-09 01:24:19 +01:00
break ;
2012-11-09 00:51:04 +01:00
}
// Need to rearrange the buffer to simulate GL_UNPACK_ROW_LENGTH etc.
int inRowBytes = bufw * pixelSize ;
int outRowBytes = w * pixelSize ;
const u8 * read = ( const u8 * ) finalBuf ;
u8 * write = 0 ;
if ( w > bufw ) {
2013-04-09 01:52:19 -07:00
write = ( u8 * ) tmpTexBufRearrange . data ( ) ;
finalBuf = tmpTexBufRearrange . data ( ) ;
2012-11-09 00:51:04 +01:00
} else {
write = ( u8 * ) finalBuf ;
}
2012-11-28 16:20:38 +01:00
for ( int y = 0 ; y < h ; y + + ) {
2012-11-09 00:51:04 +01:00
memmove ( write , read , outRowBytes ) ;
read + = inRowBytes ;
write + = outRowBytes ;
}
}
2013-04-28 11:20:56 -07:00
return finalBuf ;
}
2013-05-08 07:52:54 -07:00
void TextureCache : : CheckAlpha ( TexCacheEntry & entry , u32 * pixelData , GLenum dstFmt , int w , int h ) {
2013-05-09 01:19:55 -07:00
// TODO: Could probably be optimized more.
2013-05-08 07:52:54 -07:00
u32 hitZeroAlpha = 0 ;
u32 hitSomeAlpha = 0 ;
switch ( dstFmt ) {
case GL_UNSIGNED_SHORT_4_4_4_4 :
{
const u32 * p = pixelData ;
for ( int i = 0 ; i < ( w * h + 1 ) / 2 ; + + i ) {
u32 a = p [ i ] & 0x000F000F ;
hitZeroAlpha | = a ^ 0x000F000F ;
if ( a ! = 0x000F000F & & a ! = 0x0000000F & & a ! = 0x000F0000 & & a ! = 0 ) {
hitSomeAlpha = 1 ;
break ;
}
}
}
break ;
case GL_UNSIGNED_SHORT_5_5_5_1 :
{
const u32 * p = pixelData ;
for ( int i = 0 ; i < ( w * h + 1 ) / 2 ; + + i ) {
u32 a = p [ i ] & 0x00010001 ;
hitZeroAlpha | = a ^ 0x00010001 ;
}
}
break ;
case GL_UNSIGNED_SHORT_5_6_5 :
{
// Never has any alpha.
}
break ;
default :
{
const u32 * p = pixelData ;
for ( int i = 0 ; i < w * h ; + + i ) {
u32 a = p [ i ] & 0xFF000000 ;
hitZeroAlpha | = a ^ 0xFF000000 ;
if ( a ! = 0xFF000000 & & a ! = 0 ) {
hitSomeAlpha = 1 ;
break ;
}
}
}
break ;
}
if ( hitSomeAlpha ! = 0 )
entry . status | = TexCacheEntry : : STATUS_ALPHA_UNKNOWN ;
else if ( hitZeroAlpha ! = 0 )
entry . status | = TexCacheEntry : : STATUS_ALPHA_SIMPLE ;
else
entry . status | = TexCacheEntry : : STATUS_ALPHA_FULL ;
}
2013-10-08 15:00:48 +02:00
void TextureCache : : LoadTextureLevel ( TexCacheEntry & entry , int level , bool replaceImages , GLenum dstFmt ) {
2013-04-28 11:20:56 -07:00
// TODO: only do this once
u32 texByteAlign = 1 ;
// TODO: Look into using BGRA for 32-bit textures when the GL_EXT_texture_format_BGRA8888 extension is available, as it's faster than RGBA on some chips.
2013-07-21 19:34:55 -07:00
GEPaletteFormat clutformat = gstate . getClutPaletteFormat ( ) ;
2013-10-31 02:39:46 +01:00
int bufw ;
void * finalBuf = DecodeTextureLevel ( GETextureFormat ( entry . format ) , clutformat , level , texByteAlign , dstFmt , & bufw ) ;
2013-04-28 11:20:56 -07:00
if ( finalBuf = = NULL ) {
return ;
}
2013-07-31 01:52:29 +08:00
int w = gstate . getTextureWidth ( level ) ;
int h = gstate . getTextureHeight ( level ) ;
2013-04-28 11:20:56 -07:00
2012-12-21 21:49:09 +01:00
gpuStats . numTexturesDecoded + + ;
2013-07-22 19:22:21 +02:00
// Can restore these and remove the fixup at the end of DecodeTextureLevel on desktop GL and GLES 3.
2013-10-31 02:39:46 +01:00
bool useUnpack = false ;
2013-11-02 15:23:35 +01:00
if ( ( g_Config . iTexScalingLevel = = 1 & & gl_extensions . EXT_unpack_subimage ) & & w ! = bufw ) {
2013-10-31 02:39:46 +01:00
glPixelStorei ( GL_UNPACK_ROW_LENGTH , bufw ) ;
useUnpack = true ;
}
2013-06-11 20:21:19 +02:00
2012-12-22 00:38:17 +01:00
glPixelStorei ( GL_UNPACK_ALIGNMENT , texByteAlign ) ;
2012-12-21 21:49:09 +01:00
2013-10-11 11:49:45 +05:00
int scaleFactor ;
//Auto-texture scale upto 5x rendering resolution
if ( g_Config . iTexScalingLevel = = 0 )
2013-10-11 19:54:26 +05:00
# ifndef USING_GLES2
2013-10-11 11:49:45 +05:00
scaleFactor = std : : min ( 5 , g_Config . iInternalResolution ) ;
2013-10-11 19:54:26 +05:00
# else
scaleFactor = std : : min ( 3 , g_Config . iInternalResolution ) ;
# endif
2013-10-11 11:49:45 +05:00
else
scaleFactor = g_Config . iTexScalingLevel ;
2013-05-02 10:18:26 +02:00
// Don't scale the PPGe texture.
if ( entry . addr > 0x05000000 & & entry . addr < 0x08800000 )
scaleFactor = 1 ;
2013-07-22 19:22:21 +02:00
u32 * pixelData = ( u32 * ) finalBuf ;
2013-10-08 15:00:48 +02:00
if ( scaleFactor > 1 & & entry . numInvalidated = = 0 )
2013-05-02 10:18:26 +02:00
scaler . Scale ( pixelData , dstFmt , w , h , scaleFactor ) ;
2013-05-08 07:52:54 -07:00
// Or always?
if ( entry . numInvalidated = = 0 )
CheckAlpha ( entry , pixelData , dstFmt , w , h ) ;
2013-05-08 08:18:12 -07:00
else
entry . status | = TexCacheEntry : : STATUS_ALPHA_UNKNOWN ;
2013-04-30 03:49:12 +02:00
2012-11-26 01:21:14 +01:00
GLuint components = dstFmt = = GL_UNSIGNED_SHORT_5_6_5 ? GL_RGB : GL_RGBA ;
2013-05-05 00:00:22 -07:00
2013-06-11 20:21:19 +02:00
if ( replaceImages ) {
glTexSubImage2D ( GL_TEXTURE_2D , level , 0 , 0 , w , h , components , dstFmt , pixelData ) ;
} else {
2013-05-05 00:00:22 -07:00
glTexImage2D ( GL_TEXTURE_2D , level , components , w , h , 0 , components , dstFmt , pixelData ) ;
2013-06-11 20:21:19 +02:00
GLenum err = glGetError ( ) ;
if ( err = = GL_OUT_OF_MEMORY ) {
lowMemoryMode_ = true ;
Decimate ( ) ;
// Try again.
glTexImage2D ( GL_TEXTURE_2D , level , components , w , h , 0 , components , dstFmt , pixelData ) ;
}
2013-05-05 00:00:22 -07:00
}
2013-10-31 02:39:46 +01:00
if ( useUnpack ) {
glPixelStorei ( GL_UNPACK_ROW_LENGTH , 0 ) ;
}
2013-04-30 03:49:12 +02:00
}
2013-05-02 10:56:30 +02:00
// Only used by Qt UI?
2013-10-08 15:00:48 +02:00
bool TextureCache : : DecodeTexture ( u8 * output , GPUgstate state ) {
2013-02-17 01:06:06 +01:00
GPUgstate oldState = gstate ;
gstate = state ;
2013-09-15 21:39:28 -07:00
u32 texaddr = gstate . getTextureAddress ( 0 ) ;
2013-02-17 01:06:06 +01:00
if ( ! Memory : : IsValidAddress ( texaddr ) ) {
return false ;
}
2013-04-28 11:20:56 -07:00
u32 texByteAlign = 1 ;
GLenum dstFmt = 0 ;
2013-02-17 01:06:06 +01:00
2013-07-21 19:34:55 -07:00
GETextureFormat format = gstate . getTextureFormat ( ) ;
GEPaletteFormat clutformat = gstate . getClutPaletteFormat ( ) ;
2013-04-28 11:20:56 -07:00
u8 level = 0 ;
2013-02-17 01:06:06 +01:00
2013-09-15 21:27:13 -07:00
int bufw = GetTextureBufw ( level , texaddr , format ) ;
2013-07-31 01:52:29 +08:00
int w = gstate . getTextureWidth ( level ) ;
int h = gstate . getTextureHeight ( level ) ;
2013-02-17 01:06:06 +01:00
2013-04-28 11:20:56 -07:00
void * finalBuf = DecodeTextureLevel ( format , clutformat , level , texByteAlign , dstFmt ) ;
if ( finalBuf = = NULL ) {
2013-02-17 01:06:06 +01:00
return false ;
}
2013-10-08 15:00:48 +02:00
switch ( dstFmt ) {
2013-04-28 11:20:56 -07:00
case GL_UNSIGNED_SHORT_4_4_4_4 :
2013-10-08 15:00:48 +02:00
for ( int y = 0 ; y < h ; y + + )
for ( int x = 0 ; x < bufw ; x + + ) {
2013-07-22 19:22:21 +02:00
u32 val = ( ( u16 * ) finalBuf ) [ y * bufw + x ] ;
2013-05-02 10:56:30 +02:00
u32 r = ( ( val > > 12 ) & 0xF ) * 17 ;
u32 g = ( ( val > > 8 ) & 0xF ) * 17 ;
u32 b = ( ( val > > 4 ) & 0xF ) * 17 ;
u32 a = ( ( val > > 0 ) & 0xF ) * 17 ;
2013-07-22 19:22:21 +02:00
( ( u32 * ) output ) [ y * w + x ] = ( a < < 24 ) | ( r < < 16 ) | ( g < < 8 ) | b ;
2013-02-17 01:06:06 +01:00
}
2013-04-28 11:20:56 -07:00
break ;
case GL_UNSIGNED_SHORT_5_5_5_1 :
2013-10-08 15:00:48 +02:00
for ( int y = 0 ; y < h ; y + + )
for ( int x = 0 ; x < bufw ; x + + ) {
2013-07-22 19:22:21 +02:00
u32 val = ( ( u16 * ) finalBuf ) [ y * bufw + x ] ;
2013-05-02 10:56:30 +02:00
u32 r = Convert5To8 ( ( val > > 11 ) & 0x1F ) ;
u32 g = Convert5To8 ( ( val > > 6 ) & 0x1F ) ;
u32 b = Convert5To8 ( ( val > > 1 ) & 0x1F ) ;
2013-02-17 01:06:06 +01:00
u32 a = ( val & 0x1 ) * 255 ;
2013-07-22 19:22:21 +02:00
( ( u32 * ) output ) [ y * w + x ] = ( a < < 24 ) | ( r < < 16 ) | ( g < < 8 ) | b ;
2013-02-17 01:06:06 +01:00
}
2013-04-28 11:20:56 -07:00
break ;
case GL_UNSIGNED_SHORT_5_6_5 :
2013-10-08 15:00:48 +02:00
for ( int y = 0 ; y < h ; y + + )
for ( int x = 0 ; x < bufw ; x + + ) {
2013-07-22 19:22:21 +02:00
u32 val = ( ( u16 * ) finalBuf ) [ y * bufw + x ] ;
2013-02-17 01:06:06 +01:00
u32 a = 0xFF ;
2013-05-02 10:56:30 +02:00
u32 r = Convert5To8 ( ( val > > 11 ) & 0x1F ) ;
u32 g = Convert6To8 ( ( val > > 5 ) & 0x3F ) ;
u32 b = Convert5To8 ( ( val ) & 0x1F ) ;
2013-07-22 19:22:21 +02:00
( ( u32 * ) output ) [ y * w + x ] = ( a < < 24 ) | ( r < < 16 ) | ( g < < 8 ) | b ;
2013-02-17 01:06:06 +01:00
}
2013-04-28 11:20:56 -07:00
break ;
default :
2013-10-08 15:00:48 +02:00
for ( int y = 0 ; y < h ; y + + )
for ( int x = 0 ; x < bufw ; x + + ) {
2013-07-22 19:22:21 +02:00
u32 val = ( ( u32 * ) finalBuf ) [ y * bufw + x ] ;
( ( u32 * ) output ) [ y * w + x ] = ( ( val & 0xFF000000 ) ) | ( ( val & 0x00FF0000 ) > > 16 ) | ( ( val & 0x0000FF00 ) ) | ( ( val & 0x000000FF ) < < 16 ) ;
2013-02-17 01:06:06 +01:00
}
2013-04-28 11:20:56 -07:00
break ;
2013-02-17 01:06:06 +01:00
}
gstate = oldState ;
return true ;
}