# spritecache.cpp

Go to the documentation of this file.

    00001 /* $Id: spritecache.cpp 14268 2008-09-07 22:04:39Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "variables.h"
    00008 #include "debug.h"
    00009 #include "spritecache.h"
    00010 #include "fileio.h"
    00011 #include "spriteloader/grf.hpp"
    00012 #include "core/alloc_func.hpp"
    00013 #include "core/math_func.hpp"
    00014 #ifdef WITH_PNG
    00015 #include "spriteloader/png.hpp"
    00016 #endif /* WITH_PNG */
    00017 #include "blitter/factory.hpp"
    00018
    00019 #include "table/sprites.h"
    00020
    00021 /* Default of 4MB spritecache */
    00022 uint _sprite_cache_size = 4;
    00023
    00024
    00025 struct SpriteCache {
    00026   void *ptr;
    00027   uint32 id;
    00028   uint32 file_pos;
    00029   uint16 file_slot;
    00030   int16 lru;
    00031   bool real_sprite;
    00032 };
    00033
    00034
    00035 static uint _spritecache_items = 0;
    00036 static SpriteCache *_spritecache = NULL;
    00037
    00038
    00039 static inline SpriteCache *GetSpriteCache(uint index)
    00040 {
    00041   return &_spritecache[index];
    00042 }
    00043
    00044
    00045 static SpriteCache *AllocateSpriteCache(uint index)
    00046 {
    00047   if (index >= _spritecache_items) {
    00048     /* Add another 1024 items to the 'pool' */
    00049     uint items = Align(index + 1, 1024);
    00050
    00051     DEBUG(sprite, 4, "Increasing sprite cache to %d items (%d bytes)", items, items * sizeof(*_spritecache));
    00052
    00053     _spritecache = ReallocT(_spritecache, items);
    00054
    00055     /* Reset the new items and update the count */
    00056     memset(_spritecache + _spritecache_items, 0, (items - _spritecache_items) * sizeof(*_spritecache));
    00057     _spritecache_items = items;
    00058   }
    00059
    00060   return GetSpriteCache(index);
    00061 }
    00062
    00063
    00064 struct MemBlock {
    00065   uint32 size;
    00066   byte data[VARARRAY_SIZE];
    00067 };
    00068
    00069 static uint _sprite_lru_counter;
    00070 static MemBlock *_spritecache_ptr;
    00071 static int _compact_cache_counter;
    00072
    00073 static void CompactSpriteCache();
    00074
    00080 void SkipSpriteData(byte type, uint16 num)
    00081 {
    00082   if (type & 2) {
    00083     FioSkipBytes(num);
    00084   } else {
    00085     while (num > 0) {
    00086       int8 i = FioReadByte();
    00087       if (i >= 0) {
    00088         int size = (i == 0) ? 0x80 : i;
    00089         num -= size;
    00090         FioSkipBytes(size);
    00091       } else {
    00092         i = -(i >> 3);
    00093         num -= i;
    00094         FioReadByte();
    00095       }
    00096     }
    00097   }
    00098 }
    00099
    00104 static bool ReadSpriteHeaderSkipData()
    00105 {
    00106   uint16 num = FioReadWord();
    00107   byte type;
    00108
    00109   if (num == 0) return false;
    00110
    00111   type = FioReadByte();
    00112   if (type == 0xFF) {
    00113     FioSkipBytes(num);
    00114     /* Some NewGRF files have "empty" pseudo-sprites which are 1
    00115      * byte long. Catch these so the sprites won't be displayed. */
    00116     return num != 1;
    00117   }
    00118
    00119   FioSkipBytes(7);
    00120   SkipSpriteData(type, num - 8);
    00121
    00122   return true;
    00123 }
    00124
    00125 /* Check if the given Sprite ID exists */
    00126 bool SpriteExists(SpriteID id)
    00127 {
    00128   /* Special case for Sprite ID zero -- its position is also 0... */
    00129   if (id == 0) return true;
    00130   if (id >= _spritecache_items) return false;
    00131   return !(GetSpriteCache(id)->file_pos == 0 && GetSpriteCache(id)->file_slot == 0);
    00132 }
    00133
    00134 void* AllocSprite(size_t);
    00135
    00136 static void* ReadSprite(SpriteCache *sc, SpriteID id, bool real_sprite)
    00137 {
    00138   uint8 file_slot = sc->file_slot;
    00139   uint32 file_pos = sc->file_pos;
    00140
    00141   DEBUG(sprite, 9, "Load sprite %d", id);
    00142
    00143   if (!SpriteExists(id)) {
    00144     DEBUG(sprite, 1, "Tried to load non-existing sprite #%d. Probable cause: Wrong/missing NewGRFs", id);
    00145
    00146     /* SPR_IMG_QUERY is a BIG FAT RED ? */
    00147     id = SPR_IMG_QUERY;
    00148     file_slot = GetSpriteCache(SPR_IMG_QUERY)->file_slot;
    00149     file_pos  = GetSpriteCache(SPR_IMG_QUERY)->file_pos;
    00150   }
    00151
    00152   if (real_sprite && BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() == 32) {
    00153 #ifdef WITH_PNG
    00154     /* Try loading 32bpp graphics in case we are 32bpp output */
    00155     SpriteLoaderPNG sprite_loader;
    00156     SpriteLoader::Sprite sprite;
    00157
    00158     if (sprite_loader.LoadSprite(&sprite, file_slot, sc->id)) {
    00159       sc->ptr = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, &AllocSprite);
    00160       free(sprite.data);
    00161
    00162       sc->real_sprite = real_sprite;
    00163
    00164       return sc->ptr;
    00165     }
    00166     /* If the PNG couldn't be loaded, fall back to 8bpp grfs */
    00167 #else
    00168     static bool show_once = true;
    00169     if (show_once) {
    00170       DEBUG(misc, 0, "You are running a 32bpp blitter, but this build is without libpng support; falling back to 8bpp graphics");
    00171       show_once = false;
    00172     }
    00173 #endif /* WITH_PNG */
    00174   }
    00175
    00176   FioSeekToFile(file_slot, file_pos);
    00177
    00178   /* Read the size and type */
    00179   int num  = FioReadWord();
    00180   byte type = FioReadByte();
    00181   /* Type 0xFF indicates either a colormap or some other non-sprite info */
    00182   if (type == 0xFF) {
    00183     if (real_sprite) {
    00184       static byte warning_level = 0;
    00185       DEBUG(sprite, warning_level, "Tried to load non sprite #%d as a real sprite. Probable cause: NewGRF interference", id);
    00186       warning_level = 6;
    00187       if (id == SPR_IMG_QUERY) error("Uhm, would you be so kind not to load a NewGRF that makes the 'query' sprite a non- sprite?");
    00188       return (void*)GetRawSprite(SPR_IMG_QUERY, true);
    00189     }
    00190
    00191     byte *dest = (byte *)AllocSprite(num);
    00192
    00193     sc->ptr = dest;
    00194     sc->real_sprite = real_sprite;
    00195     FioReadBlock(dest, num);
    00196
    00197     return sc->ptr;
    00198   }
    00199   /* Ugly hack to work around the problem that the old landscape
    00200    *  generator assumes that those sprites are stored uncompressed in
    00201    *  the memory, and they are only read directly by the code, never
    00202    *  send to the blitter. So do not send it to the blitter (which will
    00203    *  result in a data array in the format the blitter likes most), but
    00204    *  read the data directly from disk and store that as sprite.
    00205    * Ugly: yes. Other solution: no. Blame the original author or
    00206    *  something ;) The image should really have been a data-stream
    00207    *  (so type = 0xFF basicly). */
    00208   if (id >= 4845 && id <= 4881) {
    00209     assert(real_sprite);
    00210     uint height = FioReadByte();
    00211     uint width  = FioReadWord();
    00212     Sprite *sprite;
    00213     byte *dest;
    00214
    00215     num = width * height;
    00216     sprite = (Sprite *)AllocSprite(sizeof(*sprite) + num);
    00217     sc->ptr = sprite;
    00218     sprite->height = height;
    00219     sprite->width  = width;
    00220     sprite->x_offs = FioReadWord();
    00221     sprite->y_offs = FioReadWord();
    00222
    00223     dest = sprite->data;
    00224     while (num > 0) {
    00225       int8 i = FioReadByte();
    00226       if (i >= 0) {
    00227         num -= i;
    00228         for (; i > 0; --i) *dest++ = FioReadByte();
    00229       } else {
    00230         const byte* rel = dest - (((i & 7) << 8) | FioReadByte());
    00231         i = -(i >> 3);
    00232         num -= i;
    00233         for (; i > 0; --i) *dest++ = *rel++;
    00234       }
    00235     }
    00236
    00237     sc->real_sprite = real_sprite;
    00238
    00239     return sc->ptr;
    00240   }
    00241
    00242   if (!real_sprite) {
    00243     static byte warning_level = 0;
    00244     DEBUG(sprite, warning_level, "Tried to load real sprite #%d as a non sprite. Probable cause: NewGRF interference", id);
    00245     warning_level = 6;
    00246     return (void*)GetRawSprite(id, true);
    00247   }
    00248
    00249   SpriteLoaderGrf sprite_loader;
    00250   SpriteLoader::Sprite sprite;
    00251
    00252   sc->real_sprite = real_sprite;
    00253
    00254   if (!sprite_loader.LoadSprite(&sprite, file_slot, file_pos)) return NULL;
    00255   if (id == 142) sprite.height = 10; // Compensate for a TTD bug
    00256   sc->ptr = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, &AllocSprite);
    00257   free(sprite.data);
    00258
    00259   return sc->ptr;
    00260 }
    00261
    00262
    00263 bool LoadNextSprite(int load_index, byte file_slot, uint file_sprite_id)
    00264 {
    00265   SpriteCache *sc;
    00266   uint32 file_pos = FioGetPos();
    00267
    00268   if (!ReadSpriteHeaderSkipData()) return false;
    00269
    00270   if (load_index >= MAX_SPRITES) {
    00271     error("Tried to load too many sprites (#%d; max %d)", load_index, MAX_SPRITES);
    00272   }
    00273
    00274   sc = AllocateSpriteCache(load_index);
    00275   sc->file_slot = file_slot;
    00276   sc->file_pos = file_pos;
    00277   sc->ptr = NULL;
    00278   sc->lru = 0;
    00279   sc->id = file_sprite_id;
    00280   sc->real_sprite = false;
    00281
    00282   return true;
    00283 }
    00284
    00285
    00286 void DupSprite(SpriteID old_spr, SpriteID new_spr)
    00287 {
    00288   SpriteCache *scnew = AllocateSpriteCache(new_spr); // may reallocate: so put it first
    00289   SpriteCache *scold = GetSpriteCache(old_spr);
    00290
    00291   scnew->file_slot = scold->file_slot;
    00292   scnew->file_pos = scold->file_pos;
    00293   scnew->ptr = NULL;
    00294   scnew->id = scold->id;
    00295   scnew->real_sprite = scold->real_sprite;
    00296 }
    00297
    00298
    00299 #define S_FREE_MASK 1
    00300
    00301 static inline MemBlock* NextBlock(MemBlock* block)
    00302 {
    00303   return (MemBlock*)((byte*)block + (block->size & ~S_FREE_MASK));
    00304 }
    00305
    00306 static uint32 GetSpriteCacheUsage()
    00307 {
    00308   uint32 tot_size = 0;
    00309   MemBlock* s;
    00310
    00311   for (s = _spritecache_ptr; s->size != 0; s = NextBlock(s)) {
    00312     if (!(s->size & S_FREE_MASK)) tot_size += s->size;
    00313   }
    00314
    00315   return tot_size;
    00316 }
    00317
    00318
    00319 void IncreaseSpriteLRU()
    00320 {
    00321   /* Increase all LRU values */
    00322   if (_sprite_lru_counter > 16384) {
    00323     SpriteID i;
    00324
    00325     DEBUG(sprite, 3, "Fixing lru %d, inuse=%d", _sprite_lru_counter, GetSpriteCacheUsage());
    00326
    00327     for (i = 0; i != _spritecache_items; i++) {
    00328       SpriteCache *sc = GetSpriteCache(i);
    00329       if (sc->ptr != NULL) {
    00330         if (sc->lru >= 0) {
    00331           sc->lru = -1;
    00332         } else if (sc->lru != -32768) {
    00333           sc->lru--;
    00334         }
    00335       }
    00336     }
    00337     _sprite_lru_counter = 0;
    00338   }
    00339
    00340   /* Compact sprite cache every now and then. */
    00341   if (++_compact_cache_counter >= 740) {
    00342     CompactSpriteCache();
    00343     _compact_cache_counter = 0;
    00344   }
    00345 }
    00346
    00349 static void CompactSpriteCache()
    00350 {
    00351   MemBlock *s;
    00352
    00353   DEBUG(sprite, 3, "Compacting sprite cache, inuse=%d", GetSpriteCacheUsage());
    00354
    00355   for (s = _spritecache_ptr; s->size != 0;) {
    00356     if (s->size & S_FREE_MASK) {
    00357       MemBlock* next = NextBlock(s);
    00358       MemBlock temp;
    00359       SpriteID i;
    00360
    00361       /* Since free blocks are automatically coalesced, this should hold true. */
    00362       assert(!(next->size & S_FREE_MASK));
    00363
    00364       /* If the next block is the sentinel block, we can safely return */
    00365       if (next->size == 0)
    00366         break;
    00367
    00368       /* Locate the sprite belonging to the next pointer. */
    00369       for (i = 0; GetSpriteCache(i)->ptr != next->data; i++) {
    00370         assert(i != _spritecache_items);
    00371       }
    00372
    00373       GetSpriteCache(i)->ptr = s->data; // Adjust sprite array entry
    00374       /* Swap this and the next block */
    00375       temp = *s;
    00376       memmove(s, next, next->size);
    00377       s = NextBlock(s);
    00378       *s = temp;
    00379
    00380       /* Coalesce free blocks */
    00381       while (NextBlock(s)->size & S_FREE_MASK) {
    00382         s->size += NextBlock(s)->size & ~S_FREE_MASK;
    00383       }
    00384     } else {
    00385       s = NextBlock(s);
    00386     }
    00387   }
    00388 }
    00389
    00390 static void DeleteEntryFromSpriteCache()
    00391 {
    00392   SpriteID i;
    00393   uint best = UINT_MAX;
    00394   MemBlock* s;
    00395   int cur_lru;
    00396
    00397   DEBUG(sprite, 3, "DeleteEntryFromSpriteCache, inuse=%d", GetSpriteCacheUsage());
    00398
    00399   cur_lru = 0xffff;
    00400   for (i = 0; i != _spritecache_items; i++) {
    00401     SpriteCache *sc = GetSpriteCache(i);
    00402     if (sc->ptr != NULL && sc->lru < cur_lru) {
    00403       cur_lru = sc->lru;
    00404       best = i;
    00405     }
    00406   }
    00407
    00408   /* Display an error message and die, in case we found no sprite at all.
    00409    * This shouldn't really happen, unless all sprites are locked. */
    00410   if (best == (uint)-1) error("Out of sprite memory");
    00411
    00412   /* Mark the block as free (the block must be in use) */
    00413   s = (MemBlock*)GetSpriteCache(best)->ptr - 1;
    00414   assert(!(s->size & S_FREE_MASK));
    00415   s->size |= S_FREE_MASK;
    00416   GetSpriteCache(best)->ptr = NULL;
    00417
    00418   /* And coalesce adjacent free blocks */
    00419   for (s = _spritecache_ptr; s->size != 0; s = NextBlock(s)) {
    00420     if (s->size & S_FREE_MASK) {
    00421       while (NextBlock(s)->size & S_FREE_MASK) {
    00422         s->size += NextBlock(s)->size & ~S_FREE_MASK;
    00423       }
    00424     }
    00425   }
    00426 }
    00427
    00428 void* AllocSprite(size_t mem_req)
    00429 {
    00430   mem_req += sizeof(MemBlock);
    00431
    00432   /* Align this to an uint32 boundary. This also makes sure that the 2 least
    00433    * bits are not used, so we could use those for other things. */
    00434   mem_req = Align(mem_req, sizeof(uint32));
    00435
    00436   for (;;) {
    00437     MemBlock* s;
    00438
    00439     for (s = _spritecache_ptr; s->size != 0; s = NextBlock(s)) {
    00440       if (s->size & S_FREE_MASK) {
    00441         size_t cur_size = s->size & ~S_FREE_MASK;
    00442
    00443         /* Is the block exactly the size we need or
    00444          * big enough for an additional free block? */
    00445         if (cur_size == mem_req ||
    00446             cur_size >= mem_req + sizeof(MemBlock)) {
    00447           /* Set size and in use */
    00448           s->size = mem_req;
    00449
    00450           /* Do we need to inject a free block too? */
    00451           if (cur_size != mem_req) {
    00452             NextBlock(s)->size = (cur_size - mem_req) | S_FREE_MASK;
    00453           }
    00454
    00455           return s->data;
    00456         }
    00457       }
    00458     }
    00459
    00460     /* Reached sentinel, but no block found yet. Delete some old entry. */
    00461     DeleteEntryFromSpriteCache();
    00462   }
    00463 }
    00464
    00465
    00466 const void *GetRawSprite(SpriteID sprite, bool real_sprite)
    00467 {
    00468   SpriteCache *sc;
    00469   void* p;
    00470
    00471   assert(sprite < _spritecache_items);
    00472
    00473   sc = GetSpriteCache(sprite);
    00474
    00475   /* Update LRU */
    00476   sc->lru = ++_sprite_lru_counter;
    00477
    00478   p = sc->ptr;
    00479
    00480   /* Load the sprite, if it is not loaded, yet */
    00481   if (p == NULL || sc->real_sprite != real_sprite) p = ReadSprite(sc, sprite, real_sprite);
    00482
    00483   return p;
    00484 }
    00485
    00486
    00487 void GfxInitSpriteMem()
    00488 {
    00489   /* initialize sprite cache heap */
    00490   if (_spritecache_ptr == NULL) _spritecache_ptr = (MemBlock*)MallocT<byte>(_sprite_cache_size * 1024 * 1024);
    00491
    00492   /* A big free block */
    00493   _spritecache_ptr->size = ((_sprite_cache_size * 1024 * 1024) - sizeof(MemBlock)) | S_FREE_MASK;
    00494   /* Sentinel block (identified by size == 0) */
    00495   NextBlock(_spritecache_ptr)->size = 0;
    00496
    00497   /* Reset the spritecache 'pool' */
    00498   free(_spritecache);
    00499   _spritecache_items = 0;
    00500   _spritecache = NULL;
    00501
    00502   _compact_cache_counter = 0;
    00503 }