From 27febd474ccfccd8e5dc09014a7ad919575575aa Mon Sep 17 00:00:00 2001 From: truelight Date: Mon, 11 Jun 2007 11:50:49 +0000 Subject: (svn r10092) -Codechange: code-seperated the spriteloader and blitter from the rest of the code -Add: make it possible to pick your own blitter (-b , -h for overview) -Add: added a new optimized 8bpp blitter (default, caches sprites of all zoom-levels) -Add: added a debug 8bpp blitter and a very slow normal 8bpp blitter --- src/blitter/8bpp_debug.cpp | 53 +++++++ src/blitter/8bpp_debug.hpp | 30 ++++ src/blitter/8bpp_optimized.cpp | 199 ++++++++++++++++++++++++++ src/blitter/8bpp_optimized.hpp | 30 ++++ src/blitter/8bpp_slow.cpp | 65 +++++++++ src/blitter/8bpp_slow.hpp | 30 ++++ src/blitter/blitter.hpp | 168 ++++++++++++++++++++++ src/fontcache.cpp | 1 - src/gfx.cpp | 286 ++++++-------------------------------- src/openttd.cpp | 18 ++- src/settings.cpp | 2 +- src/spritecache.cpp | 60 +++----- src/spritecache.h | 1 - src/spriteloader/grf.cpp | 96 +++++++++++++ src/spriteloader/grf.hpp | 18 +++ src/spriteloader/spriteloader.hpp | 34 +++++ src/zoom.hpp | 2 +- 17 files changed, 805 insertions(+), 288 deletions(-) create mode 100644 src/blitter/8bpp_debug.cpp create mode 100644 src/blitter/8bpp_debug.hpp create mode 100644 src/blitter/8bpp_optimized.cpp create mode 100644 src/blitter/8bpp_optimized.hpp create mode 100644 src/blitter/8bpp_slow.cpp create mode 100644 src/blitter/8bpp_slow.hpp create mode 100644 src/blitter/blitter.hpp create mode 100644 src/spriteloader/grf.cpp create mode 100644 src/spriteloader/grf.hpp create mode 100644 src/spriteloader/spriteloader.hpp (limited to 'src') diff --git a/src/blitter/8bpp_debug.cpp b/src/blitter/8bpp_debug.cpp new file mode 100644 index 000000000..dea100c3f --- /dev/null +++ b/src/blitter/8bpp_debug.cpp @@ -0,0 +1,53 @@ +#include "../stdafx.h" +#include "../zoom.hpp" +#include "../gfx.h" +#include "../functions.h" +#include "8bpp_debug.hpp" + +static FBlitter_8bppDebug iFBlitter_8bppDebug; + +extern void* AllocSprite(size_t); + +void Blitter_8bppDebug::Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) +{ + const byte *src, *src_line; + Pixel8 *dst, *dst_line; + + /* Find where to start reading in the source sprite */ + src_line = (const byte *)bp->sprite + (bp->skip_top * bp->sprite_width + bp->skip_left) * ScaleByZoom(1, zoom); + dst_line = (Pixel8 *)bp->dst + bp->top * bp->pitch + bp->left; + + for (int y = 0; y < bp->height; y++) { + dst = dst_line; + dst_line += bp->pitch; + + src = src_line; + src_line += bp->sprite_width * ScaleByZoom(1, zoom); + + for (int x = 0; x < bp->width; x++) { + if (*src != 0) *dst = *src; + dst++; + src += ScaleByZoom(1, zoom); + } + assert(src <= src_line); + } +} + +Sprite *Blitter_8bppDebug::Encode(SpriteLoader::Sprite *sprite) +{ + Sprite *dest_sprite; + dest_sprite = (Sprite *)AllocSprite(sizeof(*dest_sprite) + sprite->height * sprite->width); + + dest_sprite->height = sprite->height; + dest_sprite->width = sprite->width; + dest_sprite->x_offs = sprite->x_offs; + dest_sprite->y_offs = sprite->y_offs; + + /* Write a random color as sprite; this makes debugging really easy */ + uint color = InteractiveRandom() % 150 + 2; + for (int i = 0; i < sprite->height * sprite->width; i++) { + dest_sprite->data[i] = (sprite->data[i].m == 0) ? 0 : color; + } + + return dest_sprite; +} diff --git a/src/blitter/8bpp_debug.hpp b/src/blitter/8bpp_debug.hpp new file mode 100644 index 000000000..67b12b652 --- /dev/null +++ b/src/blitter/8bpp_debug.hpp @@ -0,0 +1,30 @@ +/* $Id$ */ + +/** @file debug.hpp */ + +#ifndef BLITTER_8BPP_DEBUG_HPP +#define BLITTER_8BPP_DEBUG_HPP + +#include "blitter.hpp" + +typedef Pixel Pixel8; + +class Blitter_8bppDebug : public Blitter { +public: + uint8 GetScreenDepth() { return 8; } + + void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom); + + Sprite *Encode(SpriteLoader::Sprite *sprite); +}; + +class FBlitter_8bppDebug: public BlitterFactory { +public: + /* virtual */ const char *GetName() { return "8bpp-debug"; } + + /* virtual */ const char *GetDescription() { return "8bpp Debug Blitter (testing only)"; } + + /* virtual */ Blitter *CreateInstance() { return new Blitter_8bppDebug(); } +}; + +#endif /* BLITTER_8BPP_DEBUG_HPP */ diff --git a/src/blitter/8bpp_optimized.cpp b/src/blitter/8bpp_optimized.cpp new file mode 100644 index 000000000..00c427441 --- /dev/null +++ b/src/blitter/8bpp_optimized.cpp @@ -0,0 +1,199 @@ +#include "../stdafx.h" +#include "../zoom.hpp" +#include "../gfx.h" +#include "../debug.h" +#include "8bpp_optimized.hpp" + +static FBlitter_8bppOptimized iFBlitter_8bppOptimized; + +extern void* AllocSprite(size_t); + +void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) +{ + const byte *src, *src_next; + Pixel8 *dst, *dst_line; + uint offset = 0; + + /* Find the offset of this zoom-level */ + offset = ((const byte *)bp->sprite)[(int)zoom * 2] | ((const byte *)bp->sprite)[(int)zoom * 2 + 1] << 8; + + /* Find where to start reading in the source sprite */ + src = (const byte *)bp->sprite + offset; + dst_line = (Pixel8 *)bp->dst + bp->top * bp->pitch + bp->left; + + /* Skip over the top lines in the source image */ + for (int y = 0; y < bp->skip_top; y++) { + uint trans, pixels; + for (;;) { + trans = *src++; + pixels = *src++; + if (trans == 0 && pixels == 0) break; + src += pixels; + } + } + + src_next = src; + + for (int y = 0; y < bp->height; y++) { + dst = dst_line; + dst_line += bp->pitch; + + uint skip_left = bp->skip_left; + int width = bp->width; + + for (;;) { + src = src_next; + uint8 trans = *src++; + uint8 pixels = *src++; + src_next = src + pixels; + if (trans == 0 && pixels == 0) break; + if (width <= 0) continue; + + if (skip_left != 0) { + if (skip_left < trans) { + trans -= skip_left; + skip_left = 0; + } else { + skip_left -= trans; + trans = 0; + } + if (skip_left < pixels) { + src += skip_left; + pixels -= skip_left; + skip_left = 0; + } else { + src += pixels; + skip_left -= pixels; + pixels = 0; + } + } + if (skip_left != 0) continue; + + /* Skip transparent pixels */ + dst += trans; + width -= trans; + if (width <= 0) continue; + if (pixels > width) pixels = width; + width -= pixels; + + switch (mode) { + case BM_COLOUR_REMAP: + for (uint x = 0; x < pixels; x++) { + if (bp->remap[*src] != 0) *dst = bp->remap[*src]; + dst++; src++; + } + break; + + case BM_TRANSPARENT: + for (uint x = 0; x < pixels; x++) { + *dst = bp->remap[*dst]; + dst++; src++; + } + break; + + default: + memcpy(dst, src, pixels); + dst += pixels; src += pixels; + break; + } + } + } +} + +Sprite *Blitter_8bppOptimized::Encode(SpriteLoader::Sprite *sprite) +{ + Sprite *dest_sprite; + byte *temp_dst; + uint memory = 0; + uint index = 0; + + /* Make memory for all zoom-levels */ + memory += (int)ZOOM_LVL_END * sizeof(uint16); + for (int i = 0; i < (int)ZOOM_LVL_END; i++) { + memory += UnScaleByZoom(sprite->height, (ZoomLevel)i) * UnScaleByZoom(sprite->width, (ZoomLevel)i); + index += 2; + } + + /* We have no idea how much memory we really need, so just guess something */ + memory *= 5; + temp_dst = MallocT(memory); + + /* Make the sprites per zoom-level */ + for (int i = 0; i < (int)ZOOM_LVL_END; i++) { + /* Store the scaled image */ + const SpriteLoader::CommonPixel *src; + + /* Store the index table */ + temp_dst[i * 2] = index & 0xFF; + temp_dst[i * 2 + 1] = (index >> 8) & 0xFF; + + byte *dst = &temp_dst[index]; + + for (int y = 0; y < UnScaleByZoom(sprite->height, (ZoomLevel)i); y++) { + uint trans = 0; + uint pixels = 0; + uint last_color = 0; + uint count_index = 0; + src = &sprite->data[ScaleByZoom(y, (ZoomLevel)i) * sprite->width]; + + for (int x = 0; x < UnScaleByZoom(sprite->width, (ZoomLevel)i); x++) { + uint color = 0; + int count = 0; + + /* Get the color keeping in mind the zoom-level */ + for (int j = 0; j < ScaleByZoom(1, (ZoomLevel)i); j++) { + if (src->m != 0) { color = src->m; count++; } + src++; + } + /* If more than 12.5% of the pixels are non-transparent, make thisone non-transparent too */ + if (count < ScaleByZoom(1, (ZoomLevel)i) / 8) color = 0; + + if (last_color == 0 || color == 0) { + if (count_index != 0) { + /* Write how many non-transparent bytes we get */ + temp_dst[count_index] = pixels; + pixels = 0; + count_index = 0; + } + /* As long as we find transparency bytes, keep counting */ + if (color == 0) { + last_color = 0; + trans++; + continue; + } + /* No longer transparency, so write the amount of transparent bytes */ + *dst = trans; + dst++; index++; + trans = 0; + /* Reserve a byte for the pixel counter */ + count_index = index; + dst++; index++; + } + last_color = color; + pixels++; + *dst = color; + dst++; index++; + } + + if (count_index != 0) temp_dst[count_index] = pixels; + + /* Write line-ending */ + *dst = 0; dst++; index++; + *dst = 0; dst++; index++; + } + } + + /* Safety check, to make sure we guessed the size correctly */ + assert(index < memory); + + /* Allocate the exact amount of memory we need */ + dest_sprite = (Sprite *)AllocSprite(sizeof(*dest_sprite) + index); + + dest_sprite->height = sprite->height; + dest_sprite->width = sprite->width; + dest_sprite->x_offs = sprite->x_offs; + dest_sprite->y_offs = sprite->y_offs; + memcpy(dest_sprite->data, temp_dst, index); + + return dest_sprite; +} diff --git a/src/blitter/8bpp_optimized.hpp b/src/blitter/8bpp_optimized.hpp new file mode 100644 index 000000000..8f251fb2c --- /dev/null +++ b/src/blitter/8bpp_optimized.hpp @@ -0,0 +1,30 @@ +/* $Id$ */ + +/** @file 8bpp_nice.hpp */ + +#ifndef BLITTER_8BPP_OPTIMIZED_HPP +#define BLITTER_8BPP_OPTIMIZED_HPP + +#include "blitter.hpp" + +typedef Pixel Pixel8; + +class Blitter_8bppOptimized : public Blitter { +public: + uint8 GetScreenDepth() { return 8; } + + void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom); + + Sprite *Encode(SpriteLoader::Sprite *sprite); +}; + +class FBlitter_8bppOptimized: public BlitterFactory { +public: + /* virtual */ const char *GetName() { return "8bpp-optimzed"; } + + /* virtual */ const char *GetDescription() { return "8bpp Optimized Blitter (compression + all-ZoomLevel cache)"; } + + /* virtual */ Blitter *CreateInstance() { return new Blitter_8bppOptimized(); } +}; + +#endif /* BLITTER_8BPP_OPTIMIZED_HPP */ diff --git a/src/blitter/8bpp_slow.cpp b/src/blitter/8bpp_slow.cpp new file mode 100644 index 000000000..99c43a4f5 --- /dev/null +++ b/src/blitter/8bpp_slow.cpp @@ -0,0 +1,65 @@ +#include "../stdafx.h" +#include "../zoom.hpp" +#include "../gfx.h" +#include "8bpp_slow.hpp" + +static FBlitter_8bppSimple iFBlitter_8bppSimple; + +extern void* AllocSprite(size_t); + +void Blitter_8bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) +{ + const byte *src, *src_line; + Pixel8 *dst, *dst_line; + + /* Find where to start reading in the source sprite */ + src_line = (const byte *)bp->sprite + (bp->skip_top * bp->sprite_width + bp->skip_left) * ScaleByZoom(1, zoom); + dst_line = (Pixel8 *)bp->dst + bp->top * bp->pitch + bp->left; + + for (int y = 0; y < bp->height; y++) { + dst = dst_line; + dst_line += bp->pitch; + + src = src_line; + src_line += bp->sprite_width * ScaleByZoom(1, zoom); + + for (int x = 0; x < bp->width; x++) { + uint color = 0; + + switch (mode) { + case BM_COLOUR_REMAP: + color = bp->remap[*src]; + break; + + case BM_TRANSPARENT: + if (*src != 0) color = bp->remap[*dst]; + break; + + default: + color = *src; + break; + } + if (color != 0) *dst = color; + dst++; + src += ScaleByZoom(1, zoom); + } + } +} + +Sprite *Blitter_8bppSimple::Encode(SpriteLoader::Sprite *sprite) +{ + Sprite *dest_sprite; + dest_sprite = (Sprite *)AllocSprite(sizeof(*dest_sprite) + sprite->height * sprite->width); + + dest_sprite->height = sprite->height; + dest_sprite->width = sprite->width; + dest_sprite->x_offs = sprite->x_offs; + dest_sprite->y_offs = sprite->y_offs; + + /* Copy over only the 'remap' channel, as that is what we care about in 8bpp */ + for (int i = 0; i < sprite->height * sprite->width; i++) { + dest_sprite->data[i] = sprite->data[i].m; + } + + return dest_sprite; +} diff --git a/src/blitter/8bpp_slow.hpp b/src/blitter/8bpp_slow.hpp new file mode 100644 index 000000000..ac60e3262 --- /dev/null +++ b/src/blitter/8bpp_slow.hpp @@ -0,0 +1,30 @@ +/* $Id$ */ + +/** @file 8bpp.hpp */ + +#ifndef BLITTER_8BPP_SIMPLE_HPP +#define BLITTER_8BPP_SIMPLE_HPP + +#include "blitter.hpp" + +typedef Pixel Pixel8; + +class Blitter_8bppSimple : public Blitter { +public: + uint8 GetScreenDepth() { return 8; } + + void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom); + + Sprite *Encode(SpriteLoader::Sprite *sprite); +}; + +class FBlitter_8bppSimple: public BlitterFactory { +public: + /* virtual */ const char *GetName() { return "8bpp-simple"; } + + /* virtual */ const char *GetDescription() { return "8bpp Simple Blitter (relative slow, but never wrong)"; } + + /* virtual */ Blitter *CreateInstance() { return new Blitter_8bppSimple(); } +}; + +#endif /* BLITTER_8BPP_SIMPLE_HPP */ diff --git a/src/blitter/blitter.hpp b/src/blitter/blitter.hpp new file mode 100644 index 000000000..4920da426 --- /dev/null +++ b/src/blitter/blitter.hpp @@ -0,0 +1,168 @@ +/* $Id$ */ + +/** @file blitter.hpp */ + +#ifndef BLITTER_HPP +#define BLITTER_HPP + +#include "../spriteloader/spriteloader.hpp" +#include "../spritecache.h" +#include +#include + +enum BlitterMode { + BM_NORMAL, + BM_COLOUR_REMAP, + BM_TRANSPARENT, +}; + +/** + * How all blitters should look like. Extend this class to make your own. + */ +class Blitter { +public: + struct BlitterParams { + const void *sprite; ///< Pointer to the sprite how ever the encoder stored it + const byte *remap; ///< XXX -- Temporary storage for remap array + + int skip_left, skip_top; ///< How much pixels of the source to skip on the left and top (based on zoom of dst) + int width, height; ///< The width and height in pixels that needs to be drawn to dst + int sprite_width; ///< Real width of the sprite + int sprite_height; ///< Real height of the sprite + int left, top; ///< The offset in the 'dst' in pixels to start drawing + + void *dst; ///< Destination buffer + int pitch; ///< The pitch of the destination buffer + }; + + /** + * Get the screen depth this blitter works for. + * This is either: 8, 16, 24 or 32. + */ + virtual uint8 GetScreenDepth() = 0; + + /** + * Draw an image to the screen, given an amount of params defined above. + */ + virtual void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) = 0; + + /** + * Convert a sprite from the loader to our own format. + */ + virtual Sprite *Encode(SpriteLoader::Sprite *sprite) = 0; + + virtual ~Blitter() { } +}; + +/** + * The base factory, keeping track of all blitters. + */ +class BlitterFactoryBase { +private: + char *name; + typedef std::map Blitters; + + static Blitters &GetBlitters() + { + static Blitters &s_blitters = *new Blitters(); + return s_blitters; + } + + static Blitter **GetActiveBlitter() + { + static Blitter *s_blitter = NULL; + return &s_blitter; + } + +protected: + /** + * Register a blitter internally, based on his name. + * @param name the name of the blitter. + * @note an assert() will be trigger if 2 blitters with the same name try to register. + */ + void RegisterBlitter(const char *name) + { + /* Don't register nameless Blitters */ + if (name == NULL) return; + + this->name = strdup(name); + std::pair P = GetBlitters().insert(Blitters::value_type(name, this)); + assert(P.second); + } + +public: + BlitterFactoryBase() : + name(NULL) + {} + + virtual ~BlitterFactoryBase() { if (this->name != NULL) GetBlitters().erase(this->name); free(this->name); } + + /** + * Find the requested blitter and return his class. + * @param name the blitter to select. + * @post Sets the blitter so GetCurrentBlitter() returns it too. + */ + static Blitter *SelectBlitter(const char *name) + { + if (GetBlitters().size() == 0) return NULL; + + Blitters::iterator it = GetBlitters().begin(); + for (; it != GetBlitters().end(); it++) { + BlitterFactoryBase *b = (*it).second; + if (strcasecmp(name, b->name) == 0) { + Blitter *newb = b->CreateInstance(); + *GetActiveBlitter() = newb; + return newb; + } + } + return NULL; + } + + /** + * Get the current active blitter (always set by calling SelectBlitter). + */ + static Blitter *GetCurrentBlitter() + { + return *GetActiveBlitter(); + } + + + static char *GetBlittersInfo(char *p, const char *last) + { + p += snprintf(p, last - p, "List of blitters:\n"); + Blitters::iterator it = GetBlitters().begin(); + for (; it != GetBlitters().end(); it++) { + BlitterFactoryBase *b = (*it).second; + p += snprintf(p, last - p, "%18s: %s\n", b->name, b->GetDescription()); + } + p += snprintf(p, last - p, "\n"); + + return p; + } + + /** + * Get a nice description of the blitter-class. + */ + virtual const char *GetDescription() = 0; + + /** + * Create an instance of this Blitter-class. + */ + virtual Blitter *CreateInstance() = 0; +}; + +/** + * A template factory, so ->GetName() works correctly. This because else some compiler will complain. + */ +template +class BlitterFactory: public BlitterFactoryBase { +public: + BlitterFactory() { this->RegisterBlitter(((T *)this)->GetName()); } + + /** + * Get the long, human readable, name for the Blitter-class. + */ + const char *GetName(); +}; + +#endif /* BLITTER_HPP */ diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 36a0c6523..49a5d15be 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -399,7 +399,6 @@ const Sprite *GetGlyph(FontSize size, WChar key) /* FreeType has rendered the glyph, now we allocate a sprite and copy the image into it */ sprite = (Sprite*)calloc(width * height + 8, 1); - sprite->info = 1; sprite->width = width; sprite->height = height; sprite->x_offs = slot->bitmap_left; diff --git a/src/gfx.cpp b/src/gfx.cpp index d529728de..3117d1782 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -19,6 +19,7 @@ #include "genworld.h" #include "debug.h" #include "zoom.hpp" +#include "blitter/blitter.hpp" #ifdef _DEBUG bool _dbg_screen_rect; @@ -45,14 +46,7 @@ int _pal_last_dirty; Colour _cur_palette[256]; byte _stringwidth_table[FS_END][224]; -enum BlitterMode { - BM_NORMAL, - BM_COLOUR_REMAP, - BM_TRANSPARENT, -}; - -template -static inline void GfxMainBlitter(const Sprite *sprite, int x, int y); +static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode); FontSize _cur_fontsize; static FontSize _last_fontsize; @@ -683,7 +677,7 @@ skip_cont:; if (IsPrintable(c)) { if (x >= dpi->left + dpi->width) goto skip_char; if (x + 26 >= dpi->left) { - GfxMainBlitter(GetGlyph(size, c), x, y); + GfxMainBlitter(GetGlyph(size, c), x, y, BM_COLOUR_REMAP); } x += GetCharacterWidth(size, c); } else if (c == '\n') { // newline = {} @@ -720,269 +714,81 @@ void DrawSprite(SpriteID img, SpriteID pal, int x, int y) { if (HASBIT(img, PALETTE_MODIFIER_TRANSPARENT)) { _color_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH)) + 1; - GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y); + GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y, BM_TRANSPARENT); } else if (pal != PAL_NONE) { _color_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH)) + 1; - GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y); + GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y, BM_COLOUR_REMAP); } else { - GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y); - } -} - -struct BlitterParams { - int start_x, start_y; - const byte *sprite; - Pixel *dst; - int width, height; - int width_org; - int pitch; -}; - -template -static inline void GfxBlitZoomUncomp(BlitterParams *bp) -{ - const byte *src = bp->sprite + bp->start_y * bp->width_org + bp->start_x; - Pixel *dst = bp->dst; - int height = bp->height; - int width = bp->width; - int i; - - assert(height > 0); - assert(width > 0); - - height = UnScaleByZoom(height, Tzoom); - - switch (Tmode) { - case BM_COLOUR_REMAP: { - const byte *ctab = _color_remap_ptr; - - for (; height != 0; height--) { - for (i = 0; i != UnScaleByZoom(width, Tzoom); i++) { - byte b = ctab[src[ScaleByZoom(i, Tzoom)]]; - - if (b != 0) dst[i] = b; - } - src += ScaleByZoom(bp->width_org, Tzoom); - dst += bp->pitch; - } - break; - } - - case BM_TRANSPARENT: { - const byte *ctab = _color_remap_ptr; - - for (; height != 0; height--) { - for (i = 0; i != UnScaleByZoom(width, Tzoom); i++) - if (src[ScaleByZoom(i, Tzoom)] != 0) dst[i] = ctab[dst[i]]; - src += ScaleByZoom(bp->width_org, Tzoom); - dst += bp->pitch; - } - break; - } - - default: - for (; height != 0; height--) { - for (i = 0; i != UnScaleByZoom(width, Tzoom); i++) - if (src[ScaleByZoom(i, Tzoom)] != 0) dst[i] = src[ScaleByZoom(i, Tzoom)]; - src += ScaleByZoom(bp->width_org, Tzoom); - dst += bp->pitch; - } - break; + GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y, BM_NORMAL); } } -template -static inline void GfxBlitTileZoom(BlitterParams *bp) -{ - const byte *src_o = bp->sprite; - const byte *src; - int num, skip; - byte done; - Pixel *dst; - const byte *ctab; - - src_o += ReadLE16Aligned(src_o + bp->start_y * 2); - - for (;;) { - do { - done = src_o[0]; - num = done & 0x7F; - skip = src_o[1]; - src = src_o + 2; - src_o += num + 2; - - dst = bp->dst; - - if (Tzoom >= ZOOM_LVL_OUT_2X && (skip & 1)) { - skip += 1; - src += 1; - num -= 1; - if (num <= 0) continue; - } - - if (Tzoom >= ZOOM_LVL_OUT_4X && (skip & 2)) { - skip += 2; - src += 2; - num -= 2; - if (num <= 0) continue; - } - - if (Tzoom >= ZOOM_LVL_OUT_8X && (skip & 4)) { - skip += 4; - src += 4; - num -= 4; - if (num <= 0) continue; - } - - if (Tzoom >= ZOOM_LVL_OUT_16X && (skip & 8)) { - skip += 8; - src += 8; - num -= 8; - if (num <= 0) continue; - } - - if ( (skip -= bp->start_x) > 0) { - dst += UnScaleByZoom(skip, Tzoom); - } else { - src -= skip; - num += skip; - if (num <= 0) continue; - skip = 0; - } - - skip = skip + num - bp->width; - if (skip > 0) { - num -= skip; - if (num <= 0) continue; - } - - num = UnScaleByZoom(num, Tzoom); - - switch (Tmode) { - case BM_COLOUR_REMAP: - ctab = _color_remap_ptr; - for (; num != 0; num--) { - *dst = ctab[*src]; - dst++; - src += ScaleByZoom(1, Tzoom); - } - break; - - case BM_TRANSPARENT: - ctab = _color_remap_ptr; - for (; num != 0; num--) { - *dst = ctab[*dst]; - dst++; - } - break; - - default: - if (Tzoom == ZOOM_LVL_NORMAL) { - memcpy(dst, src, num); - } else { - for (; num != 0; num--) { - *dst = *src; - dst++; - src += ScaleByZoom(1, Tzoom); - } - } - break; - } - - - } while (!(done & 0x80)); - - bp->dst += bp->pitch; - if (--bp->height == 0) return; - - for (int i = 0; i < ScaleByZoom(1, Tzoom) - 1; i++) { - do { - done = src_o[0]; - src_o += (done & 0x7F) + 2; - } while (!(done & 0x80)); - if (--bp->height == 0) return; - } - } -} - -template -static inline void GfxMainBlitter(const Sprite *sprite, int x, int y) +static inline void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode) { const DrawPixelInfo *dpi = _cur_dpi; - int start_x, start_y; - BlitterParams bp; - int zoom_mask = ~(ScaleByZoom(1, dpi->zoom) - 1); + Blitter::BlitterParams bp; - /* decode sprite header */ + /* Move to the correct offset */ x += sprite->x_offs; y += sprite->y_offs; - bp.width_org = bp.width = sprite->width; - bp.height = sprite->height; + + /* Copy the main data directly from the sprite */ bp.sprite = sprite->data; + bp.sprite_width = sprite->width; + bp.sprite_height = sprite->height; + bp.width = UnScaleByZoom(sprite->width, dpi->zoom); + bp.height = UnScaleByZoom(sprite->height, dpi->zoom); + bp.top = 0; + bp.left = 0; + bp.skip_left = 0; + bp.skip_top = 0; bp.dst = dpi->dst_ptr; bp.pitch = dpi->pitch; + bp.remap = _color_remap_ptr; - assert(bp.height > 0); - assert(bp.width > 0); + assert(sprite->width > 0); + assert(sprite->height > 0); - start_y = 0; - if (dpi->zoom > ZOOM_LVL_NORMAL) { - start_y += bp.height & ~zoom_mask; - bp.height &= zoom_mask; - if (bp.height == 0) return; - y &= zoom_mask; - } + if (bp.width <= 0) return; + if (bp.height <= 0) return; - if ( (y -= dpi->top) < 0) { - bp.height += y; + y -= dpi->top; + /* Check for top overflow */ + if (y < 0) { + bp.height -= -UnScaleByZoom(y, dpi->zoom); if (bp.height <= 0) return; - start_y -= y; + bp.skip_top += -UnScaleByZoom(y, dpi->zoom); y = 0; } else { - bp.dst += bp.pitch * UnScaleByZoom(y, dpi->zoom); + bp.top = UnScaleByZoom(y, dpi->zoom); } - bp.start_y = start_y; - if ( (y = y + bp.height - dpi->height) > 0) { - bp.height -= y; + /* Check for bottom overflow */ + y += ScaleByZoom(bp.height, dpi->zoom) - dpi->height; + if (y > 0) { + bp.height -= UnScaleByZoom(y, dpi->zoom); if (bp.height <= 0) return; } - start_x = 0; - x &= zoom_mask; - if ( (x -= dpi->left) < 0) { - bp.width += x; + x -= dpi->left; + /* Check for left overflow */ + if (x < 0) { + bp.width -= -UnScaleByZoom(x, dpi->zoom); if (bp.width <= 0) return; - start_x -= x; + bp.skip_left += -UnScaleByZoom(x, dpi->zoom); x = 0; + } else { + bp.left = UnScaleByZoom(x, dpi->zoom); } - bp.start_x = start_x; - bp.dst += UnScaleByZoom(x, dpi->zoom); - if ( (x = x + bp.width - dpi->width) > 0) { - bp.width -= x; + /* Check for right overflow */ + x += ScaleByZoom(bp.width, dpi->zoom) - dpi->width; + if (x > 0) { + bp.width -= UnScaleByZoom(x, dpi->zoom); if (bp.width <= 0) return; } - if (sprite->info & 8) { - switch (dpi->zoom) { - case ZOOM_LVL_NORMAL: GfxBlitTileZoom(&bp); break; - case ZOOM_LVL_OUT_2X: GfxBlitTileZoom(&bp); break; - case ZOOM_LVL_OUT_4X: GfxBlitTileZoom(&bp); break; - case ZOOM_LVL_OUT_8X: GfxBlitTileZoom(&bp); break; - case ZOOM_LVL_OUT_16X: GfxBlitTileZoom(&bp); break; - default: NOT_REACHED(); - } - } else { - switch (dpi->zoom) { - case ZOOM_LVL_NORMAL: GfxBlitZoomUncomp(&bp); break; - case ZOOM_LVL_OUT_2X: GfxBlitZoomUncomp(&bp); break; - case ZOOM_LVL_OUT_4X: GfxBlitZoomUncomp(&bp); break; - case ZOOM_LVL_OUT_8X: GfxBlitZoomUncomp(&bp); break; - case ZOOM_LVL_OUT_16X: GfxBlitZoomUncomp(&bp); break; - default: NOT_REACHED(); - } - } + BlitterFactoryBase::GetCurrentBlitter()->Draw(&bp, mode, dpi->zoom); } void DoPaletteAnimations(); diff --git a/src/openttd.cpp b/src/openttd.cpp index 2e20074a7..1b7b46cda 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -64,6 +64,7 @@ #include "newgrf_commons.h" #include "player_face.h" #include "group.h" +#include "blitter/blitter.hpp" #include "bridge_map.h" #include "clear_map.h" @@ -85,6 +86,8 @@ extern void SetDifficultyLevel(int mode, GameOptions *gm_opt); extern Player* DoStartupNewPlayer(bool is_ai); extern void ShowOSErrorBox(const char *buf); +const char *_default_blitter = "8bpp-optimzed"; + /* TODO: usrerror() for errors which are not of an internal nature but * caused by the user, i.e. missing files or fatal configuration errors. * Post-0.4.0 since Celestar doesn't want this in SVN before. --pasky */ @@ -159,6 +162,7 @@ static void showhelp() " -v drv = Set video driver (see below)\n" " -s drv = Set sound driver (see below) (param bufsize,hz)\n" " -m drv = Set music driver (see below)\n" + " -b drv = Set the blitter to use (see below)\n" " -r res = Set resolution (for instance 800x600)\n" " -h = Display this help text\n" " -t year = Set starting year\n" @@ -183,6 +187,9 @@ static void showhelp() p = GetDriverList(p, lastof(buf)); + /* List the blitters */ + p = BlitterFactoryBase::GetBlittersInfo(p, lastof(buf)); + /* ShowInfo put output to stderr, but version information should go * to stdout; this is the only exception */ #if !defined(WIN32) && !defined(WIN64) @@ -350,7 +357,7 @@ int ttd_main(int argc, char *argv[]) { int i; const char *optformat; - char musicdriver[32], sounddriver[32], videodriver[32]; + char musicdriver[32], sounddriver[32], videodriver[32], blitter[32]; int resolution[2] = {0, 0}; Year startyear = INVALID_YEAR; uint generation_seed = GENERATE_NEW_SEED; @@ -364,7 +371,7 @@ int ttd_main(int argc, char *argv[]) uint16 dedicated_port = 0; #endif /* ENABLE_NETWORK */ - musicdriver[0] = sounddriver[0] = videodriver[0] = '\0'; + musicdriver[0] = sounddriver[0] = videodriver[0] = blitter[0] = '\0'; _game_mode = GM_MENU; _switch_mode = SM_MENU; @@ -376,7 +383,7 @@ int ttd_main(int argc, char *argv[]) * a letter means: it accepts that param (e.g.: -h) * a ':' behind it means: it need a param (e.g.: -m) * a '::' behind it means: it can optional have a param (e.g.: -d) */ - optformat = "m:s:v:hD::n::eit:d::r:g::G:c:xl:" + optformat = "m:s:v:b:hD::n::eit:d::r:g::G:c:xl:" #if !defined(__MORPHOS__) && !defined(__AMIGA__) && !defined(WIN32) "f" #endif @@ -389,6 +396,7 @@ int ttd_main(int argc, char *argv[]) case 'm': ttd_strlcpy(musicdriver, mgo.opt, sizeof(musicdriver)); break; case 's': ttd_strlcpy(sounddriver, mgo.opt, sizeof(sounddriver)); break; case 'v': ttd_strlcpy(videodriver, mgo.opt, sizeof(videodriver)); break; + case 'b': ttd_strlcpy(blitter, mgo.opt, sizeof(blitter)); break; #if defined(ENABLE_NETWORK) case 'D': strcpy(musicdriver, "null"); @@ -461,6 +469,7 @@ int ttd_main(int argc, char *argv[]) if (!StrEmpty(musicdriver)) ttd_strlcpy(_ini_musicdriver, musicdriver, sizeof(_ini_musicdriver)); if (!StrEmpty(sounddriver)) ttd_strlcpy(_ini_sounddriver, sounddriver, sizeof(_ini_sounddriver)); if (!StrEmpty(videodriver)) ttd_strlcpy(_ini_videodriver, videodriver, sizeof(_ini_videodriver)); + if (StrEmpty(blitter)) ttd_strlcpy(blitter, _default_blitter, sizeof(blitter)); if (resolution[0] != 0) { _cur_resolution[0] = resolution[0]; _cur_resolution[1] = resolution[1]; } if (startyear != INVALID_YEAR) _patches_newgame.starting_year = startyear; if (generation_seed != GENERATE_NEW_SEED) _patches_newgame.generation_seed = generation_seed; @@ -500,6 +509,9 @@ int ttd_main(int argc, char *argv[]) /* Initialize game palette */ GfxInitPalettes(); + DEBUG(misc, 1, "Loading blitter '%s'...", blitter); + if (BlitterFactoryBase::SelectBlitter(blitter) == NULL) + error("Failed to select requested blitter '%s'; does it exist?", blitter); DEBUG(driver, 1, "Loading drivers..."); LoadDriver(SOUND_DRIVER, _ini_sounddriver); LoadDriver(MUSIC_DRIVER, _ini_musicdriver); diff --git a/src/settings.cpp b/src/settings.cpp index 01065f4eb..aa1fbc6d1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1258,7 +1258,7 @@ static const SettingDescGlobVarList _misc_settings[] = { SDTG_VAR("medium_size", SLE_UINT, S, 0, _freetype.medium_size, 10, 0, 72, 0, STR_NULL, NULL), SDTG_VAR("large_size", SLE_UINT, S, 0, _freetype.large_size, 16, 0, 72, 0, STR_NULL, NULL), #endif - SDTG_VAR("sprite_cache_size",SLE_UINT, S, 0, _sprite_cache_size, 2, 1, 64, 0, STR_NULL, NULL), + SDTG_VAR("sprite_cache_size",SLE_UINT, S, 0, _sprite_cache_size, 4, 1, 64, 0, STR_NULL, NULL), SDTG_END() }; diff --git a/src/spritecache.cpp b/src/spritecache.cpp index c45c92d96..f3ea8a1e4 100644 --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -11,10 +11,11 @@ #include "table/sprites.h" #include "fileio.h" #include "helpers.hpp" +#include "spriteloader/grf.hpp" +#include "blitter/blitter.hpp" - -/* Default of 2MB spritecache */ -uint _sprite_cache_size = 2; +/* Default of 4MB spritecache */ +uint _sprite_cache_size = 4; struct SpriteCache { @@ -115,12 +116,10 @@ bool SpriteExists(SpriteID id) return GetSpriteCache(id)->file_pos != 0; } -static void* AllocSprite(size_t); +void* AllocSprite(size_t); static void* ReadSprite(SpriteCache *sc, SpriteID id) { - uint num; - byte type; uint32 file_pos = sc->file_pos; DEBUG(sprite, 9, "Load sprite %d", id); @@ -135,49 +134,28 @@ static void* ReadSprite(SpriteCache *sc, SpriteID id) FioSeekToFile(file_pos); - num = FioReadWord(); - type = FioReadByte(); + /* Read the size and type */ + int num = FioReadWord(); + byte type = FioReadByte(); + /* Type 0xFF indicates either a colormap or some other non-sprite info */ if (type == 0xFF) { - byte* dest = (byte*)AllocSprite(num); + byte *dest = (byte *)AllocSprite(num); sc->ptr = dest; FioReadBlock(dest, num); return dest; - } else { - uint height = FioReadByte(); - uint width = FioReadWord(); - Sprite* sprite; - byte* dest; - - num = (type & 0x02) ? width * height : num - 8; - sprite = (Sprite*)AllocSprite(sizeof(*sprite) + num); - sc->ptr = sprite; - sprite->info = type; - sprite->height = (id != 142) ? height : 10; // Compensate for a TTD bug - sprite->width = width; - sprite->x_offs = FioReadWord(); - sprite->y_offs = FioReadWord(); - - dest = sprite->data; - while (num > 0) { - int8 i = FioReadByte(); - - if (i >= 0) { - num -= i; - for (; i > 0; --i) *dest++ = FioReadByte(); - } else { - const byte* rel = dest - (((i & 7) << 8) | FioReadByte()); + } - i = -(i >> 3); - num -= i; + SpriteLoaderGrf sprite_loader; + SpriteLoader::Sprite sprite; - for (; i > 0; --i) *dest++ = *rel++; - } - } + if (!sprite_loader.LoadSprite(&sprite, file_pos)) return NULL; + if (id == 142) sprite.height = 10; // Compensate for a TTD bug + sc->ptr = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite); + free(sprite.data); - return sprite; - } + return sc->ptr; } @@ -348,7 +326,7 @@ static void DeleteEntryFromSpriteCache() } } -static void* AllocSprite(size_t mem_req) +void* AllocSprite(size_t mem_req) { mem_req += sizeof(MemBlock); diff --git a/src/spritecache.h b/src/spritecache.h index c979b73c5..8356d0ff6 100644 --- a/src/spritecache.h +++ b/src/spritecache.h @@ -6,7 +6,6 @@ #define SPRITECACHE_H struct Sprite { - byte info; byte height; uint16 width; int16 x_offs; diff --git a/src/spriteloader/grf.cpp b/src/spriteloader/grf.cpp new file mode 100644 index 000000000..4d412d6ea --- /dev/null +++ b/src/spriteloader/grf.cpp @@ -0,0 +1,96 @@ +#include "../stdafx.h" +#include "../gfx.h" +#include "../fileio.h" +#include "../debug.h" +#include "grf.hpp" + +bool SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, uint32 file_pos) +{ + /* Open the right file and go to the correct position */ + FioSeekToFile(file_pos); + + /* Read the size and type */ + int num = FioReadWord(); + byte type = FioReadByte(); + + /* Type 0xFF indicates either a colormap or some other non-sprite info; we do not handle them here */ + if (type == 0xFF) return false; + + sprite->height = FioReadByte(); + sprite->width = FioReadWord(); + sprite->x_offs = FioReadWord(); + sprite->y_offs = FioReadWord(); + + /* 0x02 indicates it is a compressed sprite, so we can't rely on 'num' to be valid. + * In case it is uncompressed, the size is 'num' - 8 (header-size). */ + num = (type & 0x02) ? sprite->width * sprite->height : num - 8; + + /* XXX -- We should use a pre-located memory segment for this, malloc/free is pretty expensive */ + byte *dest_orig = MallocT(num); + byte *dest = dest_orig; + + /* Read the file, which has some kind of compression */ + while (num > 0) { + int8 code = FioReadByte(); + + if (code >= 0) { + /* Plain bytes to read */ + int size = (code == 0) ? 0x80 : code; + num -= size; + for (; size > 0; size--) { + *dest = FioReadByte(); + dest++; + } + } else { + /* Copy bytes from earlier in the sprite */ + const uint data_offset = ((code & 7) << 8) | FioReadByte(); + int size = -(code >> 3); + num -= size; + for (; size > 0; size--) { + *dest = *(dest - data_offset); + dest++; + } + } + } + + assert(num == 0); + + sprite->data = CallocT(sprite->width * sprite->height); + + /* When there are transparency pixels, this format has an other trick.. decode it */ + if (type & 0x08) { + for (int y = 0; y < sprite->height; y++) { + bool last_item = false; + /* Look up in the header-table where the real data is stored for this row */ + int offset = (dest_orig[y * 2 + 1] << 8) | dest_orig[y * 2]; + /* Go to that row */ + dest = &dest_orig[offset]; + + do { + SpriteLoader::CommonPixel *data; + /* Read the header: + * 0 .. 14 - length + * 15 - last_item + * 16 .. 31 - transparency bytes */ + last_item = ((*dest) & 0x80) != 0; + int length = (*dest++) & 0x7F; + int skip = *dest++; + + data = &sprite->data[y * sprite->width + skip]; + + for (int x = 0; x < length; x++) { + data->m = *dest; + dest++; + data++; + } + } while (!last_item); + } + } else { + dest = dest_orig; + for (int i = 0; i < sprite->width * sprite->height; i++) + sprite->data[i].m = dest[i]; + } + + free(dest_orig); + return true; +} diff --git a/src/spriteloader/grf.hpp b/src/spriteloader/grf.hpp new file mode 100644 index 000000000..27083f993 --- /dev/null +++ b/src/spriteloader/grf.hpp @@ -0,0 +1,18 @@ +/* $Id$ */ + +/** @file grf.hpp */ + +#ifndef SPRITELOADER_GRF_HPP +#define SPRITELOADER_GRF_HPP + +#include "spriteloader.hpp" + +class SpriteLoaderGrf : public SpriteLoader { +public: + /** + * Load a sprite from the disk and return a sprite struct which is the same for all loaders. + */ + bool LoadSprite(SpriteLoader::Sprite *sprite, uint32 file_pos); +}; + +#endif /* SPRITELOADER_GRF_HPP */ diff --git a/src/spriteloader/spriteloader.hpp b/src/spriteloader/spriteloader.hpp new file mode 100644 index 000000000..658277236 --- /dev/null +++ b/src/spriteloader/spriteloader.hpp @@ -0,0 +1,34 @@ +/* $Id$ */ + +/** @file spriteloader.hpp */ + +#ifndef SPRITELOADER_HPP +#define SPRITELOADER_HPP + +class SpriteLoader { +public: + struct CommonPixel { + uint8 r; ///< Red-channel + uint8 g; ///< Green-channel + uint8 b; ///< Blue-channel + uint8 a; ///< Alpha-channel + uint8 m; ///< Remap-channel + }; + + struct Sprite { + uint16 height; ///< Height of the sprite + uint16 width; ///< Width of the sprite + int16 x_offs; ///< The x-offset of where the sprite will be drawn + int16 y_offs; ///< The y-offset of where the sprite will be drawn + SpriteLoader::CommonPixel *data; ///< The sprite itself + }; + + /** + * Load a sprite from the disk and return a sprite struct which is the same for all loaders. + */ + virtual bool LoadSprite(SpriteLoader::Sprite *sprite, uint32 file_pos) = 0; + + virtual ~SpriteLoader() { } +}; + +#endif /* SPRITELOADER_HPP */ diff --git a/src/zoom.hpp b/src/zoom.hpp index 65937177b..19e64e615 100644 --- a/src/zoom.hpp +++ b/src/zoom.hpp @@ -25,7 +25,7 @@ enum ZoomLevel { ZOOM_LVL_ROADVEH = ZOOM_LVL_NORMAL, ZOOM_LVL_WORLD_SCREENSHOT = ZOOM_LVL_NORMAL, - ZOOM_LVL_DETAIL = ZOOM_LVL_OUT_2X, //! All zoomlevels below or equal to this, will result in details on the screen, like road-work, ... + ZOOM_LVL_DETAIL = ZOOM_LVL_OUT_2X, ///< All zoomlevels below or equal to this, will result in details on the screen, like road-work, ... ZOOM_LVL_MIN = ZOOM_LVL_NORMAL, ZOOM_LVL_MAX = ZOOM_LVL_OUT_16X, -- cgit v1.2.3-70-g09d2