2011-03-17 01:47:30 -04:00
|
|
|
#include "global.h"
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
#include "ImageCache.h"
|
2011-03-17 01:47:30 -04:00
|
|
|
#include "RageDisplay.h"
|
|
|
|
|
#include "RageUtil.h"
|
|
|
|
|
#include "RageLog.h"
|
|
|
|
|
#include "RageSurface_Load.h"
|
|
|
|
|
#include "SongCacheIndex.h"
|
|
|
|
|
#include "Sprite.h"
|
|
|
|
|
#include "PrefsManager.h"
|
|
|
|
|
#include "RageDisplay.h"
|
|
|
|
|
#include "RageTexture.h"
|
|
|
|
|
#include "RageTextureManager.h"
|
|
|
|
|
#include "RageSurface.h"
|
|
|
|
|
#include "RageSurfaceUtils.h"
|
|
|
|
|
#include "RageSurfaceUtils_Palettize.h"
|
|
|
|
|
#include "RageSurfaceUtils_Dither.h"
|
|
|
|
|
#include "RageSurfaceUtils_Zoom.h"
|
|
|
|
|
#include "SpecialFiles.h"
|
|
|
|
|
#include "Banner.h"
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
#include <cmath>
|
2023-04-19 23:04:25 +02:00
|
|
|
#include <cstddef>
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
static Preference<bool> g_bPalettedImageCache( "PalettedImageCache", false );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
/* Neither a global or a file scope static can be used for this because
|
|
|
|
|
* the order of initialization of nonlocal objects is unspecified. */
|
2017-09-19 23:49:42 +02:00
|
|
|
//const RString IMAGE_CACHE_INDEX = SpecialFiles::CACHE_DIR + "images.cache";
|
|
|
|
|
#define IMAGE_CACHE_INDEX (SpecialFiles::CACHE_DIR + "images.cache")
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* Call CacheImage to cache a image by path. If the image is already
|
|
|
|
|
* cached, it'll be recreated. This is efficient if the image hasn't changed,
|
2011-03-17 01:47:30 -04:00
|
|
|
* but we still only do this in TidyUpData for songs.
|
|
|
|
|
*
|
2017-09-19 23:49:42 +02:00
|
|
|
* Call LoadImage to load a cached image into main memory. This will call
|
|
|
|
|
* CacheImage only if needed. This will not do a date/size check; call CacheImage
|
2011-03-17 01:47:30 -04:00
|
|
|
* directly if you need that.
|
|
|
|
|
*
|
2017-09-19 23:49:42 +02:00
|
|
|
* Call LoadCachedImage to load a image into a texture and retrieve an ID
|
|
|
|
|
* for it. You can check if the image was actually preloaded by calling
|
|
|
|
|
* TEXTUREMAN->IsTextureRegistered() on the ID; it might not be if the image cache
|
2011-03-17 01:47:30 -04:00
|
|
|
* is missing or disabled.
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2017-09-19 23:49:42 +02:00
|
|
|
* image. The full hash includes the file size and date, and is used only by
|
|
|
|
|
* CacheImage to avoid doing extra work.
|
2011-03-17 01:47:30 -04:00
|
|
|
*/
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
ImageCache *IMAGECACHE; // global and accessible from anywhere in our program
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
|
2022-07-10 18:28:56 +03:00
|
|
|
static std::map<RString, RageSurface*> g_ImagePathToImage;
|
2011-03-17 01:47:30 -04:00
|
|
|
static int g_iDemandRefcount = 0;
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
RString ImageCache::GetImageCachePath( RString sImageDir ,RString sImagePath )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
return SongCacheIndex::GetCacheFilePath( sImageDir, sImagePath );
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* If in on-demand mode, load all cached images. This must be fast, so
|
2011-03-17 01:47:30 -04:00
|
|
|
* cache files will not be created if they don't exist; that should be done
|
2017-09-19 23:49:42 +02:00
|
|
|
* by CacheImage or LoadImage on startup. */
|
|
|
|
|
void ImageCache::Demand( RString sImageDir )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
++g_iDemandRefcount;
|
|
|
|
|
if( g_iDemandRefcount > 1 )
|
|
|
|
|
return;
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
if( PREFSMAN->m_ImageCache != IMGCACHE_LOW_RES_LOAD_ON_DEMAND )
|
2011-03-17 01:47:30 -04:00
|
|
|
return;
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
FOREACH_CONST_Child( &ImageData, p )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
RString sImagePath = p->GetName();
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
if( g_ImagePathToImage.find(sImagePath) != g_ImagePathToImage.end() )
|
2011-03-17 01:47:30 -04:00
|
|
|
continue; /* already loaded */
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
const RString sCachePath = GetImageCachePath(sImageDir,sImagePath);
|
2011-03-17 01:47:30 -04:00
|
|
|
RageSurface *pImage = RageSurfaceUtils::LoadSurface( sCachePath );
|
2019-06-22 12:35:38 -07:00
|
|
|
if( pImage == nullptr )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
continue; /* doesn't exist */
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
g_ImagePathToImage[sImagePath] = pImage;
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* Release images loaded on demand. */
|
|
|
|
|
void ImageCache::Undemand( RString sImageDir )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
--g_iDemandRefcount;
|
|
|
|
|
if( g_iDemandRefcount != 0 )
|
|
|
|
|
return;
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
if( PREFSMAN->m_ImageCache != IMGCACHE_LOW_RES_LOAD_ON_DEMAND )
|
2011-03-17 01:47:30 -04:00
|
|
|
return;
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
UnloadAllImages();
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* If in a low-res image mode, load a low-res image into memory, creating
|
|
|
|
|
* the cache file if necessary. Unlike CacheImage(), the original file will
|
|
|
|
|
* not be examined unless the cached image doesn't exist, so the image will
|
2011-03-17 01:47:30 -04:00
|
|
|
* not be updated if the original file changes, for efficiency. */
|
2017-09-19 23:49:42 +02:00
|
|
|
void ImageCache::LoadImage( RString sImageDir, RString sImagePath )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
if( sImagePath == "" )
|
2011-03-17 01:47:30 -04:00
|
|
|
return; // nothing to do
|
2017-09-19 23:49:42 +02:00
|
|
|
if( PREFSMAN->m_ImageCache != IMGCACHE_LOW_RES_PRELOAD &&
|
|
|
|
|
PREFSMAN->m_ImageCache != IMGCACHE_LOW_RES_LOAD_ON_DEMAND )
|
2011-03-17 01:47:30 -04:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* Load it. */
|
2017-09-19 23:49:42 +02:00
|
|
|
const RString sCachePath = GetImageCachePath(sImageDir,sImagePath);
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
for( int tries = 0; tries < 2; ++tries )
|
|
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
if( g_ImagePathToImage.find(sImagePath) != g_ImagePathToImage.end() )
|
2011-03-17 01:47:30 -04:00
|
|
|
return; /* already loaded */
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
CHECKPOINT_M( ssprintf( "ImageCache::LoadImage: %s", sCachePath.c_str() ) );
|
2011-03-17 01:47:30 -04:00
|
|
|
RageSurface *pImage = RageSurfaceUtils::LoadSurface( sCachePath );
|
2019-06-22 12:35:38 -07:00
|
|
|
if( pImage == nullptr )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
if( tries == 0 )
|
|
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
/* The file doesn't exist. It's possible that the image cache file is
|
2011-03-17 01:47:30 -04:00
|
|
|
* missing, so try to create it. Don't do this first, for efficiency. */
|
2017-09-19 23:49:42 +02:00
|
|
|
//LOG->Trace( "Cached image load of '%s' ('%s') failed, trying to cache ...", sImagePath.c_str(), sCachePath.c_str() );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
/* Skip the up-to-date check; it failed to load, so it can't be up
|
|
|
|
|
* to date. */
|
2017-09-19 23:49:42 +02:00
|
|
|
CacheImageInternal( sImageDir, sImagePath );
|
2011-03-17 01:47:30 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
//LOG->Trace( "Cached image load of '%s' ('%s') failed", sImagePath.c_str(), sCachePath.c_str() );
|
2011-03-17 01:47:30 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
g_ImagePathToImage[sImagePath] = pImage;
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
void ImageCache::OutputStats() const
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
int iTotalSize = 0;
|
2019-06-22 12:35:38 -07:00
|
|
|
for (auto const &it : g_ImagePathToImage)
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2019-06-22 12:35:38 -07:00
|
|
|
const RageSurface *pImage = it.second;
|
2011-03-17 01:47:30 -04:00
|
|
|
const int iSize = pImage->pitch * pImage->h;
|
|
|
|
|
iTotalSize += iSize;
|
|
|
|
|
}
|
2017-09-19 23:49:42 +02:00
|
|
|
LOG->Info( "%i bytes of images loaded", iTotalSize );
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
void ImageCache::UnloadAllImages()
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2019-06-22 12:35:38 -07:00
|
|
|
for (auto &it: g_ImagePathToImage)
|
|
|
|
|
{
|
|
|
|
|
delete it.second;
|
|
|
|
|
}
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
g_ImagePathToImage.clear();
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
ImageCache::ImageCache()
|
2019-10-12 11:24:13 -04:00
|
|
|
: delay_save_cache(false)
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
ReadFromDisk();
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
ImageCache::~ImageCache()
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
UnloadAllImages();
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
void ImageCache::ReadFromDisk()
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
ImageData.ReadFile( IMAGE_CACHE_INDEX ); // don't care if this fails
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
struct ImageTexture: public RageTexture
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2019-10-03 08:58:14 +08:00
|
|
|
uintptr_t m_uTexHandle;
|
|
|
|
|
uintptr_t GetTexHandle() const { return m_uTexHandle; }; // accessed by RageDisplay
|
2017-09-19 23:49:42 +02:00
|
|
|
/* This is a reference to a pointer in g_ImagePathToImage. */
|
2011-03-17 01:47:30 -04:00
|
|
|
RageSurface *&m_pImage;
|
|
|
|
|
int m_iWidth, m_iHeight;
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
ImageTexture( RageTextureID id, RageSurface *&pImage, int iWidth, int iHeight ):
|
2011-03-17 01:47:30 -04:00
|
|
|
RageTexture(id), m_pImage(pImage), m_iWidth(iWidth), m_iHeight(iHeight)
|
|
|
|
|
{
|
|
|
|
|
Create();
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
~ImageTexture()
|
2023-04-19 14:22:59 +02:00
|
|
|
{
|
2011-03-17 01:47:30 -04:00
|
|
|
Destroy();
|
|
|
|
|
}
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2011-03-17 01:47:30 -04:00
|
|
|
void Create()
|
|
|
|
|
{
|
2019-06-22 12:35:38 -07:00
|
|
|
ASSERT( m_pImage != nullptr );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
/* The image is preprocessed; do as little work as possible. */
|
|
|
|
|
|
|
|
|
|
/* The source width is the width of the original file. */
|
|
|
|
|
m_iSourceWidth = m_iWidth;
|
|
|
|
|
m_iSourceHeight = m_iHeight;
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
/* The image width (within the texture) is always the entire texture.
|
2011-03-17 01:47:30 -04:00
|
|
|
* Only resize if the max texture size requires it; since these images
|
|
|
|
|
* are already scaled down, this shouldn't happen often. */
|
2023-04-19 14:22:59 +02:00
|
|
|
if( m_pImage->w > DISPLAY->GetMaxTextureSize() ||
|
2011-03-17 01:47:30 -04:00
|
|
|
m_pImage->h > DISPLAY->GetMaxTextureSize() )
|
|
|
|
|
{
|
|
|
|
|
LOG->Warn( "Converted %s at runtime", GetID().filename.c_str() );
|
2022-07-10 18:28:56 +03:00
|
|
|
int iWidth = std::min( m_pImage->w, DISPLAY->GetMaxTextureSize() );
|
|
|
|
|
int iHeight = std::min( m_pImage->h, DISPLAY->GetMaxTextureSize() );
|
2011-03-17 01:47:30 -04:00
|
|
|
RageSurfaceUtils::Zoom( m_pImage, iWidth, iHeight );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* We did this when we cached it. */
|
|
|
|
|
ASSERT( m_pImage->w == power_of_two(m_pImage->w) );
|
|
|
|
|
ASSERT( m_pImage->h == power_of_two(m_pImage->h) );
|
|
|
|
|
|
|
|
|
|
m_iTextureWidth = m_iImageWidth = m_pImage->w;
|
|
|
|
|
m_iTextureHeight = m_iImageHeight = m_pImage->h;
|
|
|
|
|
|
2011-03-19 19:28:57 -05:00
|
|
|
/* 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. */
|
2013-02-21 03:18:28 -05:00
|
|
|
RagePixelFormat pf = m_pImage->format->BitsPerPixel == 8? RagePixelFormat_PAL: RagePixelFormat_RGB5A1;
|
2011-03-17 01:47:30 -04:00
|
|
|
if( !DISPLAY->SupportsTextureFormat(pf) )
|
2013-02-21 03:18:28 -05:00
|
|
|
pf = RagePixelFormat_RGBA4;
|
2011-03-19 19:28:57 -05:00
|
|
|
|
2011-03-17 01:47:30 -04:00
|
|
|
ASSERT( DISPLAY->SupportsTextureFormat(pf) );
|
|
|
|
|
|
2019-06-22 12:35:38 -07:00
|
|
|
ASSERT(m_pImage != nullptr);
|
2011-03-17 01:47:30 -04:00
|
|
|
m_uTexHandle = DISPLAY->CreateTexture( pf, m_pImage, false );
|
|
|
|
|
|
|
|
|
|
CreateFrameRects();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Destroy()
|
|
|
|
|
{
|
|
|
|
|
if( m_uTexHandle )
|
|
|
|
|
DISPLAY->DeleteTexture( m_uTexHandle );
|
|
|
|
|
m_uTexHandle = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Reload()
|
|
|
|
|
{
|
|
|
|
|
Destroy();
|
|
|
|
|
Create();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Invalidate()
|
|
|
|
|
{
|
|
|
|
|
m_uTexHandle = 0; /* don't Destroy() */
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* If a image is cached, get its ID for use. */
|
|
|
|
|
RageTextureID ImageCache::LoadCachedImage( RString sImageDir, RString sImagePath )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
RageTextureID ID( GetImageCachePath(sImageDir,sImagePath) );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2023-04-19 23:04:25 +02:00
|
|
|
std::size_t Found = sImagePath.find("_blank");
|
2017-09-19 23:49:42 +02:00
|
|
|
if( sImagePath == "" || Found!=RString::npos )
|
2011-03-17 01:47:30 -04:00
|
|
|
return ID;
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
//LOG->Trace( "ImageCache::LoadCachedImage(%s): %s", sImagePath.c_str(), ID.filename.c_str() );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* Hack: make sure Image::Load doesn't change our return value and end up
|
2011-03-17 01:47:30 -04:00
|
|
|
* reloading. */
|
2017-09-19 23:49:42 +02:00
|
|
|
if(sImageDir == "Banner")
|
|
|
|
|
ID = Sprite::SongBannerTexture(ID);
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
/* It's not in a texture. Do we have it loaded? */
|
2017-09-19 23:49:42 +02:00
|
|
|
if( g_ImagePathToImage.find(sImagePath) == g_ImagePathToImage.end() )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
/* Oops, the image is missing. Warn and continue. */
|
2017-09-19 23:49:42 +02:00
|
|
|
if(PREFSMAN->m_ImageCache != IMGCACHE_OFF)
|
2015-11-01 12:04:32 -07:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
LOG->Warn( "Image cache for '%s' wasn't loaded", sImagePath.c_str() );
|
2015-11-01 12:04:32 -07:00
|
|
|
}
|
2011-03-17 01:47:30 -04:00
|
|
|
return ID;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* This is a reference to a pointer. ImageTexture's ctor may change it
|
2011-03-17 01:47:30 -04:00
|
|
|
* when converting; this way, the conversion will end up in the map so we
|
|
|
|
|
* only have to convert once. */
|
2017-09-19 23:49:42 +02:00
|
|
|
RageSurface *&pImage = g_ImagePathToImage[sImagePath];
|
2019-06-22 12:35:38 -07:00
|
|
|
ASSERT( pImage != nullptr );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
int iSourceWidth = 0, iSourceHeight = 0;
|
2017-09-19 23:49:42 +02:00
|
|
|
ImageData.GetValue( sImagePath, "Width", iSourceWidth );
|
|
|
|
|
ImageData.GetValue( sImagePath, "Height", iSourceHeight );
|
2011-03-17 01:47:30 -04:00
|
|
|
if( iSourceWidth == 0 || iSourceHeight == 0 )
|
|
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
LOG->UserLog( "Cache file", sImagePath, "couldn't be loaded." );
|
2011-03-17 01:47:30 -04:00
|
|
|
return ID;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* Is the image already in a texture? */
|
2011-03-17 01:47:30 -04:00
|
|
|
if( TEXTUREMAN->IsTextureRegistered(ID) )
|
|
|
|
|
return ID; /* It's all set. */
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
//LOG->Trace( "Loading image texture %s; src %ix%i; image %ix%i",
|
2016-02-10 17:14:17 -07:00
|
|
|
// ID.filename.c_str(), iSourceWidth, iSourceHeight, pImage->w, pImage->h );
|
2017-09-19 23:49:42 +02:00
|
|
|
RageTexture *pTexture = new ImageTexture( ID, pImage, iSourceWidth, iSourceHeight );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
ID.Policy = RageTextureID::TEX_VOLATILE;
|
|
|
|
|
TEXTUREMAN->RegisterTexture( ID, pTexture );
|
|
|
|
|
TEXTUREMAN->UnloadTexture( pTexture );
|
|
|
|
|
|
|
|
|
|
return ID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int closest( int num, int n1, int n2 )
|
|
|
|
|
{
|
2023-04-19 14:22:59 +02:00
|
|
|
if( std::abs(num - n1) > std::abs(num - n2) )
|
2011-03-17 01:47:30 -04:00
|
|
|
return n2;
|
|
|
|
|
return n1;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
/* Create or update the image cache file as necessary. If in preload mode,
|
2011-03-17 01:47:30 -04:00
|
|
|
* load the cache file, too. (This is done at startup.) */
|
2017-09-19 23:49:42 +02:00
|
|
|
void ImageCache::CacheImage( RString sImageDir, RString sImagePath )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
if( PREFSMAN->m_ImageCache != IMGCACHE_LOW_RES_PRELOAD &&
|
|
|
|
|
PREFSMAN->m_ImageCache != IMGCACHE_LOW_RES_LOAD_ON_DEMAND )
|
2011-03-17 01:47:30 -04:00
|
|
|
return;
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
CHECKPOINT_M( sImagePath );
|
|
|
|
|
if( !DoesFileExist(sImagePath) )
|
2011-03-17 01:47:30 -04:00
|
|
|
return;
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
const RString sCachePath = GetImageCachePath(sImageDir, sImagePath);
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
/* Check the full file hash. If it's the loaded and identical, don't recache. */
|
|
|
|
|
if( DoesFileExist(sCachePath) )
|
|
|
|
|
{
|
|
|
|
|
bool bCacheUpToDate = PREFSMAN->m_bFastLoad;
|
|
|
|
|
if( !bCacheUpToDate )
|
|
|
|
|
{
|
|
|
|
|
unsigned CurFullHash;
|
2017-09-19 23:49:42 +02:00
|
|
|
const unsigned FullHash = GetHashForFile( sImagePath );
|
|
|
|
|
if( ImageData.GetValue( sImagePath, "FullHash", CurFullHash ) && CurFullHash == FullHash )
|
2011-03-17 01:47:30 -04:00
|
|
|
bCacheUpToDate = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( bCacheUpToDate )
|
|
|
|
|
{
|
|
|
|
|
/* It's identical. Just load it, if in preload. */
|
2017-09-19 23:49:42 +02:00
|
|
|
if( PREFSMAN->m_ImageCache == IMGCACHE_LOW_RES_PRELOAD )
|
|
|
|
|
LoadImage( sImageDir, sImagePath );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The cache file doesn't exist, or is out of date. Cache it. This
|
|
|
|
|
* will also load the cache into memory if in PRELOAD. */
|
2017-09-19 23:49:42 +02:00
|
|
|
CacheImageInternal( sImageDir, sImagePath );
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
void ImageCache::CacheImageInternal( RString sImageDir, RString sImagePath )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
RString sError;
|
2017-09-19 23:49:42 +02:00
|
|
|
RageSurface *pImage = RageSurfaceUtils::LoadFile( sImagePath, sError );
|
2019-06-22 12:35:38 -07:00
|
|
|
if( pImage == nullptr )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
LOG->UserLog( "Cache file", sImagePath, "couldn't be loaded: %s", sError.c_str() );
|
2011-03-17 01:47:30 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int iSourceWidth = pImage->w, iSourceHeight = pImage->h;
|
|
|
|
|
|
|
|
|
|
int iWidth = pImage->w / 2, iHeight = pImage->h / 2;
|
|
|
|
|
// int iWidth = pImage->w, iHeight = pImage->h;
|
|
|
|
|
|
|
|
|
|
/* Round to the nearest power of two. This simplifies the actual texture load. */
|
|
|
|
|
iWidth = closest( iWidth, power_of_two(iWidth), power_of_two(iWidth) / 2 );
|
|
|
|
|
iHeight = closest( iHeight, power_of_two(iHeight), power_of_two(iHeight) / 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. */
|
2022-07-10 18:28:56 +03:00
|
|
|
iWidth = std::max( iWidth, std::min(32, power_of_two(iSourceWidth)) );
|
|
|
|
|
iHeight = std::max( iHeight, std::min(32, power_of_two(iSourceHeight)) );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
//RageSurfaceUtils::ApplyHotPinkColorKey( pImage );
|
|
|
|
|
|
|
|
|
|
RageSurfaceUtils::Zoom( pImage, iWidth, iHeight );
|
|
|
|
|
|
|
|
|
|
/*
|
2017-09-19 23:49:42 +02:00
|
|
|
* When paletted image cache is enabled, cached images are paletted. Cached
|
|
|
|
|
* 32-bit images take 1/16 as much memory, 16-bit images take 1/8, and paletted
|
|
|
|
|
* images take 1/4.
|
2011-03-17 01:47:30 -04:00
|
|
|
*
|
2017-09-19 23:49:42 +02:00
|
|
|
* When paletted image cache is disabled, cached images are stored in 16-bit
|
|
|
|
|
* RGBA. Cached 32-bit images take 1/8 as much memory, cached 16-bit images
|
|
|
|
|
* take 1/4, and cached paletted images take 1/2.
|
2011-03-17 01:47:30 -04:00
|
|
|
*
|
|
|
|
|
* Paletted cache is disabled by default because palettization takes time, causing
|
|
|
|
|
* the initial cache run to take longer. Also, newer ATI hardware doesn't supported
|
|
|
|
|
* paletted textures, which would slow down runtime, because we have to depalettize
|
|
|
|
|
* on use. They'd still have the same memory benefits, though, since we only load
|
2017-09-19 23:49:42 +02:00
|
|
|
* one cached image into a texture at once, and the speed hit may not matter on
|
2011-03-17 01:47:30 -04:00
|
|
|
* newer ATI cards. RGBA is safer, though.
|
|
|
|
|
*/
|
2017-09-19 23:49:42 +02:00
|
|
|
if( g_bPalettedImageCache )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
if( pImage->fmt.BytesPerPixel != 1 )
|
|
|
|
|
RageSurfaceUtils::Palettize( pImage );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Dither to the final format. We use A1RGB5, since that's usually supported
|
|
|
|
|
* natively by both OpenGL and D3D. */
|
|
|
|
|
RageSurface *dst = CreateSurface( pImage->w, pImage->h, 16,
|
|
|
|
|
0x7C00, 0x03E0, 0x001F, 0x8000 );
|
|
|
|
|
|
|
|
|
|
/* OrderedDither is still faster than ErrorDiffusionDither, and
|
|
|
|
|
* these images are very small and only displayed briefly. */
|
|
|
|
|
RageSurfaceUtils::OrderedDither( pImage, dst );
|
|
|
|
|
delete pImage;
|
|
|
|
|
pImage = dst;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
const RString sCachePath = GetImageCachePath(sImageDir,sImagePath);
|
2011-03-17 01:47:30 -04:00
|
|
|
RageSurfaceUtils::SaveSurface( pImage, sCachePath );
|
|
|
|
|
|
|
|
|
|
/* If an old image is loaded, free it. */
|
2017-09-19 23:49:42 +02:00
|
|
|
if( g_ImagePathToImage.find(sImagePath) != g_ImagePathToImage.end() )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2017-09-19 23:49:42 +02:00
|
|
|
RageSurface *oldimg = g_ImagePathToImage[sImagePath];
|
2011-03-17 01:47:30 -04:00
|
|
|
delete oldimg;
|
2017-09-19 23:49:42 +02:00
|
|
|
g_ImagePathToImage.erase(sImagePath);
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2017-09-19 23:49:42 +02:00
|
|
|
if( PREFSMAN->m_ImageCache == IMGCACHE_LOW_RES_PRELOAD )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
/* Keep it; we're just going to load it anyway. */
|
2017-09-19 23:49:42 +02:00
|
|
|
g_ImagePathToImage[sImagePath] = pImage;
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
delete pImage;
|
|
|
|
|
|
|
|
|
|
/* Remember the original size. */
|
2017-09-19 23:49:42 +02:00
|
|
|
ImageData.SetValue( sImagePath, "Path", sCachePath );
|
|
|
|
|
ImageData.SetValue( sImagePath, "Width", iSourceWidth );
|
|
|
|
|
ImageData.SetValue( sImagePath, "Height", iSourceHeight );
|
|
|
|
|
ImageData.SetValue( sImagePath, "FullHash", GetHashForFile( sImagePath ) );
|
2019-10-12 11:24:13 -04:00
|
|
|
if (!delay_save_cache)
|
|
|
|
|
WriteToDisk();
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
2019-10-12 11:24:13 -04:00
|
|
|
void ImageCache::WriteToDisk()
|
|
|
|
|
{
|
|
|
|
|
ImageData.WriteFile(IMAGE_CACHE_INDEX);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-03-17 01:47:30 -04:00
|
|
|
/*
|
|
|
|
|
* (c) 2003 Glenn Maynard
|
|
|
|
|
* All rights reserved.
|
2023-04-19 14:22:59 +02:00
|
|
|
*
|
2011-03-17 01:47:30 -04:00
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
|
* copy of this software and associated documentation files (the
|
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
|
|
|
|
* whom the Software is furnished to do so, provided that the above
|
|
|
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
|
|
|
|
* the Software and that both the above copyright notice(s) and this
|
|
|
|
|
* permission notice appear in supporting documentation.
|
2023-04-19 14:22:59 +02:00
|
|
|
*
|
2011-03-17 01:47:30 -04:00
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
|
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
|
|
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
|
|
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
|
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
*/
|