2012-01-29 21:55:07 +01:00
// Copyright (C) 2003 Dolphin 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.
// 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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
2010-10-19 22:24:27 +00:00
#include "MemoryUtil.h"
#include "VideoConfig.h"
#include "Statistics.h"
#include "HiresTextures.h"
2010-11-18 02:21:26 +00:00
#include "RenderBase.h"
2010-10-19 22:24:27 +00:00
#include "FileUtil.h"
#include "TextureCacheBase.h"
2010-12-05 14:15:36 +00:00
#include "Debugger.h"
2011-01-31 01:28:32 +00:00
#include "ConfigManager.h"
#include "HW/Memmap.h"
2010-10-19 22:24:27 +00:00
// ugly
extern int frameCount ;
enum
{
TEXTURE_KILL_THRESHOLD = 200 ,
};
TextureCache * g_texture_cache ;
2011-12-26 22:04:59 +01:00
GC_ALIGNED16 ( u8 * TextureCache :: temp ) = NULL ;
2012-05-13 15:38:56 +02:00
unsigned int TextureCache :: temp_size ;
2011-02-05 10:08:06 +00:00
2010-10-19 22:24:27 +00:00
TextureCache :: TexCache TextureCache :: textures ;
2012-05-28 11:31:37 +02:00
TextureCache :: BackupConfig TextureCache :: backup_config ;
2010-10-19 22:24:27 +00:00
TextureCache :: TCacheEntryBase ::~ TCacheEntryBase ()
{
}
TextureCache :: TextureCache ()
{
2012-05-13 15:38:56 +02:00
temp_size = 2048 * 2048 * 4 ;
2011-02-05 10:08:06 +00:00
if ( ! temp )
2012-05-13 15:38:56 +02:00
temp = ( u8 * ) AllocateAlignedMemory ( temp_size , 16 );
2010-10-19 22:24:27 +00:00
TexDecoder_SetTexFmtOverlayOptions ( g_ActiveConfig . bTexFmtOverlayEnable , g_ActiveConfig . bTexFmtOverlayCenter );
2011-02-05 10:08:06 +00:00
if ( g_ActiveConfig . bHiresTextures && ! g_ActiveConfig . bDumpTextures )
HiresTextures :: Init ( SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID . c_str ());
SetHash64Function ( g_ActiveConfig . bHiresTextures || g_ActiveConfig . bDumpTextures );
2010-10-19 22:24:27 +00:00
}
2012-05-28 11:37:14 +02:00
void TextureCache :: Invalidate ()
2010-10-19 22:24:27 +00:00
{
TexCache :: iterator
iter = textures . begin (),
tcend = textures . end ();
for (; iter != tcend ; ++ iter )
delete iter -> second ;
textures . clear ();
}
TextureCache ::~ TextureCache ()
{
2012-06-27 20:19:04 +02:00
Invalidate ();
2011-02-05 10:08:06 +00:00
if ( temp )
{
2011-02-25 20:35:05 +00:00
FreeAlignedMemory ( temp );
2011-02-05 10:08:06 +00:00
temp = NULL ;
}
2010-10-19 22:24:27 +00:00
}
2012-05-28 11:31:37 +02:00
void TextureCache :: OnConfigChanged ( VideoConfig & config )
{
if ( ! g_texture_cache )
goto skip_checks ;
// TODO: Invalidating texcache is really stupid in some of these cases
if ( config . iSafeTextureCache_ColorSamples != backup_config . s_colorsamples ||
config . bTexFmtOverlayEnable != backup_config . s_texfmt_overlay ||
config . bTexFmtOverlayCenter != backup_config . s_texfmt_overlay_center ||
config . bHiresTextures != backup_config . s_hires_textures )
2012-05-28 11:37:14 +02:00
{
g_texture_cache -> Invalidate ();
if ( g_ActiveConfig . bHiresTextures )
HiresTextures :: Init ( SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID . c_str ());
SetHash64Function ( g_ActiveConfig . bHiresTextures || g_ActiveConfig . bDumpTextures );
2012-05-28 11:39:55 +02:00
TexDecoder_SetTexFmtOverlayOptions ( g_ActiveConfig . bTexFmtOverlayEnable , g_ActiveConfig . bTexFmtOverlayCenter );
2012-05-28 11:37:14 +02:00
}
2012-05-28 11:31:37 +02:00
// TODO: Probably shouldn't clear all render targets here, just mark them dirty or something.
if ( config . bEFBCopyCacheEnable != backup_config . s_copy_cache_enable || // TODO: not sure if this is needed?
config . bCopyEFBToTexture != backup_config . s_copy_efb_to_texture ||
config . bCopyEFBScaled != backup_config . s_copy_efb_scaled ||
config . bEFBCopyEnable != backup_config . s_copy_efb ||
config . iEFBScale != backup_config . s_efb_scale )
{
g_texture_cache -> ClearRenderTargets ();
}
skip_checks :
backup_config . s_colorsamples = config . iSafeTextureCache_ColorSamples ;
backup_config . s_copy_efb_to_texture = config . bCopyEFBToTexture ;
backup_config . s_copy_efb_scaled = config . bCopyEFBScaled ;
backup_config . s_copy_efb = config . bEFBCopyEnable ;
backup_config . s_efb_scale = config . iEFBScale ;
backup_config . s_texfmt_overlay = config . bTexFmtOverlayEnable ;
backup_config . s_texfmt_overlay_center = config . bTexFmtOverlayCenter ;
backup_config . s_hires_textures = config . bHiresTextures ;
backup_config . s_copy_cache_enable = config . bEFBCopyCacheEnable ;
}
2010-10-19 22:24:27 +00:00
void TextureCache :: Cleanup ()
{
2011-12-26 18:45:22 +01:00
TexCache :: iterator iter = textures . begin ();
TexCache :: iterator tcend = textures . end ();
2010-10-19 22:24:27 +00:00
while ( iter != tcend )
{
2011-12-26 18:33:05 +01:00
if ( frameCount > TEXTURE_KILL_THRESHOLD + iter -> second -> frameCount ) // TODO: Deleting EFB copies might not be a good idea here...
2010-10-19 22:24:27 +00:00
{
delete iter -> second ;
textures . erase ( iter ++ );
}
else
++ iter ;
}
}
void TextureCache :: InvalidateRange ( u32 start_address , u32 size )
{
TexCache :: iterator
iter = textures . begin (),
tcend = textures . end ();
while ( iter != tcend )
{
const int rangePosition = iter -> second -> IntersectsMemoryRange ( start_address , size );
if ( 0 == rangePosition )
{
delete iter -> second ;
textures . erase ( iter ++ );
}
else
++ iter ;
}
}
void TextureCache :: MakeRangeDynamic ( u32 start_address , u32 size )
{
TexCache :: iterator
2010-11-06 04:46:44 +00:00
iter = textures . lower_bound ( start_address ),
tcend = textures . upper_bound ( start_address + size );
if ( iter != textures . begin ())
iter -- ;
2010-10-19 22:24:27 +00:00
for (; iter != tcend ; ++ iter )
{
const int rangePosition = iter -> second -> IntersectsMemoryRange ( start_address , size );
if ( 0 == rangePosition )
{
2011-12-26 18:05:01 +01:00
iter -> second -> SetHashes ( TEXHASH_INVALID );
2010-10-19 22:24:27 +00:00
}
}
}
2010-11-06 04:46:44 +00:00
bool TextureCache :: Find ( u32 start_address , u64 hash )
{
2010-11-07 04:28:33 +00:00
TexCache :: iterator iter = textures . lower_bound ( start_address );
2010-11-06 04:46:44 +00:00
if ( iter -> second -> hash == hash )
return true ;
return false ;
}
2010-10-19 22:24:27 +00:00
int TextureCache :: TCacheEntryBase :: IntersectsMemoryRange ( u32 range_address , u32 range_size ) const
{
if ( addr + size_in_bytes < range_address )
return - 1 ;
if ( addr >= range_address + range_size )
return 1 ;
return 0 ;
}
void TextureCache :: ClearRenderTargets ()
{
TexCache :: iterator
iter = textures . begin (),
tcend = textures . end ();
2012-03-20 22:36:21 +01:00
2013-01-08 23:46:30 -05:00
while ( iter != tcend )
{
2012-12-17 19:31:52 +01:00
if ( iter -> second -> type == TCET_EC_VRAM )
{
delete iter -> second ;
textures . erase ( iter ++ );
}
2013-01-08 23:46:30 -05:00
else
++ iter ;
}
2010-10-19 22:24:27 +00:00
}
2012-05-12 14:31:38 +02:00
bool TextureCache :: CheckForCustomTextureLODs ( u64 tex_hash , int texformat , unsigned int levels )
{
// Just checking if the necessary files exist, if they can't be loaded or have incorrect dimensions LODs will be black
char texBasePathTemp [ MAX_PATH ];
char texPathTemp [ MAX_PATH ];
sprintf ( texBasePathTemp , "%s_%08x_%i" , SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID . c_str (), ( u32 ) ( tex_hash & 0x00000000FFFFFFFFLL ), texformat );
for ( unsigned int level = 1 ; level < levels ; ++ level )
{
sprintf ( texPathTemp , "%s_mip%i" , texBasePathTemp , level );
if ( ! HiresTextures :: HiresTexExists ( texPathTemp ))
{
if ( level > 1 )
WARN_LOG ( VIDEO , "Couldn't find custom texture LOD with index %i (filename: %s), disabling custom LODs for this texture" , level , texPathTemp );
return false ;
}
}
return true ;
}
2012-05-13 15:38:56 +02:00
PC_TexFormat TextureCache :: LoadCustomTexture ( u64 tex_hash , int texformat , unsigned int level , unsigned int & width , unsigned int & height )
2012-05-12 13:25:13 +02:00
{
char texPathTemp [ MAX_PATH ];
unsigned int newWidth = 0 ;
unsigned int newHeight = 0 ;
2012-05-12 14:31:38 +02:00
if ( level == 0 )
sprintf ( texPathTemp , "%s_%08x_%i" , SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID . c_str (), ( u32 ) ( tex_hash & 0x00000000FFFFFFFFLL ), texformat );
else
sprintf ( texPathTemp , "%s_%08x_%i_mip%i" , SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID . c_str (), ( u32 ) ( tex_hash & 0x00000000FFFFFFFFLL ), texformat , level );
2012-05-13 15:38:56 +02:00
unsigned int required_size = 0 ;
PC_TexFormat ret = HiresTextures :: GetHiresTex ( texPathTemp , & newWidth , & newHeight , & required_size , texformat , temp_size , temp );
if ( ret == PC_TEX_FMT_NONE && temp_size < required_size )
{
// Allocate more memory and try again
// TODO: Should probably check if newWidth and newHeight are texture dimensions which are actually supported by the current video backend
temp_size = required_size ;
FreeAlignedMemory ( temp );
temp = ( u8 * ) AllocateAlignedMemory ( temp_size , 16 );
ret = HiresTextures :: GetHiresTex ( texPathTemp , & newWidth , & newHeight , & required_size , texformat , temp_size , temp );
}
2012-05-12 13:25:13 +02:00
if ( ret != PC_TEX_FMT_NONE )
{
width = newWidth ;
height = newHeight ;
}
return ret ;
}
2012-05-12 13:50:03 +02:00
void TextureCache :: DumpTexture ( TCacheEntryBase * entry , unsigned int level )
2012-05-12 13:31:09 +02:00
{
char szTemp [ MAX_PATH ];
std :: string szDir = File :: GetUserPath ( D_DUMPTEXTURES_IDX ) +
SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID ;
// make sure that the directory exists
if ( false == File :: Exists ( szDir ) || false == File :: IsDirectory ( szDir ))
File :: CreateDir ( szDir . c_str ());
2012-05-12 13:50:03 +02:00
// For compatibility with old texture packs, don't print the LOD index for level 0.
// TODO: TLUT format should actually be stored in filename? :/
if ( level == 0 )
{
sprintf ( szTemp , "%s/%s_%08x_%i.png" , szDir . c_str (),
SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID . c_str (),
( u32 ) ( entry -> hash & 0x00000000FFFFFFFFLL ), entry -> format & 0xFFFF );
}
else
{
2012-05-12 14:31:38 +02:00
sprintf ( szTemp , "%s/%s_%08x_%i_mip%i.png" , szDir . c_str (),
2012-05-12 13:50:03 +02:00
SConfig :: GetInstance (). m_LocalCoreStartupParameter . m_strUniqueID . c_str (),
( u32 ) ( entry -> hash & 0x00000000FFFFFFFFLL ), entry -> format & 0xFFFF , level );
}
2012-05-12 13:31:09 +02:00
if ( false == File :: Exists ( szTemp ))
2012-05-12 13:50:03 +02:00
entry -> Save ( szTemp , level );
2012-05-12 13:31:09 +02:00
}
2010-10-19 22:24:27 +00:00
TextureCache :: TCacheEntryBase * TextureCache :: Load ( unsigned int stage ,
u32 address , unsigned int width , unsigned int height , int texformat ,
2012-01-29 21:49:50 +01:00
unsigned int tlutaddr , int tlutfmt , bool UseNativeMips , unsigned int maxlevel , bool from_tmem )
2010-10-19 22:24:27 +00:00
{
if ( 0 == address )
return NULL ;
// TexelSizeInNibbles(format)*width*height/16;
const unsigned int bsw = TexDecoder_GetBlockWidthInTexels ( texformat ) - 1 ;
const unsigned int bsh = TexDecoder_GetBlockHeightInTexels ( texformat ) - 1 ;
unsigned int expandedWidth = ( width + bsw ) & ( ~ bsw );
unsigned int expandedHeight = ( height + bsh ) & ( ~ bsh );
2010-10-20 00:12:41 +00:00
const unsigned int nativeW = width ;
const unsigned int nativeH = height ;
2010-10-19 22:24:27 +00:00
2012-05-12 14:31:38 +02:00
bool using_custom_texture = false ;
bool using_custom_lods = false ;
2010-10-19 22:24:27 +00:00
u32 texID = address ;
2012-01-24 15:25:01 +01:00
u64 tex_hash = TEXHASH_INVALID ; // Hash assigned to texcache entry (also used to generate filenames used for texture dumping and custom texture lookup)
2011-12-26 21:37:18 +01:00
u64 tlut_hash = TEXHASH_INVALID ;
2010-10-19 22:24:27 +00:00
u32 full_format = texformat ;
2010-10-20 00:12:41 +00:00
PC_TexFormat pcfmt = PC_TEX_FMT_NONE ;
2010-10-19 22:24:27 +00:00
2011-02-05 10:08:06 +00:00
const bool isPaletteTexture = ( texformat == GX_TF_C4 || texformat == GX_TF_C8 || texformat == GX_TF_C14X2 );
if ( isPaletteTexture )
2010-10-19 22:24:27 +00:00
full_format = texformat | ( tlutfmt << 16 );
2011-12-26 23:14:12 +01:00
const u32 texture_size = TexDecoder_GetTextureSizeInBytes ( expandedWidth , expandedHeight , texformat );
2012-01-29 21:49:50 +01:00
u8 * src_data ;
if ( from_tmem ) src_data = & texMem [ bpmem . tex [ stage / 4 ]. texImage1 [ stage % 4 ]. tmem_even * TMEM_LINE_SIZE ];
else src_data = Memory :: GetPointer ( address );
2011-12-26 23:14:12 +01:00
2013-01-10 15:12:21 +01:00
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data from the low tmem bank than it should)
2012-01-29 21:49:50 +01:00
tex_hash = GetHash64 ( src_data , texture_size , g_ActiveConfig . iSafeTextureCache_ColorSamples );
2011-12-26 22:04:59 +01:00
if ( isPaletteTexture )
2010-10-19 22:24:27 +00:00
{
2011-12-26 21:11:31 +01:00
const u32 palette_size = TexDecoder_GetPaletteSize ( texformat );
tlut_hash = GetHash64 ( & texMem [ tlutaddr ], palette_size , g_ActiveConfig . iSafeTextureCache_ColorSamples );
2010-10-19 22:24:27 +00:00
2011-12-26 21:11:31 +01:00
// NOTE: For non-paletted textures, texID is equal to the texture address.
// A paletted texture, however, may have multiple texIDs assigned though depending on the currently used tlut.
// This (changing texID depending on the tlut_hash) is a trick to get around
// an issue with Metroid Prime's fonts (it has multiple sets of fonts on each other
// stored in a single texture and uses the palette to make different characters
// visible or invisible. Thus, unless we want to recreate the textures for every drawn character,
// we must make sure that a paletted texture gets assigned multiple IDs for each tlut used.
//
// TODO: Because texID isn't always the same as the address now, CopyRenderTargetToTexture might be broken now
2011-12-26 22:04:59 +01:00
texID ^= (( u32 ) tlut_hash ) ^ ( u32 )( tlut_hash >> 32 );
2012-01-24 15:25:01 +01:00
tex_hash ^= tlut_hash ;
2010-10-19 22:24:27 +00:00
}
2012-02-04 13:01:52 +01:00
TCacheEntryBase * entry = textures [ texID ];
2010-10-19 22:24:27 +00:00
if ( entry )
{
2011-12-26 22:04:59 +01:00
// 1. Calculate reference hash:
// calculated from RAM texture data for normal textures. Hashes for paletted textures are modified by tlut_hash. 0 for virtual EFB copies.
2011-12-27 00:05:26 +01:00
if ( g_ActiveConfig . bCopyEFBToTexture && entry -> IsEfbCopy ())
2012-01-24 15:25:01 +01:00
tex_hash = TEXHASH_INVALID ;
2010-10-19 22:24:27 +00:00
2011-12-26 19:45:13 +01:00
// 2. a) For EFB copies, only the hash and the texture address need to match
2012-01-24 15:25:01 +01:00
if ( entry -> IsEfbCopy () && tex_hash == entry -> hash && address == entry -> addr )
2010-10-19 22:24:27 +00:00
{
2012-06-03 13:03:20 +02:00
entry -> type = TCET_EC_VRAM ;
2012-03-20 22:36:21 +01:00
2011-12-26 19:45:13 +01:00
// TODO: Print a warning if the format changes! In this case, we could reinterpret the internal texture object data to the new pixel format (similiar to what is already being done in Renderer::ReinterpretPixelFormat())
2010-10-19 22:24:27 +00:00
goto return_entry ;
}
2011-12-26 19:45:13 +01:00
// 2. b) For normal textures, all texture parameters need to match
2012-01-24 15:25:01 +01:00
if ( address == entry -> addr && tex_hash == entry -> hash && full_format == entry -> format &&
2011-12-26 19:45:13 +01:00
entry -> num_mipmaps == maxlevel && entry -> native_width == nativeW && entry -> native_height == nativeH )
{
goto return_entry ;
}
// 3. If we reach this line, we'll have to upload the new texture data to VRAM.
// If we're lucky, the texture parameters didn't change and we can reuse the internal texture object instead of destroying and recreating it.
2011-12-27 00:05:26 +01:00
//
2011-12-26 19:45:13 +01:00
// TODO: Don't we need to force texture decoding to RGBA8 for dynamic EFB copies?
// TODO: Actually, it should be enough if the internal texture format matches...
2012-12-19 16:55:44 +01:00
if (( entry -> type == TCET_NORMAL && width == entry -> virtual_width && height == entry -> virtual_height && full_format == entry -> format && entry -> num_mipmaps == maxlevel )
2012-01-29 20:24:23 +01:00
|| ( entry -> type == TCET_EC_DYNAMIC && entry -> native_width == width && entry -> native_height == height ))
2011-12-26 19:45:13 +01:00
{
// reuse the texture
}
2010-10-19 22:24:27 +00:00
else
{
2011-12-26 19:45:13 +01:00
// delete the texture and make a new one
delete entry ;
entry = NULL ;
2010-10-19 22:24:27 +00:00
}
}
2011-12-26 19:17:04 +01:00
2012-05-12 13:25:13 +02:00
2010-10-19 22:24:27 +00:00
if ( g_ActiveConfig . bHiresTextures )
{
2012-05-13 15:38:56 +02:00
pcfmt = LoadCustomTexture ( tex_hash , texformat , 0 , width , height );
2010-10-19 22:24:27 +00:00
if ( pcfmt != PC_TEX_FMT_NONE )
{
2012-12-19 16:55:44 +01:00
if ( expandedWidth != width || expandedHeight != height )
{
expandedWidth = width ;
expandedHeight = height ;
// If we thought we could reuse the texture before, make sure to delete it now!
delete entry ;
entry = NULL ;
}
2012-05-12 14:31:38 +02:00
using_custom_texture = true ;
2010-10-19 22:24:27 +00:00
}
}
2012-12-19 16:55:44 +01:00
if ( ! using_custom_texture )
2013-01-10 15:12:21 +01:00
{
if ( ! ( texformat == GX_TF_RGBA8 && from_tmem ))
{
pcfmt = TexDecoder_Decode ( temp , src_data , expandedWidth ,
expandedHeight , texformat , tlutaddr , tlutfmt , g_ActiveConfig . backend_info . bUseRGBATextures );
}
else
{
u8 * src_data_gb = & texMem [ bpmem . tex [ stage / 4 ]. texImage2 [ stage % 4 ]. tmem_odd * TMEM_LINE_SIZE ];
pcfmt = TexDecoder_DecodeRGBA8FromTmem ( temp , src_data , src_data_gb , expandedWidth , expandedHeight );
}
}
2010-10-19 22:24:27 +00:00
2012-06-08 00:18:04 +02:00
// TODO: Cleanup. Plus, we still autogenerate mipmaps in certain cases (we shouldn't do that)
2011-12-26 21:37:18 +01:00
bool isPow2 ;
unsigned int texLevels ;
2010-10-20 00:12:41 +00:00
isPow2 = ! (( width & ( width - 1 )) || ( height & ( height - 1 )));
2012-05-12 14:31:38 +02:00
texLevels = ( isPow2 && maxlevel ) ? GetPow2 ( std :: max ( width , height )) : ! isPow2 ;
texLevels = maxlevel ? std :: min ( texLevels , maxlevel + 1 ) : texLevels ;
using_custom_lods = using_custom_texture && CheckForCustomTextureLODs ( tex_hash , texformat , texLevels );
UseNativeMips = UseNativeMips && ! using_custom_lods && ( width == nativeW && height == nativeH ); // Only load native mips if their dimensions fit to our virtual texture dimensions
texLevels = ( UseNativeMips || using_custom_lods ) ? texLevels : ! isPow2 ;
2010-10-19 22:24:27 +00:00
// create the entry/texture
2010-10-24 15:16:31 +00:00
if ( NULL == entry ) {
2010-10-19 22:24:27 +00:00
textures [ texID ] = entry = g_texture_cache -> CreateTexture ( width , height , expandedWidth , texLevels , pcfmt );
2012-01-29 21:49:50 +01:00
// Sometimes, we can get around recreating a texture if only the number of mip levels changes
2010-10-24 15:16:31 +00:00
// e.g. if our texture cache entry got too many mipmap levels we can limit the number of used levels by setting the appropriate render states
// Thus, we don't update this member for every Load, but just whenever the texture gets recreated
2011-12-26 21:37:18 +01:00
//
// TODO: Won't we end up recreating textures all the time because maxlevel doesn't necessarily equal texLevels?
2011-12-26 18:05:01 +01:00
entry -> num_mipmaps = maxlevel ; // TODO: Does this actually work? We can't really adjust mipmap settings per-stage...
2012-01-29 21:49:50 +01:00
entry -> type = TCET_NORMAL ;
2010-12-05 14:15:36 +00:00
GFX_DEBUGGER_PAUSE_AT ( NEXT_NEW_TEXTURE , true );
2010-10-19 22:24:27 +00:00
}
2011-12-26 18:05:01 +01:00
entry -> SetGeneralParameters ( address , texture_size , full_format , entry -> num_mipmaps );
entry -> SetDimensions ( nativeW , nativeH , width , height );
2012-01-24 15:25:01 +01:00
entry -> hash = tex_hash ;
2012-03-20 22:36:21 +01:00
if ( entry -> IsEfbCopy () && ! g_ActiveConfig . bCopyEFBToTexture ) entry -> type = TCET_EC_DYNAMIC ;
else entry -> type = TCET_NORMAL ;
2010-10-24 15:16:31 +00:00
2010-10-19 22:24:27 +00:00
// load texture
2013-01-19 00:47:48 +01:00
entry -> Load ( stage , width , height , expandedWidth , 0 , ( texLevels == 0 ));
2010-10-19 22:24:27 +00:00
2012-05-12 14:31:38 +02:00
if ( g_ActiveConfig . bDumpTextures && ! using_custom_texture )
2012-05-12 13:50:03 +02:00
DumpTexture ( entry , 0 );
2012-01-29 21:49:50 +01:00
// load mips - TODO: Loading mipmaps from tmem is untested!
2012-05-12 14:31:38 +02:00
if ( texLevels > 1 && pcfmt != PC_TEX_FMT_NONE && UseNativeMips )
2010-10-19 22:24:27 +00:00
{
const unsigned int bsdepth = TexDecoder_GetTexelSizeInNibbles ( texformat );
unsigned int level = 1 ;
unsigned int mipWidth = ( width + 1 ) >> 1 ;
unsigned int mipHeight = ( height + 1 ) >> 1 ;
2012-01-29 21:49:50 +01:00
u8 * ptr_even = NULL , * ptr_odd = NULL ;
if ( from_tmem )
{
ptr_even = & texMem [ bpmem . tex [ stage / 4 ]. texImage1 [ stage % 4 ]. tmem_even * TMEM_LINE_SIZE + texture_size ];
ptr_odd = & texMem [ bpmem . tex [ stage / 4 ]. texImage2 [ stage % 4 ]. tmem_odd * TMEM_LINE_SIZE ];
}
src_data += texture_size ;
2010-10-19 22:24:27 +00:00
while (( mipHeight || mipWidth ) && ( level < texLevels ))
{
2012-01-29 21:49:50 +01:00
u8 ** ptr ;
if ( from_tmem ) ptr = ( level % 2 ) ? & ptr_odd : & ptr_even ;
else ptr = & src_data ;
2010-10-19 22:24:27 +00:00
const unsigned int currentWidth = ( mipWidth > 0 ) ? mipWidth : 1 ;
const unsigned int currentHeight = ( mipHeight > 0 ) ? mipHeight : 1 ;
expandedWidth = ( currentWidth + bsw ) & ( ~ bsw );
expandedHeight = ( currentHeight + bsh ) & ( ~ bsh );
2012-01-29 21:49:50 +01:00
TexDecoder_Decode ( temp , * ptr , expandedWidth , expandedHeight , texformat , tlutaddr , tlutfmt , g_ActiveConfig . backend_info . bUseRGBATextures );
2013-01-19 00:47:48 +01:00
entry -> Load ( stage , currentWidth , currentHeight , expandedWidth , level , false );
2010-10-19 22:24:27 +00:00
2012-05-12 13:50:03 +02:00
if ( g_ActiveConfig . bDumpTextures )
DumpTexture ( entry , level );
2012-01-29 21:49:50 +01:00
* ptr += (( std :: max ( mipWidth , bsw ) * std :: max ( mipHeight , bsh ) * bsdepth ) >> 1 );
2010-10-19 22:24:27 +00:00
mipWidth >>= 1 ;
mipHeight >>= 1 ;
++ level ;
}
}
2012-05-12 14:31:38 +02:00
else if ( texLevels > 1 && pcfmt != PC_TEX_FMT_NONE && using_custom_lods )
{
unsigned int level = 1 ;
unsigned int mipWidth = ( width + 1 ) >> 1 ;
unsigned int mipHeight = ( height + 1 ) >> 1 ;
while (( mipHeight || mipWidth ) && ( level < texLevels ))
{
unsigned int currentWidth = ( mipWidth > 0 ) ? mipWidth : 1 ;
unsigned int currentHeight = ( mipHeight > 0 ) ? mipHeight : 1 ;
2012-05-13 15:38:56 +02:00
LoadCustomTexture ( tex_hash , texformat , level , currentWidth , currentHeight );
2013-01-19 00:47:48 +01:00
entry -> Load ( stage , currentWidth , currentHeight , currentWidth , level , false );
2012-05-12 14:31:38 +02:00
mipWidth >>= 1 ;
mipHeight >>= 1 ;
++ level ;
}
}
2010-10-19 22:24:27 +00:00
INCSTAT ( stats . numTexturesCreated );
SETSTAT ( stats . numTexturesAlive , textures . size ());
return_entry :
entry -> frameCount = frameCount ;
entry -> Bind ( stage );
2010-12-05 14:15:36 +00:00
GFX_DEBUGGER_PAUSE_AT ( NEXT_TEXTURE_CHANGE , true );
2010-10-19 22:24:27 +00:00
return entry ;
}
2011-02-26 23:41:02 +00:00
void TextureCache :: CopyRenderTargetToTexture ( u32 dstAddr , unsigned int dstFormat , unsigned int srcFormat ,
const EFBRectangle & srcRect , bool isIntensity , bool scaleByHalf )
2010-10-19 22:24:27 +00:00
{
2011-12-26 23:14:12 +01:00
// Emulation methods:
// - EFB to RAM:
// Encodes the requested EFB data at its native resolution to the emulated RAM using shaders.
// Load() decodes the data from there again (using TextureDecoder) if the EFB copy is being used as a texture again.
// Advantage: CPU can read data from the EFB copy and we don't lose any important updates to the texture
// Disadvantage: Encoding+decoding steps often are redundant because only some games read or modify EFB copies before using them as textures.
// - EFB to texture:
// Copies the requested EFB data to a texture object in VRAM, performing any color conversion using shaders.
2012-01-24 16:38:27 +01:00
// Advantage: Works for many games, since in most cases EFB copies aren't read or modified at all before being used as a texture again.
2011-12-26 23:14:12 +01:00
// Since we don't do any further encoding or decoding here, this method is much faster.
// It also allows enhancing the visual quality by doing scaled EFB copies.
// - hybrid EFB copies:
2012-01-24 16:38:27 +01:00
// 1a) Whenever this function gets called, encode the requested EFB data to RAM (like EFB to RAM)
2012-01-29 20:24:23 +01:00
// 1b) Set type to TCET_EC_DYNAMIC for all texture cache entries in the destination address range.
2012-01-24 16:38:27 +01:00
// If EFB copy caching is enabled, further checks will (try to) prevent redundant EFB copies.
// 2) Check if a texture cache entry for the specified dstAddr already exists (i.e. if an EFB copy was triggered to that address before):
// 2a) Entry doesn't exist:
// - Also copy the requested EFB data to a texture object in VRAM (like EFB to texture)
2012-01-29 20:24:23 +01:00
// - Create a texture cache entry for the target (type = TCET_EC_VRAM)
2012-01-24 16:38:27 +01:00
// - Store a hash of the encoded RAM data in the texcache entry.
2012-01-29 20:24:23 +01:00
// 2b) Entry exists AND type is TCET_EC_VRAM:
2012-01-24 16:38:27 +01:00
// - Like case 2a, but reuse the old texcache entry instead of creating a new one.
2012-01-29 20:24:23 +01:00
// 2c) Entry exists AND type is TCET_EC_DYNAMIC:
2012-01-24 16:38:27 +01:00
// - Only encode the texture to RAM (like EFB to RAM) and store a hash of the encoded data in the existing texcache entry.
// - Do NOT copy the requested EFB data to a VRAM object. Reason: the texture is dynamic, i.e. the CPU is modifying it. Storing a VRAM copy is useless, because we'd always end up deleting it and reloading the data from RAM anyway.
2011-12-26 23:14:12 +01:00
// 3) If the EFB copy gets used as a texture, compare the source RAM hash with the hash you stored when encoding the EFB data to RAM.
2012-01-29 20:24:23 +01:00
// 3a) If the two hashes match AND type is TCET_EC_VRAM, reuse the VRAM copy you created
// 3b) If the two hashes differ AND type is TCET_EC_VRAM, screw your existing VRAM copy. Set type to TCET_EC_DYNAMIC.
2011-12-26 23:14:12 +01:00
// Redecode the source RAM data to a VRAM object. The entry basically behaves like a normal texture now.
2012-01-29 20:24:23 +01:00
// 3c) If type is TCET_EC_DYNAMIC, treat the EFB copy like a normal texture.
2012-01-24 16:38:27 +01:00
// Advantage: Non-dynamic EFB copies can be visually enhanced like with EFB to texture.
// Compatibility is as good as EFB to RAM.
// Disadvantage: Slower than EFB to texture and often even slower than EFB to RAM.
// EFB copy cache depends on accurate texture hashing being enabled. However, with accurate hashing you end up being as slow as without a copy cache anyway.
2011-12-26 23:14:12 +01:00
//
// Disadvantage of all methods: Calling this function requires the GPU to perform a pipeline flush which stalls any further CPU processing.
2012-01-24 16:38:27 +01:00
//
// For historical reasons, Dolphin doesn't actually implement "pure" EFB to RAM emulation, but only EFB to texture and hybrid EFB copies.
2011-12-26 23:14:12 +01:00
2011-01-07 19:23:57 +00:00
float colmat [ 28 ] = { 0 };
2010-10-19 22:24:27 +00:00
float * const fConstAdd = colmat + 16 ;
2011-01-07 19:23:57 +00:00
float * const ColorMask = colmat + 20 ;
2011-12-10 16:08:26 +01:00
ColorMask [ 0 ] = ColorMask [ 1 ] = ColorMask [ 2 ] = ColorMask [ 3 ] = 255.0f ;
2011-01-07 19:23:57 +00:00
ColorMask [ 4 ] = ColorMask [ 5 ] = ColorMask [ 6 ] = ColorMask [ 7 ] = 1.0f / 255.0f ;
2010-10-19 22:24:27 +00:00
unsigned int cbufid = - 1 ;
2011-02-26 23:41:02 +00:00
if ( srcFormat == PIXELFMT_Z24 )
2010-10-19 22:24:27 +00:00
{
2011-02-26 23:41:02 +00:00
switch ( dstFormat )
2010-10-19 22:24:27 +00:00
{
2011-12-10 16:08:26 +01:00
case 0 : // Z4
2011-01-07 19:23:57 +00:00
colmat [ 3 ] = colmat [ 7 ] = colmat [ 11 ] = colmat [ 15 ] = 1.0f ;
cbufid = 0 ;
break ;
case 1 : // Z8
case 8 : // Z8
colmat [ 0 ] = colmat [ 4 ] = colmat [ 8 ] = colmat [ 12 ] = 1.0f ;
cbufid = 1 ;
break ;
2010-10-19 22:24:27 +00:00
2011-12-10 16:08:26 +01:00
case 3 : // Z16
2011-01-07 19:23:57 +00:00
colmat [ 1 ] = colmat [ 5 ] = colmat [ 9 ] = colmat [ 12 ] = 1.0f ;
2011-01-29 21:13:56 +00:00
cbufid = 24 ;
2011-01-07 19:23:57 +00:00
break ;
2010-10-19 22:24:27 +00:00
2011-01-07 19:23:57 +00:00
case 11 : // Z16 (reverse order)
colmat [ 0 ] = colmat [ 4 ] = colmat [ 8 ] = colmat [ 13 ] = 1.0f ;
cbufid = 2 ;
break ;
2010-10-19 22:24:27 +00:00
2011-01-07 19:23:57 +00:00
case 6 : // Z24X8
colmat [ 0 ] = colmat [ 5 ] = colmat [ 10 ] = 1.0f ;
cbufid = 3 ;
break ;
2010-10-19 22:24:27 +00:00
2011-01-07 19:23:57 +00:00
case 9 : // Z8M
colmat [ 1 ] = colmat [ 5 ] = colmat [ 9 ] = colmat [ 13 ] = 1.0f ;
cbufid = 4 ;
break ;
2010-10-19 22:24:27 +00:00
2011-01-07 19:23:57 +00:00
case 10 : // Z8L
colmat [ 2 ] = colmat [ 6 ] = colmat [ 10 ] = colmat [ 14 ] = 1.0f ;
cbufid = 5 ;
break ;
2010-10-19 22:24:27 +00:00
2011-12-26 18:05:01 +01:00
case 12 : // Z16L - copy lower 16 depth bits
// expected to be used as an IA8 texture (upper 8 bits stored as intensity, lower 8 bits stored as alpha)
// Used e.g. in Zelda: Skyward Sword
2011-12-10 16:08:26 +01:00
colmat [ 1 ] = colmat [ 5 ] = colmat [ 9 ] = colmat [ 14 ] = 1.0f ;
2011-01-07 19:23:57 +00:00
cbufid = 6 ;
break ;
2010-10-19 22:24:27 +00:00
2011-01-07 19:23:57 +00:00
default :
2011-02-26 23:41:02 +00:00
ERROR_LOG ( VIDEO , "Unknown copy zbuf format: 0x%x" , dstFormat );
2011-01-07 19:23:57 +00:00
colmat [ 2 ] = colmat [ 5 ] = colmat [ 8 ] = 1.0f ;
cbufid = 7 ;
break ;
2010-10-19 22:24:27 +00:00
}
}
2011-02-26 23:41:02 +00:00
else if ( isIntensity )
2010-10-19 22:24:27 +00:00
{
fConstAdd [ 0 ] = fConstAdd [ 1 ] = fConstAdd [ 2 ] = 16.0f / 255.0f ;
2011-02-26 23:41:02 +00:00
switch ( dstFormat )
2010-10-19 22:24:27 +00:00
{
case 0 : // I4
case 1 : // I8
case 2 : // IA4
case 3 : // IA8
2010-12-18 18:23:22 +00:00
case 8 : // I8
2010-10-19 22:24:27 +00:00
// TODO - verify these coefficients
colmat [ 0 ] = 0.257f ; colmat [ 1 ] = 0.504f ; colmat [ 2 ] = 0.098f ;
colmat [ 4 ] = 0.257f ; colmat [ 5 ] = 0.504f ; colmat [ 6 ] = 0.098f ;
colmat [ 8 ] = 0.257f ; colmat [ 9 ] = 0.504f ; colmat [ 10 ] = 0.098f ;
2011-02-26 23:41:02 +00:00
if ( dstFormat < 2 || dstFormat == 8 )
2010-10-19 22:24:27 +00:00
{
colmat [ 12 ] = 0.257f ; colmat [ 13 ] = 0.504f ; colmat [ 14 ] = 0.098f ;
2011-01-07 19:23:57 +00:00
fConstAdd [ 3 ] = 16.0f / 255.0f ;
2011-02-26 23:41:02 +00:00
if ( dstFormat == 0 )
2011-01-07 19:23:57 +00:00
{
ColorMask [ 0 ] = ColorMask [ 1 ] = ColorMask [ 2 ] = 15.0f ;
ColorMask [ 4 ] = ColorMask [ 5 ] = ColorMask [ 6 ] = 1.0f / 15.0f ;
cbufid = 8 ;
}
else
{
cbufid = 9 ;
}
2010-10-19 22:24:27 +00:00
}
else // alpha
{
colmat [ 15 ] = 1 ;
2011-02-26 23:41:02 +00:00
if ( dstFormat == 2 )
2011-01-07 19:23:57 +00:00
{
ColorMask [ 0 ] = ColorMask [ 1 ] = ColorMask [ 2 ] = ColorMask [ 3 ] = 15.0f ;
ColorMask [ 4 ] = ColorMask [ 5 ] = ColorMask [ 6 ] = ColorMask [ 7 ] = 1.0f / 15.0f ;
cbufid = 10 ;
}
else
{
cbufid = 11 ;
}
2010-10-19 22:24:27 +00:00
}
break ;
default :
2011-02-26 23:41:02 +00:00
ERROR_LOG ( VIDEO , "Unknown copy intensity format: 0x%x" , dstFormat );
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 5 ] = colmat [ 10 ] = colmat [ 15 ] = 1.0f ;
cbufid = 23 ;
2010-10-19 22:24:27 +00:00
break ;
}
}
else
{
2011-02-26 23:41:02 +00:00
switch ( dstFormat )
2010-10-19 22:24:27 +00:00
{
case 0 : // R4
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 4 ] = colmat [ 8 ] = colmat [ 12 ] = 1 ;
ColorMask [ 0 ] = 15.0f ;
ColorMask [ 4 ] = 1.0f / 15.0f ;
cbufid = 12 ;
break ;
2010-12-18 18:23:22 +00:00
case 1 : // R8
2010-10-19 22:24:27 +00:00
case 8 : // R8
colmat [ 0 ] = colmat [ 4 ] = colmat [ 8 ] = colmat [ 12 ] = 1 ;
2011-01-07 19:23:57 +00:00
cbufid = 13 ;
2010-10-19 22:24:27 +00:00
break ;
case 2 : // RA4
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 4 ] = colmat [ 8 ] = colmat [ 15 ] = 1.0f ;
ColorMask [ 0 ] = ColorMask [ 3 ] = 15.0f ;
ColorMask [ 4 ] = ColorMask [ 7 ] = 1.0f / 15.0f ;
cbufid = 14 ;
break ;
2010-10-19 22:24:27 +00:00
case 3 : // RA8
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 4 ] = colmat [ 8 ] = colmat [ 15 ] = 1.0f ;
cbufid = 15 ;
2010-10-19 22:24:27 +00:00
break ;
case 7 : // A8
2011-01-07 19:23:57 +00:00
colmat [ 3 ] = colmat [ 7 ] = colmat [ 11 ] = colmat [ 15 ] = 1.0f ;
cbufid = 16 ;
2010-10-19 22:24:27 +00:00
break ;
case 9 : // G8
2011-01-07 19:23:57 +00:00
colmat [ 1 ] = colmat [ 5 ] = colmat [ 9 ] = colmat [ 13 ] = 1.0f ;
cbufid = 17 ;
2010-10-19 22:24:27 +00:00
break ;
case 10 : // B8
2011-01-07 19:23:57 +00:00
colmat [ 2 ] = colmat [ 6 ] = colmat [ 10 ] = colmat [ 14 ] = 1.0f ;
cbufid = 18 ;
2010-10-19 22:24:27 +00:00
break ;
case 11 : // RG8
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 4 ] = colmat [ 8 ] = colmat [ 13 ] = 1.0f ;
cbufid = 19 ;
2010-10-19 22:24:27 +00:00
break ;
case 12 : // GB8
2011-01-07 19:23:57 +00:00
colmat [ 1 ] = colmat [ 5 ] = colmat [ 9 ] = colmat [ 14 ] = 1.0f ;
cbufid = 20 ;
2010-10-19 22:24:27 +00:00
break ;
case 4 : // RGB565
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 5 ] = colmat [ 10 ] = 1.0f ;
ColorMask [ 0 ] = ColorMask [ 2 ] = 31.0f ;
ColorMask [ 4 ] = ColorMask [ 6 ] = 1.0f / 31.0f ;
ColorMask [ 1 ] = 63.0f ;
ColorMask [ 5 ] = 1.0f / 63.0f ;
fConstAdd [ 3 ] = 1.0f ; // set alpha to 1
cbufid = 21 ;
2010-10-19 22:24:27 +00:00
break ;
case 5 : // RGB5A3
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 5 ] = colmat [ 10 ] = colmat [ 15 ] = 1.0f ;
ColorMask [ 0 ] = ColorMask [ 1 ] = ColorMask [ 2 ] = 31.0f ;
ColorMask [ 4 ] = ColorMask [ 5 ] = ColorMask [ 6 ] = 1.0f / 31.0f ;
ColorMask [ 3 ] = 7.0f ;
ColorMask [ 7 ] = 1.0f / 7.0f ;
cbufid = 22 ;
break ;
2010-10-19 22:24:27 +00:00
case 6 : // RGBA8
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 5 ] = colmat [ 10 ] = colmat [ 15 ] = 1.0f ;
cbufid = 23 ;
2010-10-19 22:24:27 +00:00
break ;
default :
2011-02-26 23:41:02 +00:00
ERROR_LOG ( VIDEO , "Unknown copy color format: 0x%x" , dstFormat );
2011-01-07 19:23:57 +00:00
colmat [ 0 ] = colmat [ 5 ] = colmat [ 10 ] = colmat [ 15 ] = 1.0f ;
cbufid = 23 ;
2010-10-19 22:24:27 +00:00
break ;
}
}
2011-02-26 23:41:02 +00:00
const unsigned int tex_w = scaleByHalf ? srcRect . GetWidth () / 2 : srcRect . GetWidth ();
const unsigned int tex_h = scaleByHalf ? srcRect . GetHeight () / 2 : srcRect . GetHeight ();
2010-10-19 22:24:27 +00:00
2010-12-10 15:54:14 +00:00
unsigned int scaled_tex_w = g_ActiveConfig . bCopyEFBScaled ? Renderer :: EFBToScaledX ( tex_w ) : tex_w ;
unsigned int scaled_tex_h = g_ActiveConfig . bCopyEFBScaled ? Renderer :: EFBToScaledY ( tex_h ) : tex_h ;
2010-10-19 22:24:27 +00:00
2011-02-26 23:41:02 +00:00
TCacheEntryBase * entry = textures [ dstAddr ];
2010-10-19 22:24:27 +00:00
if ( entry )
{
2012-06-03 13:02:11 +02:00
if ( entry -> type == TCET_EC_DYNAMIC && entry -> native_width == tex_w && entry -> native_height == tex_h )
2010-10-19 22:24:27 +00:00
{
2012-06-03 13:02:11 +02:00
scaled_tex_w = tex_w ;
scaled_tex_h = tex_h ;
2010-10-19 22:24:27 +00:00
}
2012-06-03 13:02:11 +02:00
else if ( ! ( entry -> type == TCET_EC_VRAM && entry -> virtual_width == scaled_tex_w && entry -> virtual_height == scaled_tex_h ))
2010-10-19 22:24:27 +00:00
{
// remove it and recreate it as a render target
delete entry ;
entry = NULL ;
}
}
if ( NULL == entry )
{
// create the texture
2011-02-26 23:41:02 +00:00
textures [ dstAddr ] = entry = g_texture_cache -> CreateRenderTargetTexture ( scaled_tex_w , scaled_tex_h );
2010-10-19 22:24:27 +00:00
2011-12-26 18:05:01 +01:00
// TODO: Using the wrong dstFormat, dumb...
2012-12-17 19:31:52 +01:00
entry -> SetGeneralParameters ( dstAddr , 0 , dstFormat , 1 );
2011-12-26 18:05:01 +01:00
entry -> SetDimensions ( tex_w , tex_h , scaled_tex_w , scaled_tex_h );
entry -> SetHashes ( TEXHASH_INVALID );
2012-01-29 20:24:23 +01:00
entry -> type = TCET_EC_VRAM ;
2010-10-19 22:24:27 +00:00
}
entry -> frameCount = frameCount ;
2010-11-18 02:21:26 +00:00
g_renderer -> ResetAPIState (); // reset any game specific settings
2010-10-19 22:24:27 +00:00
2011-02-26 23:41:02 +00:00
entry -> FromRenderTarget ( dstAddr , dstFormat , srcFormat , srcRect , isIntensity , scaleByHalf , cbufid , colmat );
2010-10-19 22:24:27 +00:00
2010-11-18 02:21:26 +00:00
g_renderer -> RestoreAPIState ();
2010-12-27 22:17:19 +00:00
}