2009-08-21 22:21:05 +02:00
/*
* This file is part of OpenTTD .
* OpenTTD 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.
* OpenTTD 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 for more details . You should have received a copy of the GNU General Public License along with OpenTTD . If not , see < http : //www.gnu.org/licenses/>.
*/
2008-05-06 17:11:33 +02:00
/** @file spritecache.cpp Caching of sprites. */
2007-04-04 03:35:16 +02:00
2004-08-09 19:04:08 +02:00
# include "stdafx.h"
2021-04-14 17:20:39 +02:00
# include "random_access_file_type.h"
2007-06-11 13:50:49 +02:00
# include "spriteloader/grf.hpp"
2008-09-02 20:45:15 +02:00
# include "gfx_func.h"
2013-03-03 14:00:06 +01:00
# include "error.h"
2023-04-19 22:47:36 +02:00
# include "error_func.h"
2012-02-04 14:29:13 +01:00
# include "zoom_func.h"
# include "settings_type.h"
2007-06-17 22:30:28 +02:00
# include "blitter/factory.hpp"
2009-01-11 23:28:30 +01:00
# include "core/math_func.hpp"
2012-02-04 14:29:13 +01:00
# include "core/mem_func.hpp"
2021-01-16 16:43:33 +01:00
# include "video/video_driver.hpp"
2023-11-03 19:48:34 +01:00
# include "spritecache.h"
# include "spritecache_internal.h"
2004-08-09 19:04:08 +02:00
2008-01-13 02:21:35 +01:00
# include "table/sprites.h"
2013-03-03 14:00:06 +01:00
# include "table/strings.h"
2011-05-04 19:12:37 +02:00
# include "table/palette_convert.h"
2008-01-13 02:21:35 +01:00
2014-04-23 22:13:33 +02:00
# include "safeguards.h"
2007-06-11 13:50:49 +02:00
/* Default of 4MB spritecache */
uint _sprite_cache_size = 4 ;
2004-08-09 19:04:08 +02:00
2007-01-03 15:42:08 +01:00
static uint _spritecache_items = 0 ;
2019-04-10 23:07:06 +02:00
static SpriteCache * _spritecache = nullptr ;
2021-04-14 17:20:39 +02:00
static std : : vector < std : : unique_ptr < SpriteFile > > _sprite_files ;
2007-01-03 15:42:08 +01:00
static inline SpriteCache * GetSpriteCache ( uint index )
{
return & _spritecache [ index ] ;
}
2023-11-03 19:48:34 +01:00
SpriteCache * AllocateSpriteCache ( uint index )
2007-01-03 15:42:08 +01:00
{
if ( index > = _spritecache_items ) {
/* Add another 1024 items to the 'pool' */
2007-11-19 21:40:14 +01:00
uint items = Align ( index + 1 , 1024 ) ;
2007-01-03 15:42:08 +01:00
2021-06-12 09:10:17 +02:00
Debug ( sprite , 4 , " Increasing sprite cache to {} items ({} bytes) " , items , items * sizeof ( * _spritecache ) ) ;
2007-01-03 15:42:08 +01:00
2007-01-11 18:29:39 +01:00
_spritecache = ReallocT ( _spritecache , items ) ;
2007-01-03 15:42:08 +01:00
/* Reset the new items and update the count */
memset ( _spritecache + _spritecache_items , 0 , ( items - _spritecache_items ) * sizeof ( * _spritecache ) ) ;
_spritecache_items = items ;
}
return GetSpriteCache ( index ) ;
}
2021-04-14 17:20:39 +02:00
/**
* Get the cached SpriteFile given the name of the file .
* @ param filename The name of the file at the disk .
* @ return The SpriteFile or \ c null .
*/
2023-11-09 20:20:41 +01:00
static SpriteFile * GetCachedSpriteFileByName ( const std : : string & filename )
{
2021-04-14 17:20:39 +02:00
for ( auto & f : _sprite_files ) {
if ( f - > GetFilename ( ) = = filename ) {
return f . get ( ) ;
}
}
return nullptr ;
}
2024-04-06 22:16:02 +02:00
/**
* Get the list of cached SpriteFiles .
* @ return Read - only list of cache SpriteFiles .
*/
std : : span < const std : : unique_ptr < SpriteFile > > GetCachedSpriteFiles ( )
{
return _sprite_files ;
}
2021-04-14 17:20:39 +02:00
/**
* Open / get the SpriteFile that is cached for use in the sprite cache .
* @ param filename Name of the file at the disk .
* @ param subdir The sub directory to search this file in .
* @ param palette_remap Whether a palette remap needs to be performed for this file .
* @ return The reference to the SpriteCache .
*/
SpriteFile & OpenCachedSpriteFile ( const std : : string & filename , Subdirectory subdir , bool palette_remap )
{
SpriteFile * file = GetCachedSpriteFileByName ( filename ) ;
if ( file = = nullptr ) {
2023-10-20 19:40:48 +02:00
file = _sprite_files . insert ( std : : end ( _sprite_files ) , std : : make_unique < SpriteFile > ( filename , subdir , palette_remap ) ) - > get ( ) ;
2021-04-14 17:20:39 +02:00
} else {
file - > SeekToBegin ( ) ;
}
return * file ;
}
2004-08-09 19:04:08 +02:00
2007-03-07 13:11:48 +01:00
struct MemBlock {
2008-05-08 15:20:54 +02:00
size_t size ;
2024-03-16 23:59:32 +01:00
uint8_t data [ ] ;
2007-03-07 13:11:48 +01:00
} ;
2005-02-11 14:35:27 +01:00
2004-08-09 19:04:08 +02:00
static uint _sprite_lru_counter ;
2005-02-11 14:35:27 +01:00
static MemBlock * _spritecache_ptr ;
2012-05-15 22:37:42 +02:00
static uint _allocated_sprite_cache_size = 0 ;
2004-08-09 19:04:08 +02:00
static int _compact_cache_counter ;
2007-03-07 12:47:46 +01:00
static void CompactSpriteCache ( ) ;
2004-08-09 19:04:08 +02:00
2008-08-30 11:46:52 +02:00
/**
* Skip the given amount of sprite graphics data .
* @ param type the type of sprite ( compressed etc )
* @ param num the amount of sprites to skip
2009-03-19 18:58:25 +01:00
* @ return true if the data could be correctly skipped .
2008-08-30 11:46:52 +02:00
*/
2024-03-16 23:59:32 +01:00
bool SkipSpriteData ( SpriteFile & file , uint8_t type , uint16_t num )
2004-08-09 19:04:08 +02:00
{
if ( type & 2 ) {
2021-04-14 17:20:39 +02:00
file . SkipBytes ( num ) ;
2005-02-11 15:33:43 +01:00
} else {
while ( num > 0 ) {
2023-05-08 19:01:06 +02:00
int8_t i = file . ReadByte ( ) ;
2005-02-11 15:33:43 +01:00
if ( i > = 0 ) {
2008-08-30 14:14:56 +02:00
int size = ( i = = 0 ) ? 0x80 : i ;
2009-03-19 18:58:25 +01:00
if ( size > num ) return false ;
2008-08-30 14:14:56 +02:00
num - = size ;
2021-04-14 17:20:39 +02:00
file . SkipBytes ( size ) ;
2005-02-11 15:33:43 +01:00
} else {
i = - ( i > > 3 ) ;
num - = i ;
2021-04-14 17:20:39 +02:00
file . ReadByte ( ) ;
2005-02-11 15:33:43 +01:00
}
2004-08-09 19:04:08 +02:00
}
}
2009-03-19 18:58:25 +01:00
return true ;
2008-08-30 11:46:52 +02:00
}
2006-04-16 13:26:23 +02:00
/* Check if the given Sprite ID exists */
bool SpriteExists ( SpriteID id )
{
2013-10-06 21:40:40 +02:00
if ( id > = _spritecache_items ) return false ;
2006-04-16 13:26:23 +02:00
/* Special case for Sprite ID zero -- its position is also 0... */
2007-01-03 15:42:08 +01:00
if ( id = = 0 ) return true ;
2021-04-14 17:20:39 +02:00
return ! ( GetSpriteCache ( id ) - > file_pos = = 0 & & GetSpriteCache ( id ) - > file = = nullptr ) ;
2006-04-16 13:26:23 +02:00
}
2010-04-25 18:27:30 +02:00
/**
* Get the sprite type of a given sprite .
* @ param sprite The sprite to look at .
* @ return the type of sprite .
*/
SpriteType GetSpriteType ( SpriteID sprite )
{
2023-04-16 21:00:55 +02:00
if ( ! SpriteExists ( sprite ) ) return SpriteType : : Invalid ;
2010-04-25 18:27:30 +02:00
return GetSpriteCache ( sprite ) - > type ;
}
/**
2021-04-14 17:20:39 +02:00
* Get the SpriteFile of a given sprite .
2010-04-25 18:27:30 +02:00
* @ param sprite The sprite to look at .
2021-04-14 17:20:39 +02:00
* @ return The SpriteFile .
2010-04-25 18:27:30 +02:00
*/
2021-04-14 17:20:39 +02:00
SpriteFile * GetOriginFile ( SpriteID sprite )
2010-04-25 18:27:30 +02:00
{
2021-04-14 17:20:39 +02:00
if ( ! SpriteExists ( sprite ) ) return nullptr ;
return GetSpriteCache ( sprite ) - > file ;
2010-04-25 18:27:30 +02:00
}
2020-01-26 13:45:51 +01:00
/**
* Get the GRF - local sprite id of a given sprite .
* @ param sprite The sprite to look at .
* @ return The GRF - local sprite id .
*/
2023-05-08 19:01:06 +02:00
uint32_t GetSpriteLocalID ( SpriteID sprite )
2020-01-26 13:45:51 +01:00
{
if ( ! SpriteExists ( sprite ) ) return 0 ;
return GetSpriteCache ( sprite ) - > id ;
}
2017-01-14 19:30:26 +01:00
/**
2021-04-14 17:20:39 +02:00
* Count the sprites which originate from a specific file in a range of SpriteIDs .
* @ param file The loaded SpriteFile .
2017-01-14 19:30:26 +01:00
* @ param begin First sprite in range .
* @ param end First sprite not in range .
* @ return Number of sprites .
*/
2021-04-14 17:20:39 +02:00
uint GetSpriteCountForFile ( const std : : string & filename , SpriteID begin , SpriteID end )
2017-01-14 19:30:26 +01:00
{
2021-04-14 17:20:39 +02:00
SpriteFile * file = GetCachedSpriteFileByName ( filename ) ;
if ( file = = nullptr ) return 0 ;
2017-01-14 19:30:26 +01:00
uint count = 0 ;
for ( SpriteID i = begin ; i ! = end ; i + + ) {
if ( SpriteExists ( i ) ) {
SpriteCache * sc = GetSpriteCache ( i ) ;
2023-06-27 12:46:14 +02:00
if ( sc - > file = = file ) {
count + + ;
Debug ( sprite , 4 , " Sprite: {} " , i ) ;
}
2017-01-14 19:30:26 +01:00
}
}
return count ;
}
2010-04-25 18:27:30 +02:00
/**
* Get a reasonable ( upper bound ) estimate of the maximum
* SpriteID used in OpenTTD ; there will be no sprites with
* a higher SpriteID , although there might be up to roughly
* a thousand unused SpriteIDs below this number .
* @ note It ' s actually the number of spritecache items .
* @ return maximum SpriteID
*/
uint GetMaxSpriteID ( )
{
return _spritecache_items ;
}
2023-12-20 21:38:21 +01:00
static bool ResizeSpriteIn ( SpriteLoader : : SpriteCollection & sprite , ZoomLevel src , ZoomLevel tgt )
2012-02-04 14:29:13 +01:00
{
2023-05-08 19:01:06 +02:00
uint8_t scaled_1 = ScaleByZoom ( 1 , ( ZoomLevel ) ( src - tgt ) ) ;
2012-02-04 14:29:13 +01:00
/* Check for possible memory overflow. */
if ( sprite [ src ] . width * scaled_1 > UINT16_MAX | | sprite [ src ] . height * scaled_1 > UINT16_MAX ) return false ;
sprite [ tgt ] . width = sprite [ src ] . width * scaled_1 ;
sprite [ tgt ] . height = sprite [ src ] . height * scaled_1 ;
sprite [ tgt ] . x_offs = sprite [ src ] . x_offs * scaled_1 ;
sprite [ tgt ] . y_offs = sprite [ src ] . y_offs * scaled_1 ;
2021-01-16 16:43:30 +01:00
sprite [ tgt ] . colours = sprite [ src ] . colours ;
2012-02-04 14:29:13 +01:00
2023-01-02 21:30:02 +01:00
sprite [ tgt ] . AllocateData ( tgt , static_cast < size_t > ( sprite [ tgt ] . width ) * sprite [ tgt ] . height ) ;
2012-02-04 14:29:13 +01:00
SpriteLoader : : CommonPixel * dst = sprite [ tgt ] . data ;
for ( int y = 0 ; y < sprite [ tgt ] . height ; y + + ) {
const SpriteLoader : : CommonPixel * src_ln = & sprite [ src ] . data [ y / scaled_1 * sprite [ src ] . width ] ;
for ( int x = 0 ; x < sprite [ tgt ] . width ; x + + ) {
* dst = src_ln [ x / scaled_1 ] ;
dst + + ;
}
}
return true ;
}
2023-12-20 21:38:21 +01:00
static void ResizeSpriteOut ( SpriteLoader : : SpriteCollection & sprite , ZoomLevel zoom )
2012-02-04 14:29:13 +01:00
{
/* Algorithm based on 32bpp_Optimized::ResizeSprite() */
2024-04-04 19:49:37 +02:00
sprite [ zoom ] . width = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . width , zoom ) ;
sprite [ zoom ] . height = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . height , zoom ) ;
sprite [ zoom ] . x_offs = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . x_offs , zoom ) ;
sprite [ zoom ] . y_offs = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . y_offs , zoom ) ;
sprite [ zoom ] . colours = sprite [ ZOOM_LVL_MIN ] . colours ;
2012-02-04 14:29:13 +01:00
2023-01-02 21:30:02 +01:00
sprite [ zoom ] . AllocateData ( zoom , static_cast < size_t > ( sprite [ zoom ] . height ) * sprite [ zoom ] . width ) ;
2012-02-04 14:29:13 +01:00
SpriteLoader : : CommonPixel * dst = sprite [ zoom ] . data ;
const SpriteLoader : : CommonPixel * src = sprite [ zoom - 1 ] . data ;
2021-06-03 16:55:08 +02:00
[[maybe_unused]] const SpriteLoader : : CommonPixel * src_end = src + sprite [ zoom - 1 ] . height * sprite [ zoom - 1 ] . width ;
2012-02-04 14:29:13 +01:00
for ( uint y = 0 ; y < sprite [ zoom ] . height ; y + + ) {
2012-02-25 18:18:17 +01:00
const SpriteLoader : : CommonPixel * src_ln = src + sprite [ zoom - 1 ] . width ;
assert ( src_ln < = src_end ) ;
2012-02-04 14:29:13 +01:00
for ( uint x = 0 ; x < sprite [ zoom ] . width ; x + + ) {
2012-02-25 18:18:17 +01:00
assert ( src < src_ln ) ;
if ( src + 1 ! = src_ln & & ( src + 1 ) - > a ! = 0 ) {
* dst = * ( src + 1 ) ;
} else {
* dst = * src ;
}
2012-02-04 14:29:13 +01:00
dst + + ;
src + = 2 ;
}
2012-02-25 18:18:17 +01:00
src = src_ln + sprite [ zoom - 1 ] . width ;
2012-02-04 14:29:13 +01:00
}
}
static bool PadSingleSprite ( SpriteLoader : : Sprite * sprite , ZoomLevel zoom , uint pad_left , uint pad_top , uint pad_right , uint pad_bottom )
{
uint width = sprite - > width + pad_left + pad_right ;
uint height = sprite - > height + pad_top + pad_bottom ;
if ( width > UINT16_MAX | | height > UINT16_MAX ) return false ;
/* Copy source data and reallocate sprite memory. */
2023-01-02 21:30:02 +01:00
size_t sprite_size = static_cast < size_t > ( sprite - > width ) * sprite - > height ;
SpriteLoader : : CommonPixel * src_data = MallocT < SpriteLoader : : CommonPixel > ( sprite_size ) ;
MemCpyT ( src_data , sprite - > data , sprite_size ) ;
sprite - > AllocateData ( zoom , static_cast < size_t > ( width ) * height ) ;
2012-02-04 14:29:13 +01:00
/* Copy with padding to destination. */
SpriteLoader : : CommonPixel * src = src_data ;
SpriteLoader : : CommonPixel * data = sprite - > data ;
for ( uint y = 0 ; y < height ; y + + ) {
if ( y < pad_top | | pad_bottom + y > = height ) {
/* Top/bottom padding. */
MemSetT ( data , 0 , width ) ;
data + = width ;
} else {
if ( pad_left > 0 ) {
/* Pad left. */
MemSetT ( data , 0 , pad_left ) ;
data + = pad_left ;
}
/* Copy pixels. */
MemCpyT ( data , src , sprite - > width ) ;
src + = sprite - > width ;
data + = sprite - > width ;
if ( pad_right > 0 ) {
/* Pad right. */
MemSetT ( data , 0 , pad_right ) ;
data + = pad_right ;
}
}
}
free ( src_data ) ;
/* Update sprite size. */
sprite - > width = width ;
sprite - > height = height ;
sprite - > x_offs - = pad_left ;
sprite - > y_offs - = pad_top ;
return true ;
}
2023-12-20 21:38:21 +01:00
static bool PadSprites ( SpriteLoader : : SpriteCollection & sprite , uint8_t sprite_avail , SpriteEncoder * encoder )
2012-02-04 14:29:13 +01:00
{
2012-02-23 13:13:30 +01:00
/* Get minimum top left corner coordinates. */
int min_xoffs = INT32_MAX ;
int min_yoffs = INT32_MAX ;
for ( ZoomLevel zoom = ZOOM_LVL_BEGIN ; zoom ! = ZOOM_LVL_END ; zoom + + ) {
2012-02-04 14:29:13 +01:00
if ( HasBit ( sprite_avail , zoom ) ) {
2021-01-08 11:16:18 +01:00
min_xoffs = std : : min ( min_xoffs , ScaleByZoom ( sprite [ zoom ] . x_offs , zoom ) ) ;
min_yoffs = std : : min ( min_yoffs , ScaleByZoom ( sprite [ zoom ] . y_offs , zoom ) ) ;
2012-02-04 14:29:13 +01:00
}
}
2012-02-23 13:13:30 +01:00
/* Get maximum dimensions taking necessary padding at the top left into account. */
int max_width = INT32_MIN ;
int max_height = INT32_MIN ;
2012-02-04 14:29:13 +01:00
for ( ZoomLevel zoom = ZOOM_LVL_BEGIN ; zoom ! = ZOOM_LVL_END ; zoom + + ) {
if ( HasBit ( sprite_avail , zoom ) ) {
2021-01-08 11:16:18 +01:00
max_width = std : : max ( max_width , ScaleByZoom ( sprite [ zoom ] . width + sprite [ zoom ] . x_offs - UnScaleByZoom ( min_xoffs , zoom ) , zoom ) ) ;
max_height = std : : max ( max_height , ScaleByZoom ( sprite [ zoom ] . height + sprite [ zoom ] . y_offs - UnScaleByZoom ( min_yoffs , zoom ) , zoom ) ) ;
2012-02-23 13:13:30 +01:00
}
}
2012-02-04 14:29:13 +01:00
2021-01-16 16:43:29 +01:00
/* Align height and width if required to match the needs of the sprite encoder. */
uint align = encoder - > GetSpriteAlignment ( ) ;
if ( align ! = 0 ) {
max_width = Align ( max_width , align ) ;
max_height = Align ( max_height , align ) ;
}
2012-02-23 13:13:30 +01:00
/* Pad sprites where needed. */
for ( ZoomLevel zoom = ZOOM_LVL_BEGIN ; zoom ! = ZOOM_LVL_END ; zoom + + ) {
if ( HasBit ( sprite_avail , zoom ) ) {
/* Scaling the sprite dimensions in the blitter is done with rounding up,
* so a negative padding here is not an error . */
2021-01-08 11:16:18 +01:00
int pad_left = std : : max ( 0 , sprite [ zoom ] . x_offs - UnScaleByZoom ( min_xoffs , zoom ) ) ;
int pad_top = std : : max ( 0 , sprite [ zoom ] . y_offs - UnScaleByZoom ( min_yoffs , zoom ) ) ;
int pad_right = std : : max ( 0 , UnScaleByZoom ( max_width , zoom ) - sprite [ zoom ] . width - pad_left ) ;
int pad_bottom = std : : max ( 0 , UnScaleByZoom ( max_height , zoom ) - sprite [ zoom ] . height - pad_top ) ;
2012-02-23 13:13:30 +01:00
if ( pad_left > 0 | | pad_right > 0 | | pad_top > 0 | | pad_bottom > 0 ) {
2012-02-04 14:29:13 +01:00
if ( ! PadSingleSprite ( & sprite [ zoom ] , zoom , pad_left , pad_top , pad_right , pad_bottom ) ) return false ;
}
}
}
return true ;
}
2023-12-20 21:38:21 +01:00
static bool ResizeSprites ( SpriteLoader : : SpriteCollection & sprite , uint8_t sprite_avail , SpriteEncoder * encoder )
2012-02-04 14:29:13 +01:00
{
/* Create a fully zoomed image if it does not exist */
2024-01-18 19:40:49 +01:00
ZoomLevel first_avail = static_cast < ZoomLevel > ( FindFirstBit ( sprite_avail ) ) ;
2024-04-04 19:49:37 +02:00
if ( first_avail ! = ZOOM_LVL_MIN ) {
if ( ! ResizeSpriteIn ( sprite , first_avail , ZOOM_LVL_MIN ) ) return false ;
SetBit ( sprite_avail , ZOOM_LVL_MIN ) ;
2012-02-04 14:29:13 +01:00
}
/* Pad sprites to make sizes match. */
2021-01-16 16:43:29 +01:00
if ( ! PadSprites ( sprite , sprite_avail , encoder ) ) return false ;
2012-02-04 14:29:13 +01:00
/* Create other missing zoom levels */
2024-04-04 19:49:37 +02:00
for ( ZoomLevel zoom = ZOOM_LVL_BEGIN ; zoom ! = ZOOM_LVL_END ; zoom + + ) {
if ( zoom = = ZOOM_LVL_MIN ) continue ;
2012-02-04 14:29:13 +01:00
if ( HasBit ( sprite_avail , zoom ) ) {
/* Check that size and offsets match the fully zoomed image. */
2024-04-04 19:49:37 +02:00
assert ( sprite [ zoom ] . width = = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . width , zoom ) ) ;
assert ( sprite [ zoom ] . height = = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . height , zoom ) ) ;
assert ( sprite [ zoom ] . x_offs = = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . x_offs , zoom ) ) ;
assert ( sprite [ zoom ] . y_offs = = UnScaleByZoom ( sprite [ ZOOM_LVL_MIN ] . y_offs , zoom ) ) ;
2012-02-04 14:29:13 +01:00
}
/* Zoom level is not available, or unusable, so create it */
if ( ! HasBit ( sprite_avail , zoom ) ) ResizeSpriteOut ( sprite , zoom ) ;
}
2023-12-25 21:08:13 +01:00
/* Upscale to desired sprite_min_zoom if provided sprite only had zoomed in versions. */
if ( first_avail < _settings_client . gui . sprite_zoom_min ) {
2024-04-04 19:51:46 +02:00
if ( _settings_client . gui . sprite_zoom_min > = ZOOM_LVL_NORMAL ) ResizeSpriteIn ( sprite , ZOOM_LVL_NORMAL , ZOOM_LVL_IN_2X ) ;
if ( _settings_client . gui . sprite_zoom_min > = ZOOM_LVL_IN_2X ) ResizeSpriteIn ( sprite , ZOOM_LVL_IN_2X , ZOOM_LVL_IN_4X ) ;
2023-12-25 21:08:13 +01:00
}
2012-02-04 14:29:13 +01:00
return true ;
}
2012-02-04 14:28:52 +01:00
/**
* Load a recolour sprite into memory .
2021-04-14 17:20:39 +02:00
* @ param file GRF we ' re reading from .
2012-02-04 14:28:52 +01:00
* @ param num Size of the sprite in the GRF .
* @ return Sprite data .
*/
2021-04-14 17:20:39 +02:00
static void * ReadRecolourSprite ( SpriteFile & file , uint num )
2012-02-04 14:28:52 +01:00
{
/* "Normal" recolour sprites are ALWAYS 257 bytes. Then there is a small
* number of recolour sprites that are 17 bytes that only exist in DOS
* GRFs which are the same as 257 byte recolour sprites , but with the last
* 240 bytes zeroed . */
static const uint RECOLOUR_SPRITE_SIZE = 257 ;
2024-03-16 23:59:32 +01:00
uint8_t * dest = ( uint8_t * ) AllocSprite ( std : : max ( RECOLOUR_SPRITE_SIZE , num ) ) ;
2012-02-04 14:28:52 +01:00
2021-04-14 17:20:39 +02:00
if ( file . NeedsPaletteRemap ( ) ) {
2024-03-16 23:59:32 +01:00
uint8_t * dest_tmp = new uint8_t [ std : : max ( RECOLOUR_SPRITE_SIZE , num ) ] ;
2012-02-04 14:28:52 +01:00
/* Only a few recolour sprites are less than 257 bytes */
if ( num < RECOLOUR_SPRITE_SIZE ) memset ( dest_tmp , 0 , RECOLOUR_SPRITE_SIZE ) ;
2021-04-14 17:20:39 +02:00
file . ReadBlock ( dest_tmp , num ) ;
2012-02-04 14:28:52 +01:00
/* The data of index 0 is never used; "literal 00" according to the (New)GRF specs. */
for ( uint i = 1 ; i < RECOLOUR_SPRITE_SIZE ; i + + ) {
dest [ i ] = _palmap_w2d [ dest_tmp [ _palmap_d2w [ i - 1 ] + 1 ] ] ;
}
2021-05-01 22:06:17 +02:00
delete [ ] dest_tmp ;
2012-02-04 14:28:52 +01:00
} else {
2021-04-14 17:20:39 +02:00
file . ReadBlock ( dest , num ) ;
2012-02-04 14:28:52 +01:00
}
return dest ;
}
2011-01-14 17:28:23 +01:00
/**
* Read a sprite from disk .
* @ param sc Location of sprite .
* @ param id Sprite number .
* @ param sprite_type Type of sprite .
* @ param allocator Allocator function to use .
2021-01-16 16:43:27 +01:00
* @ param encoder Sprite encoder to use .
2011-01-14 17:28:23 +01:00
* @ return Read sprite data .
*/
2021-01-16 16:43:27 +01:00
static void * ReadSprite ( const SpriteCache * sc , SpriteID id , SpriteType sprite_type , AllocatorProc * allocator , SpriteEncoder * encoder )
2004-08-09 19:04:08 +02:00
{
2021-01-16 16:43:27 +01:00
/* Use current blitter if no other sprite encoder is given. */
if ( encoder = = nullptr ) encoder = BlitterFactory : : GetCurrentBlitter ( ) ;
2021-04-14 17:20:39 +02:00
SpriteFile & file = * sc - > file ;
2008-05-27 23:41:00 +02:00
size_t file_pos = sc - > file_pos ;
2005-07-05 21:54:35 +02:00
2023-04-16 21:00:55 +02:00
assert ( sprite_type ! = SpriteType : : Recolour ) ;
assert ( IsMapgenSpriteID ( id ) = = ( sprite_type = = SpriteType : : MapGen ) ) ;
2009-01-11 23:28:30 +01:00
assert ( sc - > type = = sprite_type ) ;
2007-02-08 15:02:12 +01:00
2021-06-12 09:10:17 +02:00
Debug ( sprite , 9 , " Load sprite {} " , id ) ;
2005-02-17 16:53:47 +01:00
2023-12-20 21:38:21 +01:00
SpriteLoader : : SpriteCollection sprite ;
2023-05-08 19:01:06 +02:00
uint8_t sprite_avail = 0 ;
2024-04-04 19:49:37 +02:00
sprite [ ZOOM_LVL_MIN ] . type = sprite_type ;
2012-02-04 14:29:13 +01:00
2021-04-14 17:20:39 +02:00
SpriteLoaderGrf sprite_loader ( file . GetContainerVersion ( ) ) ;
2023-04-16 21:00:55 +02:00
if ( sprite_type ! = SpriteType : : MapGen & & encoder - > Is32BppSupported ( ) ) {
2012-02-04 23:18:57 +01:00
/* Try for 32bpp sprites first. */
2022-09-21 12:37:11 +02:00
sprite_avail = sprite_loader . LoadSprite ( sprite , file , file_pos , sprite_type , true , sc - > control_flags ) ;
2012-02-04 23:18:57 +01:00
}
if ( sprite_avail = = 0 ) {
2022-09-21 12:37:11 +02:00
sprite_avail = sprite_loader . LoadSprite ( sprite , file , file_pos , sprite_type , false , sc - > control_flags ) ;
2012-02-04 23:18:57 +01:00
}
2004-09-08 20:05:49 +02:00
2012-02-04 14:29:13 +01:00
if ( sprite_avail = = 0 ) {
2023-04-16 21:00:55 +02:00
if ( sprite_type = = SpriteType : : MapGen ) return nullptr ;
2023-04-19 22:47:36 +02:00
if ( id = = SPR_IMG_QUERY ) UserError ( " Okay... something went horribly wrong. I couldn't load the fallback sprite. What should I do? " ) ;
2023-04-16 21:00:55 +02:00
return ( void * ) GetRawSprite ( SPR_IMG_QUERY , SpriteType : : Normal , allocator , encoder ) ;
2008-11-23 14:42:05 +01:00
}
2012-02-04 14:28:56 +01:00
2023-04-16 21:00:55 +02:00
if ( sprite_type = = SpriteType : : MapGen ) {
2012-02-04 14:28:56 +01:00
/* Ugly hack to work around the problem that the old landscape
* generator assumes that those sprites are stored uncompressed in
* the memory , and they are only read directly by the code , never
* send to the blitter . So do not send it to the blitter ( which will
* result in a data array in the format the blitter likes most ) , but
* extract the data directly and store that as sprite .
* Ugly : yes . Other solution : no . Blame the original author or
* something ; ) The image should really have been a data - stream
2013-01-08 23:46:42 +01:00
* ( so type = 0xFF basically ) . */
2024-04-04 19:49:37 +02:00
uint num = sprite [ ZOOM_LVL_MIN ] . width * sprite [ ZOOM_LVL_MIN ] . height ;
2012-02-04 14:28:56 +01:00
Sprite * s = ( Sprite * ) allocator ( sizeof ( * s ) + num ) ;
2024-04-04 19:49:37 +02:00
s - > width = sprite [ ZOOM_LVL_MIN ] . width ;
s - > height = sprite [ ZOOM_LVL_MIN ] . height ;
s - > x_offs = sprite [ ZOOM_LVL_MIN ] . x_offs ;
s - > y_offs = sprite [ ZOOM_LVL_MIN ] . y_offs ;
2012-02-04 14:28:56 +01:00
2024-04-04 19:49:37 +02:00
SpriteLoader : : CommonPixel * src = sprite [ ZOOM_LVL_MIN ] . data ;
2024-03-16 23:59:32 +01:00
uint8_t * dest = s - > data ;
2012-02-04 14:28:56 +01:00
while ( num - - > 0 ) {
* dest + + = src - > m ;
src + + ;
}
return s ;
}
2021-04-14 17:20:39 +02:00
if ( ! ResizeSprites ( sprite , sprite_avail , encoder ) ) {
2023-04-19 22:47:36 +02:00
if ( id = = SPR_IMG_QUERY ) UserError ( " Okay... something went horribly wrong. I couldn't resize the fallback sprite. What should I do? " ) ;
2023-04-16 21:00:55 +02:00
return ( void * ) GetRawSprite ( SPR_IMG_QUERY , SpriteType : : Normal , allocator , encoder ) ;
2012-02-04 14:29:13 +01:00
}
2014-10-12 22:43:25 +02:00
2024-04-04 19:49:37 +02:00
if ( sprite [ ZOOM_LVL_MIN ] . type = = SpriteType : : Font & & _font_zoom ! = ZOOM_LVL_MIN ) {
/* Make ZOOM_LVL_MIN be ZOOM_LVL_GUI */
sprite [ ZOOM_LVL_MIN ] . width = sprite [ _font_zoom ] . width ;
sprite [ ZOOM_LVL_MIN ] . height = sprite [ _font_zoom ] . height ;
sprite [ ZOOM_LVL_MIN ] . x_offs = sprite [ _font_zoom ] . x_offs ;
sprite [ ZOOM_LVL_MIN ] . y_offs = sprite [ _font_zoom ] . y_offs ;
sprite [ ZOOM_LVL_MIN ] . data = sprite [ _font_zoom ] . data ;
sprite [ ZOOM_LVL_MIN ] . colours = sprite [ _font_zoom ] . colours ;
2014-10-12 22:43:25 +02:00
}
2021-01-16 16:43:27 +01:00
return encoder - > Encode ( sprite , allocator ) ;
2004-08-09 19:04:08 +02:00
}
2022-09-21 12:37:11 +02:00
struct GrfSpriteOffset {
size_t file_pos ;
2024-03-16 23:59:32 +01:00
uint8_t control_flags ;
2022-09-21 12:37:11 +02:00
} ;
2004-08-09 19:04:08 +02:00
2013-06-27 23:18:56 +02:00
/** Map from sprite numbers to position in the GRF file. */
2023-05-08 19:01:06 +02:00
static std : : map < uint32_t , GrfSpriteOffset > _grf_sprite_offsets ;
2012-02-04 14:29:04 +01:00
/**
* Get the file offset for a specific sprite in the sprite section of a GRF .
* @ param id ID of the sprite to look up .
* @ return Position of the sprite in the sprite section or SIZE_MAX if no such sprite is present .
*/
2023-05-08 19:01:06 +02:00
size_t GetGRFSpriteOffset ( uint32_t id )
2012-02-04 14:29:04 +01:00
{
2022-09-21 12:37:11 +02:00
return _grf_sprite_offsets . find ( id ) ! = _grf_sprite_offsets . end ( ) ? _grf_sprite_offsets [ id ] . file_pos : SIZE_MAX ;
2012-02-04 14:29:04 +01:00
}
/**
* Parse the sprite section of GRFs .
* @ param container_version Container version of the GRF we ' re currently processing .
*/
2021-04-14 17:20:39 +02:00
void ReadGRFSpriteOffsets ( SpriteFile & file )
2012-02-04 14:29:04 +01:00
{
_grf_sprite_offsets . clear ( ) ;
2021-04-14 17:20:39 +02:00
if ( file . GetContainerVersion ( ) > = 2 ) {
2012-02-04 14:29:04 +01:00
/* Seek to sprite section of the GRF. */
2021-04-14 17:20:39 +02:00
size_t data_offset = file . ReadDword ( ) ;
size_t old_pos = file . GetPos ( ) ;
file . SeekTo ( data_offset , SEEK_CUR ) ;
2012-02-04 14:29:04 +01:00
2022-09-21 12:37:11 +02:00
GrfSpriteOffset offset = { 0 , 0 } ;
2012-02-04 14:29:04 +01:00
/* Loop over all sprite section entries and store the file
* offset for each newly encountered ID . */
2023-05-08 19:01:06 +02:00
uint32_t id , prev_id = 0 ;
2021-04-14 17:20:39 +02:00
while ( ( id = file . ReadDword ( ) ) ! = 0 ) {
2022-09-21 12:37:11 +02:00
if ( id ! = prev_id ) {
_grf_sprite_offsets [ prev_id ] = offset ;
offset . file_pos = file . GetPos ( ) - 4 ;
offset . control_flags = 0 ;
}
2012-02-04 14:29:04 +01:00
prev_id = id ;
2022-09-21 12:37:11 +02:00
uint length = file . ReadDword ( ) ;
if ( length > 0 ) {
2024-03-16 23:59:32 +01:00
uint8_t colour = file . ReadByte ( ) & SCC_MASK ;
2022-09-21 12:37:11 +02:00
length - - ;
if ( length > 0 ) {
2024-03-16 23:59:32 +01:00
uint8_t zoom = file . ReadByte ( ) ;
2022-09-21 12:37:11 +02:00
length - - ;
2024-04-04 19:51:46 +02:00
if ( colour ! = 0 & & zoom = = 0 ) { // ZOOM_LVL_NORMAL (normal zoom)
2022-09-21 12:37:11 +02:00
SetBit ( offset . control_flags , ( colour ! = SCC_PAL ) ? SCCF_ALLOW_ZOOM_MIN_1X_32BPP : SCCF_ALLOW_ZOOM_MIN_1X_PAL ) ;
SetBit ( offset . control_flags , ( colour ! = SCC_PAL ) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL ) ;
}
2024-04-04 19:51:46 +02:00
if ( colour ! = 0 & & zoom = = 2 ) { // ZOOM_LVL_IN_2X (2x zoomed in)
2022-09-21 12:37:11 +02:00
SetBit ( offset . control_flags , ( colour ! = SCC_PAL ) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL ) ;
}
}
}
file . SkipBytes ( length ) ;
2012-02-04 14:29:04 +01:00
}
2022-09-29 11:56:42 +02:00
if ( prev_id ! = 0 ) _grf_sprite_offsets [ prev_id ] = offset ;
2012-02-04 14:29:04 +01:00
/* Continue processing the data section. */
2021-04-14 17:20:39 +02:00
file . SeekTo ( old_pos , SEEK_SET ) ;
2012-02-04 14:29:04 +01:00
}
}
/**
* Load a real or recolour sprite .
* @ param load_index Global sprite index .
2021-04-14 17:20:39 +02:00
* @ param file GRF to load from .
2012-02-04 14:29:04 +01:00
* @ param file_sprite_id Sprite number in the GRF .
* @ param container_version Container version of the GRF .
* @ return True if a valid sprite was loaded , false on any error .
*/
2021-04-14 17:20:39 +02:00
bool LoadNextSprite ( int load_index , SpriteFile & file , uint file_sprite_id )
2004-08-09 19:04:08 +02:00
{
2021-04-14 17:20:39 +02:00
size_t file_pos = file . GetPos ( ) ;
2004-08-09 19:04:08 +02:00
2012-02-04 14:28:52 +01:00
/* Read sprite header. */
2023-05-08 19:01:06 +02:00
uint32_t num = file . GetContainerVersion ( ) > = 2 ? file . ReadDword ( ) : file . ReadWord ( ) ;
2012-02-04 14:28:52 +01:00
if ( num = = 0 ) return false ;
2024-03-16 23:59:32 +01:00
uint8_t grf_type = file . ReadByte ( ) ;
2012-02-04 14:28:52 +01:00
SpriteType type ;
2019-04-10 23:07:06 +02:00
void * data = nullptr ;
2024-03-16 23:59:32 +01:00
uint8_t control_flags = 0 ;
2012-02-04 14:28:52 +01:00
if ( grf_type = = 0xFF ) {
/* Some NewGRF files have "empty" pseudo-sprites which are 1
* byte long . Catch these so the sprites won ' t be displayed . */
if ( num = = 1 ) {
2021-04-14 17:20:39 +02:00
file . ReadByte ( ) ;
2012-02-04 14:28:52 +01:00
return false ;
}
2023-04-16 21:00:55 +02:00
type = SpriteType : : Recolour ;
2021-04-14 17:20:39 +02:00
data = ReadRecolourSprite ( file , num ) ;
} else if ( file . GetContainerVersion ( ) > = 2 & & grf_type = = 0xFD ) {
2012-02-04 14:29:04 +01:00
if ( num ! = 4 ) {
/* Invalid sprite section include, ignore. */
2021-04-14 17:20:39 +02:00
file . SkipBytes ( num ) ;
2012-02-04 14:29:04 +01:00
return false ;
}
/* It is not an error if no sprite with the provided ID is found in the sprite section. */
2022-09-21 12:37:11 +02:00
auto iter = _grf_sprite_offsets . find ( file . ReadDword ( ) ) ;
if ( iter ! = _grf_sprite_offsets . end ( ) ) {
file_pos = iter - > second . file_pos ;
control_flags = iter - > second . control_flags ;
} else {
file_pos = SIZE_MAX ;
}
2023-04-16 21:00:55 +02:00
type = SpriteType : : Normal ;
2012-02-04 14:28:52 +01:00
} else {
2021-04-14 17:20:39 +02:00
file . SkipBytes ( 7 ) ;
2023-04-16 21:00:55 +02:00
type = SkipSpriteData ( file , grf_type , num - 8 ) ? SpriteType : : Normal : SpriteType : : Invalid ;
2012-02-04 14:29:04 +01:00
/* Inline sprites are not supported for container version >= 2. */
2021-04-14 17:20:39 +02:00
if ( file . GetContainerVersion ( ) > = 2 ) return false ;
2012-02-04 14:28:52 +01:00
}
2009-01-11 23:28:30 +01:00
2023-04-16 21:00:55 +02:00
if ( type = = SpriteType : : Invalid ) return false ;
2004-11-12 19:47:19 +01:00
2006-04-20 07:57:47 +02:00
if ( load_index > = MAX_SPRITES ) {
2023-04-19 22:47:36 +02:00
UserError ( " Tried to load too many sprites (#{}; max {}) " , load_index , MAX_SPRITES ) ;
2006-04-20 07:57:47 +02:00
}
2009-01-11 23:28:30 +01:00
bool is_mapgen = IsMapgenSpriteID ( load_index ) ;
if ( is_mapgen ) {
2023-04-19 22:47:36 +02:00
if ( type ! = SpriteType : : Normal ) UserError ( " Uhm, would you be so kind not to load a NewGRF that changes the type of the map generator sprites? " ) ;
2023-04-16 21:00:55 +02:00
type = SpriteType : : MapGen ;
2009-01-11 23:28:30 +01:00
}
SpriteCache * sc = AllocateSpriteCache ( load_index ) ;
2021-04-14 17:20:39 +02:00
sc - > file = & file ;
2007-01-03 15:42:08 +01:00
sc - > file_pos = file_pos ;
2012-02-04 14:28:52 +01:00
sc - > ptr = data ;
2007-01-03 15:42:08 +01:00
sc - > lru = 0 ;
2007-06-14 16:31:48 +02:00
sc - > id = file_sprite_id ;
2009-01-11 23:28:30 +01:00
sc - > type = type ;
2009-01-12 00:49:51 +01:00
sc - > warned = false ;
2022-09-21 12:37:11 +02:00
sc - > control_flags = control_flags ;
2007-06-13 18:21:11 +02:00
2004-08-09 19:04:08 +02:00
return true ;
}
2005-09-10 10:17:30 +02:00
2007-01-10 19:56:51 +01:00
void DupSprite ( SpriteID old_spr , SpriteID new_spr )
2005-09-10 10:17:30 +02:00
{
2008-01-29 01:29:28 +01:00
SpriteCache * scnew = AllocateSpriteCache ( new_spr ) ; // may reallocate: so put it first
2007-01-10 19:56:51 +01:00
SpriteCache * scold = GetSpriteCache ( old_spr ) ;
2007-01-03 15:42:08 +01:00
2021-04-14 17:20:39 +02:00
scnew - > file = scold - > file ;
2007-01-03 15:42:08 +01:00
scnew - > file_pos = scold - > file_pos ;
2019-04-10 23:07:06 +02:00
scnew - > ptr = nullptr ;
2007-06-13 18:21:11 +02:00
scnew - > id = scold - > id ;
2008-09-02 17:20:38 +02:00
scnew - > type = scold - > type ;
2009-01-12 00:49:51 +01:00
scnew - > warned = false ;
2005-09-10 10:17:30 +02:00
}
2009-05-24 18:28:33 +02:00
/**
* S_FREE_MASK is used to mask - out lower bits of MemBlock : : size
* If they are non - zero , the block is free .
* S_FREE_MASK has to ensure MemBlock is correctly aligned -
* it means 8 B ( S_FREE_MASK = = 7 ) on 64 bit systems !
*/
static const size_t S_FREE_MASK = sizeof ( size_t ) - 1 ;
2005-09-10 10:17:30 +02:00
2009-05-24 18:28:33 +02:00
/* to make sure nobody adds things to MemBlock without checking S_FREE_MASK first */
2020-12-27 11:44:22 +01:00
static_assert ( sizeof ( MemBlock ) = = sizeof ( size_t ) ) ;
2009-05-24 18:28:33 +02:00
/* make sure it's a power of two */
2020-12-27 11:44:22 +01:00
static_assert ( ( sizeof ( size_t ) & ( sizeof ( size_t ) - 1 ) ) = = 0 ) ;
2005-02-11 14:35:27 +01:00
2009-01-10 01:31:47 +01:00
static inline MemBlock * NextBlock ( MemBlock * block )
2005-02-11 14:35:27 +01:00
{
2024-03-16 23:59:32 +01:00
return ( MemBlock * ) ( ( uint8_t * ) block + ( block - > size & ~ S_FREE_MASK ) ) ;
2005-02-11 14:35:27 +01:00
}
2004-08-09 19:04:08 +02:00
2008-05-27 23:41:00 +02:00
static size_t GetSpriteCacheUsage ( )
2004-08-09 19:04:08 +02:00
{
2008-05-27 23:41:00 +02:00
size_t tot_size = 0 ;
2009-01-10 01:31:47 +01:00
MemBlock * s ;
2005-02-11 14:35:27 +01:00
2008-02-14 16:13:36 +01:00
for ( s = _spritecache_ptr ; s - > size ! = 0 ; s = NextBlock ( s ) ) {
2005-02-11 14:35:27 +01:00
if ( ! ( s - > size & S_FREE_MASK ) ) tot_size + = s - > size ;
2008-02-14 16:13:36 +01:00
}
2004-08-09 19:04:08 +02:00
return tot_size ;
}
2007-03-07 12:47:46 +01:00
void IncreaseSpriteLRU ( )
2004-08-09 19:04:08 +02:00
{
2007-04-04 03:35:16 +02:00
/* Increase all LRU values */
2004-08-09 19:04:08 +02:00
if ( _sprite_lru_counter > 16384 ) {
2007-01-03 15:42:08 +01:00
SpriteID i ;
2023-06-27 12:46:14 +02:00
Debug ( sprite , 5 , " Fixing lru {}, inuse={} " , _sprite_lru_counter , GetSpriteCacheUsage ( ) ) ;
2004-08-09 19:04:08 +02:00
2007-01-03 15:42:08 +01:00
for ( i = 0 ; i ! = _spritecache_items ; i + + ) {
SpriteCache * sc = GetSpriteCache ( i ) ;
2019-04-10 23:07:06 +02:00
if ( sc - > ptr ! = nullptr ) {
2007-01-03 15:42:08 +01:00
if ( sc - > lru > = 0 ) {
sc - > lru = - 1 ;
} else if ( sc - > lru ! = - 32768 ) {
sc - > lru - - ;
2004-08-09 19:04:08 +02:00
}
}
2007-01-03 15:42:08 +01:00
}
2004-08-09 19:04:08 +02:00
_sprite_lru_counter = 0 ;
}
2007-04-04 03:35:16 +02:00
/* Compact sprite cache every now and then. */
2004-08-09 19:04:08 +02:00
if ( + + _compact_cache_counter > = 740 ) {
CompactSpriteCache ( ) ;
_compact_cache_counter = 0 ;
}
}
2010-08-01 21:22:34 +02:00
/**
* Called when holes in the sprite cache should be removed .
2010-08-01 21:44:49 +02:00
* That is accomplished by moving the cached data .
*/
2007-03-07 12:47:46 +01:00
static void CompactSpriteCache ( )
2004-08-09 19:04:08 +02:00
{
2005-02-11 14:35:27 +01:00
MemBlock * s ;
2004-09-08 20:05:49 +02:00
2021-06-12 09:10:17 +02:00
Debug ( sprite , 3 , " Compacting sprite cache, inuse={} " , GetSpriteCacheUsage ( ) ) ;
2004-08-09 19:04:08 +02:00
2005-02-11 14:35:27 +01:00
for ( s = _spritecache_ptr ; s - > size ! = 0 ; ) {
if ( s - > size & S_FREE_MASK ) {
2009-01-10 01:31:47 +01:00
MemBlock * next = NextBlock ( s ) ;
2005-02-11 14:35:27 +01:00
MemBlock temp ;
2007-01-03 15:42:08 +01:00
SpriteID i ;
2004-09-08 20:05:49 +02:00
2007-04-04 03:35:16 +02:00
/* Since free blocks are automatically coalesced, this should hold true. */
2005-02-11 14:35:27 +01:00
assert ( ! ( next - > size & S_FREE_MASK ) ) ;
2004-09-08 20:05:49 +02:00
2007-04-04 03:35:16 +02:00
/* If the next block is the sentinel block, we can safely return */
2008-03-26 20:18:30 +01:00
if ( next - > size = = 0 ) break ;
2004-08-09 19:04:08 +02:00
2007-04-04 03:35:16 +02:00
/* Locate the sprite belonging to the next pointer. */
2007-01-03 15:42:08 +01:00
for ( i = 0 ; GetSpriteCache ( i ) - > ptr ! = next - > data ; i + + ) {
assert ( i ! = _spritecache_items ) ;
2005-02-11 14:35:27 +01:00
}
2004-09-08 20:05:49 +02:00
2007-01-03 15:42:08 +01:00
GetSpriteCache ( i ) - > ptr = s - > data ; // Adjust sprite array entry
2007-04-04 03:35:16 +02:00
/* Swap this and the next block */
2005-02-11 14:35:27 +01:00
temp = * s ;
memmove ( s , next , next - > size ) ;
s = NextBlock ( s ) ;
* s = temp ;
2007-04-04 03:35:16 +02:00
/* Coalesce free blocks */
2005-02-11 14:35:27 +01:00
while ( NextBlock ( s ) - > size & S_FREE_MASK ) {
s - > size + = NextBlock ( s ) - > size & ~ S_FREE_MASK ;
}
} else {
s = NextBlock ( s ) ;
2004-08-09 19:04:08 +02:00
}
}
}
2012-02-04 14:28:52 +01:00
/**
* Delete a single entry from the sprite cache .
* @ param item Entry to delete .
*/
static void DeleteEntryFromSpriteCache ( uint item )
{
/* Mark the block as free (the block must be in use) */
MemBlock * s = ( MemBlock * ) GetSpriteCache ( item ) - > ptr - 1 ;
assert ( ! ( s - > size & S_FREE_MASK ) ) ;
s - > size | = S_FREE_MASK ;
2019-04-10 23:07:06 +02:00
GetSpriteCache ( item ) - > ptr = nullptr ;
2012-02-04 14:28:52 +01:00
/* And coalesce adjacent free blocks */
for ( s = _spritecache_ptr ; s - > size ! = 0 ; s = NextBlock ( s ) ) {
if ( s - > size & S_FREE_MASK ) {
while ( NextBlock ( s ) - > size & S_FREE_MASK ) {
s - > size + = NextBlock ( s ) - > size & ~ S_FREE_MASK ;
}
}
}
}
2007-03-07 12:47:46 +01:00
static void DeleteEntryFromSpriteCache ( )
2004-08-09 19:04:08 +02:00
{
2007-01-10 19:56:51 +01:00
uint best = UINT_MAX ;
2004-08-09 19:04:08 +02:00
int cur_lru ;
2021-06-12 09:10:17 +02:00
Debug ( sprite , 3 , " DeleteEntryFromSpriteCache, inuse={} " , GetSpriteCacheUsage ( ) ) ;
2004-08-09 19:04:08 +02:00
cur_lru = 0xffff ;
2012-02-04 14:28:52 +01:00
for ( SpriteID i = 0 ; i ! = _spritecache_items ; i + + ) {
2007-01-03 15:42:08 +01:00
SpriteCache * sc = GetSpriteCache ( i ) ;
2023-04-16 21:00:55 +02:00
if ( sc - > type ! = SpriteType : : Recolour & & sc - > ptr ! = nullptr & & sc - > lru < cur_lru ) {
2007-01-03 15:42:08 +01:00
cur_lru = sc - > lru ;
2004-08-09 19:04:08 +02:00
best = i ;
}
}
2007-04-04 03:35:16 +02:00
/* Display an error message and die, in case we found no sprite at all.
* This shouldn ' t really happen , unless all sprites are locked . */
2023-04-19 22:47:36 +02:00
if ( best = = UINT_MAX ) FatalError ( " Out of sprite memory " ) ;
2004-08-09 19:04:08 +02:00
2012-02-04 14:28:52 +01:00
DeleteEntryFromSpriteCache ( best ) ;
2004-08-09 19:04:08 +02:00
}
2023-11-02 16:06:56 +01:00
void * AllocSprite ( size_t mem_req )
2004-08-09 19:04:08 +02:00
{
2005-07-05 21:54:35 +02:00
mem_req + = sizeof ( MemBlock ) ;
2004-08-09 19:04:08 +02:00
2009-05-24 18:28:33 +02:00
/* Align this to correct boundary. This also makes sure at least one
* bit is not used , so we can use it for other things . */
mem_req = Align ( mem_req , S_FREE_MASK + 1 ) ;
2004-08-09 19:04:08 +02:00
2005-02-11 14:35:27 +01:00
for ( ; ; ) {
2009-01-10 01:31:47 +01:00
MemBlock * s ;
2004-08-09 19:04:08 +02:00
2005-02-11 14:35:27 +01:00
for ( s = _spritecache_ptr ; s - > size ! = 0 ; s = NextBlock ( s ) ) {
if ( s - > size & S_FREE_MASK ) {
size_t cur_size = s - > size & ~ S_FREE_MASK ;
2004-08-09 19:04:08 +02:00
2005-02-11 14:35:27 +01:00
/* Is the block exactly the size we need or
* big enough for an additional free block ? */
if ( cur_size = = mem_req | |
cur_size > = mem_req + sizeof ( MemBlock ) ) {
2007-04-04 03:35:16 +02:00
/* Set size and in use */
2005-02-11 14:35:27 +01:00
s - > size = mem_req ;
2004-09-08 20:05:49 +02:00
2007-04-04 03:35:16 +02:00
/* Do we need to inject a free block too? */
2005-02-11 14:35:27 +01:00
if ( cur_size ! = mem_req ) {
NextBlock ( s ) - > size = ( cur_size - mem_req ) | S_FREE_MASK ;
}
2004-08-09 19:04:08 +02:00
2005-02-11 14:35:27 +01:00
return s - > data ;
}
}
2004-08-09 19:04:08 +02:00
}
2004-09-08 20:05:49 +02:00
2007-04-04 03:35:16 +02:00
/* Reached sentinel, but no block found yet. Delete some old entry. */
2005-02-11 14:35:27 +01:00
DeleteEntryFromSpriteCache ( ) ;
2004-08-09 19:04:08 +02:00
}
}
2021-01-16 16:43:31 +01:00
/**
* Sprite allocator simply using malloc .
*/
void * SimpleSpriteAlloc ( size_t size )
{
2024-03-16 23:59:32 +01:00
return MallocT < uint8_t > ( size ) ;
2021-01-16 16:43:31 +01:00
}
2010-08-01 21:22:34 +02:00
/**
* Handles the case when a sprite of different type is requested than is present in the SpriteCache .
2023-04-16 21:00:55 +02:00
* For SpriteType : : Font sprites , it is normal . In other cases , default sprite is loaded instead .
2009-01-11 23:28:30 +01:00
* @ param sprite ID of loaded sprite
* @ param requested requested sprite type
2009-09-19 11:51:14 +02:00
* @ param sc the currently known sprite cache for the requested sprite
2009-01-11 23:28:30 +01:00
* @ return fallback sprite
2023-04-19 22:47:36 +02:00
* @ note this function will do UserError ( ) in the case the fallback sprite isn ' t available
2010-08-01 21:44:49 +02:00
*/
2011-01-14 17:36:34 +01:00
static void * HandleInvalidSpriteRequest ( SpriteID sprite , SpriteType requested , SpriteCache * sc , AllocatorProc * allocator )
2009-01-11 23:28:30 +01:00
{
2009-09-21 01:11:01 +02:00
static const char * const sprite_types [ ] = {
2023-04-16 21:00:55 +02:00
" normal " , // SpriteType::Normal
" map generator " , // SpriteType::MapGen
" character " , // SpriteType::Font
" recolour " , // SpriteType::Recolour
2009-01-11 23:28:30 +01:00
} ;
2009-01-12 00:49:51 +01:00
SpriteType available = sc - > type ;
2023-04-16 21:00:55 +02:00
if ( requested = = SpriteType : : Font & & available = = SpriteType : : Normal ) {
if ( sc - > ptr = = nullptr ) sc - > type = SpriteType : : Font ;
2011-01-14 17:36:34 +01:00
return GetRawSprite ( sprite , sc - > type , allocator ) ;
2009-02-07 03:29:08 +01:00
}
2009-01-11 23:28:30 +01:00
2024-03-16 23:59:32 +01:00
uint8_t warning_level = sc - > warned ? 6 : 0 ;
2009-01-12 00:49:51 +01:00
sc - > warned = true ;
2024-03-16 23:59:32 +01:00
Debug ( sprite , warning_level , " Tried to load {} sprite #{} as a {} sprite. Probable cause: NewGRF interference " , sprite_types [ static_cast < uint8_t > ( available ) ] , sprite , sprite_types [ static_cast < uint8_t > ( requested ) ] ) ;
2009-01-11 23:28:30 +01:00
switch ( requested ) {
2023-04-16 21:00:55 +02:00
case SpriteType : : Normal :
2023-04-19 22:47:36 +02:00
if ( sprite = = SPR_IMG_QUERY ) UserError ( " Uhm, would you be so kind not to load a NewGRF that makes the 'query' sprite a non-normal sprite? " ) ;
2024-01-31 21:03:17 +01:00
[[fallthrough]] ;
2023-04-16 21:00:55 +02:00
case SpriteType : : Font :
return GetRawSprite ( SPR_IMG_QUERY , SpriteType : : Normal , allocator ) ;
case SpriteType : : Recolour :
2023-04-19 22:47:36 +02:00
if ( sprite = = PALETTE_TO_DARK_BLUE ) UserError ( " Uhm, would you be so kind not to load a NewGRF that makes the 'PALETTE_TO_DARK_BLUE' sprite a non-remap sprite? " ) ;
2023-04-16 21:00:55 +02:00
return GetRawSprite ( PALETTE_TO_DARK_BLUE , SpriteType : : Recolour , allocator ) ;
case SpriteType : : MapGen :
/* this shouldn't happen, overriding of SpriteType::MapGen sprites is checked in LoadNextSprite()
2009-01-11 23:28:30 +01:00
* ( the only case the check fails is when these sprites weren ' t even loaded . . . ) */
default :
NOT_REACHED ( ) ;
}
}
2004-08-09 19:04:08 +02:00
2011-01-14 17:36:34 +01:00
/**
* Reads a sprite ( from disk or sprite cache ) .
* If the sprite is not available or of wrong type , a fallback sprite is returned .
* @ param sprite Sprite to read .
* @ param type Expected sprite type .
2019-04-10 23:07:06 +02:00
* @ param allocator Allocator function to use . Set to nullptr to use the usual sprite cache .
2021-01-16 16:43:27 +01:00
* @ param encoder Sprite encoder to use . Set to nullptr to use the currently active blitter .
2011-01-14 17:36:34 +01:00
* @ return Sprite raw data
*/
2021-01-16 16:43:27 +01:00
void * GetRawSprite ( SpriteID sprite , SpriteType type , AllocatorProc * allocator , SpriteEncoder * encoder )
2004-08-09 19:04:08 +02:00
{
2023-04-16 21:00:55 +02:00
assert ( type ! = SpriteType : : MapGen | | IsMapgenSpriteID ( sprite ) ) ;
assert ( type < SpriteType : : Invalid ) ;
2009-01-11 23:28:30 +01:00
if ( ! SpriteExists ( sprite ) ) {
2021-06-12 09:10:17 +02:00
Debug ( sprite , 1 , " Tried to load non-existing sprite #{}. Probable cause: Wrong/missing NewGRFs " , sprite ) ;
2009-01-11 23:28:30 +01:00
/* SPR_IMG_QUERY is a BIG FAT RED ? */
sprite = SPR_IMG_QUERY ;
}
2004-08-09 19:04:08 +02:00
2009-01-11 23:28:30 +01:00
SpriteCache * sc = GetSpriteCache ( sprite ) ;
2004-08-09 19:04:08 +02:00
2011-01-14 17:36:34 +01:00
if ( sc - > type ! = type ) return HandleInvalidSpriteRequest ( sprite , type , sc , allocator ) ;
2021-01-16 16:43:27 +01:00
if ( allocator = = nullptr & & encoder = = nullptr ) {
2011-01-14 17:36:34 +01:00
/* Load sprite into/from spritecache */
2007-01-03 15:42:08 +01:00
2011-01-14 17:36:34 +01:00
/* Update LRU */
sc - > lru = + + _sprite_lru_counter ;
2007-01-03 15:42:08 +01:00
2011-01-14 17:36:34 +01:00
/* Load the sprite, if it is not loaded, yet */
2021-01-16 16:43:27 +01:00
if ( sc - > ptr = = nullptr ) sc - > ptr = ReadSprite ( sc , sprite , type , AllocSprite , nullptr ) ;
2008-02-14 16:13:36 +01:00
2011-01-14 17:36:34 +01:00
return sc - > ptr ;
} else {
/* Do not use the spritecache, but a different allocator. */
2021-01-16 16:43:27 +01:00
return ReadSprite ( sc , sprite , type , allocator , encoder ) ;
2011-01-14 17:36:34 +01:00
}
2004-08-09 19:04:08 +02:00
}
2011-11-24 13:26:44 +01:00
static void GfxInitSpriteCache ( )
2004-08-09 19:04:08 +02:00
{
2007-04-04 03:35:16 +02:00
/* initialize sprite cache heap */
2014-01-02 23:41:58 +01:00
int bpp = BlitterFactory : : GetCurrentBlitter ( ) - > GetScreenDepth ( ) ;
2012-12-19 20:31:18 +01:00
uint target_size = ( bpp > 0 ? _sprite_cache_size * bpp / 8 : 1 ) * 1024 * 1024 ;
2012-05-15 22:37:42 +02:00
2013-03-03 14:00:06 +01:00
/* Remember 'target_size' from the previous allocation attempt, so we do not try to reach the target_size multiple times in case of failure. */
static uint last_alloc_attempt = 0 ;
2019-04-10 23:07:06 +02:00
if ( _spritecache_ptr = = nullptr | | ( _allocated_sprite_cache_size ! = target_size & & target_size ! = last_alloc_attempt ) ) {
2024-03-16 23:59:32 +01:00
delete [ ] reinterpret_cast < uint8_t * > ( _spritecache_ptr ) ;
2013-03-03 14:00:06 +01:00
last_alloc_attempt = target_size ;
2012-05-15 22:37:42 +02:00
_allocated_sprite_cache_size = target_size ;
2013-03-03 14:00:06 +01:00
do {
2023-01-02 22:57:17 +01:00
/* Try to allocate 50% more to make sure we do not allocate almost all available. */
2024-03-16 23:59:32 +01:00
_spritecache_ptr = reinterpret_cast < MemBlock * > ( new ( std : : nothrow ) uint8_t [ _allocated_sprite_cache_size + _allocated_sprite_cache_size / 2 ] ) ;
2013-03-03 14:00:06 +01:00
2019-04-10 23:07:06 +02:00
if ( _spritecache_ptr ! = nullptr ) {
2013-03-03 14:00:06 +01:00
/* Allocation succeeded, but we wanted less. */
2024-03-16 23:59:32 +01:00
delete [ ] reinterpret_cast < uint8_t * > ( _spritecache_ptr ) ;
_spritecache_ptr = reinterpret_cast < MemBlock * > ( new uint8_t [ _allocated_sprite_cache_size ] ) ;
2013-03-03 14:00:06 +01:00
} else if ( _allocated_sprite_cache_size < 2 * 1024 * 1024 ) {
2023-04-19 22:47:36 +02:00
UserError ( " Cannot allocate spritecache " ) ;
2013-03-03 14:00:06 +01:00
} else {
/* Try again to allocate half. */
_allocated_sprite_cache_size > > = 1 ;
}
2019-04-10 23:07:06 +02:00
} while ( _spritecache_ptr = = nullptr ) ;
2013-03-03 14:00:06 +01:00
if ( _allocated_sprite_cache_size ! = target_size ) {
2021-06-12 09:10:17 +02:00
Debug ( misc , 0 , " Not enough memory to allocate {} MiB of spritecache. Spritecache was reduced to {} MiB. " , target_size / 1024 / 1024 , _allocated_sprite_cache_size / 1024 / 1024 ) ;
2013-03-03 14:00:06 +01:00
ErrorMessageData msg ( STR_CONFIG_ERROR_OUT_OF_MEMORY , STR_CONFIG_ERROR_SPRITECACHE_TOO_BIG ) ;
msg . SetDParam ( 0 , target_size ) ;
msg . SetDParam ( 1 , _allocated_sprite_cache_size ) ;
ScheduleErrorMessage ( msg ) ;
}
2012-05-15 22:37:42 +02:00
}
2004-08-09 19:04:08 +02:00
2007-04-04 03:35:16 +02:00
/* A big free block */
2012-05-15 22:37:42 +02:00
_spritecache_ptr - > size = ( _allocated_sprite_cache_size - sizeof ( MemBlock ) ) | S_FREE_MASK ;
2007-04-04 03:35:16 +02:00
/* Sentinel block (identified by size == 0) */
2005-02-11 14:35:27 +01:00
NextBlock ( _spritecache_ptr ) - > size = 0 ;
2011-11-24 13:26:44 +01:00
}
void GfxInitSpriteMem ( )
{
GfxInitSpriteCache ( ) ;
2004-08-09 19:04:08 +02:00
2007-01-03 15:42:08 +01:00
/* Reset the spritecache 'pool' */
free ( _spritecache ) ;
_spritecache_items = 0 ;
2019-04-10 23:07:06 +02:00
_spritecache = nullptr ;
2004-08-09 19:04:08 +02:00
2005-08-14 20:10:18 +02:00
_compact_cache_counter = 0 ;
2021-04-14 17:20:39 +02:00
_sprite_files . clear ( ) ;
2004-08-09 19:04:08 +02:00
}
2009-02-23 11:50:25 +01:00
2011-11-24 13:26:44 +01:00
/**
* Remove all encoded sprites from the sprite cache without
* discarding sprite location information .
*/
void GfxClearSpriteCache ( )
{
/* Clear sprite ptr for all cached items */
for ( uint i = 0 ; i ! = _spritecache_items ; i + + ) {
SpriteCache * sc = GetSpriteCache ( i ) ;
2023-04-16 21:00:55 +02:00
if ( sc - > type ! = SpriteType : : Recolour & & sc - > ptr ! = nullptr ) DeleteEntryFromSpriteCache ( i ) ;
2011-11-24 13:26:44 +01:00
}
2021-01-16 16:43:33 +01:00
VideoDriver : : GetInstance ( ) - > ClearSystemSprites ( ) ;
2011-11-24 13:26:44 +01:00
}
2023-04-17 00:14:03 +02:00
/**
* Remove all encoded font sprites from the sprite cache without
* discarding sprite location information .
*/
void GfxClearFontSpriteCache ( )
{
/* Clear sprite ptr for all cached font items */
for ( uint i = 0 ; i ! = _spritecache_items ; i + + ) {
SpriteCache * sc = GetSpriteCache ( i ) ;
if ( sc - > type = = SpriteType : : Font & & sc - > ptr ! = nullptr ) DeleteEntryFromSpriteCache ( i ) ;
}
}
2023-11-30 01:29:23 +01:00
/* static */ ReusableBuffer < SpriteLoader : : CommonPixel > SpriteLoader : : Sprite : : buffer [ ZOOM_LVL_END ] ;