2014-09-13 13:15:18 +02:00
// Copyright (c) 2013- 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/.
2015-11-28 12:41:37 -08:00
# include <algorithm>
2020-09-15 22:53:37 +02:00
2019-05-10 23:25:57 +02:00
# include "ppsspp_config.h"
2021-05-01 07:15:04 -07:00
# include "Common/Data/Convert/ColorConv.h"
2020-10-04 10:04:01 +02:00
# include "Common/Profiler/Profiler.h"
2015-11-28 12:41:37 -08:00
# include "Common/MemoryUtil.h"
2021-02-03 20:46:41 -08:00
# include "Common/StringUtils.h"
2015-03-15 19:25:34 -07:00
# include "Core/Config.h"
2021-02-03 20:46:41 -08:00
# include "Core/Debugger/MemBlockInfo.h"
2015-11-28 12:41:37 -08:00
# include "Core/Reporting.h"
2016-01-03 23:06:15 -08:00
# include "Core/System.h"
2020-08-03 23:17:22 +02:00
# include "GPU/Common/FramebufferManagerCommon.h"
2014-09-13 13:15:18 +02:00
# include "GPU/Common/TextureCacheCommon.h"
2015-11-28 17:51:15 -08:00
# include "GPU/Common/TextureDecoder.h"
2015-10-24 23:49:05 +02:00
# include "GPU/Common/ShaderId.h"
# include "GPU/Common/GPUStateUtils.h"
2018-09-01 08:32:03 -07:00
# include "GPU/Debugger/Debugger.h"
2020-05-07 23:00:54 -07:00
# include "GPU/GPUCommon.h"
2015-11-28 12:41:37 -08:00
# include "GPU/GPUInterface.h"
2020-05-07 23:00:54 -07:00
# include "GPU/GPUState.h"
2021-02-19 09:52:14 +01:00
# include "Core/Util/PPGeDraw.h"
2015-03-15 19:25:34 -07:00
2016-01-05 14:23:54 +08:00
# if defined(_M_SSE)
# include <emmintrin.h>
# endif
2017-08-15 16:02:31 +02:00
# if PPSSPP_ARCH(ARM_NEON)
2019-05-10 23:25:57 +02:00
# if defined(_MSC_VER) && PPSSPP_ARCH(ARM64)
2019-05-04 05:37:28 +08:00
# include <arm64_neon.h>
# else
2017-08-15 16:02:31 +02:00
# include <arm_neon.h>
# endif
2019-05-04 05:37:28 +08:00
# endif
2016-01-05 14:23:54 +08:00
2017-03-18 11:51:05 -07:00
// Videos should be updated every few frames, so we forget quickly.
2016-05-01 08:39:18 -07:00
# define VIDEO_DECIMATE_AGE 4
2017-02-19 23:39:35 +01:00
// If a texture hasn't been seen for this many frames, get rid of it.
# define TEXTURE_KILL_AGE 200
# define TEXTURE_KILL_AGE_LOWMEM 60
// Not used in lowmem mode.
# define TEXTURE_SECOND_KILL_AGE 100
2018-02-18 12:26:00 -08:00
// Used when there are multiple CLUT variants of a texture.
2018-02-18 12:29:14 -08:00
# define TEXTURE_KILL_AGE_CLUT 6
# define TEXTURE_CLUT_VARIANTS_MIN 6
2017-02-19 23:39:35 +01:00
// Try to be prime to other decimation intervals.
# define TEXCACHE_DECIMATION_INTERVAL 13
# define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in VRAM
# define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024
2017-02-22 16:23:04 +01:00
// Just for reference
// PSP Color formats:
// 565: BBBBBGGGGGGRRRRR
// 5551: ABBBBBGGGGGRRRRR
// 4444: AAAABBBBGGGGRRRR
// 8888: AAAAAAAABBBBBBBBGGGGGGGGRRRRRRRR (Bytes in memory: RGBA)
// D3D11/9 Color formats:
// DXGI_FORMAT_B4G4R4A4/D3DFMT_A4R4G4B4: AAAARRRRGGGGBBBB
// DXGI_FORMAT_B5G5R5A1/D3DFMT_A1R5G6B5: ARRRRRGGGGGBBBBB
// DXGI_FORMAT_B5G6R6/D3DFMT_R5G6B5: RRRRRGGGGGGBBBBB
// DXGI_FORMAT_B8G8R8A8: AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB (Bytes in memory: BGRA)
// These are Data::Format:: A4R4G4B4_PACK16, A1R5G6B5_PACK16, R5G6B5_PACK16, B8G8R8A8.
// So these are good matches, just with R/B swapped.
// OpenGL ES color formats:
// GL_UNSIGNED_SHORT_4444: BBBBGGGGRRRRAAAA (4-bit rotation)
// GL_UNSIGNED_SHORT_565: BBBBBGGGGGGRRRRR (match)
// GL_UNSIGNED_SHORT_1555: BBBBBGGGGGRRRRRA (1-bit rotation)
// GL_UNSIGNED_BYTE/RGBA: AAAAAAAABBBBBBBBGGGGGGGGRRRRRRRR (match)
// These are Data::Format:: B4G4R4A4_PACK16, B5G6R6_PACK16, B5G5R5A1_PACK16, R8G8B8A8
2020-08-31 09:42:15 +02:00
// Allow the extra bits from the remasters for the purposes of this.
inline int dimWidth ( u16 dim ) {
return 1 < < ( dim & 0xFF ) ;
}
inline int dimHeight ( u16 dim ) {
return 1 < < ( ( dim > > 8 ) & 0xFF ) ;
}
2017-02-22 16:23:04 +01:00
// Vulkan color formats:
// TODO
2017-02-05 19:51:50 +01:00
TextureCacheCommon : : TextureCacheCommon ( Draw : : DrawContext * draw )
: draw_ ( draw ) ,
clutLastFormat_ ( 0xFFFFFFFF ) ,
clutTotalBytes_ ( 0 ) ,
clutMaxBytes_ ( 0 ) ,
clutRenderAddress_ ( 0xFFFFFFFF ) ,
2017-02-20 00:05:23 +01:00
clutAlphaLinear_ ( false ) ,
isBgraBackend_ ( false ) {
2017-02-19 23:39:35 +01:00
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL ;
2015-11-28 12:41:37 -08:00
// TODO: Clamp down to 256/1KB? Need to check mipmapShareClut and clamp loadclut.
clutBufRaw_ = ( u32 * ) AllocateAlignedMemory ( 1024 * sizeof ( u32 ) , 16 ) ; // 4KB
clutBufConverted_ = ( u32 * ) AllocateAlignedMemory ( 1024 * sizeof ( u32 ) , 16 ) ; // 4KB
// Zap so we get consistent behavior if the game fails to load some of the CLUT.
memset ( clutBufRaw_ , 0 , 1024 * sizeof ( u32 ) ) ;
memset ( clutBufConverted_ , 0 , 1024 * sizeof ( u32 ) ) ;
2016-06-19 07:14:31 -07:00
clutBuf_ = clutBufConverted_ ;
2015-11-28 17:51:15 -08:00
2016-03-26 22:02:38 -07:00
// These buffers will grow if necessary, but most won't need more than this.
2017-02-20 00:19:58 +01:00
tmpTexBuf32_ . resize ( 512 * 512 ) ; // 1MB
tmpTexBufRearrange_ . resize ( 512 * 512 ) ; // 1MB
2016-04-30 13:44:31 -07:00
2017-02-20 00:19:58 +01:00
replacer_ . Init ( ) ;
2015-11-28 12:41:37 -08:00
}
TextureCacheCommon : : ~ TextureCacheCommon ( ) {
FreeAlignedMemory ( clutBufConverted_ ) ;
FreeAlignedMemory ( clutBufRaw_ ) ;
}
2014-09-13 13:15:18 +02:00
2017-05-12 20:01:08 -07:00
// Produces a signed 1.23.8 value.
static int TexLog2 ( float delta ) {
union FloatBits {
float f ;
u32 u ;
} ;
FloatBits f ;
f . f = delta ;
// Use the exponent as the tex level, and the top mantissa bits for a frac.
// We can't support more than 8 bits of frac, so truncate.
int useful = ( f . u > > 15 ) & 0xFFFF ;
// Now offset so the exponent aligns with log2f (exp=127 is 0.)
return useful - 127 * 256 ;
}
2021-02-27 17:17:21 -08:00
SamplerCacheKey TextureCacheCommon : : GetSamplingParams ( int maxLevel , const TexCacheEntry * entry ) {
2020-09-13 23:46:57 +02:00
SamplerCacheKey key ;
2020-09-13 23:10:43 +02:00
int minFilt = gstate . texfilter & 0x7 ;
2020-09-14 00:03:29 +02:00
key . minFilt = minFilt & 1 ;
key . mipEnable = ( minFilt > > 2 ) & 1 ;
key . mipFilt = ( minFilt > > 1 ) & 1 ;
key . magFilt = gstate . isMagnifyFilteringEnabled ( ) ;
key . sClamp = gstate . isTexCoordClampedS ( ) ;
key . tClamp = gstate . isTexCoordClampedT ( ) ;
key . aniso = false ;
2015-03-15 19:25:34 -07:00
2017-05-12 20:01:08 -07:00
GETexLevelMode mipMode = gstate . getTexLevelMode ( ) ;
2017-11-14 13:32:16 +01:00
bool autoMip = mipMode = = GE_TEXLEVEL_MODE_AUTO ;
2020-09-13 15:36:30 +02:00
// TODO: Slope mipmap bias is still not well understood.
2020-09-13 23:10:43 +02:00
float lodBias = ( float ) gstate . getTexLevelOffset16 ( ) * ( 1.0f / 16.0f ) ;
2017-05-12 20:01:08 -07:00
if ( mipMode = = GE_TEXLEVEL_MODE_SLOPE ) {
lodBias + = 1.0f + TexLog2 ( gstate . getTextureLodSlope ( ) ) * ( 1.0f / 256.0f ) ;
}
// If mip level is forced to zero, disable mipmapping.
2017-05-26 10:16:43 +02:00
bool noMip = maxLevel = = 0 | | ( ! autoMip & & lodBias < = 0.0f ) ;
2020-09-13 15:36:30 +02:00
if ( IsFakeMipmapChange ( ) ) {
2017-05-12 20:01:08 -07:00
noMip = noMip | | ! autoMip ;
2020-09-13 15:36:30 +02:00
}
2015-03-15 19:25:34 -07:00
2017-05-12 20:01:08 -07:00
if ( noMip ) {
2015-03-15 19:25:34 -07:00
// Enforce no mip filtering, for safety.
2020-09-14 00:03:29 +02:00
key . mipEnable = false ;
key . mipFilt = 0 ;
2015-03-15 19:25:34 -07:00
lodBias = 0.0f ;
}
2017-11-14 15:56:05 +01:00
if ( ! key . mipEnable ) {
key . maxLevel = 0 ;
key . minLevel = 0 ;
key . lodBias = 0 ;
2020-09-13 23:10:43 +02:00
key . mipFilt = 0 ;
2017-11-14 15:56:05 +01:00
} else {
2020-09-13 23:10:43 +02:00
switch ( mipMode ) {
2017-11-14 15:56:05 +01:00
case GE_TEXLEVEL_MODE_AUTO :
2020-09-13 15:57:26 +02:00
key . maxLevel = maxLevel * 256 ;
2017-11-14 15:56:05 +01:00
key . minLevel = 0 ;
key . lodBias = ( int ) ( lodBias * 256.0f ) ;
2017-11-15 19:07:41 +01:00
if ( gstate_c . Supports ( GPU_SUPPORTS_ANISOTROPY ) & & g_Config . iAnisotropyLevel > 0 ) {
key . aniso = true ;
}
2017-11-21 15:18:31 +01:00
break ;
2017-11-14 15:56:05 +01:00
case GE_TEXLEVEL_MODE_CONST :
2017-11-20 12:38:37 +01:00
case GE_TEXLEVEL_MODE_UNKNOWN :
2017-11-14 15:56:05 +01:00
key . maxLevel = ( int ) ( lodBias * 256.0f ) ;
key . minLevel = ( int ) ( lodBias * 256.0f ) ;
key . lodBias = 0 ;
break ;
case GE_TEXLEVEL_MODE_SLOPE :
2017-11-14 16:34:23 +01:00
// It's incorrect to use the slope as a bias. Instead it should be passed
// into the shader directly as an explicit lod level, with the bias on top. For now, we just kill the
// lodBias in this mode, working around #9772.
2020-09-13 15:57:26 +02:00
key . maxLevel = maxLevel * 256 ;
2017-11-14 15:56:05 +01:00
key . minLevel = 0 ;
key . lodBias = 0 ;
break ;
}
}
2020-09-13 23:46:57 +02:00
2020-09-14 00:00:52 +02:00
// Video bilinear override
2021-02-27 17:17:21 -08:00
if ( ! key . magFilt & & entry ! = nullptr & & IsVideo ( entry - > addr ) ) {
2021-02-20 20:59:04 -08:00
// Enforce bilinear filtering on magnification.
key . magFilt = 1 ;
2020-09-14 00:00:52 +02:00
}
2021-02-27 17:17:21 -08:00
// Filtering overrides from replacements or settings.
TextureFiltering forceFiltering = TEX_FILTER_AUTO ;
2021-03-08 00:55:17 +01:00
u64 cachekey = replacer_ . Enabled ( ) ? ( entry ? entry - > CacheKey ( ) : 0 ) : 0 ;
2021-03-02 23:54:01 -08:00
if ( ! replacer_ . Enabled ( ) | | entry = = nullptr | | ! replacer_ . FindFiltering ( cachekey , entry - > fullhash , & forceFiltering ) ) {
2021-02-27 17:17:21 -08:00
switch ( g_Config . iTexFiltering ) {
case TEX_FILTER_AUTO :
// Follow what the game wants. We just do a single heuristic change to avoid bleeding of wacky color test colors
// in higher resolution (used by some games for sprites, and they accidentally have linear filter on).
if ( gstate . isModeThrough ( ) & & g_Config . iInternalResolution ! = 1 ) {
bool uglyColorTest = gstate . isColorTestEnabled ( ) & & ! IsColorTestTriviallyTrue ( ) & & gstate . getColorTestRef ( ) ! = 0 ;
if ( uglyColorTest )
forceFiltering = TEX_FILTER_FORCE_NEAREST ;
}
break ;
case TEX_FILTER_FORCE_LINEAR :
// Override to linear filtering if there's no alpha or color testing going on.
if ( ( ! gstate . isColorTestEnabled ( ) | | IsColorTestTriviallyTrue ( ) ) & &
( ! gstate . isAlphaTestEnabled ( ) | | IsAlphaTestTriviallyTrue ( ) ) ) {
forceFiltering = TEX_FILTER_FORCE_LINEAR ;
2020-09-13 23:59:26 +02:00
}
2021-02-27 17:17:21 -08:00
break ;
case TEX_FILTER_FORCE_NEAREST :
default :
// Just force to nearest without checks. Safe (but ugly).
forceFiltering = TEX_FILTER_FORCE_NEAREST ;
break ;
2020-09-13 23:59:26 +02:00
}
2021-02-27 17:17:21 -08:00
}
switch ( forceFiltering ) {
case TEX_FILTER_AUTO :
2020-09-13 23:59:26 +02:00
break ;
case TEX_FILTER_FORCE_LINEAR :
2021-02-27 17:17:21 -08:00
key . magFilt = 1 ;
key . minFilt = 1 ;
key . mipFilt = 1 ;
2020-09-13 23:59:26 +02:00
break ;
case TEX_FILTER_FORCE_NEAREST :
key . magFilt = 0 ;
key . minFilt = 0 ;
break ;
}
2020-09-13 23:46:57 +02:00
return key ;
2017-11-14 15:56:05 +01:00
}
2020-09-13 23:46:57 +02:00
SamplerCacheKey TextureCacheCommon : : GetFramebufferSamplingParams ( u16 bufferWidth , u16 bufferHeight ) {
2021-02-27 17:17:21 -08:00
SamplerCacheKey key = GetSamplingParams ( 0 , nullptr ) ;
2020-09-13 23:34:00 +02:00
2020-09-13 23:46:57 +02:00
// Kill any mipmapping settings.
2020-09-13 23:34:00 +02:00
key . mipEnable = false ;
2020-09-24 23:00:48 +02:00
key . mipFilt = false ;
2020-09-13 23:34:00 +02:00
key . aniso = 0.0 ;
2020-09-24 23:00:48 +02:00
key . maxLevel = 0.0f ;
2020-09-13 23:34:00 +02:00
// Often the framebuffer will not match the texture size. We'll wrap/clamp in the shader in that case.
int w = gstate . getTextureWidth ( 0 ) ;
int h = gstate . getTextureHeight ( 0 ) ;
if ( w ! = bufferWidth | | h ! = bufferHeight ) {
key . sClamp = true ;
key . tClamp = true ;
}
2020-09-13 23:46:57 +02:00
return key ;
2020-09-13 23:34:00 +02:00
}
2016-05-01 11:17:55 -07:00
void TextureCacheCommon : : UpdateMaxSeenV ( TexCacheEntry * entry , bool throughMode ) {
2016-01-22 18:48:54 -08:00
// If the texture is >= 512 pixels tall...
2016-05-01 11:17:55 -07:00
if ( entry - > dim > = 0x900 ) {
2018-08-29 22:07:27 -07:00
if ( entry - > cluthash ! = 0 & & entry - > maxSeenV = = 0 ) {
const u64 cachekeyMin = ( u64 ) ( entry - > addr & 0x3FFFFFFF ) < < 32 ;
const u64 cachekeyMax = cachekeyMin + ( 1ULL < < 32 ) ;
for ( auto it = cache_ . lower_bound ( cachekeyMin ) , end = cache_ . upper_bound ( cachekeyMax ) ; it ! = end ; + + it ) {
// They should all be the same, just make sure we take any that has already increased.
// This is for a new texture.
if ( it - > second - > maxSeenV ! = 0 ) {
entry - > maxSeenV = it - > second - > maxSeenV ;
break ;
}
}
}
2016-01-22 18:48:54 -08:00
// Texture scale/offset and gen modes don't apply in through.
// So we can optimize how much of the texture we look at.
if ( throughMode ) {
2016-05-01 11:17:55 -07:00
if ( entry - > maxSeenV = = 0 & & gstate_c . vertBounds . maxV > 0 ) {
2016-01-22 18:48:54 -08:00
// Let's not hash less than 272, we might use more later and have to rehash. 272 is very common.
2016-05-01 11:17:55 -07:00
entry - > maxSeenV = std : : max ( ( u16 ) 272 , gstate_c . vertBounds . maxV ) ;
} else if ( gstate_c . vertBounds . maxV > entry - > maxSeenV ) {
2016-01-22 18:48:54 -08:00
// The max height changed, so we're better off hashing the entire thing.
2016-05-01 11:17:55 -07:00
entry - > maxSeenV = 512 ;
entry - > status | = TexCacheEntry : : STATUS_FREE_CHANGE ;
2016-01-22 18:48:54 -08:00
}
} else {
// Otherwise, we need to reset to ensure we use the whole thing.
// Can't tell how much is used.
// TODO: We could tell for texcoord UV gen, and apply scale to max?
2016-05-01 11:17:55 -07:00
entry - > maxSeenV = 512 ;
2016-01-22 18:48:54 -08:00
}
2018-08-29 22:07:27 -07:00
// We need to keep all CLUT variants in sync so we detect changes properly.
// See HandleTextureChange / STATUS_CLUT_RECHECK.
if ( entry - > cluthash ! = 0 ) {
const u64 cachekeyMin = ( u64 ) ( entry - > addr & 0x3FFFFFFF ) < < 32 ;
const u64 cachekeyMax = cachekeyMin + ( 1ULL < < 32 ) ;
for ( auto it = cache_ . lower_bound ( cachekeyMin ) , end = cache_ . upper_bound ( cachekeyMax ) ; it ! = end ; + + it ) {
it - > second - > maxSeenV = entry - > maxSeenV ;
}
}
2016-01-22 18:48:54 -08:00
}
}
2020-09-20 20:35:42 +02:00
TexCacheEntry * TextureCacheCommon : : SetTexture ( ) {
2017-02-20 00:05:23 +01:00
u8 level = 0 ;
if ( IsFakeMipmapChange ( ) )
2017-05-31 21:42:07 -07:00
level = std : : max ( 0 , gstate . getTexLevelOffset16 ( ) / 16 ) ;
2017-02-20 00:05:23 +01:00
u32 texaddr = gstate . getTextureAddress ( level ) ;
if ( ! Memory : : IsValidAddress ( texaddr ) ) {
// Bind a null texture and return.
Unbind ( ) ;
2020-09-12 14:25:50 +02:00
return nullptr ;
2017-02-20 00:05:23 +01:00
}
const u16 dim = gstate . getTextureDimension ( level ) ;
int w = gstate . getTextureWidth ( level ) ;
int h = gstate . getTextureHeight ( level ) ;
GETextureFormat format = gstate . getTextureFormat ( ) ;
if ( format > = 11 ) {
2020-09-20 23:46:53 +02:00
// TODO: Better assumption? Doesn't really matter, these are invalid.
2017-02-20 00:05:23 +01:00
format = GE_TFMT_5650 ;
}
2020-09-20 23:46:53 +02:00
bool hasClut = gstate . isTextureFormatIndexed ( ) ;
2017-02-20 00:05:23 +01:00
u32 cluthash ;
if ( hasClut ) {
if ( clutLastFormat_ ! = gstate . clutformat ) {
// We update here because the clut format can be specified after the load.
UpdateCurrentClut ( gstate . getClutPaletteFormat ( ) , gstate . getClutIndexStartPos ( ) , gstate . isClutIndexSimple ( ) ) ;
}
cluthash = clutHash_ ^ gstate . clutformat ;
} else {
cluthash = 0 ;
}
u64 cachekey = TexCacheEntry : : CacheKey ( texaddr , format , dim , cluthash ) ;
int bufw = GetTextureBufw ( 0 , texaddr , format ) ;
u8 maxLevel = gstate . getTextureMaxLevel ( ) ;
2021-07-10 11:42:33 +02:00
u32 minihash = MiniHash ( ( const u32 * ) Memory : : GetPointerUnchecked ( texaddr ) ) ;
2017-02-20 00:05:23 +01:00
2020-09-15 22:40:46 +02:00
TexCache : : iterator entryIter = cache_ . find ( cachekey ) ;
2017-02-20 00:45:07 +01:00
TexCacheEntry * entry = nullptr ;
2017-12-09 23:56:27 +01:00
// Note: It's necessary to reset needshadertexclamp, for otherwise DIRTY_TEXCLAMP won't get set later.
// Should probably revisit how this works..
2017-04-03 18:06:49 +02:00
gstate_c . SetNeedShaderTexclamp ( false ) ;
2017-02-20 00:05:23 +01:00
gstate_c . skipDrawReason & = ~ SKIPDRAW_BAD_FB_TEXTURE ;
2017-03-19 10:25:30 -07:00
if ( gstate_c . bgraTexture ! = isBgraBackend_ ) {
gstate_c . Dirty ( DIRTY_FRAGMENTSHADER_STATE ) ;
}
2017-02-20 00:05:23 +01:00
gstate_c . bgraTexture = isBgraBackend_ ;
2020-09-15 22:40:46 +02:00
if ( entryIter ! = cache_ . end ( ) ) {
entry = entryIter - > second . get ( ) ;
2017-02-20 00:05:23 +01:00
// Validate the texture still matches the cache entry.
bool match = entry - > Matches ( dim , format , maxLevel ) ;
const char * reason = " different params " ;
2020-09-12 14:25:50 +02:00
// Check for FBO changes.
if ( entry - > status & TexCacheEntry : : STATUS_FRAMEBUFFER_OVERLAP ) {
// Fall through to the end where we'll delete the entry if there's a framebuffer.
2020-09-15 09:34:40 +02:00
entry - > status & = ~ TexCacheEntry : : STATUS_FRAMEBUFFER_OVERLAP ;
2020-09-12 14:25:50 +02:00
match = false ;
2017-02-20 00:05:23 +01:00
}
bool rehash = entry - > GetHashStatus ( ) = = TexCacheEntry : : STATUS_UNRELIABLE ;
// First let's see if another texture with the same address had a hashfail.
if ( entry - > status & TexCacheEntry : : STATUS_CLUT_RECHECK ) {
// Always rehash in this case, if one changed the rest all probably did.
rehash = true ;
entry - > status & = ~ TexCacheEntry : : STATUS_CLUT_RECHECK ;
} else if ( ! gstate_c . IsDirty ( DIRTY_TEXTURE_IMAGE ) ) {
// Okay, just some parameter change - the data didn't change, no need to rehash.
rehash = false ;
}
2020-08-23 21:29:15 -07:00
// Do we need to recreate?
if ( entry - > status & TexCacheEntry : : STATUS_FORCE_REBUILD ) {
match = false ;
entry - > status & = ~ TexCacheEntry : : STATUS_FORCE_REBUILD ;
}
2017-02-20 00:05:23 +01:00
if ( match ) {
if ( entry - > lastFrame ! = gpuStats . numFlips ) {
u32 diff = gpuStats . numFlips - entry - > lastFrame ;
entry - > numFrames + + ;
if ( entry - > framesUntilNextFullHash < diff ) {
// Exponential backoff up to 512 frames. Textures are often reused.
if ( entry - > numFrames > 32 ) {
// Also, try to add some "randomness" to avoid rehashing several textures the same frame.
2017-11-19 18:22:46 +01:00
entry - > framesUntilNextFullHash = std : : min ( 512 , entry - > numFrames ) + ( ( ( intptr_t ) ( entry - > textureName ) > > 12 ) & 15 ) ;
2017-02-20 00:05:23 +01:00
} else {
entry - > framesUntilNextFullHash = entry - > numFrames ;
}
rehash = true ;
} else {
entry - > framesUntilNextFullHash - = diff ;
}
}
// If it's not huge or has been invalidated many times, recheck the whole texture.
if ( entry - > invalidHint > 180 | | ( entry - > invalidHint > 15 & & ( dim > > 8 ) < 9 & & ( dim & 0xF ) < 9 ) ) {
entry - > invalidHint = 0 ;
rehash = true ;
}
2021-07-10 11:42:33 +02:00
if ( minihash ! = entry - > minihash ) {
2017-02-20 00:05:23 +01:00
match = false ;
2021-02-20 20:59:04 -08:00
reason = " minihash " ;
2017-02-20 00:05:23 +01:00
} else if ( entry - > GetHashStatus ( ) = = TexCacheEntry : : STATUS_RELIABLE ) {
rehash = false ;
}
}
if ( match & & ( entry - > status & TexCacheEntry : : STATUS_TO_SCALE ) & & standardScaleFactor_ ! = 1 & & texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED ) {
if ( ( entry - > status & TexCacheEntry : : STATUS_CHANGE_FREQUENT ) = = 0 ) {
// INFO_LOG(G3D, "Reloading texture to do the scaling we skipped..");
match = false ;
reason = " scaling " ;
}
}
if ( match ) {
2020-09-12 14:25:50 +02:00
// got one!
2017-02-20 00:05:23 +01:00
gstate_c . curTextureWidth = w ;
gstate_c . curTextureHeight = h ;
if ( rehash ) {
// Update in case any of these changed.
entry - > sizeInRAM = ( textureBitsPerPixel [ format ] * bufw * h / 2 ) / 8 ;
entry - > bufw = bufw ;
entry - > cluthash = cluthash ;
}
nextTexture_ = entry ;
nextNeedsRehash_ = rehash ;
nextNeedsChange_ = false ;
2017-03-18 11:44:06 -07:00
// Might need a rebuild if the hash fails, but that will be set later.
2017-02-20 00:05:23 +01:00
nextNeedsRebuild_ = false ;
2020-09-29 00:29:40 +02:00
VERBOSE_LOG ( G3D , " Texture at %08x found in cache, applying " , texaddr ) ;
2020-09-12 14:25:50 +02:00
return entry ; //Done!
2017-02-20 00:05:23 +01:00
} else {
2017-03-18 11:44:06 -07:00
// Wasn't a match, we will rebuild.
2017-02-20 00:05:23 +01:00
nextChangeReason_ = reason ;
nextNeedsChange_ = true ;
2020-09-12 14:25:50 +02:00
// Fall through to the rebuild case.
2017-02-20 00:05:23 +01:00
}
2020-09-12 14:25:50 +02:00
}
// No texture found, or changed (depending on entry).
// Check for framebuffers.
2020-11-03 11:32:57 +01:00
TextureDefinition def { } ;
2020-09-12 14:25:50 +02:00
def . addr = texaddr ;
def . dim = dim ;
def . format = format ;
def . bufw = bufw ;
std : : vector < AttachCandidate > candidates = GetFramebufferCandidates ( def , 0 ) ;
if ( candidates . size ( ) > 0 ) {
int index = GetBestCandidateIndex ( candidates ) ;
if ( index ! = - 1 ) {
2020-09-15 22:40:46 +02:00
// If we had a texture entry here, let's get rid of it.
if ( entryIter ! = cache_ . end ( ) ) {
DeleteTexture ( entryIter ) ;
}
2020-11-06 11:54:57 +01:00
const AttachCandidate & candidate = candidates [ index ] ;
2020-09-12 14:25:50 +02:00
nextTexture_ = nullptr ;
2020-09-12 15:25:54 +02:00
nextNeedsRebuild_ = false ;
2020-11-06 11:54:57 +01:00
SetTextureFramebuffer ( candidate ) ;
2020-09-12 14:25:50 +02:00
return nullptr ;
}
}
// Didn't match a framebuffer, keep going.
if ( ! entry ) {
VERBOSE_LOG ( G3D , " No texture in cache for %08x, decoding... " , texaddr ) ;
2020-10-14 00:08:10 +02:00
entry = new TexCacheEntry { } ;
cache_ [ cachekey ] . reset ( entry ) ;
2017-02-20 00:05:23 +01:00
if ( hasClut & & clutRenderAddress_ ! = 0xFFFFFFFF ) {
WARN_LOG_REPORT_ONCE ( clutUseRender , G3D , " Using texture with rendered CLUT: texfmt=%d, clutfmt=%d " , gstate . getTextureFormat ( ) , gstate . getClutPaletteFormat ( ) ) ;
}
2021-02-19 09:52:14 +01:00
if ( PPGeIsFontTextureAddress ( texaddr ) ) {
2020-09-22 21:13:30 +02:00
// It's the builtin font texture.
entry - > status = TexCacheEntry : : STATUS_RELIABLE ;
} else if ( g_Config . bTextureBackoffCache ) {
2017-02-20 00:05:23 +01:00
entry - > status = TexCacheEntry : : STATUS_HASHING ;
} else {
entry - > status = TexCacheEntry : : STATUS_UNRELIABLE ;
}
2018-02-18 12:26:00 -08:00
if ( hasClut & & clutRenderAddress_ = = 0xFFFFFFFF ) {
const u64 cachekeyMin = ( u64 ) ( texaddr & 0x3FFFFFFF ) < < 32 ;
const u64 cachekeyMax = cachekeyMin + ( 1ULL < < 32 ) ;
2018-02-18 12:29:14 -08:00
int found = 0 ;
2018-02-18 12:26:00 -08:00
for ( auto it = cache_ . lower_bound ( cachekeyMin ) , end = cache_ . upper_bound ( cachekeyMax ) ; it ! = end ; + + it ) {
2018-02-18 12:29:14 -08:00
found + + ;
}
if ( found > = TEXTURE_CLUT_VARIANTS_MIN ) {
for ( auto it = cache_ . lower_bound ( cachekeyMin ) , end = cache_ . upper_bound ( cachekeyMax ) ; it ! = end ; + + it ) {
2018-02-18 12:26:00 -08:00
it - > second - > status | = TexCacheEntry : : STATUS_CLUT_VARIANTS ;
}
entry - > status | = TexCacheEntry : : STATUS_CLUT_VARIANTS ;
}
}
2017-02-20 00:05:23 +01:00
nextNeedsChange_ = false ;
}
// We have to decode it, let's setup the cache entry first.
entry - > addr = texaddr ;
2021-07-10 11:42:33 +02:00
entry - > minihash = minihash ;
2017-02-20 00:05:23 +01:00
entry - > dim = dim ;
entry - > format = format ;
entry - > maxLevel = maxLevel ;
// This would overestimate the size in many case so we underestimate instead
// to avoid excessive clearing caused by cache invalidations.
entry - > sizeInRAM = ( textureBitsPerPixel [ format ] * bufw * h / 2 ) / 8 ;
entry - > bufw = bufw ;
entry - > cluthash = cluthash ;
gstate_c . curTextureWidth = w ;
gstate_c . curTextureHeight = h ;
nextTexture_ = entry ;
2020-09-15 09:34:40 +02:00
if ( nextFramebufferTexture_ ) {
nextFramebufferTexture_ = nullptr ; // in case it was accidentally set somehow?
}
2020-09-29 00:29:40 +02:00
nextNeedsRehash_ = true ;
2017-02-20 00:05:23 +01:00
// We still need to rebuild, to allocate a texture. But we'll bail early.
nextNeedsRebuild_ = true ;
2020-09-12 14:25:50 +02:00
return entry ;
2017-02-20 00:05:23 +01:00
}
2020-09-12 14:25:50 +02:00
std : : vector < AttachCandidate > TextureCacheCommon : : GetFramebufferCandidates ( const TextureDefinition & entry , u32 texAddrOffset ) {
2020-09-12 15:37:03 +02:00
gpuStats . numFramebufferEvaluations + + ;
2020-08-29 22:15:33 +02:00
std : : vector < AttachCandidate > candidates ;
2020-09-12 14:25:50 +02:00
FramebufferNotificationChannel channel = Memory : : IsDepthTexVRAMAddress ( entry . addr ) ? FramebufferNotificationChannel : : NOTIFY_FB_DEPTH : FramebufferNotificationChannel : : NOTIFY_FB_COLOR ;
2020-11-10 23:36:09 +01:00
if ( channel = = FramebufferNotificationChannel : : NOTIFY_FB_DEPTH & & ! gstate_c . Supports ( GPU_SUPPORTS_DEPTH_TEXTURE ) ) {
// Depth texture not supported. Don't try to match it, fall back to the memory behind..
return std : : vector < AttachCandidate > ( ) ;
}
2020-09-12 12:37:05 +02:00
2020-09-22 21:24:16 +02:00
const std : : vector < VirtualFramebuffer * > & framebuffers = framebufferManager_ - > Framebuffers ( ) ;
2020-09-12 12:37:05 +02:00
2020-09-22 21:24:16 +02:00
for ( VirtualFramebuffer * framebuffer : framebuffers ) {
2020-09-13 09:03:31 +02:00
FramebufferMatchInfo match = MatchFramebuffer ( entry , framebuffer , texAddrOffset , channel ) ;
switch ( match . match ) {
case FramebufferMatch : : VALID :
2020-09-06 22:53:27 -07:00
candidates . push_back ( AttachCandidate { match , entry , framebuffer , channel } ) ;
2020-09-13 09:03:31 +02:00
break ;
default :
break ;
2020-08-29 22:15:33 +02:00
}
}
2020-08-31 09:42:15 +02:00
if ( candidates . size ( ) > 1 ) {
bool depth = channel = = FramebufferNotificationChannel : : NOTIFY_FB_DEPTH ;
2020-09-12 14:25:50 +02:00
WARN_LOG_REPORT_ONCE ( multifbcandidate , G3D , " GetFramebufferCandidates(%s): Multiple (%d) candidate framebuffers. texaddr: %08x offset: %d (%dx%d stride %d, %s) " ,
depth ? " DEPTH " : " COLOR " , ( int ) candidates . size ( ) , entry . addr , texAddrOffset , dimWidth ( entry . dim ) , dimHeight ( entry . dim ) , entry . bufw , GeTextureFormatToString ( entry . format ) ) ;
2020-08-31 09:42:15 +02:00
}
2020-09-12 14:25:50 +02:00
return candidates ;
2020-08-29 22:25:50 +02:00
}
2020-09-12 14:25:50 +02:00
int TextureCacheCommon : : GetBestCandidateIndex ( const std : : vector < AttachCandidate > & candidates ) {
2020-08-29 22:25:50 +02:00
_dbg_assert_ ( ! candidates . empty ( ) ) ;
2020-08-29 22:15:33 +02:00
if ( candidates . size ( ) = = 1 ) {
2020-09-12 14:25:50 +02:00
return 0 ;
2020-08-29 22:15:33 +02:00
}
// OK, multiple possible candidates. Will need to figure out which one is the most relevant.
int bestRelevancy = - 1 ;
int bestIndex = - 1 ;
2020-09-13 09:03:31 +02:00
// TODO: Instead of scores, we probably want to use std::min_element to pick the top element, using
// a comparison function.
2020-08-29 22:15:33 +02:00
for ( int i = 0 ; i < ( int ) candidates . size ( ) ; i + + ) {
const AttachCandidate & candidate = candidates [ i ] ;
int relevancy = 0 ;
switch ( candidate . match . match ) {
case FramebufferMatch : : VALID :
relevancy + = 1000 ;
break ;
2020-12-14 19:54:39 +01:00
default :
break ;
2020-08-29 22:15:33 +02:00
}
// Bonus point for matching stride.
2020-09-12 14:25:50 +02:00
if ( candidate . channel = = NOTIFY_FB_COLOR & & candidate . fb - > fb_stride = = candidate . entry . bufw ) {
2020-09-13 09:03:31 +02:00
relevancy + = 100 ;
2020-08-29 22:15:33 +02:00
}
2020-09-01 00:05:37 +02:00
// Bonus points for no offset.
if ( candidate . match . xOffset = = 0 & & candidate . match . yOffset = = 0 ) {
2020-09-13 09:03:31 +02:00
relevancy + = 10 ;
2020-09-01 00:05:37 +02:00
}
2020-09-12 14:53:33 +02:00
if ( candidate . channel = = NOTIFY_FB_COLOR & & candidate . fb - > last_frame_render = = gpuStats . numFlips ) {
relevancy + = 5 ;
} else if ( candidate . channel = = NOTIFY_FB_DEPTH & & candidate . fb - > last_frame_depth_render = = gpuStats . numFlips ) {
relevancy + = 5 ;
}
2020-09-01 00:05:37 +02:00
if ( relevancy > bestRelevancy ) {
2020-08-29 22:15:33 +02:00
bestRelevancy = relevancy ;
bestIndex = i ;
}
}
2020-09-12 14:25:50 +02:00
return bestIndex ;
2020-08-26 22:16:48 +02:00
}
2017-02-19 23:39:35 +01:00
// Removes old textures.
2018-04-06 21:25:35 -07:00
void TextureCacheCommon : : Decimate ( bool forcePressure ) {
2017-02-19 23:39:35 +01:00
if ( - - decimationCounter_ < = 0 ) {
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL ;
} else {
return ;
}
2018-04-06 21:25:35 -07:00
if ( forcePressure | | cacheSizeEstimate_ > = TEXCACHE_MIN_PRESSURE ) {
2017-02-19 23:39:35 +01:00
const u32 had = cacheSizeEstimate_ ;
ForgetLastTexture ( ) ;
2018-02-18 12:26:00 -08:00
int killAgeBase = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE ;
2017-02-20 00:19:58 +01:00
for ( TexCache : : iterator iter = cache_ . begin ( ) ; iter ! = cache_ . end ( ) ; ) {
2018-02-18 12:26:00 -08:00
bool hasClut = ( iter - > second - > status & TexCacheEntry : : STATUS_CLUT_VARIANTS ) ! = 0 ;
int killAge = hasClut ? TEXTURE_KILL_AGE_CLUT : killAgeBase ;
2017-02-20 00:45:07 +01:00
if ( iter - > second - > lastFrame + killAge < gpuStats . numFlips ) {
2017-02-19 23:39:35 +01:00
DeleteTexture ( iter + + ) ;
} else {
+ + iter ;
}
}
VERBOSE_LOG ( G3D , " Decimated texture cache, saved %d estimated bytes - now %d bytes " , had - cacheSizeEstimate_ , cacheSizeEstimate_ ) ;
}
2017-03-26 10:18:43 -07:00
// If enabled, we also need to clear the secondary cache.
2018-04-06 21:25:35 -07:00
if ( g_Config . bTextureSecondaryCache & & ( forcePressure | | secondCacheSizeEstimate_ > = TEXCACHE_SECOND_MIN_PRESSURE ) ) {
2017-02-19 23:39:35 +01:00
const u32 had = secondCacheSizeEstimate_ ;
2017-02-20 00:19:58 +01:00
for ( TexCache : : iterator iter = secondCache_ . begin ( ) ; iter ! = secondCache_ . end ( ) ; ) {
2017-03-26 10:18:43 -07:00
// In low memory mode, we kill them all since secondary cache is disabled.
2017-02-20 00:45:07 +01:00
if ( lowMemoryMode_ | | iter - > second - > lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats . numFlips ) {
2017-02-23 17:31:24 +01:00
ReleaseTexture ( iter - > second . get ( ) , true ) ;
2017-02-20 00:45:07 +01:00
secondCacheSizeEstimate_ - = EstimateTexMemoryUsage ( iter - > second . get ( ) ) ;
2017-02-20 00:19:58 +01:00
secondCache_ . erase ( iter + + ) ;
2017-02-19 23:39:35 +01:00
} else {
+ + iter ;
}
}
VERBOSE_LOG ( G3D , " Decimated second texture cache, saved %d estimated bytes - now %d bytes " , had - secondCacheSizeEstimate_ , secondCacheSizeEstimate_ ) ;
}
DecimateVideos ( ) ;
}
2016-05-01 08:39:18 -07:00
void TextureCacheCommon : : DecimateVideos ( ) {
2021-02-20 20:59:04 -08:00
for ( auto iter = videos_ . begin ( ) ; iter ! = videos_ . end ( ) ; ) {
if ( iter - > flips + VIDEO_DECIMATE_AGE < gpuStats . numFlips ) {
iter = videos_ . erase ( iter + + ) ;
} else {
+ + iter ;
2016-05-01 08:39:18 -07:00
}
}
}
2021-02-20 20:59:04 -08:00
bool TextureCacheCommon : : IsVideo ( u32 texaddr ) {
texaddr & = 0x3FFFFFFF ;
for ( auto info : videos_ ) {
if ( texaddr < info . addr ) {
continue ;
}
if ( texaddr < info . addr + info . size ) {
return true ;
}
}
return false ;
}
2018-03-25 10:49:28 +02:00
void TextureCacheCommon : : HandleTextureChange ( TexCacheEntry * const entry , const char * reason , bool initialMatch , bool doDelete ) {
2017-02-20 00:13:09 +01:00
cacheSizeEstimate_ - = EstimateTexMemoryUsage ( entry ) ;
entry - > numInvalidated + + ;
gpuStats . numTextureInvalidations + + ;
DEBUG_LOG ( G3D , " Texture different or overwritten, reloading at %08x: %s " , entry - > addr , reason ) ;
if ( doDelete ) {
2018-03-25 10:49:28 +02:00
InvalidateLastTexture ( ) ;
ReleaseTexture ( entry , true ) ;
entry - > status & = ~ TexCacheEntry : : STATUS_IS_SCALED ;
2017-02-20 00:13:09 +01:00
}
2020-09-22 21:13:30 +02:00
// Mark as hashing, if marked as reliable.
2017-02-20 00:13:09 +01:00
if ( entry - > GetHashStatus ( ) = = TexCacheEntry : : STATUS_RELIABLE ) {
entry - > SetHashStatus ( TexCacheEntry : : STATUS_HASHING ) ;
}
// Also, mark any textures with the same address but different clut. They need rechecking.
if ( entry - > cluthash ! = 0 ) {
const u64 cachekeyMin = ( u64 ) ( entry - > addr & 0x3FFFFFFF ) < < 32 ;
const u64 cachekeyMax = cachekeyMin + ( 1ULL < < 32 ) ;
2017-02-20 00:19:58 +01:00
for ( auto it = cache_ . lower_bound ( cachekeyMin ) , end = cache_ . upper_bound ( cachekeyMax ) ; it ! = end ; + + it ) {
2017-02-20 00:45:07 +01:00
if ( it - > second - > cluthash ! = entry - > cluthash ) {
it - > second - > status | = TexCacheEntry : : STATUS_CLUT_RECHECK ;
2017-02-20 00:13:09 +01:00
}
}
}
2017-03-18 11:44:06 -07:00
if ( entry - > numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT ) {
if ( entry - > status & TexCacheEntry : : STATUS_FREE_CHANGE ) {
entry - > status & = ~ TexCacheEntry : : STATUS_FREE_CHANGE ;
} else {
entry - > status | = TexCacheEntry : : STATUS_CHANGE_FREQUENT ;
}
}
entry - > numFrames = 0 ;
2017-02-20 00:13:09 +01:00
}
2020-09-20 21:46:40 +02:00
void TextureCacheCommon : : NotifyFramebuffer ( VirtualFramebuffer * framebuffer , FramebufferNotification msg ) {
2018-11-12 07:41:01 +01:00
const u32 mirrorMask = 0x00600000 ;
2020-09-20 21:46:40 +02:00
const u32 fb_addr = framebuffer - > fb_address ;
const u32 z_addr = framebuffer - > z_address & ~ mirrorMask ; // Probably unnecessary.
const u32 fb_bpp = framebuffer - > format = = GE_FORMAT_8888 ? 4 : 2 ;
const u32 z_bpp = 2 ; // No other format exists.
const u32 fb_stride = framebuffer - > fb_stride ;
const u32 z_stride = framebuffer - > z_stride ;
2020-09-15 22:53:37 +02:00
// NOTE: Some games like Burnout massively misdetects the height of some framebuffers, leading to a lot of unnecessary invalidations.
// Let's only actually get rid of textures that cover the very start of the framebuffer.
2020-09-20 21:46:40 +02:00
const u32 fb_endAddr = fb_addr + fb_stride * std : : min ( ( int ) framebuffer - > height , 16 ) * fb_bpp ;
const u32 z_endAddr = z_addr + z_stride * std : : min ( ( int ) framebuffer - > height , 16 ) * z_bpp ;
2015-11-28 12:41:37 -08:00
switch ( msg ) {
case NOTIFY_FB_CREATED :
case NOTIFY_FB_UPDATED :
2020-08-29 22:25:50 +02:00
{
2020-08-26 22:16:48 +02:00
// Try to match the new framebuffer to existing textures.
// Backwards from the "usual" texturing case so can't share a utility function.
2020-08-29 22:25:50 +02:00
std : : vector < AttachCandidate > candidates ;
2020-09-20 21:46:40 +02:00
u64 cacheKey = ( u64 ) fb_addr < < 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.
u64 cacheKeyEnd = ( u64 ) fb_endAddr < < 32 ;
// Color - no need to look in the mirrors.
for ( auto it = cache_ . lower_bound ( cacheKey ) , end = cache_ . upper_bound ( cacheKeyEnd ) ; it ! = end ; + + it ) {
it - > second - > status | = TexCacheEntry : : STATUS_FRAMEBUFFER_OVERLAP ;
gpuStats . numTextureInvalidationsByFramebuffer + + ;
}
if ( z_stride ! = 0 ) {
2020-09-15 22:53:37 +02:00
// Depth. Just look at the range, but in each mirror (0x04200000 and 0x04600000).
// Games don't use 0x04400000 as far as I know - it has no swizzle effect so kinda useless.
2020-09-20 21:46:40 +02:00
cacheKey = ( u64 ) z_addr < < 32 ;
cacheKeyEnd = ( u64 ) z_endAddr < < 32 ;
2020-09-15 22:53:37 +02:00
for ( auto it = cache_ . lower_bound ( cacheKey | 0x200000 ) , end = cache_ . upper_bound ( cacheKeyEnd | 0x200000 ) ; it ! = end ; + + it ) {
it - > second - > status | = TexCacheEntry : : STATUS_FRAMEBUFFER_OVERLAP ;
gpuStats . numTextureInvalidationsByFramebuffer + + ;
}
for ( auto it = cache_ . lower_bound ( cacheKey | 0x600000 ) , end = cache_ . upper_bound ( cacheKeyEnd | 0x600000 ) ; it ! = end ; + + it ) {
it - > second - > status | = TexCacheEntry : : STATUS_FRAMEBUFFER_OVERLAP ;
gpuStats . numTextureInvalidationsByFramebuffer + + ;
2020-09-01 23:04:16 +02:00
}
2015-11-28 12:41:37 -08:00
}
break ;
}
2020-12-14 19:54:39 +01:00
default :
break ;
2020-08-22 10:36:02 +02:00
}
}
2017-02-08 15:37:40 +01:00
2020-09-12 14:25:50 +02:00
FramebufferMatchInfo TextureCacheCommon : : MatchFramebuffer (
const TextureDefinition & entry ,
2020-09-13 09:03:31 +02:00
VirtualFramebuffer * framebuffer , u32 texaddrOffset , FramebufferNotificationChannel channel ) const {
2020-08-22 10:36:02 +02:00
static const u32 MAX_SUBAREA_Y_OFFSET_SAFE = 32 ;
2017-02-08 15:37:40 +01:00
2020-09-13 09:03:31 +02:00
uint32_t fb_address = channel = = NOTIFY_FB_DEPTH ? framebuffer - > z_address : framebuffer - > fb_address ;
2020-08-23 22:11:18 +02:00
2020-09-01 00:05:37 +02:00
u32 addr = fb_address & 0x3FFFFFFF ;
2020-09-12 14:25:50 +02:00
u32 texaddr = entry . addr + texaddrOffset ;
2020-08-29 22:15:33 +02:00
bool texInVRAM = Memory : : IsVRAMAddress ( texaddr ) ;
2020-09-01 00:05:37 +02:00
bool fbInVRAM = Memory : : IsVRAMAddress ( fb_address ) ;
2020-08-29 22:15:33 +02:00
if ( texInVRAM ! = fbInVRAM ) {
// Shortcut. Cannot possibly be a match.
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
}
if ( texInVRAM ) {
2020-09-13 09:03:31 +02:00
const u32 mirrorMask = 0x00600000 ;
2020-08-23 22:11:18 +02:00
// This bit controls swizzle. The swizzles at 0x00200000 and 0x00600000 are designed
// to perfectly match reading depth as color (which one to use I think might be related
// to the bpp of the color format used when rendering to it).
// It's fairly unlikely that games would screw this up since the result will be garbage so
// we use it to filter out unlikely matches.
2020-09-12 14:25:50 +02:00
switch ( entry . addr & mirrorMask ) {
2020-08-23 22:11:18 +02:00
case 0x00000000 :
case 0x00400000 :
// Don't match the depth channel with these addresses when texturing.
if ( channel = = FramebufferNotificationChannel : : NOTIFY_FB_DEPTH ) {
2020-09-13 09:03:31 +02:00
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2020-08-23 22:11:18 +02:00
}
break ;
case 0x00200000 :
case 0x00600000 :
// Don't match the color channel with these addresses when texturing.
if ( channel = = FramebufferNotificationChannel : : NOTIFY_FB_COLOR ) {
2020-09-13 09:03:31 +02:00
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2020-08-23 22:11:18 +02:00
}
break ;
}
2018-11-12 07:41:01 +01:00
addr & = ~ mirrorMask ;
texaddr & = ~ mirrorMask ;
}
2020-09-01 00:05:37 +02:00
2017-02-08 15:37:40 +01:00
const bool noOffset = texaddr = = addr ;
2020-09-12 14:25:50 +02:00
const bool exactMatch = noOffset & & entry . format < 4 & & channel = = NOTIFY_FB_COLOR ;
const u32 w = 1 < < ( ( entry . dim > > 0 ) & 0xf ) ;
const u32 h = 1 < < ( ( entry . dim > > 8 ) & 0xf ) ;
2017-02-08 15:37:40 +01:00
// 512 on a 272 framebuffer is sane, so let's be lenient.
const u32 minSubareaHeight = h / 4 ;
2020-08-10 20:17:01 +02:00
// If they match "exactly", it's non-CLUT and from the top left.
2017-02-08 15:37:40 +01:00
if ( exactMatch ) {
2020-09-12 14:25:50 +02:00
if ( framebuffer - > fb_stride ! = entry . bufw ) {
WARN_LOG_ONCE ( diffStrides1 , G3D , " Texturing from framebuffer with different strides %d != %d " , entry . bufw , framebuffer - > fb_stride ) ;
2017-02-08 15:37:40 +01:00
}
2020-08-04 14:45:14 +02:00
// NOTE: This check is okay because the first texture formats are the same as the buffer formats.
2020-11-06 11:54:57 +01:00
if ( IsTextureFormatBufferCompatible ( entry . format ) ) {
if ( TextureFormatMatchesBufferFormat ( entry . format , framebuffer - > format ) ) {
return FramebufferMatchInfo { FramebufferMatch : : VALID } ;
} else if ( IsTextureFormat16Bit ( entry . format ) & & IsBufferFormat16Bit ( framebuffer - > format ) ) {
WARN_LOG_ONCE ( diffFormat1 , G3D , " Texturing from framebuffer with reinterpretable format: %s != %s " , GeTextureFormatToString ( entry . format ) , GeBufferFormatToString ( framebuffer - > format ) ) ;
return FramebufferMatchInfo { FramebufferMatch : : VALID , 0 , 0 , true , TextureFormatToBufferFormat ( entry . format ) } ;
} else {
WARN_LOG_ONCE ( diffFormat2 , G3D , " Texturing from framebuffer with incompatible formats %s != %s " , GeTextureFormatToString ( entry . format ) , GeBufferFormatToString ( framebuffer - > format ) ) ;
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
}
2020-11-03 11:32:57 +01:00
} else {
2020-11-06 11:54:57 +01:00
// Format incompatible, ignoring without comment. (maybe some really gnarly hacks will end up here...)
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2017-02-08 15:37:40 +01:00
}
} else {
// Apply to buffered mode only.
2020-08-22 10:36:02 +02:00
if ( ! framebufferManager_ - > UseBufferedRendering ( ) ) {
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
}
2017-02-08 15:37:40 +01:00
2017-11-22 12:24:05 +01:00
// Check works for D16 too (???)
2020-08-04 14:45:14 +02:00
const bool matchingClutFormat =
2020-09-12 14:25:50 +02:00
( channel ! = NOTIFY_FB_COLOR & & entry . format = = GE_TFMT_CLUT16 ) | |
( channel = = NOTIFY_FB_COLOR & & framebuffer - > format = = GE_FORMAT_8888 & & entry . format = = GE_TFMT_CLUT32 ) | |
( channel = = NOTIFY_FB_COLOR & & framebuffer - > format ! = GE_FORMAT_8888 & & entry . format = = GE_TFMT_CLUT16 ) ;
2017-02-08 15:37:40 +01:00
2020-08-22 10:36:02 +02:00
// To avoid ruining git blame, kept the same name as the old struct.
FramebufferMatchInfo fbInfo { FramebufferMatch : : VALID } ;
2020-09-01 00:05:37 +02:00
const u32 bitOffset = ( texaddr - addr ) * 8 ;
if ( bitOffset ! = 0 ) {
2020-09-12 14:25:50 +02:00
const u32 pixelOffset = bitOffset / std : : max ( 1U , ( u32 ) textureBitsPerPixel [ entry . format ] ) ;
2020-09-01 00:05:37 +02:00
2020-09-12 14:25:50 +02:00
fbInfo . yOffset = entry . bufw = = 0 ? 0 : pixelOffset / entry . bufw ;
fbInfo . xOffset = entry . bufw = = 0 ? 0 : pixelOffset % entry . bufw ;
2020-09-01 00:05:37 +02:00
}
if ( fbInfo . yOffset + minSubareaHeight > = framebuffer - > height ) {
// Can't be inside the framebuffer.
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
}
2017-02-08 15:37:40 +01:00
2020-09-12 14:25:50 +02:00
if ( framebuffer - > fb_stride ! = entry . bufw ) {
2017-02-08 15:37:40 +01:00
if ( noOffset ) {
2020-09-12 14:25:50 +02:00
WARN_LOG_ONCE ( diffStrides2 , G3D , " Texturing from framebuffer (matching_clut=%s) different strides %d != %d " , matchingClutFormat ? " yes " : " no " , entry . bufw , framebuffer - > fb_stride ) ;
2020-08-22 10:36:02 +02:00
// Continue on with other checks.
// Not actually sure why we even try here. There's no way it'll go well if the strides are different.
2017-02-08 15:37:40 +01:00
} else {
// Assume any render-to-tex with different bufw + offset is a render from ram.
2020-08-22 10:36:02 +02:00
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2017-02-08 15:37:40 +01:00
}
}
2017-11-05 13:01:50 -08:00
// Check if it's in bufferWidth (which might be higher than width and may indicate the framebuffer includes the data.)
2017-11-05 17:59:34 -08:00
if ( fbInfo . xOffset > = framebuffer - > bufferWidth & & fbInfo . xOffset + w < = ( u32 ) framebuffer - > fb_stride ) {
2017-11-05 13:01:50 -08:00
// This happens in Brave Story, see #10045 - the texture is in the space between strides, with matching stride.
2020-08-22 10:36:02 +02:00
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2017-11-05 13:01:50 -08:00
}
2017-02-08 15:37:40 +01:00
// Trying to play it safe. Below 0x04110000 is almost always framebuffers.
// TODO: Maybe we can reduce this check and find a better way above 0x04110000?
if ( fbInfo . yOffset > MAX_SUBAREA_Y_OFFSET_SAFE & & addr > 0x04110000 ) {
2020-09-01 00:05:37 +02:00
WARN_LOG_REPORT_ONCE ( subareaIgnored , G3D , " Ignoring possible texturing from framebuffer at %08x +%dx%d / %dx%d " , fb_address , fbInfo . xOffset , fbInfo . yOffset , framebuffer - > width , framebuffer - > height ) ;
2020-08-22 10:36:02 +02:00
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2017-02-08 15:37:40 +01:00
}
// Check for CLUT. The framebuffer is always RGB, but it can be interpreted as a CLUT texture.
// 3rd Birthday (and a bunch of other games) render to a 16 bit clut texture.
2020-08-04 14:45:14 +02:00
if ( matchingClutFormat ) {
2017-02-08 15:37:40 +01:00
if ( ! noOffset ) {
2020-09-01 00:05:37 +02:00
WARN_LOG_ONCE ( subareaClut , G3D , " Texturing from framebuffer using CLUT with offset at %08x +%dx%d " , fb_address , fbInfo . xOffset , fbInfo . yOffset ) ;
2017-02-08 15:37:40 +01:00
}
2020-09-13 09:03:31 +02:00
fbInfo . match = FramebufferMatch : : VALID ; // We check the format again later, no need to return a special value here.
2020-08-22 10:36:02 +02:00
return fbInfo ;
2020-09-12 14:25:50 +02:00
} else if ( IsClutFormat ( ( GETextureFormat ) ( entry . format ) ) | | IsDXTFormat ( ( GETextureFormat ) ( entry . format ) ) ) {
WARN_LOG_ONCE ( fourEightBit , G3D , " %s format not supported when texturing from framebuffer of format %s " , GeTextureFormatToString ( entry . format ) , GeBufferFormatToString ( framebuffer - > format ) ) ;
2020-08-22 10:36:02 +02:00
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2017-02-08 15:37:40 +01:00
}
// This is either normal or we failed to generate a shader to depalettize
2021-02-14 10:14:23 -08:00
if ( ( int ) framebuffer - > format = = ( int ) entry . format | | matchingClutFormat ) {
if ( ( int ) framebuffer - > format ! = ( int ) entry . format ) {
2020-08-10 20:17:01 +02:00
WARN_LOG_ONCE ( diffFormat2 , G3D , " Texturing from framebuffer with different formats %s != %s at %08x " ,
2020-09-12 14:25:50 +02:00
GeTextureFormatToString ( entry . format ) , GeBufferFormatToString ( framebuffer - > format ) , fb_address ) ;
2020-09-13 09:03:31 +02:00
return fbInfo ;
2017-02-08 15:37:40 +01:00
} else {
2020-09-13 09:03:31 +02:00
WARN_LOG_ONCE ( subarea , G3D , " Texturing from framebuffer at %08x +%dx%d " , fb_address , fbInfo . xOffset , fbInfo . yOffset ) ;
2020-08-22 10:36:02 +02:00
return fbInfo ;
2017-02-08 15:37:40 +01:00
}
} else {
2020-08-22 10:36:02 +02:00
WARN_LOG_ONCE ( diffFormat2 , G3D , " Texturing from framebuffer with incompatible format %s != %s at %08x " ,
2020-09-12 14:25:50 +02:00
GeTextureFormatToString ( entry . format ) , GeBufferFormatToString ( framebuffer - > format ) , fb_address ) ;
2020-08-22 10:36:02 +02:00
return FramebufferMatchInfo { FramebufferMatch : : NO_MATCH } ;
2017-02-08 15:37:40 +01:00
}
}
}
2020-09-12 14:25:50 +02:00
void TextureCacheCommon : : SetTextureFramebuffer ( const AttachCandidate & candidate ) {
VirtualFramebuffer * framebuffer = candidate . fb ;
FramebufferMatchInfo fbInfo = candidate . match ;
2020-11-06 11:54:57 +01:00
if ( candidate . match . reinterpret ) {
GEBufferFormat oldFormat = candidate . fb - > format ;
candidate . fb - > format = candidate . match . reinterpretTo ;
2020-12-13 00:20:47 +01:00
framebufferManager_ - > ReinterpretFramebuffer ( candidate . fb , oldFormat , candidate . match . reinterpretTo ) ;
2020-11-06 11:54:57 +01:00
}
2020-07-19 17:47:02 +02:00
_dbg_assert_msg_ ( framebuffer ! = nullptr , " Framebuffer must not be null. " ) ;
2017-02-08 15:43:53 +01:00
framebuffer - > usageFlags | = FB_USAGE_TEXTURE ;
2020-04-04 10:51:47 -07:00
if ( framebufferManager_ - > UseBufferedRendering ( ) ) {
2017-02-08 15:43:53 +01:00
// Keep the framebuffer alive.
framebuffer - > last_frame_used = gpuStats . numFlips ;
// We need to force it, since we may have set it on a texture before attaching.
gstate_c . curTextureWidth = framebuffer - > bufferWidth ;
gstate_c . curTextureHeight = framebuffer - > bufferHeight ;
2017-03-19 10:25:30 -07:00
if ( gstate_c . bgraTexture ) {
gstate_c . Dirty ( DIRTY_FRAGMENTSHADER_STATE ) ;
} else if ( ( gstate_c . curTextureXOffset = = 0 ) ! = ( fbInfo . xOffset = = 0 ) | | ( gstate_c . curTextureYOffset = = 0 ) ! = ( fbInfo . yOffset = = 0 ) ) {
gstate_c . Dirty ( DIRTY_FRAGMENTSHADER_STATE ) ;
}
2017-02-08 15:43:53 +01:00
gstate_c . bgraTexture = false ;
gstate_c . curTextureXOffset = fbInfo . xOffset ;
gstate_c . curTextureYOffset = fbInfo . yOffset ;
2017-12-07 21:38:02 +01:00
u32 texW = ( u32 ) gstate . getTextureWidth ( 0 ) ;
u32 texH = ( u32 ) gstate . getTextureHeight ( 0 ) ;
gstate_c . SetNeedShaderTexclamp ( gstate_c . curTextureWidth ! = texW | | gstate_c . curTextureHeight ! = texH ) ;
2017-02-08 15:43:53 +01:00
if ( gstate_c . curTextureXOffset ! = 0 | | gstate_c . curTextureYOffset ! = 0 ) {
2017-04-03 18:06:49 +02:00
gstate_c . SetNeedShaderTexclamp ( true ) ;
2017-02-08 15:43:53 +01:00
}
2020-09-12 14:25:50 +02:00
nextFramebufferTexture_ = framebuffer ;
2020-09-15 09:34:40 +02:00
nextTexture_ = nullptr ;
2017-02-08 15:43:53 +01:00
} else {
if ( framebuffer - > fbo ) {
2017-11-05 12:45:02 -08:00
framebuffer - > fbo - > Release ( ) ;
2017-02-08 15:43:53 +01:00
framebuffer - > fbo = nullptr ;
}
Unbind ( ) ;
2017-04-03 18:06:49 +02:00
gstate_c . SetNeedShaderTexclamp ( false ) ;
2020-09-15 09:34:40 +02:00
nextFramebufferTexture_ = nullptr ;
nextTexture_ = nullptr ;
2017-02-08 15:43:53 +01:00
}
nextNeedsRehash_ = false ;
nextNeedsChange_ = false ;
nextNeedsRebuild_ = false ;
}
2020-09-12 14:25:50 +02:00
// Only looks for framebuffers.
2020-05-07 23:00:54 -07:00
bool TextureCacheCommon : : SetOffsetTexture ( u32 yOffset ) {
2020-04-04 10:51:47 -07:00
if ( ! framebufferManager_ - > UseBufferedRendering ( ) ) {
2017-02-08 15:48:36 +01:00
return false ;
}
2020-05-07 23:22:51 -07:00
2017-02-08 15:48:36 +01:00
u32 texaddr = gstate . getTextureAddress ( 0 ) ;
2020-05-07 23:22:51 -07:00
GETextureFormat fmt = gstate . getTextureFormat ( ) ;
2020-05-11 19:25:33 -07:00
const u32 bpp = fmt = = GE_TFMT_8888 ? 4 : 2 ;
2020-05-07 23:22:51 -07:00
const u32 texaddrOffset = yOffset * gstate . getTextureWidth ( 0 ) * bpp ;
if ( ! Memory : : IsValidAddress ( texaddr ) | | ! Memory : : IsValidAddress ( texaddr + texaddrOffset ) ) {
2017-02-08 15:48:36 +01:00
return false ;
}
2020-09-12 14:25:50 +02:00
TextureDefinition def ;
def . addr = texaddr ;
def . format = fmt ;
def . bufw = GetTextureBufw ( 0 , texaddr , fmt ) ;
def . dim = gstate . getTextureDimension ( 0 ) ;
2017-02-08 15:48:36 +01:00
2020-09-12 14:25:50 +02:00
std : : vector < AttachCandidate > candidates = GetFramebufferCandidates ( def , texaddrOffset ) ;
if ( candidates . size ( ) > 0 ) {
int index = GetBestCandidateIndex ( candidates ) ;
if ( index ! = - 1 ) {
SetTextureFramebuffer ( candidates [ index ] ) ;
return true ;
}
2017-02-08 15:48:36 +01:00
}
return false ;
}
2016-01-03 23:06:15 -08:00
void TextureCacheCommon : : NotifyConfigChanged ( ) {
int scaleFactor ;
// 0 means automatic texture scaling, up to 5x, based on resolution.
if ( g_Config . iTexScalingLevel = = 0 ) {
scaleFactor = g_Config . iInternalResolution ;
// Automatic resolution too? Okay.
if ( scaleFactor = = 0 ) {
if ( ! g_Config . IsPortrait ( ) ) {
scaleFactor = ( PSP_CoreParameter ( ) . pixelWidth + 479 ) / 480 ;
} else {
scaleFactor = ( PSP_CoreParameter ( ) . pixelHeight + 479 ) / 480 ;
}
}
2017-12-19 17:59:00 +01:00
scaleFactor = std : : min ( 5 , scaleFactor ) ;
2016-01-03 23:06:15 -08:00
} else {
scaleFactor = g_Config . iTexScalingLevel ;
}
2020-12-14 20:06:06 +01:00
if ( ! gstate_c . Supports ( GPU_SUPPORTS_TEXTURE_NPOT ) ) {
2016-01-03 23:06:15 -08:00
// Reduce the scale factor to a power of two (e.g. 2 or 4) if textures must be a power of two.
while ( ( scaleFactor & ( scaleFactor - 1 ) ) ! = 0 ) {
- - scaleFactor ;
}
}
// Just in case, small display with auto resolution or something.
if ( scaleFactor < = 0 ) {
scaleFactor = 1 ;
}
standardScaleFactor_ = scaleFactor ;
2016-04-30 13:44:31 -07:00
2017-02-20 00:19:58 +01:00
replacer_ . NotifyConfigChanged ( ) ;
2016-01-03 23:06:15 -08:00
}
2016-05-01 08:39:18 -07:00
void TextureCacheCommon : : NotifyVideoUpload ( u32 addr , int size , int width , GEBufferFormat fmt ) {
2016-05-01 08:53:48 -07:00
addr & = 0x3FFFFFFF ;
2021-02-20 20:59:04 -08:00
videos_ . push_back ( { addr , ( u32 ) size , gpuStats . numFlips } ) ;
2016-05-01 08:39:18 -07:00
}
2015-11-28 12:41:37 -08:00
void TextureCacheCommon : : LoadClut ( u32 clutAddr , u32 loadBytes ) {
2015-11-28 12:46:25 -08:00
clutTotalBytes_ = loadBytes ;
2015-11-28 12:41:37 -08:00
clutRenderAddress_ = 0xFFFFFFFF ;
if ( Memory : : IsValidAddress ( clutAddr ) ) {
2015-11-28 12:46:25 -08:00
if ( Memory : : IsVRAMAddress ( clutAddr ) ) {
// Clear the uncached bit, etc. to match framebuffers.
const u32 clutFramebufAddr = clutAddr & 0x3FFFFFFF ;
2016-01-05 00:02:58 -08:00
const u32 clutFramebufEnd = clutFramebufAddr + loadBytes ;
2016-01-05 00:39:33 -08:00
static const u32 MAX_CLUT_OFFSET = 4096 ;
2015-11-28 12:46:25 -08:00
2016-01-05 00:39:33 -08:00
clutRenderOffset_ = MAX_CLUT_OFFSET ;
2020-09-22 21:24:16 +02:00
const std : : vector < VirtualFramebuffer * > & framebuffers = framebufferManager_ - > Framebuffers ( ) ;
for ( VirtualFramebuffer * framebuffer : framebuffers ) {
2018-11-11 10:54:28 +01:00
const u32 fb_address = framebuffer - > fb_address & 0x3FFFFFFF ;
2016-01-05 00:02:58 -08:00
const u32 bpp = framebuffer - > drawnFormat = = GE_FORMAT_8888 ? 4 : 2 ;
2016-01-05 00:39:33 -08:00
u32 offset = clutFramebufAddr - fb_address ;
2016-01-23 09:02:30 -08:00
// Is this inside the framebuffer at all?
bool matchRange = fb_address + framebuffer - > fb_stride * bpp > clutFramebufAddr & & fb_address < clutFramebufEnd ;
// And is it inside the rendered area? Sometimes games pack data outside.
bool matchRegion = ( ( offset / bpp ) % framebuffer - > fb_stride ) < framebuffer - > width ;
if ( matchRange & & matchRegion & & offset < clutRenderOffset_ ) {
2015-11-28 12:46:25 -08:00
framebuffer - > last_frame_clut = gpuStats . numFlips ;
framebuffer - > usageFlags | = FB_USAGE_CLUT ;
clutRenderAddress_ = framebuffer - > fb_address ;
2016-01-05 00:39:33 -08:00
clutRenderOffset_ = offset ;
if ( offset = = 0 ) {
break ;
}
2015-11-28 12:46:25 -08:00
}
}
2021-02-03 20:46:41 -08:00
NotifyMemInfo ( MemBlockFlags : : ALLOC , clutAddr , loadBytes , " CLUT " ) ;
2015-11-28 12:46:25 -08:00
}
2015-11-28 12:41:37 -08:00
// It's possible for a game to (successfully) access outside valid memory.
u32 bytes = Memory : : ValidSize ( clutAddr , loadBytes ) ;
2015-11-28 12:46:25 -08:00
if ( clutRenderAddress_ ! = 0xFFFFFFFF & & ! g_Config . bDisableSlowFramebufEffects ) {
2017-02-08 15:58:46 +01:00
framebufferManager_ - > DownloadFramebufferForClut ( clutRenderAddress_ , clutRenderOffset_ + bytes ) ;
2016-01-05 00:02:58 -08:00
Memory : : MemcpyUnchecked ( clutBufRaw_ , clutAddr , bytes ) ;
2016-01-04 21:29:03 -08:00
if ( bytes < loadBytes ) {
memset ( ( u8 * ) clutBufRaw_ + bytes , 0x00 , loadBytes - bytes ) ;
2015-11-28 12:41:37 -08:00
}
} else {
2016-01-04 21:29:03 -08:00
# ifdef _M_SSE
if ( bytes = = loadBytes ) {
const __m128i * source = ( const __m128i * ) Memory : : GetPointerUnchecked ( clutAddr ) ;
__m128i * dest = ( __m128i * ) clutBufRaw_ ;
2016-07-09 10:13:43 -07:00
int numBlocks = bytes / 32 ;
2016-01-04 21:29:03 -08:00
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 {
Memory : : MemcpyUnchecked ( clutBufRaw_ , clutAddr , bytes ) ;
if ( bytes < loadBytes ) {
memset ( ( u8 * ) clutBufRaw_ + bytes , 0x00 , loadBytes - bytes ) ;
}
}
2017-08-15 16:02:31 +02:00
# elif PPSSPP_ARCH(ARM_NEON)
if ( bytes = = loadBytes ) {
const uint32_t * source = ( const uint32_t * ) Memory : : GetPointerUnchecked ( clutAddr ) ;
uint32_t * dest = ( uint32_t * ) clutBufRaw_ ;
int numBlocks = bytes / 32 ;
for ( int i = 0 ; i < numBlocks ; i + + , source + = 8 , dest + = 8 ) {
uint32x4_t data1 = vld1q_u32 ( source ) ;
uint32x4_t data2 = vld1q_u32 ( source + 4 ) ;
vst1q_u32 ( dest , data1 ) ;
vst1q_u32 ( dest + 4 , data2 ) ;
}
} else {
Memory : : MemcpyUnchecked ( clutBufRaw_ , clutAddr , bytes ) ;
if ( bytes < loadBytes ) {
memset ( ( u8 * ) clutBufRaw_ + bytes , 0x00 , loadBytes - bytes ) ;
}
}
2016-01-04 21:29:03 -08:00
# else
2015-11-28 12:41:37 -08:00
Memory : : MemcpyUnchecked ( clutBufRaw_ , clutAddr , bytes ) ;
if ( bytes < loadBytes ) {
memset ( ( u8 * ) clutBufRaw_ + bytes , 0x00 , loadBytes - bytes ) ;
}
# endif
2016-01-04 21:29:03 -08:00
}
2015-11-28 12:41:37 -08:00
} else {
memset ( clutBufRaw_ , 0x00 , loadBytes ) ;
}
// Reload the clut next time.
clutLastFormat_ = 0xFFFFFFFF ;
clutMaxBytes_ = std : : max ( clutMaxBytes_ , loadBytes ) ;
}
2015-11-28 17:51:15 -08:00
2016-03-26 21:50:49 -07:00
void TextureCacheCommon : : UnswizzleFromMem ( u32 * dest , u32 destPitch , const u8 * texptr , u32 bufw , u32 height , u32 bytesPerPixel ) {
2016-03-26 21:29:48 -07:00
// Note: bufw is always aligned to 16 bytes, so rowWidth is always >= 16.
2015-11-28 17:51:15 -08:00
const u32 rowWidth = ( bytesPerPixel > 0 ) ? ( bufw * bytesPerPixel ) : ( bufw / 2 ) ;
2016-03-26 21:29:48 -07:00
// A visual mapping of unswizzling, where each letter is 16-byte and 8 letters is a block:
//
// ABCDEFGH IJKLMNOP
// ->
// AI
// BJ
// CK
// ...
//
// bxc is the number of blocks in the x direction, and byc the number in the y direction.
2015-11-28 17:51:15 -08:00
const int bxc = rowWidth / 16 ;
2016-03-26 21:29:48 -07:00
// The height is not always aligned to 8, but rounds up.
2015-11-28 17:51:15 -08:00
int byc = ( height + 7 ) / 8 ;
2016-03-26 21:29:48 -07:00
2016-03-26 21:50:49 -07:00
DoUnswizzleTex16 ( texptr , dest , bxc , byc , destPitch ) ;
2015-11-28 17:51:15 -08:00
}
2016-01-10 09:25:19 -08:00
bool TextureCacheCommon : : GetCurrentClutBuffer ( GPUDebugBuffer & buffer ) {
const u32 bpp = gstate . getClutPaletteFormat ( ) = = GE_CMODE_32BIT_ABGR8888 ? 4 : 2 ;
const u32 pixels = 1024 / bpp ;
buffer . Allocate ( pixels , 1 , ( GEBufferFormat ) gstate . getClutPaletteFormat ( ) ) ;
memcpy ( buffer . GetData ( ) , clutBufRaw_ , 1024 ) ;
return true ;
}
2016-03-26 11:49:16 -07:00
2017-02-20 21:35:34 +01:00
// Host memory usage, not PSP memory usage.
2016-03-26 11:49:16 -07:00
u32 TextureCacheCommon : : EstimateTexMemoryUsage ( const TexCacheEntry * entry ) {
const u16 dim = entry - > dim ;
2017-02-20 21:35:34 +01:00
// TODO: This does not take into account the HD remaster's larger textures.
2016-03-26 11:49:16 -07:00
const u8 dimW = ( ( dim > > 0 ) & 0xf ) ;
const u8 dimH = ( ( dim > > 8 ) & 0xf ) ;
u32 pixelSize = 2 ;
switch ( entry - > format ) {
case GE_TFMT_CLUT4 :
case GE_TFMT_CLUT8 :
case GE_TFMT_CLUT16 :
case GE_TFMT_CLUT32 :
// We assume cluts always point to 8888 for simplicity.
pixelSize = 4 ;
break ;
case GE_TFMT_4444 :
case GE_TFMT_5551 :
case GE_TFMT_5650 :
break ;
case GE_TFMT_8888 :
case GE_TFMT_DXT1 :
case GE_TFMT_DXT3 :
case GE_TFMT_DXT5 :
default :
pixelSize = 4 ;
break ;
}
// This in other words multiplies by w and h.
return pixelSize < < ( dimW + dimH ) ;
}
2016-06-19 07:14:31 -07:00
2016-06-19 07:55:38 -07:00
static void ReverseColors ( void * dstBuf , const void * srcBuf , GETextureFormat fmt , int numPixels , bool useBGRA ) {
switch ( fmt ) {
case GE_TFMT_4444 :
ConvertRGBA4444ToABGR4444 ( ( u16 * ) dstBuf , ( const u16 * ) srcBuf , numPixels ) ;
break ;
// Final Fantasy 2 uses this heavily in animated textures.
case GE_TFMT_5551 :
ConvertRGBA5551ToABGR1555 ( ( u16 * ) dstBuf , ( const u16 * ) srcBuf , numPixels ) ;
break ;
case GE_TFMT_5650 :
ConvertRGB565ToBGR565 ( ( u16 * ) dstBuf , ( const u16 * ) srcBuf , numPixels ) ;
break ;
default :
if ( useBGRA ) {
ConvertRGBA8888ToBGRA8888 ( ( u32 * ) dstBuf , ( const u32 * ) srcBuf , numPixels ) ;
} else {
// No need to convert RGBA8888, right order already
if ( dstBuf ! = srcBuf )
memcpy ( dstBuf , srcBuf , numPixels * sizeof ( u32 ) ) ;
}
break ;
}
}
2017-03-18 20:09:38 -07:00
static inline void ConvertFormatToRGBA8888 ( GETextureFormat format , u32 * dst , const u16 * src , u32 numPixels ) {
switch ( format ) {
case GE_TFMT_4444 :
ConvertRGBA4444ToRGBA8888 ( dst , src , numPixels ) ;
break ;
case GE_TFMT_5551 :
ConvertRGBA5551ToRGBA8888 ( dst , src , numPixels ) ;
break ;
case GE_TFMT_5650 :
2020-05-13 18:17:58 -07:00
ConvertRGB565ToRGBA8888 ( dst , src , numPixels ) ;
2017-03-18 20:09:38 -07:00
break ;
default :
2020-07-19 17:47:02 +02:00
_dbg_assert_msg_ ( false , " Incorrect texture format. " ) ;
2017-03-18 20:09:38 -07:00
break ;
}
}
static inline void ConvertFormatToRGBA8888 ( GEPaletteFormat format , u32 * dst , const u16 * src , u32 numPixels ) {
// The supported values are 1:1 identical.
ConvertFormatToRGBA8888 ( GETextureFormat ( format ) , dst , src , numPixels ) ;
}
2021-06-06 11:52:26 -07:00
template < typename DXTBlock , int n >
static void DecodeDXTBlock ( uint8_t * out , int outPitch , uint32_t texaddr , const uint8_t * texptr , int w , int h , int bufw , bool reverseColors , bool useBGRA ) {
int minw = std : : min ( bufw , w ) ;
uint32_t * dst = ( uint32_t * ) out ;
int outPitch32 = outPitch / sizeof ( uint32_t ) ;
const DXTBlock * src = ( const DXTBlock * ) texptr ;
if ( ! Memory : : IsValidRange ( texaddr , ( h / 4 ) * ( bufw / 4 ) * sizeof ( DXTBlock ) ) ) {
ERROR_LOG_REPORT ( G3D , " DXT%d texture extends beyond valid RAM: %08x + %d x %d " , n , texaddr , bufw , h ) ;
uint32_t limited = Memory : : ValidSize ( texaddr , ( h / 4 ) * ( bufw / 4 ) * sizeof ( DXTBlock ) ) ;
// This might possibly be 0, but try to decode what we can (might even be how the PSP behaves.)
h = ( ( ( int ) limited / sizeof ( DXTBlock ) ) / ( bufw / 4 ) ) * 4 ;
}
for ( int y = 0 ; y < h ; y + = 4 ) {
u32 blockIndex = ( y / 4 ) * ( bufw / 4 ) ;
int blockHeight = std : : min ( h - y , 4 ) ;
for ( int x = 0 ; x < minw ; x + = 4 ) {
if ( n = = 1 )
DecodeDXT1Block ( dst + outPitch32 * y + x , ( const DXT1Block * ) src + blockIndex , outPitch32 , blockHeight , false ) ;
if ( n = = 3 )
DecodeDXT3Block ( dst + outPitch32 * y + x , ( const DXT3Block * ) src + blockIndex , outPitch32 , blockHeight ) ;
if ( n = = 5 )
DecodeDXT5Block ( dst + outPitch32 * y + x , ( const DXT5Block * ) src + blockIndex , outPitch32 , blockHeight ) ;
blockIndex + + ;
}
}
w = ( w + 3 ) & ~ 3 ;
if ( reverseColors ) {
ReverseColors ( out , out , GE_TFMT_8888 , outPitch32 * h , useBGRA ) ;
}
}
2017-02-22 16:23:04 +01:00
void TextureCacheCommon : : DecodeTextureLevel ( u8 * out , int outPitch , GETextureFormat format , GEPaletteFormat clutformat , uint32_t texaddr , int level , int bufw , bool reverseColors , bool useBGRA , bool expandTo32bit ) {
2016-06-19 07:55:38 -07:00
bool swizzled = gstate . isTextureSwizzled ( ) ;
if ( ( texaddr & 0x00600000 ) ! = 0 & & Memory : : IsVRAMAddress ( texaddr ) ) {
// This means it's in a mirror, possibly a swizzled mirror. Let's report.
WARN_LOG_REPORT_ONCE ( texmirror , G3D , " Decoding texture from VRAM mirror at %08x swizzle=%d " , texaddr , swizzled ? 1 : 0 ) ;
if ( ( texaddr & 0x00200000 ) = = 0x00200000 ) {
// Technically 2 and 6 are slightly different, but this is better than nothing probably.
swizzled = ! swizzled ;
}
// Note that (texaddr & 0x00600000) == 0x00600000 is very likely to be depth texturing.
}
int w = gstate . getTextureWidth ( level ) ;
int h = gstate . getTextureHeight ( level ) ;
const u8 * texptr = Memory : : GetPointer ( texaddr ) ;
2021-02-03 20:46:41 -08:00
const uint32_t byteSize = ( textureBitsPerPixel [ format ] * bufw * h ) / 8 ;
2021-03-13 17:51:40 +01:00
char buf [ 128 ] ;
size_t len = snprintf ( buf , sizeof ( buf ) , " Tex_%08x_%dx%d_%s " , texaddr , w , h , GeTextureFormatToString ( format , clutformat ) ) ;
NotifyMemInfo ( MemBlockFlags : : TEXTURE , texaddr , byteSize , buf , len ) ;
2016-06-19 07:55:38 -07:00
switch ( format ) {
case GE_TFMT_CLUT4 :
{
const bool mipmapShareClut = gstate . isClutSharedForMipmaps ( ) ;
const int clutSharingOffset = mipmapShareClut ? 0 : level * 16 ;
if ( swizzled ) {
2017-02-20 00:19:58 +01:00
tmpTexBuf32_ . resize ( bufw * ( ( h + 7 ) & ~ 7 ) ) ;
UnswizzleFromMem ( tmpTexBuf32_ . data ( ) , bufw / 2 , texptr , bufw , h , 0 ) ;
texptr = ( u8 * ) tmpTexBuf32_ . data ( ) ;
2016-06-19 07:55:38 -07:00
}
switch ( clutformat ) {
case GE_CMODE_16BIT_BGR5650 :
case GE_CMODE_16BIT_ABGR5551 :
case GE_CMODE_16BIT_ABGR4444 :
{
2017-02-22 17:26:52 +01:00
if ( clutAlphaLinear_ & & mipmapShareClut & & ! expandTo32bit ) {
2016-06-19 07:55:38 -07:00
// Here, reverseColors means the CLUT is already reversed.
if ( reverseColors ) {
for ( int y = 0 ; y < h ; + + y ) {
DeIndexTexture4Optimal ( ( u16 * ) ( out + outPitch * y ) , texptr + ( bufw * y ) / 2 , w , clutAlphaLinearColor_ ) ;
}
} else {
for ( int y = 0 ; y < h ; + + y ) {
DeIndexTexture4OptimalRev ( ( u16 * ) ( out + outPitch * y ) , texptr + ( bufw * y ) / 2 , w , clutAlphaLinearColor_ ) ;
}
}
} else {
2019-09-27 23:25:30 +02:00
const u16 * clut = GetCurrentClut < u16 > ( ) + clutSharingOffset ;
2017-02-22 17:26:52 +01:00
if ( expandTo32bit & & ! reverseColors ) {
// We simply expand the CLUT to 32-bit, then we deindex as usual. Probably the fastest way.
2017-03-18 20:09:38 -07:00
ConvertFormatToRGBA8888 ( clutformat , expandClut_ , clut , 16 ) ;
2017-02-22 17:26:52 +01:00
for ( int y = 0 ; y < h ; + + y ) {
DeIndexTexture4 ( ( u32 * ) ( out + outPitch * y ) , texptr + ( bufw * y ) / 2 , w , expandClut_ ) ;
}
} else {
for ( int y = 0 ; y < h ; + + y ) {
DeIndexTexture4 ( ( u16 * ) ( out + outPitch * y ) , texptr + ( bufw * y ) / 2 , w , clut ) ;
}
2016-06-19 07:55:38 -07:00
}
}
}
break ;
case GE_CMODE_32BIT_ABGR8888 :
{
const u32 * clut = GetCurrentClut < u32 > ( ) + clutSharingOffset ;
for ( int y = 0 ; y < h ; + + y ) {
DeIndexTexture4 ( ( u32 * ) ( out + outPitch * y ) , texptr + ( bufw * y ) / 2 , w , clut ) ;
}
}
break ;
default :
ERROR_LOG_REPORT ( G3D , " Unknown CLUT4 texture mode %d " , gstate . getClutPaletteFormat ( ) ) ;
2017-02-21 11:29:51 +01:00
return ;
2016-06-19 07:55:38 -07:00
}
}
break ;
case GE_TFMT_CLUT8 :
2017-02-22 17:26:52 +01:00
ReadIndexedTex ( out , outPitch , level , texptr , 1 , bufw , expandTo32bit ) ;
2016-06-19 07:55:38 -07:00
break ;
case GE_TFMT_CLUT16 :
2017-02-22 17:26:52 +01:00
ReadIndexedTex ( out , outPitch , level , texptr , 2 , bufw , expandTo32bit ) ;
2016-06-19 07:55:38 -07:00
break ;
case GE_TFMT_CLUT32 :
2017-02-22 17:26:52 +01:00
ReadIndexedTex ( out , outPitch , level , texptr , 4 , bufw , expandTo32bit ) ;
2016-06-19 07:55:38 -07:00
break ;
case GE_TFMT_4444 :
case GE_TFMT_5551 :
case GE_TFMT_5650 :
if ( ! swizzled ) {
// Just a simple copy, we swizzle the color format.
if ( reverseColors ) {
for ( int y = 0 ; y < h ; + + y ) {
ReverseColors ( out + outPitch * y , texptr + bufw * sizeof ( u16 ) * y , format , w , useBGRA ) ;
}
2017-02-22 17:26:52 +01:00
} else if ( expandTo32bit ) {
for ( int y = 0 ; y < h ; + + y ) {
2017-03-18 20:09:38 -07:00
ConvertFormatToRGBA8888 ( format , ( u32 * ) ( out + outPitch * y ) , ( const u16 * ) texptr + bufw * y , w ) ;
2017-02-22 17:26:52 +01:00
}
2016-06-19 07:55:38 -07:00
} else {
for ( int y = 0 ; y < h ; + + y ) {
memcpy ( out + outPitch * y , texptr + bufw * sizeof ( u16 ) * y , w * sizeof ( u16 ) ) ;
}
}
2020-03-19 20:56:02 -07:00
} else if ( h > = 8 & & bufw < = w & & ! expandTo32bit ) {
2017-03-18 20:10:21 -07:00
// Note: this is always safe since h must be a power of 2, so a multiple of 8.
2017-11-14 02:35:14 +01:00
UnswizzleFromMem ( ( u32 * ) out , outPitch , texptr , bufw , h , 2 ) ;
if ( reverseColors ) {
ReverseColors ( out , out , format , h * outPitch / 2 , useBGRA ) ;
2016-06-19 07:55:38 -07:00
}
} else {
// We don't have enough space for all rows in out, so use a temp buffer.
2017-02-20 00:19:58 +01:00
tmpTexBuf32_ . resize ( bufw * ( ( h + 7 ) & ~ 7 ) ) ;
UnswizzleFromMem ( tmpTexBuf32_ . data ( ) , bufw * 2 , texptr , bufw , h , 2 ) ;
const u8 * unswizzled = ( u8 * ) tmpTexBuf32_ . data ( ) ;
2016-06-19 07:55:38 -07:00
if ( reverseColors ) {
for ( int y = 0 ; y < h ; + + y ) {
ReverseColors ( out + outPitch * y , unswizzled + bufw * sizeof ( u16 ) * y , format , w , useBGRA ) ;
}
2017-02-22 17:26:52 +01:00
} else if ( expandTo32bit ) {
for ( int y = 0 ; y < h ; + + y ) {
2017-03-18 20:09:38 -07:00
ConvertFormatToRGBA8888 ( format , ( u32 * ) ( out + outPitch * y ) , ( const u16 * ) unswizzled + bufw * y , w ) ;
2017-02-22 17:26:52 +01:00
}
2016-06-19 07:55:38 -07:00
} else {
for ( int y = 0 ; y < h ; + + y ) {
memcpy ( out + outPitch * y , unswizzled + bufw * sizeof ( u16 ) * y , w * sizeof ( u16 ) ) ;
}
}
}
break ;
case GE_TFMT_8888 :
if ( ! swizzled ) {
if ( reverseColors ) {
for ( int y = 0 ; y < h ; + + y ) {
ReverseColors ( out + outPitch * y , texptr + bufw * sizeof ( u32 ) * y , format , w , useBGRA ) ;
}
} else {
for ( int y = 0 ; y < h ; + + y ) {
memcpy ( out + outPitch * y , texptr + bufw * sizeof ( u32 ) * y , w * sizeof ( u32 ) ) ;
}
}
2020-03-19 20:56:02 -07:00
} else if ( h > = 8 & & bufw < = w ) {
2016-06-19 07:55:38 -07:00
UnswizzleFromMem ( ( u32 * ) out , outPitch , texptr , bufw , h , 4 ) ;
if ( reverseColors ) {
ReverseColors ( out , out , format , h * outPitch / 4 , useBGRA ) ;
}
} else {
// We don't have enough space for all rows in out, so use a temp buffer.
2017-02-20 00:19:58 +01:00
tmpTexBuf32_ . resize ( bufw * ( ( h + 7 ) & ~ 7 ) ) ;
UnswizzleFromMem ( tmpTexBuf32_ . data ( ) , bufw * 4 , texptr , bufw , h , 4 ) ;
const u8 * unswizzled = ( u8 * ) tmpTexBuf32_ . data ( ) ;
2016-06-19 07:55:38 -07:00
if ( reverseColors ) {
for ( int y = 0 ; y < h ; + + y ) {
ReverseColors ( out + outPitch * y , unswizzled + bufw * sizeof ( u32 ) * y , format , w , useBGRA ) ;
}
} else {
for ( int y = 0 ; y < h ; + + y ) {
memcpy ( out + outPitch * y , unswizzled + bufw * sizeof ( u32 ) * y , w * sizeof ( u32 ) ) ;
}
}
}
break ;
case GE_TFMT_DXT1 :
2021-06-06 11:52:26 -07:00
DecodeDXTBlock < DXT1Block , 1 > ( out , outPitch , texaddr , texptr , w , h , bufw , reverseColors , useBGRA ) ;
2017-02-21 11:29:51 +01:00
break ;
2016-06-19 07:55:38 -07:00
case GE_TFMT_DXT3 :
2021-06-06 11:52:26 -07:00
DecodeDXTBlock < DXT3Block , 3 > ( out , outPitch , texaddr , texptr , w , h , bufw , reverseColors , useBGRA ) ;
2017-02-21 11:29:51 +01:00
break ;
2016-06-19 07:55:38 -07:00
case GE_TFMT_DXT5 :
2021-06-06 11:52:26 -07:00
DecodeDXTBlock < DXT5Block , 5 > ( out , outPitch , texaddr , texptr , w , h , bufw , reverseColors , useBGRA ) ;
2017-02-21 11:29:51 +01:00
break ;
2016-06-19 07:55:38 -07:00
default :
ERROR_LOG_REPORT ( G3D , " Unknown Texture Format %d!!! " , format ) ;
2017-02-21 11:29:51 +01:00
break ;
2016-06-19 07:55:38 -07:00
}
}
2017-02-22 17:26:52 +01:00
void TextureCacheCommon : : ReadIndexedTex ( u8 * out , int outPitch , int level , const u8 * texptr , int bytesPerIndex , int bufw , bool expandTo32Bit ) {
2016-06-19 07:14:31 -07:00
int w = gstate . getTextureWidth ( level ) ;
int h = gstate . getTextureHeight ( level ) ;
if ( gstate . isTextureSwizzled ( ) ) {
2017-02-20 00:19:58 +01:00
tmpTexBuf32_ . resize ( bufw * ( ( h + 7 ) & ~ 7 ) ) ;
UnswizzleFromMem ( tmpTexBuf32_ . data ( ) , bufw * bytesPerIndex , texptr , bufw , h , bytesPerIndex ) ;
texptr = ( u8 * ) tmpTexBuf32_ . data ( ) ;
2016-06-19 07:14:31 -07:00
}
2017-02-22 17:26:52 +01:00
int palFormat = gstate . getClutPaletteFormat ( ) ;
const u16 * clut16 = ( const u16 * ) clutBuf_ ;
const u32 * clut32 = ( const u32 * ) clutBuf_ ;
if ( expandTo32Bit & & palFormat ! = GE_CMODE_32BIT_ABGR8888 ) {
2017-03-18 20:09:38 -07:00
ConvertFormatToRGBA8888 ( GEPaletteFormat ( palFormat ) , expandClut_ , clut16 , 256 ) ;
2017-02-22 17:26:52 +01:00
clut32 = expandClut_ ;
palFormat = GE_CMODE_32BIT_ABGR8888 ;
}
switch ( palFormat ) {
2016-06-19 07:14:31 -07:00
case GE_CMODE_16BIT_BGR5650 :
case GE_CMODE_16BIT_ABGR5551 :
case GE_CMODE_16BIT_ABGR4444 :
{
switch ( bytesPerIndex ) {
case 1 :
for ( int y = 0 ; y < h ; + + y ) {
2017-02-22 17:26:52 +01:00
DeIndexTexture ( ( u16 * ) ( out + outPitch * y ) , ( const u8 * ) texptr + bufw * y , w , clut16 ) ;
2016-06-19 07:14:31 -07:00
}
break ;
case 2 :
for ( int y = 0 ; y < h ; + + y ) {
2017-02-22 17:26:52 +01:00
DeIndexTexture ( ( u16 * ) ( out + outPitch * y ) , ( const u16_le * ) texptr + bufw * y , w , clut16 ) ;
2016-06-19 07:14:31 -07:00
}
break ;
case 4 :
for ( int y = 0 ; y < h ; + + y ) {
2017-02-22 17:26:52 +01:00
DeIndexTexture ( ( u16 * ) ( out + outPitch * y ) , ( const u32_le * ) texptr + bufw * y , w , clut16 ) ;
2016-06-19 07:14:31 -07:00
}
break ;
}
}
break ;
case GE_CMODE_32BIT_ABGR8888 :
{
switch ( bytesPerIndex ) {
case 1 :
for ( int y = 0 ; y < h ; + + y ) {
2017-02-22 17:26:52 +01:00
DeIndexTexture ( ( u32 * ) ( out + outPitch * y ) , ( const u8 * ) texptr + bufw * y , w , clut32 ) ;
2016-06-19 07:14:31 -07:00
}
break ;
case 2 :
for ( int y = 0 ; y < h ; + + y ) {
2017-02-22 17:26:52 +01:00
DeIndexTexture ( ( u32 * ) ( out + outPitch * y ) , ( const u16_le * ) texptr + bufw * y , w , clut32 ) ;
2016-06-19 07:14:31 -07:00
}
break ;
case 4 :
for ( int y = 0 ; y < h ; + + y ) {
2017-02-22 17:26:52 +01:00
DeIndexTexture ( ( u32 * ) ( out + outPitch * y ) , ( const u32_le * ) texptr + bufw * y , w , clut32 ) ;
2016-06-19 07:14:31 -07:00
}
break ;
}
}
break ;
default :
ERROR_LOG_REPORT ( G3D , " Unhandled clut texture mode %d!!! " , gstate . getClutPaletteFormat ( ) ) ;
2017-02-21 11:29:51 +01:00
break ;
2016-06-19 07:14:31 -07:00
}
}
2017-02-08 15:24:27 +01:00
2017-02-19 23:25:09 +01:00
void TextureCacheCommon : : ApplyTexture ( ) {
TexCacheEntry * entry = nextTexture_ ;
2020-09-15 09:34:40 +02:00
if ( ! entry ) {
2020-09-12 14:25:50 +02:00
// Maybe we bound a framebuffer?
2020-09-20 23:46:53 +02:00
InvalidateLastTexture ( ) ;
2020-09-12 14:25:50 +02:00
if ( nextFramebufferTexture_ ) {
bool depth = Memory : : IsDepthTexVRAMAddress ( gstate . getTextureAddress ( 0 ) ) ;
2020-09-20 23:57:43 +02:00
// ApplyTextureFrameBuffer is responsible for setting SetTextureFullAlpha.
2020-09-12 14:25:50 +02:00
ApplyTextureFramebuffer ( nextFramebufferTexture_ , gstate . getTextureFormat ( ) , depth ? NOTIFY_FB_DEPTH : NOTIFY_FB_COLOR ) ;
nextFramebufferTexture_ = nullptr ;
}
2017-02-19 23:25:09 +01:00
return ;
}
2020-09-12 14:25:50 +02:00
2017-02-19 23:25:09 +01:00
nextTexture_ = nullptr ;
UpdateMaxSeenV ( entry , gstate . isModeThrough ( ) ) ;
if ( nextNeedsRebuild_ ) {
2017-03-18 11:49:30 -07:00
// Regardless of hash fails or otherwise, if this is a video, mark it frequently changing.
// This prevents temporary scaling perf hits on the first second of video.
2021-02-20 20:59:04 -08:00
if ( IsVideo ( entry - > addr ) ) {
2017-03-18 11:49:30 -07:00
entry - > status | = TexCacheEntry : : STATUS_CHANGE_FREQUENT ;
}
2017-02-19 23:25:09 +01:00
if ( nextNeedsRehash_ ) {
2017-12-30 10:12:10 -08:00
PROFILE_THIS_SCOPE ( " texhash " ) ;
2017-02-19 23:25:09 +01:00
// Update the hash on the texture.
int w = gstate . getTextureWidth ( 0 ) ;
int h = gstate . getTextureHeight ( 0 ) ;
2017-02-20 00:19:58 +01:00
entry - > fullhash = QuickTexHash ( replacer_ , entry - > addr , entry - > bufw , w , h , GETextureFormat ( entry - > format ) , entry ) ;
2017-03-26 10:18:43 -07:00
// TODO: Here we could check the secondary cache; maybe the texture is in there?
// We would need to abort the build if so.
2017-02-19 23:25:09 +01:00
}
if ( nextNeedsChange_ ) {
// This texture existed previously, let's handle the change.
2018-03-25 10:49:28 +02:00
HandleTextureChange ( entry , nextChangeReason_ , false , true ) ;
2017-02-19 23:25:09 +01:00
}
// We actually build afterward (shared with rehash rebuild.)
} else if ( nextNeedsRehash_ ) {
// Okay, this matched and didn't change - but let's check the hash. Maybe it will change.
bool doDelete = true ;
if ( ! CheckFullHash ( entry , doDelete ) ) {
2018-03-25 10:49:28 +02:00
HandleTextureChange ( entry , " hash fail " , true , doDelete ) ;
2017-02-19 23:25:09 +01:00
nextNeedsRebuild_ = true ;
} else if ( nextTexture_ ! = nullptr ) {
2017-03-26 10:18:43 -07:00
// The secondary cache may choose an entry from its storage by setting nextTexture_.
// This means we should set that, instead of our previous entry.
2017-02-19 23:25:09 +01:00
entry = nextTexture_ ;
nextTexture_ = nullptr ;
UpdateMaxSeenV ( entry , gstate . isModeThrough ( ) ) ;
}
}
// Okay, now actually rebuild the texture if needed.
if ( nextNeedsRebuild_ ) {
2018-03-25 10:49:28 +02:00
_assert_ ( ! entry - > texturePtr ) ;
BuildTexture ( entry ) ;
2020-09-20 23:22:54 +02:00
InvalidateLastTexture ( ) ;
2017-02-19 23:25:09 +01:00
}
entry - > lastFrame = gpuStats . numFlips ;
2020-09-12 14:25:50 +02:00
BindTexture ( entry ) ;
gstate_c . SetTextureFullAlpha ( entry - > GetAlphaStatus ( ) = = TexCacheEntry : : STATUS_ALPHA_FULL ) ;
2017-02-19 23:25:09 +01:00
}
2017-02-19 22:31:07 +01:00
void TextureCacheCommon : : Clear ( bool delete_them ) {
ForgetLastTexture ( ) ;
2017-02-23 17:31:24 +01:00
for ( TexCache : : iterator iter = cache_ . begin ( ) ; iter ! = cache_ . end ( ) ; + + iter ) {
ReleaseTexture ( iter - > second . get ( ) , delete_them ) ;
}
2017-03-26 10:18:43 -07:00
// In case the setting was changed, we ALWAYS clear the secondary cache (enabled or not.)
2017-02-23 17:31:24 +01:00
for ( TexCache : : iterator iter = secondCache_ . begin ( ) ; iter ! = secondCache_ . end ( ) ; + + iter ) {
ReleaseTexture ( iter - > second . get ( ) , delete_them ) ;
2017-02-19 22:31:07 +01:00
}
2017-02-20 00:19:58 +01:00
if ( cache_ . size ( ) + secondCache_ . size ( ) ) {
INFO_LOG ( G3D , " Texture cached cleared from %i textures " , ( int ) ( cache_ . size ( ) + secondCache_ . size ( ) ) ) ;
cache_ . clear ( ) ;
secondCache_ . clear ( ) ;
2017-02-19 22:31:07 +01:00
cacheSizeEstimate_ = 0 ;
secondCacheSizeEstimate_ = 0 ;
}
videos_ . clear ( ) ;
}
2017-02-08 15:24:27 +01:00
2017-02-19 22:50:04 +01:00
void TextureCacheCommon : : DeleteTexture ( TexCache : : iterator it ) {
2017-02-23 17:31:24 +01:00
ReleaseTexture ( it - > second . get ( ) , true ) ;
2017-02-20 00:45:07 +01:00
cacheSizeEstimate_ - = EstimateTexMemoryUsage ( it - > second . get ( ) ) ;
2017-02-20 00:19:58 +01:00
cache_ . erase ( it ) ;
2017-02-19 22:50:04 +01:00
}
2017-02-20 00:45:07 +01:00
bool TextureCacheCommon : : CheckFullHash ( TexCacheEntry * entry , bool & doDelete ) {
2017-02-08 15:24:27 +01:00
int w = gstate . getTextureWidth ( 0 ) ;
int h = gstate . getTextureHeight ( 0 ) ;
2017-12-30 10:12:10 -08:00
u32 fullhash ;
{
PROFILE_THIS_SCOPE ( " texhash " ) ;
fullhash = QuickTexHash ( replacer_ , entry - > addr , entry - > bufw , w , h , GETextureFormat ( entry - > format ) , entry ) ;
}
2017-03-18 11:42:57 -07:00
if ( fullhash = = entry - > fullhash ) {
2017-02-08 15:24:27 +01:00
if ( g_Config . bTextureBackoffCache ) {
if ( entry - > GetHashStatus ( ) ! = TexCacheEntry : : STATUS_HASHING & & entry - > numFrames > TexCacheEntry : : FRAMES_REGAIN_TRUST ) {
// Reset to STATUS_HASHING.
entry - > SetHashStatus ( TexCacheEntry : : STATUS_HASHING ) ;
entry - > status & = ~ TexCacheEntry : : STATUS_CHANGE_FREQUENT ;
}
} else if ( entry - > numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST ) {
entry - > status & = ~ TexCacheEntry : : STATUS_CHANGE_FREQUENT ;
}
2017-03-18 11:42:57 -07:00
return true ;
2017-02-08 15:24:27 +01:00
}
2017-03-18 11:42:57 -07:00
// Don't give up just yet. Let's try the secondary cache if it's been invalidated before.
if ( g_Config . bTextureSecondaryCache ) {
2017-03-18 11:44:06 -07:00
// Don't forget this one was unreliable (in case we match a secondary entry.)
entry - > status | = TexCacheEntry : : STATUS_UNRELIABLE ;
2017-03-26 10:18:43 -07:00
// If it's failed a bunch of times, then the second cache is just wasting time and VRAM.
// In that case, skip.
2017-03-18 11:42:57 -07:00
if ( entry - > numInvalidated > 2 & & entry - > numInvalidated < 128 & & ! lowMemoryMode_ ) {
2017-03-26 10:18:43 -07:00
// We have a new hash: look for that hash in the secondary cache.
2017-03-18 11:42:57 -07:00
u64 secondKey = fullhash | ( u64 ) entry - > cluthash < < 32 ;
TexCache : : iterator secondIter = secondCache_ . find ( secondKey ) ;
if ( secondIter ! = secondCache_ . end ( ) ) {
2017-03-26 10:18:43 -07:00
// Found it, but does it match our current params? If not, abort.
2017-03-18 11:42:57 -07:00
TexCacheEntry * secondEntry = secondIter - > second . get ( ) ;
if ( secondEntry - > Matches ( entry - > dim , entry - > format , entry - > maxLevel ) ) {
// Reset the numInvalidated value lower, we got a match.
if ( entry - > numInvalidated > 8 ) {
- - entry - > numInvalidated ;
2017-02-08 15:24:27 +01:00
}
2017-03-26 10:18:43 -07:00
// Now just use our archived texture, instead of entry.
2017-03-18 11:42:57 -07:00
nextTexture_ = secondEntry ;
return true ;
2017-02-08 15:24:27 +01:00
}
2017-03-18 11:42:57 -07:00
} else {
2020-09-08 15:52:01 -07:00
// It wasn't found, so we're about to throw away the entry and rebuild a texture.
2017-03-26 10:18:43 -07:00
// Let's save this in the secondary cache in case it gets used again.
2017-03-18 11:42:57 -07:00
secondKey = entry - > fullhash | ( ( u64 ) entry - > cluthash < < 32 ) ;
secondCacheSizeEstimate_ + = EstimateTexMemoryUsage ( entry ) ;
2017-03-26 18:33:11 +02:00
// If the entry already exists in the secondary texture cache, drop it nicely.
auto oldIter = secondCache_ . find ( secondKey ) ;
if ( oldIter ! = secondCache_ . end ( ) ) {
ReleaseTexture ( oldIter - > second . get ( ) , true ) ;
}
2017-03-26 10:18:43 -07:00
// Archive the entire texture entry as is, since we'll use its params if it is seen again.
// We keep parameters on the current entry, since we are STILL building a new texture here.
2017-03-18 11:42:57 -07:00
secondCache_ [ secondKey ] . reset ( new TexCacheEntry ( * entry ) ) ;
2017-03-25 15:30:58 -07:00
2017-03-26 10:18:43 -07:00
// Make sure we don't delete the texture we just archived.
2017-03-25 15:30:58 -07:00
entry - > texturePtr = nullptr ;
2017-03-18 11:42:57 -07:00
doDelete = false ;
2017-02-08 15:24:27 +01:00
}
}
}
2017-03-18 11:42:57 -07:00
// We know it failed, so update the full hash right away.
entry - > fullhash = fullhash ;
return false ;
2017-02-08 15:24:27 +01:00
}
2017-02-08 15:24:33 +01:00
void TextureCacheCommon : : Invalidate ( u32 addr , int size , GPUInvalidationType type ) {
2017-03-25 11:43:19 -07:00
// They could invalidate inside the texture, let's just give a bit of leeway.
2020-09-24 00:16:45 +02:00
// TODO: Keep track of the largest texture size in bytes, and use that instead of this
// humongous unrealistic value.
2017-03-25 11:43:19 -07:00
const int LARGEST_TEXTURE_SIZE = 512 * 512 * 4 ;
addr & = 0x3FFFFFFF ;
const u32 addr_end = addr + size ;
if ( type = = GPU_INVALIDATE_ALL ) {
// This is an active signal from the game that something in the texture cache may have changed.
gstate_c . Dirty ( DIRTY_TEXTURE_IMAGE ) ;
} else {
2020-09-20 21:33:06 +02:00
// Do a quick check to see if the current texture could potentially be in range.
2017-03-25 11:43:19 -07:00
const u32 currentAddr = gstate . getTextureAddress ( 0 ) ;
2020-09-20 21:33:06 +02:00
// TODO: This can be made tighter.
2017-03-25 11:43:19 -07:00
if ( addr_end > = currentAddr & & addr < currentAddr + LARGEST_TEXTURE_SIZE ) {
gstate_c . Dirty ( DIRTY_TEXTURE_IMAGE ) ;
}
}
2017-02-08 15:24:33 +01:00
// If we're hashing every use, without backoff, then this isn't needed.
2021-02-07 09:02:28 -08:00
if ( ! g_Config . bTextureBackoffCache & & type ! = GPU_INVALIDATE_FORCE ) {
2017-02-08 15:24:33 +01:00
return ;
}
const u64 startKey = ( u64 ) ( addr - LARGEST_TEXTURE_SIZE ) < < 32 ;
u64 endKey = ( u64 ) ( addr + size + LARGEST_TEXTURE_SIZE ) < < 32 ;
if ( endKey < startKey ) {
endKey = ( u64 ) - 1 ;
}
2017-02-20 00:19:58 +01:00
for ( TexCache : : iterator iter = cache_ . lower_bound ( startKey ) , end = cache_ . upper_bound ( endKey ) ; iter ! = end ; + + iter ) {
2021-02-07 09:02:28 -08:00
auto & entry = iter - > second ;
u32 texAddr = entry - > addr ;
u32 texEnd = entry - > addr + entry - > sizeInRAM ;
2017-02-08 15:24:33 +01:00
2020-09-24 00:16:45 +02:00
// Quick check for overlap. Yes the check is right.
if ( addr < texEnd & & addr_end > texAddr ) {
2021-02-07 09:02:28 -08:00
if ( entry - > GetHashStatus ( ) = = TexCacheEntry : : STATUS_RELIABLE ) {
entry - > SetHashStatus ( TexCacheEntry : : STATUS_HASHING ) ;
}
if ( type = = GPU_INVALIDATE_FORCE ) {
// Just random values to force the hash not to match.
entry - > fullhash = ( entry - > fullhash ^ 0x12345678 ) + 13 ;
2021-07-10 11:42:33 +02:00
entry - > minihash = ( entry - > minihash ^ 0x89ABCDEF ) + 89 ;
2017-02-08 15:24:33 +01:00
}
if ( type ! = GPU_INVALIDATE_ALL ) {
gpuStats . numTextureInvalidations + + ;
// Start it over from 0 (unless it's safe.)
2021-02-07 09:02:28 -08:00
entry - > numFrames = type = = GPU_INVALIDATE_SAFE ? 256 : 0 ;
2017-02-08 15:24:33 +01:00
if ( type = = GPU_INVALIDATE_SAFE ) {
2021-02-07 09:02:28 -08:00
u32 diff = gpuStats . numFlips - entry - > lastFrame ;
2017-02-08 15:24:33 +01:00
// We still need to mark if the texture is frequently changing, even if it's safely changing.
if ( diff < TEXCACHE_FRAME_CHANGE_FREQUENT ) {
2021-02-07 09:02:28 -08:00
entry - > status | = TexCacheEntry : : STATUS_CHANGE_FREQUENT ;
2017-02-08 15:24:33 +01:00
}
}
2021-02-07 09:02:28 -08:00
entry - > framesUntilNextFullHash = 0 ;
2020-09-12 14:25:50 +02:00
} else {
2021-02-07 09:02:28 -08:00
entry - > invalidHint + + ;
2017-02-08 15:24:33 +01:00
}
}
}
}
void TextureCacheCommon : : InvalidateAll ( GPUInvalidationType /*unused*/ ) {
// If we're hashing every use, without backoff, then this isn't needed.
if ( ! g_Config . bTextureBackoffCache ) {
return ;
}
if ( timesInvalidatedAllThisFrame_ > 5 ) {
return ;
}
timesInvalidatedAllThisFrame_ + + ;
2017-02-20 00:19:58 +01:00
for ( TexCache : : iterator iter = cache_ . begin ( ) , end = cache_ . end ( ) ; iter ! = end ; + + iter ) {
2017-02-20 00:45:07 +01:00
if ( iter - > second - > GetHashStatus ( ) = = TexCacheEntry : : STATUS_RELIABLE ) {
iter - > second - > SetHashStatus ( TexCacheEntry : : STATUS_HASHING ) ;
2017-02-08 15:24:33 +01:00
}
2020-09-12 14:25:50 +02:00
iter - > second - > invalidHint + + ;
2017-02-08 15:24:33 +01:00
}
}
void TextureCacheCommon : : ClearNextFrame ( ) {
clearCacheNextFrame_ = true ;
}