diff --git a/src/blitter/32bpp_anim.cpp b/src/blitter/32bpp_anim.cpp index 3c79e3b152..8eb1dbb102 100644 --- a/src/blitter/32bpp_anim.cpp +++ b/src/blitter/32bpp_anim.cpp @@ -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)); diff --git a/src/blitter/32bpp_anim_sse4.hpp b/src/blitter/32bpp_anim_sse4.hpp index 1b32e085c5..3e3deddecc 100644 --- a/src/blitter/32bpp_anim_sse4.hpp +++ b/src/blitter/32bpp_anim_sse4.hpp @@ -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 void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom); void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; diff --git a/src/blitter/32bpp_optimized.cpp b/src/blitter/32bpp_optimized.cpp index 049d1f4bf5..1f29e75fd2 100644 --- a/src/blitter/32bpp_optimized.cpp +++ b/src/blitter/32bpp_optimized.cpp @@ -301,6 +301,7 @@ template 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 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 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 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); diff --git a/src/blitter/32bpp_optimized.hpp b/src/blitter/32bpp_optimized.hpp index df64b69888..af6415f4fe 100644 --- a/src/blitter/32bpp_optimized.hpp +++ b/src/blitter/32bpp_optimized.hpp @@ -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; diff --git a/src/blitter/32bpp_simple.cpp b/src/blitter/32bpp_simple.cpp index 569ea5c419..b5407a3508 100644 --- a/src/blitter/32bpp_simple.cpp +++ b/src/blitter/32bpp_simple.cpp @@ -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; diff --git a/src/blitter/32bpp_sse2.cpp b/src/blitter/32bpp_sse2.cpp index 3858bd45b3..dc8550e4f0 100644 --- a/src/blitter/32bpp_sse2.cpp +++ b/src/blitter/32bpp_sse2.cpp @@ -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]; diff --git a/src/blitter/32bpp_sse2.hpp b/src/blitter/32bpp_sse2.hpp index 42fcffd24a..9a2f14db06 100644 --- a/src/blitter/32bpp_sse2.hpp +++ b/src/blitter/32bpp_sse2.hpp @@ -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 void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom); diff --git a/src/blitter/8bpp_optimized.cpp b/src/blitter/8bpp_optimized.cpp index cf84bed2b0..1109d0b0df 100644 --- a/src/blitter/8bpp_optimized.cpp +++ b/src/blitter/8bpp_optimized.cpp @@ -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; diff --git a/src/blitter/8bpp_simple.cpp b/src/blitter/8bpp_simple.cpp index d0d30a2d3c..c137b9d559 100644 --- a/src/blitter/8bpp_simple.cpp +++ b/src/blitter/8bpp_simple.cpp @@ -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++) { diff --git a/src/blitter/null.cpp b/src/blitter/null.cpp index 29747b0f96..e27318c016 100644 --- a/src/blitter/null.cpp +++ b/src/blitter/null.cpp @@ -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; } diff --git a/src/core/alloc_type.hpp b/src/core/alloc_type.hpp index ae396eb8bd..555c8aea7c 100644 --- a/src/core/alloc_type.hpp +++ b/src/core/alloc_type.hpp @@ -120,4 +120,9 @@ struct FreeDeleter void operator()(const void* ptr) { free(ptr); } }; +struct NoOpDeleter +{ + void operator()(const void* ptr) {} +}; + #endif /* ALLOC_TYPE_HPP */ diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index da25a9b125..093fce7b6a 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -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() diff --git a/src/gfx.cpp b/src/gfx.cpp index 91a20b023f..ba8a87368f 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -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 -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 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(dim.width) * dim.height; std::unique_ptr 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; diff --git a/src/gfx_func.h b/src/gfx_func.h index 7ef1acb86b..a0531d4f4b 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -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 DrawSpriteToRgbaBuffer(SpriteID spriteId, ZoomLevel zoom = ZOOM_LVL_GUI); diff --git a/src/landscape.cpp b/src/landscape.cpp index 2aac3fe701..6675728c54 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -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. */ diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index 826ae4f636..f944c2c676 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -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(GetSprite(this->current_sprite, SpriteType::Normal)); + Sprite *spr = const_cast(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) { diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 0631e9b04a..00be64507a 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -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; } diff --git a/src/schdispatch_gui.cpp b/src/schdispatch_gui.cpp index fa7d1c19ce..38d48f4892 100644 --- a/src/schdispatch_gui.cpp +++ b/src/schdispatch_gui.cpp @@ -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); diff --git a/src/sprite.cpp b/src/sprite.cpp index 23dc63448b..9539ce03e4 100644 --- a/src/sprite.cpp +++ b/src/sprite.cpp @@ -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 { diff --git a/src/spritecache.cpp b/src/spritecache.cpp index 6f4b84b9ae..80d706266c 100644 --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -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 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(size); + this->ptr.reset(MallocT(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 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; 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(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(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(sprite->width) * sprite->height; - SpriteLoader::CommonPixel *src_data = MallocT(sprite_size); - MemCpyT(src_data, sprite->data, sprite_size); - sprite->AllocateData(zoom, static_cast(width) * height); + if (sprite->data != nullptr) { + /* Copy source data and reallocate sprite memory. */ + size_t sprite_size = static_cast(sprite->width) * sprite->height; + SpriteLoader::CommonPixel *src_data = MallocT(sprite_size); + MemCpyT(src_data, sprite->data, sprite_size); + sprite->AllocateData(zoom, static_cast(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(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; diff --git a/src/spritecache.h b/src/spritecache.h index 9e358bbccb..6a7c732b58 100644 --- a/src/spritecache.h +++ b/src/spritecache.h @@ -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(type) << 29)] = GetRawSprite(sprite, type); + this->cache[sprite | (static_cast(type) << 29)] = GetRawSprite(sprite, type, ZoomMask(zoom_level)); + } + + inline void CacheRecolourSprite(SpriteID sprite) + { + this->cache[sprite | (static_cast(SpriteType::Recolour) << 29)] = GetRawSprite(sprite, SpriteType::Recolour, 0); } }; diff --git a/src/spriteloader/grf.cpp b/src/spriteloader/grf.cpp index bae60eda22..c802a34072 100644 --- a/src/spriteloader/grf.cpp +++ b/src/spriteloader/grf.cpp @@ -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); } diff --git a/src/spriteloader/grf.hpp b/src/spriteloader/grf.hpp index 246998ec96..171088ceb1 100644 --- a/src/spriteloader/grf.hpp +++ b/src/spriteloader/grf.hpp @@ -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 */ diff --git a/src/spriteloader/spriteloader.hpp b/src/spriteloader/spriteloader.hpp index dd60edfc55..acda8a855c 100644 --- a/src/spriteloader/spriteloader.hpp +++ b/src/spriteloader/spriteloader.hpp @@ -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? */ diff --git a/src/vehicle.cpp b/src/vehicle.cpp index fdedb5cdec..365aa45539 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -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; diff --git a/src/video/opengl.cpp b/src/video/opengl.cpp index 94e6c5222e..ec2a30fb84 100644 --- a/src/video/opengl.cpp +++ b/src/video/opengl.cpp @@ -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; } diff --git a/src/viewport.cpp b/src/viewport.cpp index 7b24b877d8..f2d92604b6 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -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++; diff --git a/src/zoom_type.h b/src/zoom_type.h index 2b0ff96362..243e0ff1bd 100644 --- a/src/zoom_type.h +++ b/src/zoom_type.h @@ -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;