OpenTTD Source 20250816-master-g94ba9b1454
spritecache.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "spriteloader/grf.hpp"
13#include "error_func.h"
14#include "strings_func.h"
15#include "zoom_func.h"
16#include "settings_type.h"
17#include "blitter/factory.hpp"
18#include "core/math_func.hpp"
20#include "spritecache.h"
22
23#include "table/sprites.h"
25
26#include "safeguards.h"
27
28/* Default of 4MB spritecache */
29uint _sprite_cache_size = 4;
30
31
32static std::vector<SpriteCache> _spritecache;
33static size_t _spritecache_bytes_used = 0;
34static uint32_t _sprite_lru_counter;
35static std::vector<std::unique_ptr<SpriteFile>> _sprite_files;
36
37static inline SpriteCache *GetSpriteCache(uint index)
38{
39 return &_spritecache[index];
40}
41
42SpriteCache *AllocateSpriteCache(uint index)
43{
44 if (index >= _spritecache.size()) {
45 /* Add another 1024 items to the 'pool' */
46 uint items = Align(index + 1, 1024);
47
48 Debug(sprite, 4, "Increasing sprite cache to {} items ({} bytes)", items, items * sizeof(SpriteCache));
49
50 _spritecache.resize(items);
51 }
52
53 return GetSpriteCache(index);
54}
55
61static SpriteFile *GetCachedSpriteFileByName(const std::string &filename)
62{
63 for (auto &f : _sprite_files) {
64 if (f->GetFilename() == filename) {
65 return f.get();
66 }
67 }
68 return nullptr;
69}
70
75std::span<const std::unique_ptr<SpriteFile>> GetCachedSpriteFiles()
76{
77 return _sprite_files;
78}
79
87SpriteFile &OpenCachedSpriteFile(const std::string &filename, Subdirectory subdir, bool palette_remap)
88{
89 SpriteFile *file = GetCachedSpriteFileByName(filename);
90 if (file == nullptr) {
91 file = _sprite_files.insert(std::end(_sprite_files), std::make_unique<SpriteFile>(filename, subdir, palette_remap))->get();
92 } else {
93 file->SeekToBegin();
94 }
95 return *file;
96}
97
104bool SkipSpriteData(SpriteFile &file, uint8_t type, uint16_t num)
105{
106 if (type & 2) {
107 file.SkipBytes(num);
108 } else {
109 while (num > 0) {
110 int8_t i = file.ReadByte();
111 if (i >= 0) {
112 int size = (i == 0) ? 0x80 : i;
113 if (size > num) return false;
114 num -= size;
115 file.SkipBytes(size);
116 } else {
117 i = -(i >> 3);
118 num -= i;
119 file.ReadByte();
120 }
121 }
122 }
123 return true;
124}
125
126/* Check if the given Sprite ID exists */
127bool SpriteExists(SpriteID id)
128{
129 if (id >= _spritecache.size()) return false;
130
131 /* Special case for Sprite ID zero -- its position is also 0... */
132 if (id == 0) return true;
133 return !(GetSpriteCache(id)->file_pos == 0 && GetSpriteCache(id)->file == nullptr);
134}
135
142{
143 if (!SpriteExists(sprite)) return SpriteType::Invalid;
144 return GetSpriteCache(sprite)->type;
145}
146
153{
154 if (!SpriteExists(sprite)) return nullptr;
155 return GetSpriteCache(sprite)->file;
156}
157
164{
165 if (!SpriteExists(sprite)) return 0;
166 return GetSpriteCache(sprite)->id;
167}
168
176uint GetSpriteCountForFile(const std::string &filename, SpriteID begin, SpriteID end)
177{
178 SpriteFile *file = GetCachedSpriteFileByName(filename);
179 if (file == nullptr) return 0;
180
181 uint count = 0;
182 for (SpriteID i = begin; i != end; i++) {
183 if (SpriteExists(i)) {
184 SpriteCache *sc = GetSpriteCache(i);
185 if (sc->file == file) {
186 count++;
187 Debug(sprite, 4, "Sprite: {}", i);
188 }
189 }
190 }
191 return count;
192}
193
203{
204 return static_cast<SpriteID>(_spritecache.size());
205}
206
207static bool ResizeSpriteIn(SpriteLoader::SpriteCollection &sprite, ZoomLevel src, ZoomLevel tgt)
208{
209 uint8_t scaled_1 = AdjustByZoom(1, src - tgt);
210 const auto &src_sprite = sprite[src];
211 auto &dest_sprite = sprite[tgt];
212
213 /* Check for possible memory overflow. */
214 if (src_sprite.width * scaled_1 > UINT16_MAX || src_sprite.height * scaled_1 > UINT16_MAX) return false;
215
216 dest_sprite.width = src_sprite.width * scaled_1;
217 dest_sprite.height = src_sprite.height * scaled_1;
218 dest_sprite.x_offs = src_sprite.x_offs * scaled_1;
219 dest_sprite.y_offs = src_sprite.y_offs * scaled_1;
220 dest_sprite.colours = src_sprite.colours;
221
222 dest_sprite.AllocateData(tgt, static_cast<size_t>(dest_sprite.width) * dest_sprite.height);
223
224 SpriteLoader::CommonPixel *dst = dest_sprite.data;
225 for (int y = 0; y < dest_sprite.height; y++) {
226 const SpriteLoader::CommonPixel *src_ln = &src_sprite.data[y / scaled_1 * src_sprite.width];
227 for (int x = 0; x < dest_sprite.width; x++) {
228 *dst = src_ln[x / scaled_1];
229 dst++;
230 }
231 }
232
233 return true;
234}
235
236static void ResizeSpriteOut(SpriteLoader::SpriteCollection &sprite, ZoomLevel zoom)
237{
238 const auto &root_sprite = sprite.Root();
239 const auto &src_sprite = sprite[zoom - 1];
240 auto &dest_sprite = sprite[zoom];
241
242 /* Algorithm based on 32bpp_Optimized::ResizeSprite() */
243 dest_sprite.width = UnScaleByZoom(root_sprite.width, zoom);
244 dest_sprite.height = UnScaleByZoom(root_sprite.height, zoom);
245 dest_sprite.x_offs = UnScaleByZoom(root_sprite.x_offs, zoom);
246 dest_sprite.y_offs = UnScaleByZoom(root_sprite.y_offs, zoom);
247 dest_sprite.colours = root_sprite.colours;
248
249 dest_sprite.AllocateData(zoom, static_cast<size_t>(dest_sprite.height) * dest_sprite.width);
250
251 SpriteLoader::CommonPixel *dst = dest_sprite.data;
252 const SpriteLoader::CommonPixel *src = src_sprite.data;
253 [[maybe_unused]] const SpriteLoader::CommonPixel *src_end = src + src_sprite.height * src_sprite.width;
254
255 for (uint y = 0; y < dest_sprite.height; y++) {
256 const SpriteLoader::CommonPixel *src_ln = src + src_sprite.width;
257 assert(src_ln <= src_end);
258 for (uint x = 0; x < dest_sprite.width; x++) {
259 assert(src < src_ln);
260 if (src + 1 != src_ln && (src + 1)->a != 0) {
261 *dst = *(src + 1);
262 } else {
263 *dst = *src;
264 }
265 dst++;
266 src += 2;
267 }
268 src = src_ln + src_sprite.width;
269 }
270}
271
272static bool PadSingleSprite(SpriteLoader::Sprite *sprite, ZoomLevel zoom, uint pad_left, uint pad_top, uint pad_right, uint pad_bottom)
273{
274 uint width = sprite->width + pad_left + pad_right;
275 uint height = sprite->height + pad_top + pad_bottom;
276
277 if (width > UINT16_MAX || height > UINT16_MAX) return false;
278
279 /* Copy source data and reallocate sprite memory. */
280 size_t sprite_size = static_cast<size_t>(sprite->width) * sprite->height;
281 std::vector<SpriteLoader::CommonPixel> src_data(sprite->data, sprite->data + sprite_size);
282 sprite->AllocateData(zoom, static_cast<size_t>(width) * height);
283
284 /* Copy with padding to destination. */
285 SpriteLoader::CommonPixel *src = src_data.data();
286 SpriteLoader::CommonPixel *data = sprite->data;
287 for (uint y = 0; y < height; y++) {
288 if (y < pad_top || pad_bottom + y >= height) {
289 /* Top/bottom padding. */
290 std::fill_n(data, width, SpriteLoader::CommonPixel{});
291 data += width;
292 } else {
293 if (pad_left > 0) {
294 /* Pad left. */
295 std::fill_n(data, pad_left, SpriteLoader::CommonPixel{});
296 data += pad_left;
297 }
298
299 /* Copy pixels. */
300 std::copy_n(src, sprite->width, data);
301 src += sprite->width;
302 data += sprite->width;
303
304 if (pad_right > 0) {
305 /* Pad right. */
306 std::fill_n(data, pad_right, SpriteLoader::CommonPixel{});
307 data += pad_right;
308 }
309 }
310 }
311
312 /* Update sprite size. */
313 sprite->width = width;
314 sprite->height = height;
315 sprite->x_offs -= pad_left;
316 sprite->y_offs -= pad_top;
317
318 return true;
319}
320
321static bool PadSprites(SpriteLoader::SpriteCollection &sprite, ZoomLevels sprite_avail, SpriteEncoder *encoder)
322{
323 /* Get minimum top left corner coordinates. */
324 int min_xoffs = INT32_MAX;
325 int min_yoffs = INT32_MAX;
326 for (ZoomLevel zoom : sprite_avail) {
327 min_xoffs = std::min(min_xoffs, ScaleByZoom(sprite[zoom].x_offs, zoom));
328 min_yoffs = std::min(min_yoffs, ScaleByZoom(sprite[zoom].y_offs, zoom));
329 }
330
331 /* Get maximum dimensions taking necessary padding at the top left into account. */
332 int max_width = INT32_MIN;
333 int max_height = INT32_MIN;
334 for (ZoomLevel zoom : sprite_avail) {
335 max_width = std::max(max_width, ScaleByZoom(sprite[zoom].width + sprite[zoom].x_offs - UnScaleByZoom(min_xoffs, zoom), zoom));
336 max_height = std::max(max_height, ScaleByZoom(sprite[zoom].height + sprite[zoom].y_offs - UnScaleByZoom(min_yoffs, zoom), zoom));
337 }
338
339 /* Align height and width if required to match the needs of the sprite encoder. */
340 uint align = encoder->GetSpriteAlignment();
341 if (align != 0) {
342 max_width = Align(max_width, align);
343 max_height = Align(max_height, align);
344 }
345
346 /* Pad sprites where needed. */
347 for (ZoomLevel zoom : sprite_avail) {
348 auto &cur_sprite = sprite[zoom];
349 /* Scaling the sprite dimensions in the blitter is done with rounding up,
350 * so a negative padding here is not an error. */
351 int pad_left = std::max(0, cur_sprite.x_offs - UnScaleByZoom(min_xoffs, zoom));
352 int pad_top = std::max(0, cur_sprite.y_offs - UnScaleByZoom(min_yoffs, zoom));
353 int pad_right = std::max(0, UnScaleByZoom(max_width, zoom) - cur_sprite.width - pad_left);
354 int pad_bottom = std::max(0, UnScaleByZoom(max_height, zoom) - cur_sprite.height - pad_top);
355
356 if (pad_left > 0 || pad_right > 0 || pad_top > 0 || pad_bottom > 0) {
357 if (!PadSingleSprite(&cur_sprite, zoom, pad_left, pad_top, pad_right, pad_bottom)) return false;
358 }
359 }
360
361 return true;
362}
363
364static bool ResizeSprites(SpriteLoader::SpriteCollection &sprite, ZoomLevels sprite_avail, SpriteEncoder *encoder)
365{
366 /* Create a fully zoomed image if it does not exist */
367 ZoomLevel first_avail = ZoomLevel::End;
368 for (ZoomLevel zoom = ZoomLevel::Min; zoom <= ZoomLevel::Max; ++zoom) {
369 if (!sprite_avail.Test(zoom)) continue;
370 first_avail = zoom;
371 if (zoom != ZoomLevel::Min) {
372 if (!ResizeSpriteIn(sprite, zoom, ZoomLevel::Min)) return false;
373 sprite_avail.Set(ZoomLevel::Min);
374 }
375 break;
376 }
377
378 /* Pad sprites to make sizes match. */
379 if (!PadSprites(sprite, sprite_avail, encoder)) return false;
380
381 /* Create other missing zoom levels */
382 for (ZoomLevel zoom = ZoomLevel::Begin; zoom != ZoomLevel::End; zoom++) {
383 if (zoom == ZoomLevel::Min) continue;
384
385 if (sprite_avail.Test(zoom)) {
386 /* Check that size and offsets match the fully zoomed image. */
387 [[maybe_unused]] const auto &root_sprite = sprite[ZoomLevel::Min];
388 [[maybe_unused]] const auto &dest_sprite = sprite[zoom];
389 assert(dest_sprite.width == UnScaleByZoom(root_sprite.width, zoom));
390 assert(dest_sprite.height == UnScaleByZoom(root_sprite.height, zoom));
391 assert(dest_sprite.x_offs == UnScaleByZoom(root_sprite.x_offs, zoom));
392 assert(dest_sprite.y_offs == UnScaleByZoom(root_sprite.y_offs, zoom));
393 } else {
394 /* Zoom level is not available, or unusable, so create it */
395 ResizeSpriteOut(sprite, zoom);
396 }
397 }
398
399 /* Replace sprites with higher resolution than the desired maximum source resolution with scaled up sprites, if not already done. */
400 if (first_avail < _settings_client.gui.sprite_zoom_min) {
401 for (ZoomLevel zoom = std::min(ZoomLevel::Normal, _settings_client.gui.sprite_zoom_min); zoom > ZoomLevel::Min; --zoom) {
402 ResizeSpriteIn(sprite, zoom, zoom - 1);
403 }
404 }
405
406 return true;
407}
408
417static void *ReadRecolourSprite(SpriteFile &file, size_t file_pos, uint num, SpriteAllocator &allocator)
418{
419 /* "Normal" recolour sprites are ALWAYS 257 bytes. Then there is a small
420 * number of recolour sprites that are 17 bytes that only exist in DOS
421 * GRFs which are the same as 257 byte recolour sprites, but with the last
422 * 240 bytes zeroed. */
423 static const uint RECOLOUR_SPRITE_SIZE = 257;
424 uint8_t *dest = allocator.Allocate<uint8_t>(std::max(RECOLOUR_SPRITE_SIZE, num));
425
426 file.SeekTo(file_pos, SEEK_SET);
427 if (file.NeedsPaletteRemap()) {
428 uint8_t *dest_tmp = new uint8_t[std::max(RECOLOUR_SPRITE_SIZE, num)];
429
430 /* Only a few recolour sprites are less than 257 bytes */
431 if (num < RECOLOUR_SPRITE_SIZE) std::fill_n(dest_tmp, RECOLOUR_SPRITE_SIZE, 0);
432 file.ReadBlock(dest_tmp, num);
433
434 /* The data of index 0 is never used; "literal 00" according to the (New)GRF specs. */
435 for (uint i = 1; i < RECOLOUR_SPRITE_SIZE; i++) {
436 dest[i] = _palmap_w2d[dest_tmp[_palmap_d2w[i - 1] + 1]];
437 }
438 delete[] dest_tmp;
439 } else {
440 file.ReadBlock(dest, num);
441 }
442
443 return dest;
444}
445
455static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_type, SpriteAllocator &allocator, SpriteEncoder *encoder)
456{
457 /* Use current blitter if no other sprite encoder is given. */
458 if (encoder == nullptr) encoder = BlitterFactory::GetCurrentBlitter();
459
460 SpriteFile &file = *sc->file;
461 size_t file_pos = sc->file_pos;
462
463 assert(sprite_type != SpriteType::Recolour);
464 assert(IsMapgenSpriteID(id) == (sprite_type == SpriteType::MapGen));
465 assert(sc->type == sprite_type);
466
467 Debug(sprite, 9, "Load sprite {}", id);
468
470 ZoomLevels sprite_avail;
471 ZoomLevels avail_8bpp;
472 ZoomLevels avail_32bpp;
473
474 SpriteLoaderGrf sprite_loader(file.GetContainerVersion());
475 if (sprite_type != SpriteType::MapGen && encoder->Is32BppSupported()) {
476 /* Try for 32bpp sprites first. */
477 sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, true, sc->control_flags, avail_8bpp, avail_32bpp);
478 }
479 if (sprite_avail.None()) {
480 sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, false, sc->control_flags, avail_8bpp, avail_32bpp);
481 if (sprite_type == SpriteType::Normal && avail_32bpp.Any() && !encoder->Is32BppSupported() && sprite_avail.None()) {
482 /* No 8bpp available, try converting from 32bpp. */
483 SpriteLoaderMakeIndexed make_indexed(sprite_loader);
484 sprite_avail = make_indexed.LoadSprite(sprite, file, file_pos, sprite_type, true, sc->control_flags, sprite_avail, avail_32bpp);
485 }
486 }
487
488 if (sprite_avail.None()) {
489 if (sprite_type == SpriteType::MapGen) return nullptr;
490 if (id == SPR_IMG_QUERY) UserError("Okay... something went horribly wrong. I couldn't load the fallback sprite. What should I do?");
491 return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, &allocator, encoder);
492 }
493
494 if (sprite_type == SpriteType::MapGen) {
495 /* Ugly hack to work around the problem that the old landscape
496 * generator assumes that those sprites are stored uncompressed in
497 * the memory, and they are only read directly by the code, never
498 * send to the blitter. So do not send it to the blitter (which will
499 * result in a data array in the format the blitter likes most), but
500 * extract the data directly and store that as sprite.
501 * Ugly: yes. Other solution: no. Blame the original author or
502 * something ;) The image should really have been a data-stream
503 * (so type = 0xFF basically). */
504 const auto &root_sprite = sprite.Root();
505 uint num = root_sprite.width * root_sprite.height;
506
507 Sprite *s = allocator.Allocate<Sprite>(sizeof(*s) + num);
508 s->width = root_sprite.width;
509 s->height = root_sprite.height;
510 s->x_offs = root_sprite.x_offs;
511 s->y_offs = root_sprite.y_offs;
512
513 SpriteLoader::CommonPixel *src = root_sprite.data;
514 uint8_t *dest = reinterpret_cast<uint8_t *>(s->data);
515 while (num-- > 0) {
516 *dest++ = src->m;
517 src++;
518 }
519
520 return s;
521 }
522
523 if (!ResizeSprites(sprite, sprite_avail, encoder)) {
524 if (id == SPR_IMG_QUERY) UserError("Okay... something went horribly wrong. I couldn't resize the fallback sprite. What should I do?");
525 return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, &allocator, encoder);
526 }
527
528 if (sprite_type == SpriteType::Font && _font_zoom != ZoomLevel::Min) {
529 /* Make ZoomLevel::Min the desired font zoom level. */
530 sprite[ZoomLevel::Min] = sprite[_font_zoom];
531 }
532
533 return encoder->Encode(sprite_type, sprite, allocator);
534}
535
537 size_t file_pos;
538 SpriteCacheCtrlFlags control_flags{};
539};
540
542static std::map<uint32_t, GrfSpriteOffset> _grf_sprite_offsets;
543
549size_t GetGRFSpriteOffset(uint32_t id)
550{
551 return _grf_sprite_offsets.find(id) != _grf_sprite_offsets.end() ? _grf_sprite_offsets[id].file_pos : SIZE_MAX;
552}
553
559{
560 _grf_sprite_offsets.clear();
561
562 if (file.GetContainerVersion() >= 2) {
563 /* Seek to sprite section of the GRF. */
564 size_t data_offset = file.ReadDword();
565 size_t old_pos = file.GetPos();
566 file.SeekTo(data_offset, SEEK_CUR);
567
568 GrfSpriteOffset offset{0};
569
570 /* Loop over all sprite section entries and store the file
571 * offset for each newly encountered ID. */
572 SpriteID id, prev_id = 0;
573 while ((id = file.ReadDword()) != 0) {
574 if (id != prev_id) {
575 _grf_sprite_offsets[prev_id] = offset;
576 offset.file_pos = file.GetPos() - 4;
577 }
578 prev_id = id;
579 uint length = file.ReadDword();
580 if (length > 0) {
581 SpriteComponents colour{file.ReadByte()};
582 length--;
583 if (length > 0) {
584 uint8_t zoom = file.ReadByte();
585 length--;
586 if (colour.Any() && zoom == 0) { // ZoomLevel::Normal (normal zoom)
589 }
590 if (colour.Any() && zoom == 2) { // ZoomLevel::In2x (2x zoomed in)
592 }
593 }
594 }
595 file.SkipBytes(length);
596 }
597 if (prev_id != 0) _grf_sprite_offsets[prev_id] = offset;
598
599 /* Continue processing the data section. */
600 file.SeekTo(old_pos, SEEK_SET);
601 }
602}
603
604
613bool LoadNextSprite(SpriteID load_index, SpriteFile &file, uint file_sprite_id)
614{
615 size_t file_pos = file.GetPos();
616
617 /* Read sprite header. */
618 uint32_t num = file.GetContainerVersion() >= 2 ? file.ReadDword() : file.ReadWord();
619 if (num == 0) return false;
620 uint8_t grf_type = file.ReadByte();
621
622 SpriteType type;
623 SpriteCacheCtrlFlags control_flags;
624 if (grf_type == 0xFF) {
625 /* Some NewGRF files have "empty" pseudo-sprites which are 1
626 * byte long. Catch these so the sprites won't be displayed. */
627 if (num == 1) {
628 file.ReadByte();
629 return false;
630 }
631 file_pos = file.GetPos();
633 file.SkipBytes(num);
634 } else if (file.GetContainerVersion() >= 2 && grf_type == 0xFD) {
635 if (num != 4) {
636 /* Invalid sprite section include, ignore. */
637 file.SkipBytes(num);
638 return false;
639 }
640 /* It is not an error if no sprite with the provided ID is found in the sprite section. */
641 auto iter = _grf_sprite_offsets.find(file.ReadDword());
642 if (iter != _grf_sprite_offsets.end()) {
643 file_pos = iter->second.file_pos;
644 control_flags = iter->second.control_flags;
645 } else {
646 file_pos = SIZE_MAX;
647 }
648 type = SpriteType::Normal;
649 } else {
650 file.SkipBytes(7);
651 type = SkipSpriteData(file, grf_type, num - 8) ? SpriteType::Normal : SpriteType::Invalid;
652 /* Inline sprites are not supported for container version >= 2. */
653 if (file.GetContainerVersion() >= 2) return false;
654 }
655
656 if (type == SpriteType::Invalid) return false;
657
658 if (load_index >= MAX_SPRITES) {
659 UserError("Tried to load too many sprites (#{}; max {})", load_index, MAX_SPRITES);
660 }
661
662 bool is_mapgen = IsMapgenSpriteID(load_index);
663
664 if (is_mapgen) {
665 if (type != SpriteType::Normal) UserError("Uhm, would you be so kind not to load a NewGRF that changes the type of the map generator sprites?");
666 type = SpriteType::MapGen;
667 }
668
669 SpriteCache *sc = AllocateSpriteCache(load_index);
670 sc->file = &file;
671 sc->file_pos = file_pos;
672 sc->length = num;
673 sc->lru = 0;
674 sc->id = file_sprite_id;
675 sc->type = type;
676 sc->warned = false;
677 sc->control_flags = control_flags;
678
679 return true;
680}
681
682
683void DupSprite(SpriteID old_spr, SpriteID new_spr)
684{
685 SpriteCache *scnew = AllocateSpriteCache(new_spr); // may reallocate: so put it first
686 SpriteCache *scold = GetSpriteCache(old_spr);
687
688 scnew->file = scold->file;
689 scnew->file_pos = scold->file_pos;
690 scnew->ClearSpriteData();
691 scnew->id = scold->id;
692 scnew->type = scold->type;
693 scnew->warned = false;
694 scnew->control_flags = scold->control_flags;
695}
696
703static void DeleteEntriesFromSpriteCache(size_t to_remove)
704{
705 const size_t initial_in_use = _spritecache_bytes_used;
706
707 struct SpriteInfo {
708 uint32_t lru;
709 SpriteID id;
710 size_t size;
711
712 bool operator<(const SpriteInfo &other) const
713 {
714 return this->lru < other.lru;
715 }
716 };
717
718 std::vector<SpriteInfo> candidates; // max heap, ordered by LRU
719 size_t candidate_bytes = 0; // total bytes that would be released when clearing all sprites in candidates
720
721 auto push = [&](SpriteInfo info) {
722 candidates.push_back(info);
723 std::push_heap(candidates.begin(), candidates.end());
724 candidate_bytes += info.size;
725 };
726
727 auto pop = [&]() {
728 candidate_bytes -= candidates.front().size;
729 std::pop_heap(candidates.begin(), candidates.end());
730 candidates.pop_back();
731 };
732
733 SpriteID i = 0;
734 for (; i != static_cast<SpriteID>(_spritecache.size()) && candidate_bytes < to_remove; i++) {
735 const SpriteCache *sc = GetSpriteCache(i);
736 if (sc->ptr != nullptr) {
737 push({ sc->lru, i, sc->length });
738 if (candidate_bytes >= to_remove) break;
739 }
740 }
741 /* candidates now contains enough bytes to meet to_remove.
742 * only sprites with LRU values <= the maximum (i.e. the top of the heap) need to be considered */
743 for (; i != static_cast<SpriteID>(_spritecache.size()); i++) {
744 const SpriteCache *sc = GetSpriteCache(i);
745 if (sc->ptr != nullptr && sc->lru <= candidates.front().lru) {
746 push({ sc->lru, i, sc->length });
747 while (!candidates.empty() && candidate_bytes - candidates.front().size >= to_remove) {
748 pop();
749 }
750 }
751 }
752
753 for (const auto &it : candidates) {
754 GetSpriteCache(it.id)->ClearSpriteData();
755 }
756
757 Debug(sprite, 3, "DeleteEntriesFromSpriteCache, deleted: {}, freed: {}, in use: {} --> {}, requested: {}",
758 candidates.size(), candidate_bytes, initial_in_use, _spritecache_bytes_used, to_remove);
759}
760
761void IncreaseSpriteLRU()
762{
764 uint target_size = (bpp > 0 ? _sprite_cache_size * bpp / 8 : 1) * 1024 * 1024;
765 if (_spritecache_bytes_used > target_size) {
766 DeleteEntriesFromSpriteCache(_spritecache_bytes_used - target_size + 512 * 1024);
767 }
768
769 if (_sprite_lru_counter >= 0xC0000000) {
770 Debug(sprite, 3, "Fixing lru {}, inuse={}", _sprite_lru_counter, _spritecache_bytes_used);
771
772 for (SpriteCache &sc : _spritecache) {
773 if (sc.ptr != nullptr) {
774 if (sc.lru > 0x80000000) {
775 sc.lru -= 0x80000000;
776 } else {
777 sc.lru = 0;
778 }
779 }
780 }
781 _sprite_lru_counter -= 0x80000000;
782 }
783}
784
785void SpriteCache::ClearSpriteData()
786{
787 _spritecache_bytes_used -= this->length;
788 this->ptr.reset();
789}
790
792{
793 this->data = std::make_unique<std::byte[]>(size);
794 this->size = size;
795 return this->data.get();
796}
797
807static void *HandleInvalidSpriteRequest(SpriteID sprite, SpriteType requested, SpriteCache *sc, SpriteAllocator *allocator)
808{
809 static const std::string_view sprite_types[] = {
810 "normal", // SpriteType::Normal
811 "map generator", // SpriteType::MapGen
812 "character", // SpriteType::Font
813 "recolour", // SpriteType::Recolour
814 };
815
816 SpriteType available = sc->type;
817 if (requested == SpriteType::Font && available == SpriteType::Normal) {
818 if (sc->ptr == nullptr) sc->type = SpriteType::Font;
819 return GetRawSprite(sprite, sc->type, allocator);
820 }
821
822 uint8_t warning_level = sc->warned ? 6 : 0;
823 sc->warned = true;
824 Debug(sprite, warning_level, "Tried to load {} sprite #{} as a {} sprite. Probable cause: NewGRF interference", sprite_types[static_cast<uint8_t>(available)], sprite, sprite_types[static_cast<uint8_t>(requested)]);
825
826 switch (requested) {
828 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?");
829 [[fallthrough]];
830 case SpriteType::Font:
831 return GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, allocator);
833 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?");
834 return GetRawSprite(PALETTE_TO_DARK_BLUE, SpriteType::Recolour, allocator);
836 /* this shouldn't happen, overriding of SpriteType::MapGen sprites is checked in LoadNextSprite()
837 * (the only case the check fails is when these sprites weren't even loaded...) */
838 default:
839 NOT_REACHED();
840 }
841}
842
852void *GetRawSprite(SpriteID sprite, SpriteType type, SpriteAllocator *allocator, SpriteEncoder *encoder)
853{
854 assert(type != SpriteType::MapGen || IsMapgenSpriteID(sprite));
855 assert(type < SpriteType::Invalid);
856
857 if (!SpriteExists(sprite)) {
858 Debug(sprite, 1, "Tried to load non-existing sprite #{}. Probable cause: Wrong/missing NewGRFs", sprite);
859
860 /* SPR_IMG_QUERY is a BIG FAT RED ? */
861 sprite = SPR_IMG_QUERY;
862 }
863
864 SpriteCache *sc = GetSpriteCache(sprite);
865
866 if (sc->type != type) return HandleInvalidSpriteRequest(sprite, type, sc, allocator);
867
868 if (allocator == nullptr && encoder == nullptr) {
869 /* Load sprite into/from spritecache */
870
871 /* Update LRU */
872 sc->lru = ++_sprite_lru_counter;
873
874 /* Load the sprite, if it is not loaded, yet */
875 if (sc->ptr == nullptr) {
876 UniquePtrSpriteAllocator cache_allocator;
877 if (sc->type == SpriteType::Recolour) {
878 ReadRecolourSprite(*sc->file, sc->file_pos, sc->length, cache_allocator);
879 } else {
880 ReadSprite(sc, sprite, type, cache_allocator, nullptr);
881 }
882 sc->ptr = std::move(cache_allocator.data);
883 sc->length = static_cast<uint32_t>(cache_allocator.size);
884 _spritecache_bytes_used += sc->length;
885 }
886
887 return static_cast<void *>(sc->ptr.get());
888 } else {
889 /* Do not use the spritecache, but a different allocator. */
890 return ReadSprite(sc, sprite, type, *allocator, encoder);
891 }
892}
893
894void GfxInitSpriteMem()
895{
896 /* Reset the spritecache 'pool' */
897 _spritecache.clear();
898 _spritecache.shrink_to_fit();
899
900 _sprite_files.clear();
901 _spritecache_bytes_used = 0;
902}
903
909{
910 /* Clear sprite ptr for all cached items */
911 for (SpriteCache &sc : _spritecache) {
912 if (sc.ptr != nullptr) sc.ClearSpriteData();
913 }
914
916}
917
923{
924 /* Clear sprite ptr for all cached font items */
925 for (SpriteCache &sc : _spritecache) {
926 if (sc.type == SpriteType::Font && sc.ptr != nullptr) sc.ClearSpriteData();
927 }
928}
929
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr bool None() const
Test if none of the values are set.
constexpr Timpl & Set()
Set all bits.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
Definition factory.hpp:136
virtual uint8_t GetScreenDepth()=0
Get the screen depth this blitter works for.
Enum-as-bit-set wrapper.
void ReadBlock(void *ptr, size_t size)
Read a block.
size_t GetPos() const
Get position in the file.
void SeekTo(size_t pos, int mode)
Seek in the current file.
uint8_t ReadByte()
Read a byte from the file.
uint32_t ReadDword()
Read a double word (32 bits) from the file (in low endian format).
void SkipBytes(size_t n)
Skip n bytes ahead in the file.
uint16_t ReadWord()
Read a word (16 bits) from the file (in low endian format).
Interface for something that can allocate memory for a sprite.
T * Allocate(size_t size)
Allocate memory for a sprite.
Map zoom level to data.
Interface for something that can encode a sprite.
virtual bool Is32BppSupported()=0
Can the sprite encoder make use of RGBA sprites?
virtual uint GetSpriteAlignment()
Get the value which the height and width on a sprite have to be aligned by.
virtual Sprite * Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator)=0
Convert a sprite from the loader to our own format.
RandomAccessFile with some extra information specific for sprite files.
bool NeedsPaletteRemap() const
Whether a palette remap is needed when loading sprites from this file.
uint8_t GetContainerVersion() const
Get the version number of container type used by the file.
void SeekToBegin()
Seek to the begin of the content, i.e.
Sprite loader for graphics coming from a (New)GRF.
Definition grf.hpp:16
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override
Load a sprite from the disk and return a sprite struct which is the same for all loaders.
Definition grf.cpp:362
Sprite loader for converting graphics coming from another source.
Definition makeindexed.h:16
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override
Load a sprite from the disk and return a sprite struct which is the same for all loaders.
SpriteAllocator that allocates memory via a unique_ptr array.
Definition spritecache.h:20
void * AllocatePtr(size_t size) override
Allocate memory for a sprite.
virtual void ClearSystemSprites()
Clear all cached sprites.
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
Error reporting related functions.
Factory to 'query' all available blitters.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
ZoomLevel _font_zoom
Sprite font Zoom level (not clamped)
Definition gfx.cpp:63
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
SpriteType
Types of sprites that might be loaded.
Definition gfx_type.h:358
@ Recolour
Recolour sprite.
@ Font
A sprite used for fonts.
@ MapGen
Special sprite for the map generator.
@ Invalid
Pseudosprite or other unusable sprite, used only internally.
@ Normal
The most basic (normal) sprite.
Base for reading sprites from (New)GRFs.
Base for converting sprites from another source from 32bpp RGBA to indexed 8bpp.
Integer math functions.
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition math_func.hpp:37
Translation tables from one GRF to another GRF.
static const uint8_t _palmap_d2w[]
Converting from the DOS palette to the Windows palette.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Types related to global configuration settings.
static void DeleteEntriesFromSpriteCache(size_t to_remove)
Delete entries from the sprite cache to remove the requested number of bytes.
static std::map< uint32_t, GrfSpriteOffset > _grf_sprite_offsets
Map from sprite numbers to position in the GRF file.
SpriteType GetSpriteType(SpriteID sprite)
Get the sprite type of a given sprite.
void GfxClearSpriteCache()
Remove all encoded sprites from the sprite cache without discarding sprite location information.
static SpriteFile * GetCachedSpriteFileByName(const std::string &filename)
Get the cached SpriteFile given the name of the file.
uint GetSpriteCountForFile(const std::string &filename, SpriteID begin, SpriteID end)
Count the sprites which originate from a specific file in a range of SpriteIDs.
static void * ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_type, SpriteAllocator &allocator, SpriteEncoder *encoder)
Read a sprite from disk.
void GfxClearFontSpriteCache()
Remove all encoded font sprites from the sprite cache without discarding sprite location information.
SpriteID GetMaxSpriteID()
Get a reasonable (upper bound) estimate of the maximum SpriteID used in OpenTTD; there will be no spr...
SpriteFile & OpenCachedSpriteFile(const std::string &filename, Subdirectory subdir, bool palette_remap)
Open/get the SpriteFile that is cached for use in the sprite cache.
static void * HandleInvalidSpriteRequest(SpriteID sprite, SpriteType requested, SpriteCache *sc, SpriteAllocator *allocator)
Handles the case when a sprite of different type is requested than is present in the SpriteCache.
void * GetRawSprite(SpriteID sprite, SpriteType type, SpriteAllocator *allocator, SpriteEncoder *encoder)
Reads a sprite (from disk or sprite cache).
std::span< const std::unique_ptr< SpriteFile > > GetCachedSpriteFiles()
Get the list of cached SpriteFiles.
bool LoadNextSprite(SpriteID load_index, SpriteFile &file, uint file_sprite_id)
Load a real or recolour sprite.
SpriteFile * GetOriginFile(SpriteID sprite)
Get the SpriteFile of a given sprite.
size_t GetGRFSpriteOffset(uint32_t id)
Get the file offset for a specific sprite in the sprite section of a GRF.
static void * ReadRecolourSprite(SpriteFile &file, size_t file_pos, uint num, SpriteAllocator &allocator)
Load a recolour sprite into memory.
bool SkipSpriteData(SpriteFile &file, uint8_t type, uint16_t num)
Skip the given amount of sprite graphics data.
uint32_t GetSpriteLocalID(SpriteID sprite)
Get the GRF-local sprite id of a given sprite.
void ReadGRFSpriteOffsets(SpriteFile &file)
Parse the sprite section of GRFs.
Functions to cache sprites in memory.
Internal functions to cache sprites in memory.
@ AllowZoomMin1x32bpp
Allow use of sprite min zoom setting at 1x in 32bpp mode.
@ AllowZoomMin2x32bpp
Allow use of sprite min zoom setting at 2x in 32bpp mode.
@ AllowZoomMin1xPal
Allow use of sprite min zoom setting at 1x in palette mode.
@ AllowZoomMin2xPal
Allow use of sprite min zoom setting at 2x in palette mode.
@ Palette
Sprite has palette data.
This file contains all sprite-related enums and defines.
static constexpr uint32_t MAX_SPRITES
Masks needed for sprite operations.
Definition sprites.h:1561
Definition of base types and functions in a cross-platform compatible way.
Functions related to OTTD's strings.
GUISettings gui
settings related to the GUI
ZoomLevel sprite_zoom_min
maximum zoom level at which higher-resolution alternative sprites will be used (if available) instead...
uint32_t length
Length of sprite data.
bool warned
True iff the user has been warned about incorrect use of this sprite.
SpriteCacheCtrlFlags control_flags
Control flags, see SpriteCacheCtrlFlags.
SpriteType type
In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour spr...
SpriteFile * file
The file the sprite in this entry can be found in.
Definition of a common pixel in OpenTTD's realm.
uint8_t m
Remap-channel.
Structure for passing information from the sprite loader to the blitter.
static SpriteCollMap< ReusableBuffer< SpriteLoader::CommonPixel > > buffer
Allocated memory to pass sprite data around.
void AllocateData(ZoomLevel zoom, size_t size)
Allocate the sprite data of this sprite.
uint16_t width
Width of the sprite.
int16_t x_offs
The x-offset of where the sprite will be drawn.
SpriteLoader::CommonPixel * data
The sprite itself.
uint16_t height
Height of the sprite.
int16_t y_offs
The y-offset of where the sprite will be drawn.
Data structure describing a sprite.
uint16_t width
Width of the sprite.
uint16_t height
Height of the sprite.
int16_t y_offs
Number of pixels to shift the sprite downwards.
std::byte data[]
Sprite data.
int16_t x_offs
Number of pixels to shift the sprite to the right.
Base of all video drivers.
Functions related to zooming.
int ScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift left (when zoom > ZoomLevel::Min) When shifting right,...
Definition zoom_func.h:22
int AdjustByZoom(int value, int zoom)
Adjust by zoom level; zoom < 0 shifts right, zoom >= 0 shifts left.
Definition zoom_func.h:45
int UnScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift right (when zoom > ZoomLevel::Min) When shifting right,...
Definition zoom_func.h:34
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:16
@ Begin
Begin for iteration.
@ Max
Maximum zoom level.
@ Min
Minimum zoom level.
@ End
End for iteration.
@ Normal
The normal zoom level.