Sprite cache: Allow caching only required subset of sprite zoom levels

Enable for blitters based on 32bpp_optimized or SSE
This commit is contained in:
Jonathan G Rennison 2023-08-22 22:16:04 +01:00
parent 785216db73
commit 46f5fb9f25
28 changed files with 423 additions and 172 deletions

View File

@ -210,7 +210,6 @@ inline void Blitter_32bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel
}
break;
case BM_BLACK_REMAP:
memset_colour(dst, _black_colour, n);
memset(anim, 0, n * sizeof(*anim));

View File

@ -36,6 +36,12 @@ class Blitter_32bppSSE4_Anim FINAL : public Blitter_32bppSSE2_Anim, public Blitt
private:
public:
Blitter_32bppSSE4_Anim()
{
this->Blitter_32bppSSE2_Anim::SetSupportsMissingZoomLevels(true);
this->Blitter_32bppSSE4::SetSupportsMissingZoomLevels(true);
}
template <BlitterMode mode, Blitter_32bppSSE_Base::ReadMode read_mode, Blitter_32bppSSE_Base::BlockType bt_last, bool translucent, bool animated>
void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom);
void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override;

View File

@ -301,6 +301,7 @@ template <bool Tpal_to_rgb> Sprite *Blitter_32bppOptimized::EncodeInternal(const
ZoomLevel zoom_min;
ZoomLevel zoom_max;
uint8 missing_zoom_levels = 0;
if (sprite->type == SpriteType::Font) {
zoom_min = ZOOM_LVL_NORMAL;
@ -317,6 +318,7 @@ template <bool Tpal_to_rgb> Sprite *Blitter_32bppOptimized::EncodeInternal(const
uint n_size = 0;
for (ZoomLevel z = zoom_min; z <= zoom_max; z++) {
const SpriteLoader::Sprite *src_orig = &sprite[z];
if (src_orig->data == nullptr) continue;
uint size = src_orig->height * src_orig->width;
@ -339,6 +341,13 @@ template <bool Tpal_to_rgb> Sprite *Blitter_32bppOptimized::EncodeInternal(const
const SpriteLoader::CommonPixel *src = (const SpriteLoader::CommonPixel *)src_orig->data;
if (src == nullptr) {
lengths[z][0] = 0;
lengths[z][1] = 0;
SetBit(missing_zoom_levels, z);
continue;
}
for (uint y = src_orig->height; y > 0; y--) {
/* Index 0 of dst_px and dst_n is left as space to save the length of the row to be filled later. */
Colour *dst_px = (Colour *)&dst_px_ln[1];
@ -448,24 +457,34 @@ template <bool Tpal_to_rgb> Sprite *Blitter_32bppOptimized::EncodeInternal(const
len += lengths[z][0] + lengths[z][1];
}
Sprite *dest_sprite = (Sprite *)allocator(sizeof(*dest_sprite) + sizeof(SpriteData) + len);
Sprite *dest_sprite = (Sprite *)allocator(sizeof(Sprite) + sizeof(SpriteData) + len);
if (len == 0) {
/* Mark sprite as having no levels at all, and therefore replaceable */
missing_zoom_levels = UINT8_MAX;
}
dest_sprite->height = sprite->height;
dest_sprite->width = sprite->width;
dest_sprite->x_offs = sprite->x_offs;
dest_sprite->y_offs = sprite->y_offs;
dest_sprite->next = nullptr;
dest_sprite->missing_zoom_levels = missing_zoom_levels;
SpriteData *dst = (SpriteData *)dest_sprite->data;
memset(dst, 0, sizeof(*dst));
/* Store sprite flags. */
dst->flags = flags;
uint32 next_offset = 0;
for (ZoomLevel z = zoom_min; z <= zoom_max; z++) {
dst->offset[z][0] = z == zoom_min ? 0 : lengths[z - 1][1] + dst->offset[z - 1][1];
dst->offset[z][1] = lengths[z][0] + dst->offset[z][0];
dst->offset[z][0] = next_offset;
dst->offset[z][1] = lengths[z][0] + next_offset;
memcpy(dst->data + dst->offset[z][0], dst_px_orig[z], lengths[z][0]);
memcpy(dst->data + dst->offset[z][1], dst_n_orig[z], lengths[z][1]);
next_offset += lengths[z][0] + lengths[z][1];
if (lengths[z][0] != 0) memcpy(dst->data + dst->offset[z][0], dst_px_orig[z], lengths[z][0]);
if (lengths[z][1] != 0) memcpy(dst->data + dst->offset[z][1], dst_n_orig[z], lengths[z][1]);
}
free(px_buffer);

View File

@ -22,6 +22,11 @@ public:
byte data[]; ///< Data, all zoomlevels.
};
Blitter_32bppOptimized()
{
this->SetSupportsMissingZoomLevels(true);
}
void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override;
Sprite *Encode(const SpriteLoader::Sprite *sprite, AllocatorProc *allocator) override;

View File

@ -137,6 +137,8 @@ Sprite *Blitter_32bppSimple::Encode(const SpriteLoader::Sprite *sprite, Allocato
dest_sprite->width = sprite->width;
dest_sprite->x_offs = sprite->x_offs;
dest_sprite->y_offs = sprite->y_offs;
dest_sprite->next = nullptr;
dest_sprite->missing_zoom_levels = 0;
dst = (Blitter_32bppSimple::Pixel *)dest_sprite->data;
SpriteLoader::CommonPixel *src = (SpriteLoader::CommonPixel *)sprite->data;

View File

@ -28,6 +28,7 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::Sprite *sprite, Alloca
*/
ZoomLevel zoom_min = ZOOM_LVL_NORMAL;
ZoomLevel zoom_max = ZOOM_LVL_NORMAL;
uint8 missing_zoom_levels = 0;
if (sprite->type != SpriteType::Font) {
zoom_min = _settings_client.gui.zoom_min;
zoom_max = (ZoomLevel) std::min(_settings_client.gui.zoom_max, ZOOM_LVL_DRAW_SPR);
@ -40,6 +41,15 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::Sprite *sprite, Alloca
uint all_sprites_size = 0;
for (ZoomLevel z = zoom_min; z <= zoom_max; z++) {
const SpriteLoader::Sprite *src_sprite = &sprite[z];
if (src_sprite->data == nullptr) {
sd.infos[z].sprite_offset = 0;
sd.infos[z].mv_offset = 0;
sd.infos[z].sprite_line_size = 0;
sd.infos[z].sprite_width = 0;
SetBit(missing_zoom_levels, z);
continue;
}
sd.infos[z].sprite_width = src_sprite->width;
sd.infos[z].sprite_offset = all_sprites_size;
sd.infos[z].sprite_line_size = sizeof(Colour) * src_sprite->width + sizeof(uint32) * META_LENGTH;
@ -56,6 +66,8 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::Sprite *sprite, Alloca
dst_sprite->width = sprite->width;
dst_sprite->x_offs = sprite->x_offs;
dst_sprite->y_offs = sprite->y_offs;
dst_sprite->next = nullptr;
dst_sprite->missing_zoom_levels = missing_zoom_levels;
memcpy(dst_sprite->data, &sd, sizeof(SpriteData));
/* Copy colours and determine flags. */
@ -64,6 +76,9 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::Sprite *sprite, Alloca
bool has_translucency = false;
for (ZoomLevel z = zoom_min; z <= zoom_max; z++) {
const SpriteLoader::Sprite *src_sprite = &sprite[z];
if (src_sprite->data == nullptr) {
continue;
}
const SpriteLoader::CommonPixel *src = (const SpriteLoader::CommonPixel *) src_sprite->data;
Colour *dst_rgba_line = (Colour *) &dst_sprite->data[sizeof(SpriteData) + sd.infos[z].sprite_offset];
MapValue *dst_mv = (MapValue *) &dst_sprite->data[sizeof(SpriteData) + sd.infos[z].mv_offset];

View File

@ -70,6 +70,11 @@ public:
/** The SSE2 32 bpp blitter (without palette animation). */
class Blitter_32bppSSE2 : public Blitter_32bppSimple, public Blitter_32bppSSE_Base {
public:
Blitter_32bppSSE2()
{
this->SetSupportsMissingZoomLevels(true);
}
void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override;
template <BlitterMode mode, Blitter_32bppSSE_Base::ReadMode read_mode, Blitter_32bppSSE_Base::BlockType bt_last, bool translucent>
void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom);

View File

@ -225,6 +225,8 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::Sprite *sprite, Alloca
dest_sprite->width = sprite->width;
dest_sprite->x_offs = sprite->x_offs;
dest_sprite->y_offs = sprite->y_offs;
dest_sprite->next = nullptr;
dest_sprite->missing_zoom_levels = 0;
memcpy(dest_sprite->data, temp_dst, size);
return dest_sprite;

View File

@ -70,6 +70,8 @@ Sprite *Blitter_8bppSimple::Encode(const SpriteLoader::Sprite *sprite, Allocator
dest_sprite->width = sprite->width;
dest_sprite->x_offs = sprite->x_offs;
dest_sprite->y_offs = sprite->y_offs;
dest_sprite->next = nullptr;
dest_sprite->missing_zoom_levels = 0;
/* 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++) {

View File

@ -24,6 +24,8 @@ Sprite *Blitter_Null::Encode(const SpriteLoader::Sprite *sprite, AllocatorProc *
dest_sprite->width = sprite->width;
dest_sprite->x_offs = sprite->x_offs;
dest_sprite->y_offs = sprite->y_offs;
dest_sprite->next = nullptr;
dest_sprite->missing_zoom_levels = 0;
return dest_sprite;
}

View File

@ -120,4 +120,9 @@ struct FreeDeleter
void operator()(const void* ptr) { free(ptr); }
};
struct NoOpDeleter
{
void operator()(const void* ptr) {}
};
#endif /* ALLOC_TYPE_HPP */

View File

@ -124,14 +124,14 @@ const Sprite *SpriteFontCache::GetGlyph(GlyphID key)
{
SpriteID sprite = this->GetUnicodeGlyph(key);
if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
return GetSprite(sprite, SpriteType::Font);
return GetSprite(sprite, SpriteType::Font, 0);
}
uint SpriteFontCache::GetGlyphWidth(GlyphID key)
{
SpriteID sprite = this->GetUnicodeGlyph(key);
if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0;
return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font, 0)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0;
}
bool SpriteFontCache::GetDrawGlyphShadow()

View File

@ -979,7 +979,7 @@ void DrawCharCentered(WChar c, const Rect &r, TextColour colour)
*/
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
{
const Sprite *sprite = GetSprite(sprid, SpriteType::Normal);
const Sprite *sprite = GetSprite(sprid, SpriteType::Normal, ZoomMask(zoom));
if (offset != nullptr) {
offset->x = UnScaleByZoom(sprite->x_offs, zoom);
@ -1044,15 +1044,15 @@ void DrawSpriteViewport(const SpritePointerHolder &sprite_store, const DrawPixel
}
}
void PrepareDrawSpriteViewportSpriteStore(SpritePointerHolder &sprite_store, SpriteID img, PaletteID pal)
void PrepareDrawSpriteViewportSpriteStore(SpritePointerHolder &sprite_store, const DrawPixelInfo *dpi, SpriteID img, PaletteID pal)
{
SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
sprite_store.CacheSprite(real_sprite, SpriteType::Normal);
sprite_store.CacheSprite(real_sprite, SpriteType::Normal, dpi->zoom);
if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
sprite_store.CacheSprite(GB(pal, 0, PALETTE_WIDTH), SpriteType::Recolour);
sprite_store.CacheRecolourSprite(GB(pal, 0, PALETTE_WIDTH));
} else if (pal != PAL_NONE) {
if (!HasBit(pal, PALETTE_TEXT_RECOLOUR) && GB(pal, 0, PALETTE_WIDTH) != PAL_NONE) {
sprite_store.CacheSprite(GB(pal, 0, PALETTE_WIDTH), SpriteType::Recolour);
sprite_store.CacheRecolourSprite(GB(pal, 0, PALETTE_WIDTH));
}
}
}
@ -1072,16 +1072,16 @@ void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub,
SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
ctx.colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), SpriteType::Recolour) + 1;
GfxMainBlitter(ctx, GetSprite(real_sprite, SpriteType::Normal), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
GfxMainBlitter(ctx, GetSprite(real_sprite, SpriteType::Normal, ZoomMask(zoom)), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
} else if (pal != PAL_NONE) {
if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
ctx.SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
} else {
ctx.colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), SpriteType::Recolour) + 1;
}
GfxMainBlitter(ctx, GetSprite(real_sprite, SpriteType::Normal), x, y, GetBlitterMode(pal), sub, real_sprite, zoom);
GfxMainBlitter(ctx, GetSprite(real_sprite, SpriteType::Normal, ZoomMask(zoom)), x, y, GetBlitterMode(pal), sub, real_sprite, zoom);
} else {
GfxMainBlitter(ctx, GetSprite(real_sprite, SpriteType::Normal), x, y, BM_NORMAL, sub, real_sprite, zoom);
GfxMainBlitter(ctx, GetSprite(real_sprite, SpriteType::Normal, ZoomMask(zoom)), x, y, BM_NORMAL, sub, real_sprite, zoom);
}
}
@ -1098,7 +1098,7 @@ void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub,
* @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
*/
template <int ZOOM_BASE, bool SCALED_XY>
static void GfxBlitter(const GfxBlitterCtx &ctx, const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
static void GfxBlitter(const GfxBlitterCtx &ctx, const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
{
const DrawPixelInfo *dpi = ctx.dpi;
Blitter::BlitterParams bp;
@ -1139,6 +1139,14 @@ static void GfxBlitter(const GfxBlitterCtx &ctx, const Sprite * const sprite, in
y += ScaleByZoom(bp.skip_top, zoom);
}
while (sprite != nullptr && HasBit(sprite->missing_zoom_levels, zoom)) {
sprite = sprite->next;
}
if (sprite == nullptr) {
DEBUG(sprite, 0, "Failed to draw sprite %u at zoom level %u as required zoom level is missing", sprite_id, zoom);
return;
}
/* Copy the main data directly from the sprite */
bp.sprite = sprite->data;
bp.sprite_width = sprite->width;
@ -1231,7 +1239,7 @@ std::unique_ptr<uint32[]> DrawSpriteToRgbaBuffer(SpriteID spriteId, ZoomLevel zo
/* Gather information about the sprite to write, reserve memory */
const SpriteID real_sprite = GB(spriteId, 0, SPRITE_WIDTH);
const Sprite *sprite = GetSprite(real_sprite, SpriteType::Normal);
const Sprite *sprite = GetSprite(real_sprite, SpriteType::Normal, ZoomMask(zoom));
Dimension dim = GetSpriteSize(real_sprite, nullptr, zoom);
size_t dim_size = static_cast<size_t>(dim.width) * dim.height;
std::unique_ptr<uint32[]> result(new uint32[dim_size]);
@ -2121,7 +2129,7 @@ void UpdateCursorSize()
static_assert(lengthof(_cursor.sprite_seq) == lengthof(_cursor.sprite_pos));
assert(_cursor.sprite_count <= lengthof(_cursor.sprite_seq));
for (uint i = 0; i < _cursor.sprite_count; ++i) {
const Sprite *p = GetSprite(GB(_cursor.sprite_seq[i].sprite, 0, SPRITE_WIDTH), SpriteType::Normal);
const Sprite *p = GetSprite(GB(_cursor.sprite_seq[i].sprite, 0, SPRITE_WIDTH), SpriteType::Normal, 0);
Point offs, size;
offs.x = UnScaleGUI(p->x_offs) + _cursor.sprite_pos[i].x;
offs.y = UnScaleGUI(p->y_offs) + _cursor.sprite_pos[i].y;

View File

@ -105,7 +105,7 @@ Dimension GetSpriteSize(SpriteID sprid, Point *offset = nullptr, ZoomLevel zoom
Dimension GetScaledSpriteSize(SpriteID sprid); /* widget.cpp */
struct SpritePointerHolder;
void DrawSpriteViewport(const SpritePointerHolder &sprite_store, const DrawPixelInfo *dpi, SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub = nullptr);
void PrepareDrawSpriteViewportSpriteStore(SpritePointerHolder &sprite_store, SpriteID img, PaletteID pal);
void PrepareDrawSpriteViewportSpriteStore(SpritePointerHolder &sprite_store, const DrawPixelInfo *dpi, SpriteID img, PaletteID pal);
void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub = nullptr, ZoomLevel zoom = ZOOM_LVL_GUI);
void DrawSpriteIgnorePadding(SpriteID img, PaletteID pal, const Rect &r, bool clicked, StringAlignment align); /* widget.cpp */
std::unique_ptr<uint32[]> DrawSpriteToRgbaBuffer(SpriteID spriteId, ZoomLevel zoom = ZOOM_LVL_GUI);

View File

@ -838,7 +838,7 @@ static void GenerateTerrain(int type, uint flag)
uint32 r = Random();
/* Choose one of the templates from the graphics file. */
const Sprite *templ = GetSprite((((r >> 24) * _genterrain_tbl_1[type]) >> 8) + _genterrain_tbl_2[type] + SPR_MAPGEN_BEGIN, SpriteType::MapGen);
const Sprite *templ = GetSprite((((r >> 24) * _genterrain_tbl_1[type]) >> 8) + _genterrain_tbl_2[type] + SPR_MAPGEN_BEGIN, SpriteType::MapGen, 0);
if (templ == nullptr) usererror("Map generator sprites could not be loaded");
/* Chose a random location to apply the template to. */

View File

@ -1286,7 +1286,7 @@ struct SpriteAlignerWindow : Window {
void SetStringParameters(int widget) const override
{
const Sprite *spr = GetSprite(this->current_sprite, SpriteType::Normal);
const Sprite *spr = GetSprite(this->current_sprite, SpriteType::Normal, ZoomMask(ZOOM_LVL_GUI));
switch (widget) {
case WID_SA_CAPTION:
SetDParam(0, this->current_sprite);
@ -1341,7 +1341,7 @@ struct SpriteAlignerWindow : Window {
switch (widget) {
case WID_SA_SPRITE: {
/* Center the sprite ourselves */
const Sprite *spr = GetSprite(this->current_sprite, SpriteType::Normal);
const Sprite *spr = GetSprite(this->current_sprite, SpriteType::Normal, ZoomMask(ZOOM_LVL_GUI));
Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
int x;
int y;
@ -1437,7 +1437,7 @@ struct SpriteAlignerWindow : Window {
* used by someone and the sprite cache isn't big enough for that
* particular NewGRF developer.
*/
Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, SpriteType::Normal));
Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, SpriteType::Normal, 0));
/* Remember the original offsets of the current sprite, if not already in mapping. */
if (this->offs_start_map.count(this->current_sprite) == 0) {

View File

@ -3294,7 +3294,7 @@ void DrawSingleSignal(TileIndex tile, const RailtypeInfo *rti, Track track, Sign
} else {
AddSortableSpriteToDraw(sprite, pal, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, z);
}
const Sprite *sp = GetSprite(sprite, SpriteType::Normal);
const Sprite *sp = GetSprite(sprite, SpriteType::Normal, 0);
if (sp->x_offs < -SIGNAL_DIRTY_LEFT || sp->x_offs + sp->width > SIGNAL_DIRTY_RIGHT || sp->y_offs < -SIGNAL_DIRTY_TOP || sp->y_offs + sp->height > SIGNAL_DIRTY_BOTTOM) {
_signal_sprite_oversized = true;
}

View File

@ -259,7 +259,7 @@ struct SchdispatchWindow : GeneralVehicleWindow {
SetDParamMaxValue(0, _settings_time.time_in_minutes ? 0 : MAX_YEAR * DAYS_IN_YEAR);
Dimension unumber = GetStringBoundingBox(STR_JUST_DATE_WALLCLOCK_TINY);
const Sprite *spr = GetSprite(SPR_FLAG_VEH_STOPPED, SpriteType::Normal);
const Sprite *spr = GetSprite(SPR_FLAG_VEH_STOPPED, SpriteType::Normal, ZoomMask(ZOOM_LVL_GUI));
this->flag_width = UnScaleGUI(spr->width) + WidgetDimensions::scaled.framerect.right;
this->flag_height = UnScaleGUI(spr->height);

View File

@ -119,7 +119,7 @@ void DrawCommonTileSeqInGUI(int x, int y, const DrawTileSprites *dts, int32 orig
Point pt = RemapCoords(dtss->delta_x, dtss->delta_y, dtss->delta_z);
DrawSprite(image, pal, x + UnScaleGUI(pt.x), y + UnScaleGUI(pt.y));
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal, ZoomMask(_cur_dpi->zoom));
child_offset.x = UnScaleGUI(pt.x + spr->x_offs);
child_offset.y = UnScaleGUI(pt.y + spr->y_offs);
} else {

View File

@ -38,76 +38,137 @@ uint _sprite_cache_size = 4;
static size_t _spritecache_bytes_used = 0;
static uint32 _sprite_lru_counter;
PACK_N(class SpriteDataBuffer {
void *ptr = nullptr;
struct SpriteCache;
class SpriteDataBuffer {
friend SpriteCache;
std::unique_ptr<void, FreeDeleter> ptr;
uint32 size = 0;
public:
void *GetPtr() { return this->ptr; }
void *GetPtr() { return this->ptr.get(); }
uint32 GetSize() { return this->size; }
void Allocate(uint32 size)
{
_spritecache_bytes_used -= this->size;
free(this->ptr);
this->ptr = MallocT<byte>(size);
this->ptr.reset(MallocT<byte>(size));
this->size = size;
_spritecache_bytes_used += this->size;
}
void Clear()
{
_spritecache_bytes_used -= this->size;
free(this->ptr);
this->ptr = nullptr;
this->ptr.reset();
this->size = 0;
}
};
SpriteDataBuffer() {}
SpriteDataBuffer(uint32 size) { this->Allocate(size); }
SpriteDataBuffer(SpriteDataBuffer &&other) noexcept
{
*this = std::move(other);
}
~SpriteDataBuffer()
{
this->Clear();
}
SpriteDataBuffer& operator=(SpriteDataBuffer &&other) noexcept
{
this->Clear();
this->ptr = other.ptr;
this->size = other.size;
other.ptr = nullptr;
other.size = 0;
return *this;
}
}, 4);
PACK_N(struct SpriteCache {
struct SpriteCache {
SpriteFile *file; ///< The file the sprite in this entry can be found in.
size_t file_pos;
SpriteDataBuffer buffer;
private:
std::unique_ptr<void, NoOpDeleter> ptr;
uint32 totalsize = 0;
public:
uint32 id;
uint32 lru;
uint count;
SpriteType type; ///< In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour sprite. If the recolour sprite gets into the cache it might be drawn as real sprite which causes enormous trouble.
uint8 total_missing_zoom_levels = 0; ///< Zoom levels missing entirely
uint16 flags; ///< Control flags, see SpriteCacheCtrlFlags
byte flags; ///< Control flags, see SpriteCacheCtrlFlags
void *GetPtr() { return this->buffer.GetPtr(); }
void *GetPtr() { return this->ptr.get(); }
uint32 GetTotalSize() { return this->totalsize; }
SpriteType GetType() const { return this->type; }
void SetType(SpriteType type) { this->type = type; }
bool GetWarned() const { return HasBit(this->flags, SCCF_WARNED); }
void SetWarned(bool warned) { SB(this->flags, SCCF_WARNED, 1, warned ? 1 : 0); }
bool GetHasNonPalette() const { return HasBit(this->flags, SCCF_HAS_NON_PALETTE); }
}, 4);
bool GetHasNonPalette() const { return GB(this->flags, SCC_32BPP_ZOOM_START, 6) != 0; }
private:
void Deallocate()
{
if (!this->ptr) return;
_spritecache_bytes_used -= this->totalsize;
if (this->GetType() != SpriteType::Normal) {
free(this->ptr.release());
return;
}
Sprite *p = (Sprite *)this->ptr.release();
while (p != nullptr) {
Sprite *next = p->next;
free(p);
p = next;
}
}
Sprite *GetSpritePtr() { return (Sprite *)this->ptr.get(); }
public:
void Clear()
{
this->Deallocate();
this->totalsize = 0;
this->total_missing_zoom_levels = 0;
}
void Assign(SpriteDataBuffer &&other)
{
this->Clear();
this->ptr.reset(other.ptr.release());
this->totalsize = other.size;
if (this->ptr && this->GetType() == SpriteType::Normal) {
this->GetSpritePtr()->size = other.size;
this->total_missing_zoom_levels = this->GetSpritePtr()->missing_zoom_levels;
}
_spritecache_bytes_used += other.size;
other.size = 0;
}
void Append(SpriteDataBuffer &&other)
{
assert(this->GetType() == SpriteType::Normal);
if (!this->ptr || this->total_missing_zoom_levels == UINT8_MAX) {
/* Top level has no data or no zoom levels at all, it's safe to replace it because it cannot be cached for a render job */
this->Assign(std::move(other));
return;
}
Sprite *sp = (Sprite *)other.ptr.release();
if (sp == nullptr) return;
sp->size = other.size;
Sprite *p = this->GetSpritePtr();
while (p->next != nullptr) {
p = p->next;
}
p->next = sp;
this->total_missing_zoom_levels &= sp->missing_zoom_levels;
this->totalsize += other.size;
_spritecache_bytes_used += other.size;
other.size = 0;
}
~SpriteCache()
{
this->Clear();
}
SpriteCache() = default;
SpriteCache(const SpriteCache &other) = delete;
SpriteCache(SpriteCache &&other) = default;
SpriteCache& operator=(const SpriteCache &other) = delete;
SpriteCache& operator=(SpriteCache &&other) = default;
};
static std::vector<SpriteCache> _spritecache;
static SpriteDataBuffer _last_sprite_allocation;
@ -271,7 +332,7 @@ uint GetMaxSpriteID()
return (uint)_spritecache.size();
}
static bool ResizeSpriteIn(SpriteLoader::Sprite *sprite, ZoomLevel src, ZoomLevel tgt)
static bool ResizeSpriteIn(SpriteLoader::Sprite *sprite, ZoomLevel src, ZoomLevel tgt, bool dry_run)
{
uint8 scaled_1 = ScaleByZoom(1, (ZoomLevel)(src - tgt));
@ -284,6 +345,11 @@ static bool ResizeSpriteIn(SpriteLoader::Sprite *sprite, ZoomLevel src, ZoomLeve
sprite[tgt].y_offs = sprite[src].y_offs * scaled_1;
sprite[tgt].colours = sprite[src].colours;
if (dry_run) {
sprite[tgt].data = nullptr;
return true;
}
sprite[tgt].AllocateData(tgt, static_cast<size_t>(sprite[tgt].width) * sprite[tgt].height);
SpriteLoader::CommonPixel *dst = sprite[tgt].data;
@ -298,7 +364,7 @@ static bool ResizeSpriteIn(SpriteLoader::Sprite *sprite, ZoomLevel src, ZoomLeve
return true;
}
static void ResizeSpriteOut(SpriteLoader::Sprite *sprite, ZoomLevel zoom)
static void ResizeSpriteOut(SpriteLoader::Sprite *sprite, ZoomLevel zoom, bool dry_run)
{
/* Algorithm based on 32bpp_Optimized::ResizeSprite() */
sprite[zoom].width = UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].width, zoom);
@ -307,6 +373,11 @@ static void ResizeSpriteOut(SpriteLoader::Sprite *sprite, ZoomLevel zoom)
sprite[zoom].y_offs = UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].y_offs, zoom);
sprite[zoom].colours = sprite[ZOOM_LVL_NORMAL].colours;
if (dry_run) {
sprite[zoom].data = nullptr;
return;
}
sprite[zoom].AllocateData(zoom, static_cast<size_t>(sprite[zoom].height) * sprite[zoom].width);
SpriteLoader::CommonPixel *dst = sprite[zoom].data;
@ -337,40 +408,42 @@ static bool PadSingleSprite(SpriteLoader::Sprite *sprite, ZoomLevel zoom, uint p
if (width > UINT16_MAX || height > UINT16_MAX) return false;
/* Copy source data and reallocate sprite memory. */
size_t sprite_size = static_cast<size_t>(sprite->width) * sprite->height;
SpriteLoader::CommonPixel *src_data = MallocT<SpriteLoader::CommonPixel>(sprite_size);
MemCpyT(src_data, sprite->data, sprite_size);
sprite->AllocateData(zoom, static_cast<size_t>(width) * height);
if (sprite->data != nullptr) {
/* Copy source data and reallocate sprite memory. */
size_t sprite_size = static_cast<size_t>(sprite->width) * sprite->height;
SpriteLoader::CommonPixel *src_data = MallocT<SpriteLoader::CommonPixel>(sprite_size);
MemCpyT(src_data, sprite->data, sprite_size);
sprite->AllocateData(zoom, static_cast<size_t>(width) * height);
/* Copy with padding to destination. */
SpriteLoader::CommonPixel *src = src_data;
SpriteLoader::CommonPixel *data = sprite->data;
for (uint y = 0; y < height; y++) {
if (y < pad_top || pad_bottom + y >= height) {
/* Top/bottom padding. */
MemSetT(data, 0, width);
data += width;
} else {
if (pad_left > 0) {
/* Pad left. */
MemSetT(data, 0, pad_left);
data += pad_left;
}
/* Copy with padding to destination. */
SpriteLoader::CommonPixel *src = src_data;
SpriteLoader::CommonPixel *data = sprite->data;
for (uint y = 0; y < height; y++) {
if (y < pad_top || pad_bottom + y >= height) {
/* Top/bottom padding. */
MemSetT(data, 0, width);
data += width;
} else {
if (pad_left > 0) {
/* Pad left. */
MemSetT(data, 0, pad_left);
data += pad_left;
}
/* Copy pixels. */
MemCpyT(data, src, sprite->width);
src += sprite->width;
data += sprite->width;
/* Copy pixels. */
MemCpyT(data, src, sprite->width);
src += sprite->width;
data += sprite->width;
if (pad_right > 0) {
/* Pad right. */
MemSetT(data, 0, pad_right);
data += pad_right;
if (pad_right > 0) {
/* Pad right. */
MemSetT(data, 0, pad_right);
data += pad_right;
}
}
}
free(src_data);
}
free(src_data);
/* Update sprite size. */
sprite->width = width;
@ -429,12 +502,23 @@ static bool PadSprites(SpriteLoader::Sprite *sprite, unsigned int sprite_avail,
return true;
}
static bool ResizeSprites(SpriteLoader::Sprite *sprite, unsigned int sprite_avail, SpriteEncoder *encoder)
static bool ResizeSprites(SpriteLoader::Sprite *sprite, unsigned int sprite_avail, SpriteEncoder *encoder, uint8 zoom_levels)
{
bool needed = false;
for (ZoomLevel zoom = ZOOM_LVL_SPR_END; zoom-- > ZOOM_LVL_NORMAL; ) {
if (HasBit(sprite_avail, zoom) && sprite[zoom].data != nullptr) {
needed = false;
} else if (HasBit(zoom_levels, zoom)) {
needed = true;
} else if (needed) {
SetBit(zoom_levels, zoom);
}
}
/* Create a fully zoomed image if it does not exist */
ZoomLevel first_avail = static_cast<ZoomLevel>(FindFirstBit(sprite_avail));
if (first_avail != ZOOM_LVL_NORMAL) {
if (!ResizeSpriteIn(sprite, first_avail, ZOOM_LVL_NORMAL)) return false;
if (!ResizeSpriteIn(sprite, first_avail, ZOOM_LVL_NORMAL, !HasBit(zoom_levels, ZOOM_LVL_NORMAL))) return false;
SetBit(sprite_avail, ZOOM_LVL_NORMAL);
}
@ -452,10 +536,10 @@ static bool ResizeSprites(SpriteLoader::Sprite *sprite, unsigned int sprite_avai
}
/* Zoom level is not available, or unusable, so create it */
if (!HasBit(sprite_avail, zoom)) ResizeSpriteOut(sprite, zoom);
if (!HasBit(sprite_avail, zoom)) ResizeSpriteOut(sprite, zoom, !HasBit(zoom_levels, zoom));
}
return true;
return true;
}
/**
@ -512,10 +596,15 @@ static const char *GetSpriteTypeName(SpriteType type)
* @param encoder Sprite encoder to use.
* @return Read sprite data.
*/
static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_type, AllocatorProc *allocator, SpriteEncoder *encoder)
static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_type, AllocatorProc *allocator, SpriteEncoder *encoder, uint8 zoom_levels)
{
/* Use current blitter if no other sprite encoder is given. */
if (encoder == nullptr) encoder = BlitterFactory::GetCurrentBlitter();
if (encoder == nullptr) {
encoder = BlitterFactory::GetCurrentBlitter();
if (!encoder->SupportsMissingZoomLevels()) zoom_levels = UINT8_MAX;
} else {
zoom_levels = UINT8_MAX;
}
SpriteFile &file = *sc->file;
size_t file_pos = sc->file_pos;
@ -535,16 +624,16 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
SpriteLoaderGrf sprite_loader(file.GetContainerVersion());
if (sprite_type != SpriteType::MapGen && sc->GetHasNonPalette() && encoder->Is32BppSupported()) {
/* Try for 32bpp sprites first. */
sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, true, sc->count, sc->flags);
sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, true, sc->count, sc->flags, zoom_levels);
}
if (sprite_avail == 0) {
sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, false, sc->count, sc->flags);
sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, false, sc->count, sc->flags, zoom_levels);
}
if (sprite_avail == 0) {
if (sprite_type == SpriteType::MapGen) return nullptr;
if (id == SPR_IMG_QUERY) usererror("Okay... something went horribly wrong. I couldn't load the fallback sprite. What should I do?");
return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, allocator, encoder);
return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, UINT8_MAX, allocator, encoder);
}
if (sprite_type == SpriteType::MapGen) {
@ -564,6 +653,8 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
s->height = sprite[ZOOM_LVL_NORMAL].height;
s->x_offs = sprite[ZOOM_LVL_NORMAL].x_offs;
s->y_offs = sprite[ZOOM_LVL_NORMAL].y_offs;
s->next = nullptr;
s->missing_zoom_levels = 0;
SpriteLoader::CommonPixel *src = sprite[ZOOM_LVL_NORMAL].data;
byte *dest = s->data;
@ -575,9 +666,9 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
return s;
}
if (!ResizeSprites(sprite, sprite_avail, encoder)) {
if (!ResizeSprites(sprite, sprite_avail, encoder, zoom_levels)) {
if (id == SPR_IMG_QUERY) usererror("Okay... something went horribly wrong. I couldn't resize the fallback sprite. What should I do?");
return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, allocator, encoder);
return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, UINT8_MAX, allocator, encoder);
}
if (sprite->type == SpriteType::Font && _font_zoom != ZOOM_LVL_NORMAL) {
@ -590,13 +681,20 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
sprite[ZOOM_LVL_NORMAL].colours = sprite[_font_zoom].colours;
}
if (sprite->type == SpriteType::Normal) {
/* Remove unwanted zoom levels before encoding */
for (ZoomLevel zoom = ZOOM_LVL_BEGIN; zoom != ZOOM_LVL_SPR_END; zoom++) {
if (!HasBit(zoom_levels, zoom)) sprite[zoom].data = nullptr;
}
}
return encoder->Encode(sprite, allocator);
}
struct GrfSpriteOffset {
size_t file_pos;
uint count;
byte control_flags;
uint16 control_flags;
};
/** Map from sprite numbers to position in the GRF file. */
@ -644,17 +742,13 @@ void ReadGRFSpriteOffsets(SpriteFile &file)
uint length = file.ReadDword();
if (length > 0) {
byte colour = file.ReadByte() & SCC_MASK;
if (colour != SCC_PAL) SetBit(offset.control_flags, SCCF_HAS_NON_PALETTE);
length--;
if (length > 0) {
byte zoom = file.ReadByte();
length--;
if (colour != 0 && zoom == 0) { // ZOOM_LVL_OUT_4X (normal zoom)
SetBit(offset.control_flags, (colour != SCC_PAL) ? SCCF_ALLOW_ZOOM_MIN_1X_32BPP : SCCF_ALLOW_ZOOM_MIN_1X_PAL);
SetBit(offset.control_flags, (colour != SCC_PAL) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL);
}
if (colour != 0 && zoom == 2) { // ZOOM_LVL_OUT_2X (2x zoomed in)
SetBit(offset.control_flags, (colour != SCC_PAL) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL);
if (colour != 0) {
static const ZoomLevel zoom_lvl_map[6] = {ZOOM_LVL_OUT_4X, ZOOM_LVL_NORMAL, ZOOM_LVL_OUT_2X, ZOOM_LVL_OUT_8X, ZOOM_LVL_OUT_16X, ZOOM_LVL_OUT_32X};
if (zoom < 6) SetBit(offset.control_flags, zoom_lvl_map[zoom] + ((colour != SCC_PAL) ? SCC_32BPP_ZOOM_START : SCC_PAL_ZOOM_START));
}
}
}
@ -690,7 +784,7 @@ bool LoadNextSprite(int load_index, SpriteFile &file, uint file_sprite_id)
SpriteType type;
void *data = nullptr;
uint count = 0;
byte control_flags = 0;
uint16 control_flags = 0;
if (grf_type == 0xFF) {
/* Some NewGRF files have "empty" pseudo-sprites which are 1
* byte long. Catch these so the sprites won't be displayed. */
@ -744,16 +838,16 @@ bool LoadNextSprite(int load_index, SpriteFile &file, uint file_sprite_id)
SpriteCache *sc = AllocateSpriteCache(load_index);
sc->file = &file;
sc->file_pos = file_pos;
sc->SetType(type);
if (data != nullptr) {
assert(data == _last_sprite_allocation.GetPtr());
sc->buffer = std::move(_last_sprite_allocation);
sc->Assign(std::move(_last_sprite_allocation));
} else {
sc->buffer.Clear();
sc->Clear();
}
sc->lru = 0;
sc->id = file_sprite_id;
sc->count = count;
sc->SetType(type);
sc->flags = control_flags;
return true;
@ -784,7 +878,7 @@ static size_t GetSpriteCacheUsage()
*/
static void DeleteEntryFromSpriteCache(uint item)
{
GetSpriteCache(item)->buffer.Clear();
GetSpriteCache(item)->Clear();
}
static void DeleteEntriesFromSpriteCache(size_t target)
@ -821,7 +915,7 @@ static void DeleteEntriesFromSpriteCache(size_t target)
for (; i != _spritecache.size() && candidate_bytes < target; i++) {
SpriteCache *sc = GetSpriteCache(i);
if (sc->GetType() != SpriteType::Recolour && sc->GetPtr() != nullptr) {
push({ sc->lru, i, sc->buffer.GetSize() });
push({ sc->lru, i, sc->GetTotalSize() });
total_candidates++;
if (candidate_bytes >= target) break;
}
@ -833,7 +927,7 @@ static void DeleteEntriesFromSpriteCache(size_t target)
/* Only add to candidates if LRU <= current highest */
if (sc->lru <= candidates.front().lru) {
push({ sc->lru, i, sc->buffer.GetSize() });
push({ sc->lru, i, sc->GetTotalSize() });
while (!candidates.empty() && candidate_bytes - candidates.front().size >= target) {
pop();
}
@ -906,7 +1000,7 @@ static void *HandleInvalidSpriteRequest(SpriteID sprite, SpriteType requested, S
SpriteType available = sc->GetType();
if (requested == SpriteType::Font && available == SpriteType::Normal) {
if (sc->GetPtr() == nullptr) sc->SetType(SpriteType::Font);
return GetRawSprite(sprite, sc->GetType(), allocator);
return GetRawSprite(sprite, sc->GetType(), UINT8_MAX, allocator);
}
byte warning_level = sc->GetWarned() ? 6 : 0;
@ -918,10 +1012,10 @@ static void *HandleInvalidSpriteRequest(SpriteID sprite, SpriteType requested, S
if (sprite == SPR_IMG_QUERY) usererror("Uhm, would you be so kind not to load a NewGRF that makes the 'query' sprite a non-normal sprite?");
FALLTHROUGH;
case SpriteType::Font:
return GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, allocator);
return GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, UINT8_MAX, allocator);
case SpriteType::Recolour:
if (sprite == PALETTE_TO_DARK_BLUE) usererror("Uhm, would you be so kind not to load a NewGRF that makes the 'PALETTE_TO_DARK_BLUE' sprite a non-remap sprite?");
return GetRawSprite(PALETTE_TO_DARK_BLUE, SpriteType::Recolour, allocator);
return GetRawSprite(PALETTE_TO_DARK_BLUE, SpriteType::Recolour, UINT8_MAX, allocator);
case SpriteType::MapGen:
/* this shouldn't happen, overriding of SpriteType::MapGen sprites is checked in LoadNextSprite()
* (the only case the check fails is when these sprites weren't even loaded...) */
@ -939,7 +1033,7 @@ static void *HandleInvalidSpriteRequest(SpriteID sprite, SpriteType requested, S
* @param encoder Sprite encoder to use. Set to nullptr to use the currently active blitter.
* @return Sprite raw data
*/
void *GetRawSprite(SpriteID sprite, SpriteType type, AllocatorProc *allocator, SpriteEncoder *encoder)
void *GetRawSprite(SpriteID sprite, SpriteType type, uint8 zoom_levels, AllocatorProc *allocator, SpriteEncoder *encoder)
{
assert(type != SpriteType::MapGen || IsMapgenSpriteID(sprite));
assert(type < SpriteType::Invalid);
@ -961,17 +1055,23 @@ void *GetRawSprite(SpriteID sprite, SpriteType type, AllocatorProc *allocator, S
/* Update LRU */
sc->lru = ++_sprite_lru_counter;
if (type != SpriteType::Normal) zoom_levels = UINT8_MAX;
/* Load the sprite, if it is not loaded, yet */
if (sc->GetPtr() == nullptr) {
void *ptr = ReadSprite(sc, sprite, type, AllocSprite, nullptr);
void *ptr = ReadSprite(sc, sprite, type, AllocSprite, nullptr, zoom_levels);
assert(ptr == _last_sprite_allocation.GetPtr());
sc->buffer = std::move(_last_sprite_allocation);
sc->Assign(std::move(_last_sprite_allocation));
} else if ((sc->total_missing_zoom_levels & zoom_levels) != 0) {
void *ptr = ReadSprite(sc, sprite, type, AllocSprite, nullptr, sc->total_missing_zoom_levels & zoom_levels);
assert(ptr == _last_sprite_allocation.GetPtr());
sc->Append(std::move(_last_sprite_allocation));
}
return sc->GetPtr();
} else {
/* Do not use the spritecache, but a different allocator. */
return ReadSprite(sc, sprite, type, allocator, encoder);
return ReadSprite(sc, sprite, type, allocator, encoder, UINT8_MAX);
}
}
@ -999,9 +1099,13 @@ uint32 GetSpriteMainColour(SpriteID sprite_id, PaletteID palette_id)
uint8 sprite_avail;
const uint8 screen_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
auto zoom_mask = [&](bool is32bpp) -> uint8 {
return 1 << FindFirstBit(GB(sc->flags, is32bpp ? SCC_32BPP_ZOOM_START : SCC_PAL_ZOOM_START, 6));
};
/* Try to read the 32bpp sprite first. */
if (screen_depth == 32 && sc->GetHasNonPalette()) {
sprite_avail = sprite_loader.LoadSprite(sprites, file, file_pos, SpriteType::Normal, true, sc->count, sc->flags);
sprite_avail = sprite_loader.LoadSprite(sprites, file, file_pos, SpriteType::Normal, true, sc->count, sc->flags, zoom_mask(true));
if (sprite_avail != 0) {
SpriteLoader::Sprite *sprite = &sprites[FindFirstBit(sprite_avail)];
/* Return the average colour. */
@ -1031,7 +1135,7 @@ uint32 GetSpriteMainColour(SpriteID sprite_id, PaletteID palette_id)
}
/* No 32bpp, try 8bpp. */
sprite_avail = sprite_loader.LoadSprite(sprites, file, file_pos, SpriteType::Normal, false, sc->count, sc->flags);
sprite_avail = sprite_loader.LoadSprite(sprites, file, file_pos, SpriteType::Normal, false, sc->count, sc->flags, zoom_mask(false));
if (sprite_avail != 0) {
SpriteLoader::Sprite *sprite = &sprites[FindFirstBit(sprite_avail)];
SpriteLoader::CommonPixel *pixel = sprite->data;

View File

@ -11,25 +11,30 @@
#define SPRITECACHE_H
#include "gfx_type.h"
#include "zoom_type.h"
#include "spriteloader/spriteloader.hpp"
#include "3rdparty/cpp-btree/btree_map.h"
/** Data structure describing a sprite. */
struct Sprite {
uint32 size; ///< Size of the allocation for this sprite structure
uint16 height; ///< Height of the sprite.
uint16 width; ///< Width of the sprite.
int16 x_offs; ///< Number of pixels to shift the sprite to the right.
int16 y_offs; ///< Number of pixels to shift the sprite downwards.
uint8 missing_zoom_levels; ///< Bitmask of zoom levels missing in data
Sprite *next = nullptr; ///< Next sprite structure, this is the only member which may be changed after the sprite has been inserted in the sprite cache
byte data[]; ///< Sprite data.
};
/*
* Allow skipping sprites with zoom < ZOOM_LVL_OUT_4X, for sprite min zoom setting at 1x, if ZOOM_LVL_OUT_4X bit of present zoom levels is set.
* Allow skipping sprites with zoom < ZOOM_LVL_OUT_2X, for sprite min zoom setting at 2x, if either ZOOM_LVL_OUT_4X or ZOOM_LVL_OUT_2X bits of present zoom levels are set.
*/
enum SpriteCacheCtrlFlags {
SCCF_WARNED = 0, ///< True iff the user has been warned about incorrect use of this sprite.
SCCF_HAS_NON_PALETTE = 1, ///< True iff there is at least one non-paletter sprite present (such that 32bpp mode can be used).
SCCF_ALLOW_ZOOM_MIN_1X_PAL = 2, ///< Allow use of sprite min zoom setting at 1x in palette mode.
SCCF_ALLOW_ZOOM_MIN_1X_32BPP = 3, ///< Allow use of sprite min zoom setting at 1x in 32bpp mode.
SCCF_ALLOW_ZOOM_MIN_2X_PAL = 4, ///< Allow use of sprite min zoom setting at 2x in palette mode.
SCCF_ALLOW_ZOOM_MIN_2X_32BPP = 5, ///< Allow use of sprite min zoom setting at 2x in 32bpp mode.
SCC_PAL_ZOOM_START = 0, ///< Start bit of present zoom levels in palette mode.
SCC_32BPP_ZOOM_START = 6, ///< Start bit of present zoom levels in 32bpp mode.
SCCF_WARNED = 12, ///< True iff the user has been warned about incorrect use of this sprite.
};
extern uint _sprite_cache_size;
@ -37,7 +42,7 @@ extern uint _sprite_cache_size;
typedef void *AllocatorProc(size_t size);
void *SimpleSpriteAlloc(size_t size);
void *GetRawSprite(SpriteID sprite, SpriteType type, AllocatorProc *allocator = nullptr, SpriteEncoder *encoder = nullptr);
void *GetRawSprite(SpriteID sprite, SpriteType type, uint8 zoom_levels, AllocatorProc *allocator = nullptr, SpriteEncoder *encoder = nullptr);
bool SpriteExists(SpriteID sprite);
SpriteType GetSpriteType(SpriteID sprite);
@ -46,17 +51,16 @@ uint32 GetSpriteLocalID(SpriteID sprite);
uint GetSpriteCountForFile(const std::string &filename, SpriteID begin, SpriteID end);
uint GetMaxSpriteID();
static inline const Sprite *GetSprite(SpriteID sprite, SpriteType type)
static inline const Sprite *GetSprite(SpriteID sprite, SpriteType type, uint8 zoom_levels)
{
dbg_assert(type != SpriteType::Recolour);
return (Sprite*)GetRawSprite(sprite, type);
return (Sprite*)GetRawSprite(sprite, type, zoom_levels);
}
static inline const byte *GetNonSprite(SpriteID sprite, SpriteType type)
{
dbg_assert(type == SpriteType::Recolour);
return (byte*)GetRawSprite(sprite, type);
return (byte*)GetRawSprite(sprite, type, UINT8_MAX);
}
void GfxInitSpriteMem();
@ -94,9 +98,14 @@ public:
this->cache.clear();
}
inline void CacheSprite(SpriteID sprite, SpriteType type)
inline void CacheSprite(SpriteID sprite, SpriteType type, ZoomLevel zoom_level)
{
this->cache[sprite | (static_cast<uint32>(type) << 29)] = GetRawSprite(sprite, type);
this->cache[sprite | (static_cast<uint32>(type) << 29)] = GetRawSprite(sprite, type, ZoomMask(zoom_level));
}
inline void CacheRecolourSprite(SpriteID sprite)
{
this->cache[sprite | (static_cast<uint32>(SpriteType::Recolour) << 29)] = GetRawSprite(sprite, SpriteType::Recolour, 0);
}
};

View File

@ -256,13 +256,52 @@ uint8 LoadSpriteV1(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_p
return 0;
}
uint8 LoadSpriteV2(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, byte control_flags)
uint8 LoadSpriteV2(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, uint16 control_flags, uint8 zoom_levels)
{
static const ZoomLevel zoom_lvl_map[6] = {ZOOM_LVL_OUT_4X, ZOOM_LVL_NORMAL, ZOOM_LVL_OUT_2X, ZOOM_LVL_OUT_8X, ZOOM_LVL_OUT_16X, ZOOM_LVL_OUT_32X};
/* Is the sprite not present/stripped in the GRF? */
if (file_pos == SIZE_MAX) return 0;
/* clamp to first 6 zoom levels, as in zoom_lvl_map */
zoom_levels &= 0x3F;
uint8 available_levels = GB(control_flags, load_32bpp ? SCC_32BPP_ZOOM_START : SCC_PAL_ZOOM_START, 6);
uint8 skip_levels = 0;
ZoomLevel zoom_min = sprite_type == SpriteType::Font ? ZOOM_LVL_NORMAL : _settings_client.gui.sprite_zoom_min;
if (unlikely(sprite_type == SpriteType::MapGen)) {
available_levels = UINT8_MAX;
zoom_levels = 0x3F;
} else if (available_levels != 0) {
if (zoom_min >= ZOOM_LVL_OUT_2X && (HasBit(available_levels, ZOOM_LVL_OUT_2X) || HasBit(available_levels, ZOOM_LVL_OUT_4X))) {
ClrBit(available_levels, ZOOM_LVL_NORMAL);
}
if (zoom_min >= ZOOM_LVL_OUT_4X && HasBit(available_levels, ZOOM_LVL_OUT_4X)) {
ClrBit(available_levels, ZOOM_LVL_NORMAL);
ClrBit(available_levels, ZOOM_LVL_OUT_2X);
}
if (zoom_levels == 0) {
skip_levels = available_levels;
} else if (zoom_levels != 0x3F) {
uint8 keep_levels = 0;
for (uint8 bit : SetBitIterator(zoom_levels)) {
if (HasBit(available_levels, bit)) {
SetBit(keep_levels, bit);
continue;
}
uint8 below = ((1 << bit) - 1) & available_levels;
if (below != 0) {
SetBit(keep_levels, FindLastBit(below));
} else {
SetBit(keep_levels, FindFirstBit((~below) & available_levels));
}
}
skip_levels = available_levels & (~keep_levels);
}
}
/* Open the right file and go to the correct position */
file.SeekTo(file_pos, SEEK_SET);
@ -285,16 +324,7 @@ uint8 LoadSpriteV2(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_p
if (sprite_type != SpriteType::MapGen) {
if (zoom < lengthof(zoom_lvl_map)) {
is_wanted_zoom_lvl = true;
ZoomLevel zoom_min = sprite_type == SpriteType::Font ? ZOOM_LVL_NORMAL : _settings_client.gui.sprite_zoom_min;
if (zoom_min >= ZOOM_LVL_OUT_2X &&
HasBit(control_flags, load_32bpp ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL) && zoom_lvl_map[zoom] < ZOOM_LVL_OUT_2X) {
is_wanted_zoom_lvl = false;
}
if (zoom_min >= ZOOM_LVL_OUT_4X &&
HasBit(control_flags, load_32bpp ? SCCF_ALLOW_ZOOM_MIN_1X_32BPP : SCCF_ALLOW_ZOOM_MIN_1X_PAL) && zoom_lvl_map[zoom] < ZOOM_LVL_OUT_4X) {
is_wanted_zoom_lvl = false;
}
is_wanted_zoom_lvl = HasBit(available_levels, zoom_lvl_map[zoom]);
} else {
is_wanted_zoom_lvl = false;
}
@ -316,12 +346,28 @@ uint8 LoadSpriteV2(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_p
sprite[zoom_lvl].width = file.ReadWord();
sprite[zoom_lvl].x_offs = file.ReadWord();
sprite[zoom_lvl].y_offs = file.ReadWord();
sprite[zoom_lvl].colours = (SpriteColourComponent)colour;
if (sprite[zoom_lvl].width > INT16_MAX || sprite[zoom_lvl].height > INT16_MAX) {
WarnCorruptSprite(file, file_pos, __LINE__);
return 0;
}
ClrBit(available_levels, zoom_lvl);
if (HasBit(skip_levels, zoom_lvl)) {
sprite[zoom_lvl].data = nullptr;
SetBit(loaded_sprites, zoom_lvl);
if (available_levels == 0) {
/* nothing more to do */
break;
}
file.SkipBytes(num - 2 - 8);
continue;
}
/* Mask out colour information. */
type = type & ~SCC_MASK;
@ -331,8 +377,6 @@ uint8 LoadSpriteV2(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_p
if (colour & SCC_ALPHA) bpp++; // Has alpha data.
if (colour & SCC_PAL) bpp++; // Has palette data.
sprite[zoom_lvl].colours = (SpriteColourComponent)colour;
/* For chunked encoding we store the decompressed size in the file,
* otherwise we can calculate it from the image dimensions. */
uint decomp_size = (type & 0x08) ? file.ReadDword() : sprite[zoom_lvl].width * sprite[zoom_lvl].height * bpp;
@ -345,6 +389,10 @@ uint8 LoadSpriteV2(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_p
if (valid) SetBit(loaded_sprites, zoom_lvl);
if (--count == 0) break;
if (available_levels == 0) {
/* nothing more to do */
break;
}
} else {
if (--count == 0) break;
/* Not the wanted zoom level or colour depth, continue searching. */
@ -356,10 +404,10 @@ uint8 LoadSpriteV2(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_p
return loaded_sprites;
}
uint8 SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, byte control_flags)
uint8 SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, uint16 control_flags, uint8 zoom_levels)
{
if (this->container_ver >= 2) {
return LoadSpriteV2(sprite, file, file_pos, sprite_type, load_32bpp, count, control_flags);
return LoadSpriteV2(sprite, file, file_pos, sprite_type, load_32bpp, count, control_flags, zoom_levels);
} else {
return LoadSpriteV1(sprite, file, file_pos, sprite_type, load_32bpp);
}

View File

@ -13,11 +13,11 @@
#include "spriteloader.hpp"
/** Sprite loader for graphics coming from a (New)GRF. */
class SpriteLoaderGrf : public SpriteLoader {
class SpriteLoaderGrf FINAL : public SpriteLoader {
byte container_ver;
public:
SpriteLoaderGrf(byte container_ver) : container_ver(container_ver) {}
uint8 LoadSprite(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, byte control_flags);
uint8 LoadSprite(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, uint16 control_flags, uint8 zoom_levels) override;
};
#endif /* SPRITELOADER_GRF_HPP */

View File

@ -75,17 +75,30 @@ public:
* @param control_flags Control flags, see SpriteCacheCtrlFlags.
* @return Bit mask of the zoom levels successfully loaded or 0 if no sprite could be loaded.
*/
virtual uint8 LoadSprite(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, byte control_flags) = 0;
virtual uint8 LoadSprite(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, uint16 control_flags, uint8 zoom_levels) = 0;
virtual ~SpriteLoader() = default;
};
/** Interface for something that can encode a sprite. */
class SpriteEncoder {
bool supports_missing_zoom_levels = false;
protected:
inline void SetSupportsMissingZoomLevels(bool supported)
{
this->supports_missing_zoom_levels = supported;
}
public:
virtual ~SpriteEncoder() = default;
inline bool SupportsMissingZoomLevels() const
{
return this->supports_missing_zoom_levels;
}
/**
* Can the sprite encoder make use of RGBA sprites?
*/

View File

@ -118,7 +118,7 @@ Rect16 VehicleSpriteSeq::GetBounds() const
Rect16 bounds;
bounds.left = bounds.top = bounds.right = bounds.bottom = 0;
for (uint i = 0; i < this->count; ++i) {
const Sprite *spr = GetSprite(this->seq[i].sprite, SpriteType::Normal);
const Sprite *spr = GetSprite(this->seq[i].sprite, SpriteType::Normal, 0);
if (i == 0) {
bounds.left = spr->x_offs;
bounds.top = spr->y_offs;

View File

@ -1114,7 +1114,7 @@ void OpenGLBackend::PopulateCursorCache()
SpriteID sprite = _cursor.sprite_seq[i].sprite;
if (!this->cursor_cache.Contains(sprite)) {
Sprite *old = this->cursor_cache.Insert(sprite, (Sprite *)GetRawSprite(sprite, SpriteType::Normal, &SimpleSpriteAlloc, this));
Sprite *old = this->cursor_cache.Insert(sprite, (Sprite *)GetRawSprite(sprite, SpriteType::Normal, UINT8_MAX, &SimpleSpriteAlloc, this));
if (old != nullptr) {
OpenGLSprite *gl_sprite = (OpenGLSprite *)old->data;
gl_sprite->~OpenGLSprite();
@ -1283,6 +1283,8 @@ void OpenGLBackend::ReleaseAnimBuffer(const Rect &update_rect)
dest_sprite->width = sprite->width;
dest_sprite->x_offs = sprite->x_offs;
dest_sprite->y_offs = sprite->y_offs;
dest_sprite->next = nullptr;
dest_sprite->missing_zoom_levels = 0;
return dest_sprite;
}

View File

@ -1061,7 +1061,7 @@ void OffsetGroundSprite(int x, int y)
static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z, const SubSprite *sub)
{
Point pt = RemapCoords(x, y, z);
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal, ZoomMask(_vdd->dpi.zoom));
int left = pt.x + spr->x_offs;
int right = pt.x + spr->x_offs + spr->width;
@ -1137,7 +1137,7 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
tmp_width = right - left;
tmp_height = bottom - top;
} else {
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal, ZoomMask(_vdd->dpi.zoom));
left = tmp_left = (pt.x += spr->x_offs);
right = (pt.x + spr->width );
top = tmp_top = (pt.y += spr->y_offs);
@ -3762,13 +3762,13 @@ void ViewportDoDraw(Viewport *vp, int left, int top, int right, int bottom, uint
ViewportAddVehicles(&_vdd->dpi, vp->update_vehicles);
for (const TileSpriteToDraw &ts : _vdd->tile_sprites_to_draw) {
PrepareDrawSpriteViewportSpriteStore(_vdd->sprite_data, ts.image, ts.pal);
PrepareDrawSpriteViewportSpriteStore(_vdd->sprite_data, &_vdd->dpi, ts.image, ts.pal);
}
for (const ParentSpriteToDraw &ps : _vdd->parent_sprites_to_draw) {
if (ps.image != SPR_EMPTY_BOUNDING_BOX) PrepareDrawSpriteViewportSpriteStore(_vdd->sprite_data, ps.image, ps.pal);
if (ps.image != SPR_EMPTY_BOUNDING_BOX) PrepareDrawSpriteViewportSpriteStore(_vdd->sprite_data, &_vdd->dpi, ps.image, ps.pal);
}
for (const ChildScreenSpriteToDraw &cs : _vdd->child_screen_sprites_to_draw) {
PrepareDrawSpriteViewportSpriteStore(_vdd->sprite_data, cs.image, cs.pal);
PrepareDrawSpriteViewportSpriteStore(_vdd->sprite_data, &_vdd->dpi, cs.image, cs.pal);
}
_viewport_drawer_jobs++;

View File

@ -56,6 +56,11 @@ enum ZoomLevel : byte {
};
DECLARE_POSTFIX_INCREMENT(ZoomLevel)
static inline uint8 ZoomMask(ZoomLevel level)
{
return 1 << level;
}
extern int _gui_scale;
extern int _gui_scale_cfg;