tron@2186: /* $Id$ */ tron@2186: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@1299: #include "debug.h" tron@2163: #include "functions.h" tron@2548: #include "macros.h" tron@1349: #include "spritecache.h" tron@1363: #include "table/sprites.h" truelight@0: #include "fileio.h" celestar@5650: #include "helpers.hpp" truelight@0: truelight@0: #define SPRITE_CACHE_SIZE 1024*1024 truelight@0: celestar@5648: celestar@5648: typedef struct SpriteCache { celestar@5648: void *ptr; celestar@5648: uint32 file_pos; celestar@5648: int16 lru; celestar@5648: } SpriteCache; truelight@0: truelight@0: celestar@5648: static uint _spritecache_items = 0; celestar@5648: static SpriteCache *_spritecache = NULL; truelight@0: celestar@5648: celestar@5648: static inline SpriteCache *GetSpriteCache(uint index) celestar@5648: { celestar@5648: return &_spritecache[index]; celestar@5648: } celestar@5648: celestar@5648: celestar@5648: static SpriteCache *AllocateSpriteCache(uint index) celestar@5648: { celestar@5648: if (index >= _spritecache_items) { celestar@5648: /* Add another 1024 items to the 'pool' */ celestar@5648: uint items = ALIGN(index + 1, 1024); celestar@5648: celestar@5648: DEBUG(sprite, 4, "Increasing sprite cache to %d items (%d bytes)", items, items * sizeof(*_spritecache)); celestar@5648: celestar@5650: ReallocT(&_spritecache, items); celestar@5648: celestar@5648: if (_spritecache == NULL) { celestar@5648: error("Unable to allocate sprite cache of %d items (%d bytes)", items, items * sizeof(*_spritecache)); celestar@5648: } celestar@5648: celestar@5648: /* Reset the new items and update the count */ celestar@5648: memset(_spritecache + _spritecache_items, 0, (items - _spritecache_items) * sizeof(*_spritecache)); celestar@5648: _spritecache_items = items; celestar@5648: } celestar@5648: celestar@5648: return GetSpriteCache(index); celestar@5648: } celestar@5648: truelight@0: tron@1353: typedef struct MemBlock { tron@1353: uint32 size; tron@1353: byte data[VARARRAY_SIZE]; tron@1353: } MemBlock; tron@1353: truelight@0: static uint _sprite_lru_counter; tron@1353: static MemBlock *_spritecache_ptr; truelight@0: static int _compact_cache_counter; truelight@0: tron@1093: static void CompactSpriteCache(void); truelight@0: tron@2342: static bool ReadSpriteHeaderSkipData(void) truelight@0: { tron@2329: uint16 num = FioReadWord(); truelight@0: byte type; truelight@0: tron@2329: if (num == 0) return false; tron@2329: truelight@0: type = FioReadByte(); truelight@0: if (type == 0xFF) { tron@2342: FioSkipBytes(num); peter1138@5150: /* Some NewGRF files have "empty" pseudo-sprites which are 1 peter1138@5150: * byte long. Catch these so the sprites won't be displayed. */ peter1138@5150: return num != 1; truelight@0: } truelight@184: truelight@0: FioSkipBytes(7); truelight@0: num -= 8; tron@2329: if (num == 0) return true; truelight@0: truelight@0: if (type & 2) { truelight@0: FioSkipBytes(num); tron@1355: } else { tron@1355: while (num > 0) { tron@1355: int8 i = FioReadByte(); tron@1355: if (i >= 0) { tron@1355: num -= i; tron@1355: FioSkipBytes(i); tron@1355: } else { tron@1355: i = -(i >> 3); tron@1355: num -= i; tron@1355: FioReadByte(); tron@1355: } truelight@0: } truelight@0: } tron@2329: tron@2329: return true; truelight@0: } truelight@0: peter1138@3565: /* Check if the given Sprite ID exists */ peter1138@3565: bool SpriteExists(SpriteID id) peter1138@3565: { peter1138@3565: /* Special case for Sprite ID zero -- its position is also 0... */ celestar@5648: if (id == 0) return true; celestar@5648: celestar@5648: return GetSpriteCache(id)->file_pos != 0; peter1138@3565: } peter1138@3565: tron@2014: static void* AllocSprite(size_t); tron@2014: celestar@5648: static void* ReadSprite(SpriteCache *sc, SpriteID id) truelight@0: { tron@2321: uint num; truelight@0: byte type; tron@2014: Darkvater@5568: DEBUG(sprite, 9, "Load sprite %d", id); truelight@184: peter1138@3565: if (!SpriteExists(id)) { tron@1378: error( tron@1378: "Tried to load non-existing sprite #%d.\n" tron@1378: "Probable cause: Wrong/missing NewGRFs", tron@1378: id tron@1378: ); tron@1378: } tron@1378: celestar@5648: FioSeekToFile(sc->file_pos); tron@1354: tron@2321: num = FioReadWord(); truelight@0: type = FioReadByte(); tron@2014: if (type == 0xFF) { celestar@5650: byte* dest = (byte*)AllocSprite(num); tron@2014: celestar@5648: sc->ptr = dest; tron@2014: FioReadBlock(dest, num); tron@2014: tron@2014: return dest; tron@2014: } else { tron@2014: uint height = FioReadByte(); tron@2014: uint width = FioReadWord(); tron@2014: Sprite* sprite; tron@2014: byte* dest; tron@2014: tron@2014: num = (type & 0x02) ? width * height : num - 8; celestar@5650: sprite = (Sprite*)AllocSprite(sizeof(*sprite) + num); celestar@5648: sc->ptr = sprite; tron@2014: sprite->info = type; tron@2015: sprite->height = (id != 142) ? height : 10; // Compensate for a TTD bug tron@2014: sprite->width = width; tron@1351: sprite->x_offs = FioReadWord(); tron@1351: sprite->y_offs = FioReadWord(); tron@2014: tron@1351: dest = sprite->data; tron@1355: while (num > 0) { tron@1355: int8 i = FioReadByte(); truelight@0: tron@1355: if (i >= 0) { tron@1355: num -= i; tron@2014: for (; i > 0; --i) *dest++ = FioReadByte(); tron@1355: } else { tron@1355: const byte* rel = dest - (((i & 7) << 8) | FioReadByte()); truelight@184: tron@1355: i = -(i >> 3); tron@1355: num -= i; tron@1355: tron@2014: for (; i > 0; --i) *dest++ = *rel++; tron@1355: } truelight@0: } tron@2014: tron@2014: return sprite; truelight@0: } truelight@0: } truelight@0: truelight@0: tron@2340: bool LoadNextSprite(int load_index, byte file_index) truelight@0: { celestar@5648: SpriteCache *sc; tron@2321: uint32 file_pos = FioGetPos() | (file_index << 24); truelight@0: tron@2342: if (!ReadSpriteHeaderSkipData()) return false; darkvater@361: peter1138@3591: if (load_index >= MAX_SPRITES) { peter1138@3591: error("Tried to load too many sprites (#%d; max %d)", load_index, MAX_SPRITES); peter1138@3591: } peter1138@3591: celestar@5648: sc = AllocateSpriteCache(load_index); celestar@5648: sc->file_pos = file_pos; celestar@5648: sc->ptr = NULL; celestar@5648: sc->lru = 0; truelight@0: truelight@0: return true; truelight@0: } truelight@0: tron@2407: celestar@5650: void DupSprite(SpriteID old_spr, SpriteID new_spr) tron@2407: { celestar@5650: SpriteCache *scold = GetSpriteCache(old_spr); celestar@5650: SpriteCache *scnew = AllocateSpriteCache(new_spr); celestar@5648: celestar@5648: scnew->file_pos = scold->file_pos; celestar@5648: scnew->ptr = NULL; tron@2407: } tron@2407: tron@2407: tron@2340: void SkipSprites(uint count) dominik@37: { tron@2329: for (; count > 0; --count) { tron@2342: if (!ReadSpriteHeaderSkipData()) return; dominik@37: } dominik@37: } dominik@37: truelight@0: truelight@0: #define S_FREE_MASK 1 tron@1353: tron@1353: static inline MemBlock* NextBlock(MemBlock* block) tron@1353: { tron@1353: return (MemBlock*)((byte*)block + (block->size & ~S_FREE_MASK)); tron@1353: } truelight@0: tron@1093: static uint32 GetSpriteCacheUsage(void) truelight@0: { truelight@4321: uint32 tot_size = 0; tron@1353: MemBlock* s; tron@1353: tron@1353: for (s = _spritecache_ptr; s->size != 0; s = NextBlock(s)) tron@1353: if (!(s->size & S_FREE_MASK)) tot_size += s->size; truelight@0: truelight@0: return tot_size; truelight@0: } truelight@0: truelight@0: tron@1093: void IncreaseSpriteLRU(void) truelight@0: { celestar@5648: // Increase all LRU values celestar@5648: if (_sprite_lru_counter > 16384) { celestar@5648: SpriteID i; truelight@0: Darkvater@5568: DEBUG(sprite, 3, "Fixing lru %d, inuse=%d", _sprite_lru_counter, GetSpriteCacheUsage()); truelight@0: celestar@5648: for (i = 0; i != _spritecache_items; i++) { celestar@5648: SpriteCache *sc = GetSpriteCache(i); celestar@5648: if (sc->ptr != NULL) { celestar@5648: if (sc->lru >= 0) { celestar@5648: sc->lru = -1; celestar@5648: } else if (sc->lru != -32768) { celestar@5648: sc->lru--; truelight@0: } truelight@0: } celestar@5648: } truelight@0: _sprite_lru_counter = 0; truelight@0: } truelight@0: truelight@0: // Compact sprite cache every now and then. truelight@0: if (++_compact_cache_counter >= 740) { truelight@0: CompactSpriteCache(); truelight@0: _compact_cache_counter = 0; truelight@0: } truelight@0: } truelight@0: truelight@0: // Called when holes in the sprite cache should be removed. truelight@0: // That is accomplished by moving the cached data. tron@1093: static void CompactSpriteCache(void) truelight@0: { tron@1353: MemBlock *s; truelight@184: Darkvater@5568: DEBUG(sprite, 3, "Compacting sprite cache, inuse=%d", GetSpriteCacheUsage()); truelight@184: tron@1353: for (s = _spritecache_ptr; s->size != 0;) { tron@1353: if (s->size & S_FREE_MASK) { tron@1353: MemBlock* next = NextBlock(s); tron@1353: MemBlock temp; celestar@5648: SpriteID i; tron@1353: truelight@0: // Since free blocks are automatically coalesced, this should hold true. tron@1353: assert(!(next->size & S_FREE_MASK)); truelight@184: truelight@0: // If the next block is the sentinel block, we can safely return tron@1353: if (next->size == 0) truelight@0: break; truelight@0: tron@1353: // Locate the sprite belonging to the next pointer. celestar@5648: for (i = 0; GetSpriteCache(i)->ptr != next->data; i++) { celestar@5648: assert(i != _spritecache_items); tron@1353: } truelight@0: celestar@5648: GetSpriteCache(i)->ptr = s->data; // Adjust sprite array entry tron@1353: // Swap this and the next block tron@1353: temp = *s; tron@1353: memmove(s, next, next->size); tron@1353: s = NextBlock(s); tron@1353: *s = temp; tron@1353: tron@1353: // Coalesce free blocks tron@1353: while (NextBlock(s)->size & S_FREE_MASK) { tron@1353: s->size += NextBlock(s)->size & ~S_FREE_MASK; truelight@0: } tron@1353: } else { tron@1353: s = NextBlock(s); truelight@0: } truelight@0: } truelight@0: } truelight@0: tron@1093: static void DeleteEntryFromSpriteCache(void) truelight@0: { celestar@5648: SpriteID i; celestar@5650: uint best = UINT_MAX; tron@1353: MemBlock* s; truelight@0: int cur_lru; truelight@0: Darkvater@5568: DEBUG(sprite, 3, "DeleteEntryFromSpriteCache, inuse=%d", GetSpriteCacheUsage()); truelight@0: truelight@0: cur_lru = 0xffff; celestar@5648: for (i = 0; i != _spritecache_items; i++) { celestar@5648: SpriteCache *sc = GetSpriteCache(i); celestar@5648: if (sc->ptr != NULL && sc->lru < cur_lru) { celestar@5648: cur_lru = sc->lru; truelight@0: best = i; truelight@0: } truelight@0: } truelight@0: truelight@0: // Display an error message and die, in case we found no sprite at all. truelight@0: // This shouldn't really happen, unless all sprites are locked. celestar@5648: if (best == (uint)-1) truelight@0: error("Out of sprite memory"); truelight@0: truelight@0: // Mark the block as free (the block must be in use) celestar@5648: s = (MemBlock*)GetSpriteCache(best)->ptr - 1; tron@1353: assert(!(s->size & S_FREE_MASK)); tron@1353: s->size |= S_FREE_MASK; celestar@5648: GetSpriteCache(best)->ptr = NULL; truelight@0: truelight@0: // And coalesce adjacent free blocks tron@1353: for (s = _spritecache_ptr; s->size != 0; s = NextBlock(s)) { tron@1353: if (s->size & S_FREE_MASK) { tron@1353: while (NextBlock(s)->size & S_FREE_MASK) { tron@1353: s->size += NextBlock(s)->size & ~S_FREE_MASK; truelight@0: } truelight@0: } truelight@0: } truelight@0: } truelight@0: tron@2014: static void* AllocSprite(size_t mem_req) truelight@0: { tron@2014: mem_req += sizeof(MemBlock); truelight@0: tron@1353: /* Align this to an uint32 boundary. This also makes sure that the 2 least tron@1353: * bits are not used, so we could use those for other things. */ tron@2548: mem_req = ALIGN(mem_req, sizeof(uint32)); truelight@0: tron@1353: for (;;) { tron@1353: MemBlock* s; truelight@0: tron@1353: for (s = _spritecache_ptr; s->size != 0; s = NextBlock(s)) { tron@1353: if (s->size & S_FREE_MASK) { tron@1353: size_t cur_size = s->size & ~S_FREE_MASK; truelight@0: tron@1353: /* Is the block exactly the size we need or tron@1353: * big enough for an additional free block? */ tron@1353: if (cur_size == mem_req || tron@1353: cur_size >= mem_req + sizeof(MemBlock)) { tron@1353: // Set size and in use tron@1353: s->size = mem_req; truelight@184: tron@1353: // Do we need to inject a free block too? tron@1353: if (cur_size != mem_req) { tron@1353: NextBlock(s)->size = (cur_size - mem_req) | S_FREE_MASK; tron@1353: } truelight@0: tron@1353: return s->data; tron@1353: } tron@1353: } truelight@0: } truelight@0: tron@1353: // Reached sentinel, but no block found yet. Delete some old entry. tron@1353: DeleteEntryFromSpriteCache(); truelight@0: } truelight@0: } truelight@0: truelight@0: tron@1361: const void *GetRawSprite(SpriteID sprite) truelight@0: { celestar@5648: SpriteCache *sc; tron@2014: void* p; truelight@0: celestar@2187: assert(sprite < MAX_SPRITES); truelight@0: celestar@5648: sc = GetSpriteCache(sprite); truelight@0: truelight@0: // Update LRU celestar@5648: sc->lru = ++_sprite_lru_counter; truelight@0: celestar@5648: p = sc->ptr; celestar@5648: tron@2014: // Load the sprite, if it is not loaded, yet celestar@5648: if (p == NULL) p = ReadSprite(sc, sprite); truelight@184: return p; truelight@0: } truelight@0: dominik@961: tron@2340: void GfxInitSpriteMem(void) truelight@0: { truelight@0: // initialize sprite cache heap celestar@5650: if (_spritecache_ptr == NULL) _spritecache_ptr = (MemBlock*)malloc(SPRITE_CACHE_SIZE); truelight@0: truelight@0: // A big free block tron@2339: _spritecache_ptr->size = (SPRITE_CACHE_SIZE - sizeof(MemBlock)) | S_FREE_MASK; tron@1353: // Sentinel block (identified by size == 0) tron@1353: NextBlock(_spritecache_ptr)->size = 0; truelight@0: celestar@5648: /* Reset the spritecache 'pool' */ celestar@5648: free(_spritecache); celestar@5648: _spritecache_items = 0; celestar@5648: _spritecache = NULL; truelight@0: tron@2340: _compact_cache_counter = 0; truelight@0: }