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 }

Generated on Wed Oct 1 17:03:23 2008 for openttd by  doxygen 1.5.6