tron@2186: /* $Id$ */ tron@2186: rubidium@10455: /** @file spritecache.cpp Caching of sprites. */ glx@9574: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" rubidium@9628: #include "variables.h" tron@1299: #include "debug.h" tron@1349: #include "spritecache.h" truelight@0: #include "fileio.h" glx@9626: #include "spriteloader/grf.hpp" rubidium@9723: #include "core/alloc_func.hpp" rubidium@9723: #include "core/math_func.hpp" rubidium@9628: #ifdef WITH_PNG rubidium@9628: #include "spriteloader/png.hpp" rubidium@9628: #endif /* WITH_PNG */ rubidium@9628: #include "blitter/factory.hpp" truelight@0: rubidium@9724: #include "table/sprites.h" rubidium@9724: glx@9626: /* Default of 4MB spritecache */ glx@9626: uint _sprite_cache_size = 4; truelight@0: truelight@0: rubidium@6574: struct SpriteCache { rubidium@9826: void *ptr; rubidium@9628: uint32 id; glx@10776: size_t file_pos; rubidium@9724: uint16 file_slot; rubidium@9826: int16 lru; rubidium@6574: }; peter1138@5755: peter1138@5755: peter1138@5755: static uint _spritecache_items = 0; peter1138@5755: static SpriteCache *_spritecache = NULL; peter1138@5755: peter1138@5755: peter1138@5755: static inline SpriteCache *GetSpriteCache(uint index) peter1138@5755: { peter1138@5755: return &_spritecache[index]; peter1138@5755: } peter1138@5755: peter1138@5755: peter1138@5755: static SpriteCache *AllocateSpriteCache(uint index) peter1138@5755: { peter1138@5755: if (index >= _spritecache_items) { peter1138@5755: /* Add another 1024 items to the 'pool' */ rubidium@9722: uint items = Align(index + 1, 1024); peter1138@5755: peter1138@5755: DEBUG(sprite, 4, "Increasing sprite cache to %d items (%d bytes)", items, items * sizeof(*_spritecache)); peter1138@5755: KUDr@5860: _spritecache = ReallocT(_spritecache, items); peter1138@5755: peter1138@5755: /* Reset the new items and update the count */ peter1138@5755: memset(_spritecache + _spritecache_items, 0, (items - _spritecache_items) * sizeof(*_spritecache)); peter1138@5755: _spritecache_items = items; peter1138@5755: } peter1138@5755: peter1138@5755: return GetSpriteCache(index); peter1138@5755: } peter1138@5755: truelight@0: rubidium@6574: struct MemBlock { rubidium@10513: size_t size; tron@1353: byte data[VARARRAY_SIZE]; rubidium@6574: }; tron@1353: truelight@0: static uint _sprite_lru_counter; tron@1353: static MemBlock *_spritecache_ptr; truelight@0: static int _compact_cache_counter; truelight@0: rubidium@6573: static void CompactSpriteCache(); truelight@0: rubidium@6573: static bool ReadSpriteHeaderSkipData() 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... */ peter1138@5755: if (id == 0) return true; peter1138@5953: if (id >= _spritecache_items) return false; rubidium@9703: return !(GetSpriteCache(id)->file_pos == 0 && GetSpriteCache(id)->file_slot == 0); peter1138@3565: } peter1138@3565: glx@9626: void* AllocSprite(size_t); tron@2014: glx@9627: static void* ReadSprite(SpriteCache *sc, SpriteID id, bool real_sprite) truelight@0: { rubidium@9703: uint8 file_slot = sc->file_slot; glx@10776: size_t file_pos = sc->file_pos; tron@2014: Darkvater@5568: DEBUG(sprite, 9, "Load sprite %d", id); truelight@184: peter1138@3565: if (!SpriteExists(id)) { rubidium@6213: DEBUG(sprite, 1, "Tried to load non-existing sprite #%d. Probable cause: Wrong/missing NewGRFs", id); rubidium@6213: rubidium@6213: /* SPR_IMG_QUERY is a BIG FAT RED ? */ rubidium@6213: id = SPR_IMG_QUERY; rubidium@9703: file_slot = GetSpriteCache(SPR_IMG_QUERY)->file_slot; rubidium@9703: file_pos = GetSpriteCache(SPR_IMG_QUERY)->file_pos; tron@1378: } tron@1378: rubidium@9628: if (BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() == 32) { rubidium@9628: #ifdef WITH_PNG rubidium@9628: /* Try loading 32bpp graphics in case we are 32bpp output */ rubidium@9628: SpriteLoaderPNG sprite_loader; rubidium@9628: SpriteLoader::Sprite sprite; rubidium@9628: rubidium@9724: if (sprite_loader.LoadSprite(&sprite, file_slot, sc->id)) { rubidium@9628: sc->ptr = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, &AllocSprite); rubidium@9628: free(sprite.data); rubidium@9628: rubidium@9628: return sc->ptr; rubidium@9628: } rubidium@9628: /* If the PNG couldn't be loaded, fall back to 8bpp grfs */ rubidium@9628: #else rubidium@9628: static bool show_once = true; rubidium@9628: if (show_once) { rubidium@9628: DEBUG(misc, 0, "You are running a 32bpp blitter, but this build is without libpng support; falling back to 8bpp graphics"); rubidium@9628: show_once = false; rubidium@9628: } rubidium@9628: #endif /* WITH_PNG */ rubidium@9628: } rubidium@9628: rubidium@9703: FioSeekToFile(file_slot, file_pos); tron@1354: glx@9626: /* Read the size and type */ glx@9626: int num = FioReadWord(); glx@9626: byte type = FioReadByte(); glx@9626: /* Type 0xFF indicates either a colormap or some other non-sprite info */ tron@2014: if (type == 0xFF) { glx@9627: if (real_sprite) { glx@9627: static byte warning_level = 0; glx@9627: DEBUG(sprite, warning_level, "Tried to load non sprite #%d as a real sprite. Probable cause: NewGRF interference", id); glx@9627: warning_level = 6; rubidium@10867: if (id == SPR_IMG_QUERY) usererror("Uhm, would you be so kind not to load a NewGRF that makes the 'query' sprite a non- sprite?"); glx@9627: return (void*)GetSprite(SPR_IMG_QUERY); glx@9627: } glx@9627: glx@9626: byte *dest = (byte *)AllocSprite(num); tron@2014: peter1138@5755: sc->ptr = dest; tron@2014: FioReadBlock(dest, num); tron@2014: glx@9627: return sc->ptr; glx@9627: } glx@9627: /* Ugly hack to work around the problem that the old landscape glx@9627: * generator assumes that those sprites are stored uncompressed in glx@9627: * the memory, and they are only read directly by the code, never glx@9627: * send to the blitter. So do not send it to the blitter (which will glx@9627: * result in a data array in the format the blitter likes most), but glx@9627: * read the data directly from disk and store that as sprite. glx@9627: * Ugly: yes. Other solution: no. Blame the original author or glx@9627: * something ;) The image should really have been a data-stream glx@9627: * (so type = 0xFF basicly). */ glx@9627: if (id >= 4845 && id <= 4881) { glx@9627: uint height = FioReadByte(); glx@9627: uint width = FioReadWord(); glx@9627: Sprite *sprite; glx@9627: byte *dest; glx@9627: glx@9627: num = width * height; glx@9627: sprite = (Sprite *)AllocSprite(sizeof(*sprite) + num); glx@9627: sc->ptr = sprite; glx@9627: sprite->height = height; glx@9627: sprite->width = width; glx@9627: sprite->x_offs = FioReadWord(); glx@9627: sprite->y_offs = FioReadWord(); glx@9627: glx@9627: dest = sprite->data; glx@9627: while (num > 0) { glx@9627: int8 i = FioReadByte(); glx@9627: if (i >= 0) { glx@9627: num -= i; glx@9627: for (; i > 0; --i) *dest++ = FioReadByte(); glx@9627: } else { glx@9627: const byte* rel = dest - (((i & 7) << 8) | FioReadByte()); glx@9627: i = -(i >> 3); glx@9627: num -= i; glx@9627: for (; i > 0; --i) *dest++ = *rel++; glx@9627: } glx@9627: } glx@9627: glx@9627: return sc->ptr; glx@9627: } glx@9627: glx@9627: if (!real_sprite) { glx@9627: static byte warning_level = 0; glx@9627: DEBUG(sprite, warning_level, "Tried to load real sprite #%d as a non sprite. Probable cause: NewGRF interference", id); glx@9627: warning_level = 6; glx@9626: } tron@2014: glx@9626: SpriteLoaderGrf sprite_loader; glx@9626: SpriteLoader::Sprite sprite; truelight@0: rubidium@9724: if (!sprite_loader.LoadSprite(&sprite, file_slot, file_pos)) return NULL; glx@9626: if (id == 142) sprite.height = 10; // Compensate for a TTD bug glx@9626: sc->ptr = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, &AllocSprite); glx@9626: free(sprite.data); truelight@184: glx@9626: return sc->ptr; truelight@0: } truelight@0: truelight@0: rubidium@9703: bool LoadNextSprite(int load_index, byte file_slot, uint file_sprite_id) truelight@0: { peter1138@5755: SpriteCache *sc; glx@10776: size_t file_pos = FioGetPos(); truelight@0: tron@2342: if (!ReadSpriteHeaderSkipData()) return false; darkvater@361: peter1138@3591: if (load_index >= MAX_SPRITES) { rubidium@10867: usererror("Tried to load too many sprites (#%d; max %d)", load_index, MAX_SPRITES); peter1138@3591: } peter1138@3591: peter1138@5755: sc = AllocateSpriteCache(load_index); rubidium@9703: sc->file_slot = file_slot; peter1138@5755: sc->file_pos = file_pos; peter1138@5755: sc->ptr = NULL; peter1138@5755: sc->lru = 0; rubidium@9628: sc->id = file_sprite_id; rubidium@9628: truelight@0: return true; truelight@0: } truelight@0: tron@2407: rubidium@5838: void DupSprite(SpriteID old_spr, SpriteID new_spr) tron@2407: { rubidium@9724: SpriteCache *scnew = AllocateSpriteCache(new_spr); // may reallocate: so put it first rubidium@5838: SpriteCache *scold = GetSpriteCache(old_spr); peter1138@5755: rubidium@9703: scnew->file_slot = scold->file_slot; peter1138@5755: scnew->file_pos = scold->file_pos; peter1138@5755: scnew->ptr = NULL; rubidium@9628: scnew->id = scold->id; 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: glx@10776: static size_t GetSpriteCacheUsage() truelight@0: { glx@10776: size_t tot_size = 0; tron@1353: MemBlock* s; tron@1353: glx@9732: for (s = _spritecache_ptr; s->size != 0; s = NextBlock(s)) { tron@1353: if (!(s->size & S_FREE_MASK)) tot_size += s->size; glx@9732: } truelight@0: truelight@0: return tot_size; truelight@0: } truelight@0: truelight@0: rubidium@6573: void IncreaseSpriteLRU() truelight@0: { glx@9574: /* Increase all LRU values */ truelight@0: if (_sprite_lru_counter > 16384) { peter1138@5755: SpriteID i; peter1138@5755: Darkvater@5568: DEBUG(sprite, 3, "Fixing lru %d, inuse=%d", _sprite_lru_counter, GetSpriteCacheUsage()); truelight@0: peter1138@5755: for (i = 0; i != _spritecache_items; i++) { peter1138@5755: SpriteCache *sc = GetSpriteCache(i); peter1138@5755: if (sc->ptr != NULL) { peter1138@5755: if (sc->lru >= 0) { peter1138@5755: sc->lru = -1; peter1138@5755: } else if (sc->lru != -32768) { peter1138@5755: sc->lru--; truelight@0: } truelight@0: } peter1138@5755: } truelight@0: _sprite_lru_counter = 0; truelight@0: } truelight@0: glx@9574: /* 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: glx@9574: /** Called when holes in the sprite cache should be removed. glx@9574: * That is accomplished by moving the cached data. */ rubidium@6573: static void CompactSpriteCache() 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; peter1138@5755: SpriteID i; tron@1353: glx@9574: /* Since free blocks are automatically coalesced, this should hold true. */ tron@1353: assert(!(next->size & S_FREE_MASK)); truelight@184: glx@9574: /* If the next block is the sentinel block, we can safely return */ rubidium@9826: if (next->size == 0) break; truelight@0: glx@9574: /* Locate the sprite belonging to the next pointer. */ peter1138@5755: for (i = 0; GetSpriteCache(i)->ptr != next->data; i++) { peter1138@5755: assert(i != _spritecache_items); tron@1353: } truelight@0: peter1138@5755: GetSpriteCache(i)->ptr = s->data; // Adjust sprite array entry glx@9574: /* 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: glx@9574: /* 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: rubidium@6573: static void DeleteEntryFromSpriteCache() truelight@0: { peter1138@5755: SpriteID i; rubidium@5838: 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; peter1138@5755: for (i = 0; i != _spritecache_items; i++) { peter1138@5755: SpriteCache *sc = GetSpriteCache(i); peter1138@5755: if (sc->ptr != NULL && sc->lru < cur_lru) { peter1138@5755: cur_lru = sc->lru; truelight@0: best = i; truelight@0: } truelight@0: } truelight@0: glx@9574: /* Display an error message and die, in case we found no sprite at all. glx@9574: * This shouldn't really happen, unless all sprites are locked. */ rubidium@9723: if (best == (uint)-1) error("Out of sprite memory"); truelight@0: glx@9574: /* Mark the block as free (the block must be in use) */ peter1138@5755: s = (MemBlock*)GetSpriteCache(best)->ptr - 1; tron@1353: assert(!(s->size & S_FREE_MASK)); tron@1353: s->size |= S_FREE_MASK; peter1138@5755: GetSpriteCache(best)->ptr = NULL; truelight@0: glx@9574: /* 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: glx@9626: 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. */ rubidium@9722: 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)) { glx@9574: /* Set size and in use */ tron@1353: s->size = mem_req; truelight@184: glx@9574: /* 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: glx@9574: /* Reached sentinel, but no block found yet. Delete some old entry. */ tron@1353: DeleteEntryFromSpriteCache(); truelight@0: } truelight@0: } truelight@0: truelight@0: glx@9627: const void *GetRawSprite(SpriteID sprite, bool real_sprite) truelight@0: { peter1138@5755: SpriteCache *sc; tron@2014: void* p; truelight@0: peter1138@5953: assert(sprite < _spritecache_items); truelight@0: peter1138@5755: sc = GetSpriteCache(sprite); truelight@0: glx@9574: /* Update LRU */ peter1138@5755: sc->lru = ++_sprite_lru_counter; peter1138@5755: peter1138@5755: p = sc->ptr; peter1138@5755: glx@9574: /* Load the sprite, if it is not loaded, yet */ glx@9627: if (p == NULL) p = ReadSprite(sc, sprite, real_sprite); glx@9732: truelight@184: return p; truelight@0: } truelight@0: dominik@961: rubidium@6573: void GfxInitSpriteMem() truelight@0: { glx@9574: /* initialize sprite cache heap */ rubidium@9723: if (_spritecache_ptr == NULL) _spritecache_ptr = (MemBlock*)MallocT(_sprite_cache_size * 1024 * 1024); truelight@0: glx@9574: /* A big free block */ glx@9626: _spritecache_ptr->size = ((_sprite_cache_size * 1024 * 1024) - sizeof(MemBlock)) | S_FREE_MASK; glx@9574: /* Sentinel block (identified by size == 0) */ tron@1353: NextBlock(_spritecache_ptr)->size = 0; truelight@0: peter1138@5755: /* Reset the spritecache 'pool' */ peter1138@5755: free(_spritecache); peter1138@5755: _spritecache_items = 0; peter1138@5755: _spritecache = NULL; truelight@0: tron@2340: _compact_cache_counter = 0; truelight@0: }