Files
itgmania212121/stepmania/src/BannerCache.cpp
T

430 lines
13 KiB
C++
Raw Normal View History

2003-06-15 01:56:27 +00:00
/*
* BannerCache
*
* Maintains a cache of reduced-quality banners.
*/
2003-06-04 22:34:08 +00:00
#include "global.h"
2003-07-22 07:47:27 +00:00
#include "arch/arch.h"
2003-06-04 22:34:08 +00:00
#include "RageDisplay.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "BannerCache.h"
#include "Sprite.h"
#include "PrefsManager.h"
2003-06-04 22:34:08 +00:00
#include "SDL_utils.h"
#include "SDL_dither.h"
#include "SDL_image.h"
#include "SDL_rotozoom.h"
#include "Banner.h"
2003-07-22 07:47:27 +00:00
#define CACHE_DIR BASE_PATH "Cache" SLASH
2003-06-15 00:57:05 +00:00
/* Call CacheBanner to cache a banner by path. If the banner is already
* cached, it'll be recreated. This is efficient if the banner hasn't changed,
* but we still only do this in TidyUpData for songs.
2003-06-11 01:49:01 +00:00
*
2003-06-15 00:57:05 +00:00
* Call LoadBanner to load a cached banner into main memory. This will call
* CacheBanner only if needed. This will not do a date/size check; call CacheBanner
* directly if you need that.
2003-06-11 01:49:01 +00:00
*
2003-06-15 00:57:05 +00:00
* Call LoadCachedBanner to load a banner into a texture and retrieve an ID
2003-06-11 01:49:01 +00:00
* for it. You can check if the banner was actually preloaded by calling
* TEXTUREMAN->IsTextureRegistered() on the ID; it might not be if the banner cache
* is missing or disabled.
2003-06-14 23:30:21 +00:00
*
2003-06-15 00:57:05 +00:00
* Note that each cache entries has two hashes. The cache path is based soley
* on the pathname; this way, loading the cache doesn't have to do a stat on every
* banner. The full hash includes the file size and date, and is used only by
* CacheBanner to avoid doing extra work.
*/
2003-06-14 23:30:21 +00:00
2003-06-04 22:34:08 +00:00
BannerCache *BANNERCACHE;
2003-06-14 23:44:24 +00:00
static map<CString,SDL_Surface *> m_BannerPathToImage;
2003-06-04 22:34:08 +00:00
CString BannerCache::GetBannerCachePath( CString BannerPath )
{
2003-06-15 00:57:05 +00:00
/* Use GetHashForString, not ForFile, since we don't want to spend time
* checking the file size and date. */
2003-07-22 07:47:27 +00:00
return ssprintf( CACHE_DIR "Banners" SLASH "%u", GetHashForString(BannerPath) );
2003-06-04 22:34:08 +00:00
}
2003-06-15 00:57:05 +00:00
void BannerCache::LoadBanner( CString BannerPath )
2003-06-04 22:34:08 +00:00
{
2003-06-15 02:19:58 +00:00
if( !PREFSMAN->m_bBannerCache || BannerPath == "" )
return;
2003-06-15 00:57:05 +00:00
/* Load it. */
const CString CachePath = GetBannerCachePath(BannerPath);
for( int tries = 0; tries < 2; ++tries )
2003-06-04 22:34:08 +00:00
{
if( m_BannerPathToImage.find(BannerPath) != m_BannerPathToImage.end() )
2003-06-15 00:57:05 +00:00
return; /* already loaded */
2003-06-04 22:34:08 +00:00
CHECKPOINT_M( ssprintf( "BannerCache::LoadBanner: %s", CachePath.c_str() ) );
2003-06-04 22:34:08 +00:00
SDL_Surface *img = mySDL_LoadSurface( CachePath );
if( img == NULL )
{
2003-06-15 00:57:05 +00:00
if(tries == 0)
{
/* The file doesn't exist. It's possible that the banner cache file is
* missing, so try to create it. Don't do this first, for efficiency. */
LOG->Trace( "Cached banner load of '%s' ('%s') failed, trying to cache ...", BannerPath.c_str(), CachePath.c_str() );
2003-06-15 01:53:51 +00:00
/* Skip the up-to-date check; it failed to load, so it can't be up
* to date. */
CacheBannerInternal( BannerPath );
2003-06-15 00:57:05 +00:00
continue;
}
else
{
LOG->Trace( "Cached banner load of '%s' ('%s') failed", BannerPath.c_str(), CachePath.c_str() );
return;
}
2003-06-04 22:34:08 +00:00
}
m_BannerPathToImage[BannerPath] = img;
}
2003-06-15 00:57:05 +00:00
}
2003-06-15 00:57:05 +00:00
void BannerCache::OutputStats() const
{
2003-06-14 23:21:47 +00:00
map<CString,SDL_Surface *>::const_iterator ban;
int total_size = 0;
for( ban = m_BannerPathToImage.begin(); ban != m_BannerPathToImage.end(); ++ban )
{
2003-06-14 23:21:47 +00:00
SDL_Surface * const &img = ban->second;
const int size = img->pitch * img->h;
total_size += size;
}
LOG->Info( "%i bytes of banners loaded", total_size );
2003-06-04 22:34:08 +00:00
}
void BannerCache::UnloadAllBanners()
2003-06-04 22:34:08 +00:00
{
map<CString,SDL_Surface *>::iterator it;
for( it = m_BannerPathToImage.begin(); it != m_BannerPathToImage.end(); ++it )
SDL_FreeSurface(it->second);
2003-06-04 22:34:08 +00:00
m_BannerPathToImage.clear();
}
BannerCache::BannerCache()
{
2003-07-22 07:47:27 +00:00
CreateDirectories( CACHE_DIR "Banners" SLASH );
BannerData.SetPath( CACHE_DIR "banners.cache" );
BannerData.ReadFile(); // don't care if this fails
}
BannerCache::~BannerCache()
{
UnloadAllBanners();
2003-06-04 22:34:08 +00:00
}
#include "SDL_utils.h"
#include "RageDisplay.h"
#include "RageTexture.h"
#include "RageTextureManager.h"
struct BannerTexture: public RageTexture
{
unsigned m_uTexHandle;
2003-06-22 01:28:23 +00:00
unsigned GetTexHandle() const { return m_uTexHandle; }; // accessed by RageDisplay
2003-06-07 02:11:31 +00:00
/* This is a reference to a pointer in m_BannerPathToImage. */
SDL_Surface *&img;
int width, height;
2003-06-04 22:34:08 +00:00
2003-06-07 02:11:31 +00:00
BannerTexture( RageTextureID name, SDL_Surface *&img_, int width_, int height_ ):
RageTexture(name), img(img_), width(width_), height(height_)
{
Create();
}
~BannerTexture()
{
Destroy();
}
void Create()
2003-06-04 22:34:08 +00:00
{
2003-07-14 22:24:32 +00:00
ASSERT( img );
2003-06-04 22:34:08 +00:00
/* The image is preprocessed; do as little work as possible. */
/* The source width is the width of the original file. */
m_iSourceWidth = width;
m_iSourceHeight = height;
/* The image width (within the texture) is always the entire texture.
* Only resize if the max texture size requires it; since these images
* are already scaled down, this shouldn't happen often. */
if( img->w > DISPLAY->GetMaxTextureSize() ||
img->h > DISPLAY->GetMaxTextureSize() )
{
2003-06-07 02:11:31 +00:00
LOG->Warn("Converted %s at runtime", GetID().filename.c_str() );
2003-06-04 22:34:08 +00:00
ConvertSDLSurface(img, img->w, img->h, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
2003-07-14 22:24:32 +00:00
zoomSurface(img, min( img->w, DISPLAY->GetMaxTextureSize() ),
min( img->h, DISPLAY->GetMaxTextureSize() ));
2003-06-04 22:34:08 +00:00
}
2003-07-14 22:24:32 +00:00
/* We did this when we cached it. */
ASSERT( img->w == power_of_two(img->w) );
ASSERT( img->h == power_of_two(img->h) );
2003-06-04 22:34:08 +00:00
m_iTextureWidth = m_iImageWidth = img->w;
m_iTextureHeight = m_iImageHeight = img->h;
/* Find a supported texture format. If it happens to match the stored
* file, we won't have to do any conversion here, and that'll happen often
* with paletted images. */
2003-10-03 21:38:38 +00:00
RageDisplay::PixelFormat pf = img->format->BitsPerPixel == 8? RageDisplay::FMT_PAL: RageDisplay::FMT_RGB5A1;
2003-06-04 22:34:08 +00:00
if( !DISPLAY->SupportsTextureFormat(pf) )
2003-10-03 21:38:38 +00:00
pf = RageDisplay::FMT_RGBA4;
2003-06-04 22:34:08 +00:00
ASSERT( DISPLAY->SupportsTextureFormat(pf) );
ASSERT(img);
2003-06-30 03:12:36 +00:00
m_uTexHandle = DISPLAY->CreateTexture( pf, img );
2003-06-04 22:34:08 +00:00
CreateFrameRects();
}
2003-06-07 02:11:31 +00:00
void Destroy()
{
2003-07-09 04:29:23 +00:00
if( m_uTexHandle )
DISPLAY->DeleteTexture( m_uTexHandle );
m_uTexHandle = 0;
2003-06-04 22:34:08 +00:00
}
2003-06-07 02:11:31 +00:00
void Reload()
{
Destroy();
Create();
}
void Invalidate()
{
m_uTexHandle = 0; /* don't Destroy() */
}
2003-06-04 22:34:08 +00:00
};
2003-06-15 00:57:05 +00:00
RageTextureID BannerCache::LoadCachedBanner( CString BannerPath )
2003-06-04 22:34:08 +00:00
{
RageTextureID ID( GetBannerCachePath(BannerPath) );
2003-06-15 02:19:58 +00:00
if( BannerPath == "" )
return ID;
2003-06-15 00:57:05 +00:00
LOG->Trace("BannerCache::LoadCachedBanner(%s): %s", BannerPath.c_str(), ID.filename.c_str() );
2003-06-04 22:34:08 +00:00
/* Hack: make sure Banner::Load doesn't change our return value and end up
* reloading. */
ID = Banner::BannerTex(ID);
/* It's not in a texture. Do we have it loaded? */
if( m_BannerPathToImage.find(BannerPath) == m_BannerPathToImage.end() )
{
/* Oops, the image is missing. Warn and continue. */
LOG->Warn( "Banner cache for '%s' wasn't loaded", BannerPath.c_str() );
return ID;
}
/* This is a reference to a pointer. BannerTexture's ctor may change it
* when converting; this way, the conversion will end up in the map so we
* only have to convert once. */
SDL_Surface *&img = m_BannerPathToImage[BannerPath];
ASSERT( img );
int src_width = 0, src_height = 0;
bool WasRotatedBanner = false;
2003-10-02 02:03:29 +00:00
BannerData.GetValue( BannerPath, "Width", src_width );
BannerData.GetValue( BannerPath, "Height", src_height );
BannerData.GetValue( BannerPath, "Rotated", WasRotatedBanner );
2003-06-04 22:34:08 +00:00
if(src_width == 0 || src_height == 0)
{
LOG->Warn("Couldn't load '%s'", BannerPath.c_str() );
return ID;
}
if( WasRotatedBanner )
{
/* We need to tell Sprite that this was originally a rotated
2003-06-04 22:34:08 +00:00
* sprite. */
ID.filename += "(was rotated)";
}
/* Is the banner already in a texture? */
if( TEXTUREMAN->IsTextureRegistered(ID) )
return ID; /* It's all set. */
2003-07-14 22:24:32 +00:00
LOG->Trace("Loading banner texture %s; src %ix%i; image %ix%i",
ID.filename.c_str(), src_width, src_height, img->w, img->h );
2003-06-04 22:34:08 +00:00
RageTexture *pTexture = new BannerTexture( ID, img, src_width, src_height );
TEXTUREMAN->RegisterTexture( ID, pTexture );
2003-09-06 05:23:26 +00:00
pTexture->m_Policy = RageTexture::TEX_VOLATILE;
2003-07-10 01:58:48 +00:00
TEXTUREMAN->UnloadTexture( pTexture );
2003-06-04 22:34:08 +00:00
return ID;
}
static inline int closest( int num, int n1, int n2 )
{
if( abs(num - n1) > abs(num - n2) )
return n2;
return n1;
}
/* We write the cache even if we won't use it, so we don't have to recache everything
* if the memory or settings change. */
2003-06-15 00:57:05 +00:00
void BannerCache::CacheBanner( CString BannerPath )
2003-06-04 22:34:08 +00:00
{
2003-06-15 01:53:51 +00:00
if( !DoesFileExist(BannerPath) )
return;
2003-06-15 00:57:05 +00:00
const CString CachePath = GetBannerCachePath(BannerPath);
2003-06-15 01:53:51 +00:00
/* Check the full file hash. If it's the loaded and identical, don't recache. */
2003-06-15 00:57:05 +00:00
if( DoesFileExist(CachePath) )
{
unsigned CurFullHash;
2003-06-15 01:53:51 +00:00
const unsigned FullHash = GetHashForFile( BannerPath );
2003-10-02 02:03:29 +00:00
if( BannerData.GetValue( BannerPath, "FullHash", CurFullHash ) &&
2003-06-15 00:57:05 +00:00
CurFullHash == FullHash )
2003-06-15 01:53:51 +00:00
{
/* It's identical. Just load it. */
LoadBanner( BannerPath );
2003-06-15 00:57:05 +00:00
return;
2003-06-15 01:53:51 +00:00
}
2003-06-15 00:57:05 +00:00
}
2003-06-15 01:53:51 +00:00
CacheBannerInternal( BannerPath );
}
void BannerCache::CacheBannerInternal( CString BannerPath )
{
2003-06-04 22:34:08 +00:00
SDL_Surface *img = IMG_Load( BannerPath );
if(img == NULL)
2003-06-15 00:57:05 +00:00
RageException::Throw( "BannerCache::CacheBanner: Couldn't load %s: %s", BannerPath.c_str(), SDL_GetError() );
2003-06-04 22:34:08 +00:00
bool WasRotatedBanner = false;
if( Sprite::IsDiagonalBanner(img->w , img->h) )
2003-06-04 22:34:08 +00:00
{
/* Ack. It's a diagonal banner. Problem: if we resize a diagonal banner, we
* get ugly checker patterns. We need to un-rotate it.
*
* If we spin the banner by hand, we need to do a linear filter, or the
* fade to the full resolution banner is misaligned, which looks strange.
*
* To do a linear filter, we need to lose the palette. Oh well.
*
* This also makes the banner take less memory, though that could also be
* done by RLEing the surface.
*/
2003-07-14 22:42:12 +00:00
int color = mySDL_MapRGBExact(img->format, 0xFF, 0, 0xFF);
if( color != -1 )
SDL_SetColorKey( img, SDL_SRCCOLORKEY, color );
2003-06-04 22:34:08 +00:00
ConvertSDLSurface(img, img->w, img->h, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
SDL_Surface *dst = SDL_CreateRGBSurface(
SDL_SWSURFACE, 256, 64, img->format->BitsPerPixel,
img->format->Rmask, img->format->Gmask, img->format->Bmask, img->format->Amask );
if( img->format->BitsPerPixel == 8 )
{
ASSERT( img->format->palette );
mySDL_SetPalette(dst, img->format->palette->colors, 0, 256);
}
if( img->flags & SDL_SRCCOLORKEY )
SDL_SetColorKey( dst, SDL_SRCCOLORKEY, img->format->colorkey);
const float fCustomImageCoords[8] = {
0.02f, 0.78f, // top left
0.22f, 0.98f, // bottom left
0.98f, 0.22f, // bottom right
0.78f, 0.02f, // top right
};
mySDL_BlitTransform( img, dst, fCustomImageCoords );
// SDL_SaveBMP( dst, BannerPath + "-test.bmp" );
SDL_FreeSurface( img );
img = dst;
WasRotatedBanner = true;
}
const int src_width = img->w, src_height = img->h;
int width = img->w / 2, height = img->h / 2;
// int width = img->w, height = img->h;
/* Round to the nearest power of two. This simplifies the actual texture load. */
width = closest(width, power_of_two(width), power_of_two(width) / 2);
height = closest(height, power_of_two(height), power_of_two(height) / 2);
/* Don't resize the image to less than 32 pixels in either dimension or the next
* power of two of the source (whichever is smaller); it's already very low res. */
width = max( width, min(32, power_of_two(src_width)) );
height = max( height, min(32, power_of_two(src_height)) );
{
const int color = mySDL_MapRGBExact(img->format, 0xFF, 0, 0xFF);
if( color != -1 )
SDL_SetColorKey( img, SDL_SRCCOLORKEY, color );
}
{
ConvertSDLSurface(img, img->w, img->h, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
zoomSurface(img, width, height);
2003-06-30 03:14:13 +00:00
/* Dither to the final format. We use A1RGB5, since that's usually supported
* natively by both OpenGL and D3D. */
2003-06-04 22:34:08 +00:00
SDL_Surface *dst = SDL_CreateRGBSurfaceSane(SDL_SWSURFACE, img->w, img->h,
2003-06-30 03:14:13 +00:00
16, 0x7C00, 0x03E0, 0x001F, 0x8000 );
2003-06-04 22:34:08 +00:00
SM_SDL_ErrorDiffusionDither(img, dst);
SDL_FreeSurface( img );
img = dst;
}
2003-06-15 01:53:51 +00:00
const CString CachePath = GetBannerCachePath(BannerPath);
2003-06-14 23:44:24 +00:00
mySDL_SaveSurface( img, CachePath );
2003-06-04 22:34:08 +00:00
if( PREFSMAN->m_bBannerCache )
2003-06-15 00:57:05 +00:00
{
/* If an old image is loaded, free it. */
if( m_BannerPathToImage.find(BannerPath) != m_BannerPathToImage.end() )
{
2003-07-09 04:20:37 +00:00
SDL_Surface *oldimg = m_BannerPathToImage[BannerPath];
SDL_FreeSurface( oldimg );
2003-06-15 00:57:05 +00:00
m_BannerPathToImage.erase(BannerPath);
}
/* Keep it; we're just going to load it anyway. */
m_BannerPathToImage[BannerPath] = img;
2003-06-15 00:57:05 +00:00
}
else
SDL_FreeSurface(img);
2003-06-04 22:34:08 +00:00
/* Remember the original size. */
2003-10-02 02:11:47 +00:00
BannerData.SetValue( BannerPath, "Path", CachePath );
BannerData.SetValue( BannerPath, "Width", src_width );
BannerData.SetValue( BannerPath, "Height", src_height );
BannerData.SetValue( BannerPath, "FullHash", GetHashForFile( BannerPath ) );
/* Remember this, so we can hint Sprite. */
2003-10-02 02:11:47 +00:00
BannerData.SetValue( BannerPath, "Rotated", WasRotatedBanner );
2003-06-04 22:34:08 +00:00
BannerData.WriteFile();
}
2003-06-15 01:56:27 +00:00
/*
Copyright (c) 2003 by the person(s) listed below. All rights reserved.
Glenn Maynard
*/