OpenTTD Source 20260621-master-g720d10536d
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
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
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
105bool SkipSpriteData(SpriteFile &file, uint8_t type, uint16_t num)
106{
107 if (type & 2) {
108 file.SkipBytes(num);
109 } else {
110 while (num > 0) {
111 int8_t i = file.ReadByte();
112 if (i >= 0) {
113 int size = (i == 0) ? 0x80 : i;
114 if (size > num) return false;
115 num -= size;
116 file.SkipBytes(size);
117 } else {
118 i = -(i >> 3);
119 num -= i;
120 file.ReadByte();
121 }
122 }
123 }
124 return true;
125}
126
127/* Check if the given Sprite ID exists */
128bool SpriteExists(SpriteID id)
129{
130 if (id >= _spritecache.size()) return false;
131
132 /* Special case for Sprite ID zero -- its position is also 0... */
133 if (id == 0) return true;
134 return !(GetSpriteCache(id)->file_pos == 0 && GetSpriteCache(id)->file == nullptr);
135}
136
143{
144 if (!SpriteExists(sprite)) return SpriteType::Invalid;
145 return GetSpriteCache(sprite)->type;
146}
147
154{
155 if (!SpriteExists(sprite)) return nullptr;
156 return GetSpriteCache(sprite)->file;
157}
158
165{
166 if (!SpriteExists(sprite)) return 0;
167 return GetSpriteCache(sprite)->id;
168}
169
177uint GetSpriteCountForFile(const std::string &filename, SpriteID begin, SpriteID end)
178{
179 SpriteFile *file = GetCachedSpriteFileByName(filename);
180 if (file == nullptr) return 0;
181
182 uint count = 0;
183 for (SpriteID i = begin; i != end; i++) {
184 if (SpriteExists(i)) {
185 SpriteCache *sc = GetSpriteCache(i);
186 if (sc->file == file) {
187 count++;
188 Debug(sprite, 4, "Sprite: {}", i);
189 }
190 }
191 }
192 return count;
193}
194
204{
205 return static_cast<SpriteID>(_spritecache.size());
206}
207
208static bool ResizeSpriteIn(SpriteLoader::SpriteCollection &sprite, ZoomLevel src, ZoomLevel tgt)
209{
210 uint8_t scaled_1 = AdjustByZoom(1, src - tgt);
211 const auto &src_sprite = sprite[src];
212 auto &dest_sprite = sprite[tgt];
213
214 /* Check for possible memory overflow. */
215 if (src_sprite.width * scaled_1 > UINT16_MAX || src_sprite.height * scaled_1 > UINT16_MAX) return false;
216
217 dest_sprite.width = src_sprite.width * scaled_1;
218 dest_sprite.height = src_sprite.height * scaled_1;
219 dest_sprite.x_offs = src_sprite.x_offs * scaled_1;
220 dest_sprite.y_offs = src_sprite.y_offs * scaled_1;
221 dest_sprite.colours = src_sprite.colours;
222
223 dest_sprite.AllocateData(tgt, static_cast<size_t>(dest_sprite.width) * dest_sprite.height);
224
225 SpriteLoader::CommonPixel *dst = dest_sprite.data;
226 for (int y = 0; y < dest_sprite.height; y++) {
227 const SpriteLoader::CommonPixel *src_ln = &src_sprite.data[y / scaled_1 * src_sprite.width];
228 for (int x = 0; x < dest_sprite.width; x++) {
229 *dst = src_ln[x / scaled_1];
230 dst++;
231 }
232 }
233
234 return true;
235}
236
237static void ResizeSpriteOut(SpriteLoader::SpriteCollection &sprite, ZoomLevel zoom)
238{
239 const auto &root_sprite = sprite.Root();
240 const auto &src_sprite = sprite[zoom - 1];
241 auto &dest_sprite = sprite[zoom];
242
243 /* Algorithm based on 32bpp_Optimized::ResizeSprite() */
244 dest_sprite.width = UnScaleByZoom(root_sprite.width, zoom);
245 dest_sprite.height = UnScaleByZoom(root_sprite.height, zoom);
246 dest_sprite.x_offs = UnScaleByZoom(root_sprite.x_offs, zoom);
247 dest_sprite.y_offs = UnScaleByZoom(root_sprite.y_offs, zoom);
248 dest_sprite.colours = root_sprite.colours;
249
250 dest_sprite.AllocateData(zoom, static_cast<size_t>(dest_sprite.height) * dest_sprite.width);
251
252 SpriteLoader::CommonPixel *dst = dest_sprite.data;
253 const SpriteLoader::CommonPixel *src = src_sprite.data;
254 [[maybe_unused]] const SpriteLoader::CommonPixel *src_end = src + src_sprite.height * src_sprite.width;
255
256 for (uint y = 0; y < dest_sprite.height; y++) {
257 const SpriteLoader::CommonPixel *src_ln = src + src_sprite.width;
258 assert(src_ln <= src_end);
259 for (uint x = 0; x < dest_sprite.width; x++) {
260 assert(src < src_ln);
261 if (src + 1 != src_ln && (src + 1)->a != 0) {
262 *dst = *(src + 1);
263 } else {
264 *dst = *src;
265 }
266 dst++;
267 src += 2;
268 }
269 src = src_ln + src_sprite.width;
270 }
271}
272
273static bool PadSingleSprite(SpriteLoader::Sprite *sprite, ZoomLevel zoom, uint pad_left, uint pad_top, uint pad_right, uint pad_bottom)
274{
275 uint width = sprite->width + pad_left + pad_right;
276 uint height = sprite->height + pad_top + pad_bottom;
277
278 if (width > UINT16_MAX || height > UINT16_MAX) return false;
279
280 /* Copy source data and reallocate sprite memory. */
281 size_t sprite_size = static_cast<size_t>(sprite->width) * sprite->height;
282 std::vector<SpriteLoader::CommonPixel> src_data(sprite->data, sprite->data + sprite_size);
283 sprite->AllocateData(zoom, static_cast<size_t>(width) * height);
284
285 /* Copy with padding to destination. */
286 SpriteLoader::CommonPixel *src = src_data.data();
287 SpriteLoader::CommonPixel *data = sprite->data;
288 for (uint y = 0; y < height; y++) {
289 if (y < pad_top || pad_bottom + y >= height) {
290 /* Top/bottom padding. */
291 std::fill_n(data, width, SpriteLoader::CommonPixel{});
292 data += width;
293 } else {
294 if (pad_left > 0) {
295 /* Pad left. */
296 std::fill_n(data, pad_left, SpriteLoader::CommonPixel{});
297 data += pad_left;
298 }
299
300 /* Copy pixels. */
301 std::copy_n(src, sprite->width, data);
302 src += sprite->width;
303 data += sprite->width;
304
305 if (pad_right > 0) {
306 /* Pad right. */
307 std::fill_n(data, pad_right, SpriteLoader::CommonPixel{});
308 data += pad_right;
309 }
310 }
311 }
312
313 /* Update sprite size. */
314 sprite->width = width;
315 sprite->height = height;
316 sprite->x_offs -= pad_left;
317 sprite->y_offs -= pad_top;
318
319 return true;
320}
321
322static bool PadSprites(SpriteLoader::SpriteCollection &sprite, ZoomLevels sprite_avail, SpriteEncoder *encoder)
323{
324 /* Get minimum top left corner coordinates. */
325 int min_xoffs = INT32_MAX;
326 int min_yoffs = INT32_MAX;
327 for (ZoomLevel zoom : sprite_avail) {
328 min_xoffs = std::min(min_xoffs, ScaleByZoom(sprite[zoom].x_offs, zoom));
329 min_yoffs = std::min(min_yoffs, ScaleByZoom(sprite[zoom].y_offs, zoom));
330 }
331
332 /* Get maximum dimensions taking necessary padding at the top left into account. */
333 int max_width = INT32_MIN;
334 int max_height = INT32_MIN;
335 for (ZoomLevel zoom : sprite_avail) {
336 max_width = std::max(max_width, ScaleByZoom(sprite[zoom].width + sprite[zoom].x_offs - UnScaleByZoom(min_xoffs, zoom), zoom));
337 max_height = std::max(max_height, ScaleByZoom(sprite[zoom].height + sprite[zoom].y_offs - UnScaleByZoom(min_yoffs, zoom), zoom));
338 }
339
340 /* Align height and width if required to match the needs of the sprite encoder. */
341 uint align = encoder->GetSpriteAlignment();
342 if (align != 0) {
343 max_width = Align(max_width, align);
344 max_height = Align(max_height, align);
345 }
346
347 /* Pad sprites where needed. */
348 for (ZoomLevel zoom : sprite_avail) {
349 auto &cur_sprite = sprite[zoom];
350 /* Scaling the sprite dimensions in the blitter is done with rounding up,
351 * so a negative padding here is not an error. */
352 int pad_left = std::max(0, cur_sprite.x_offs - UnScaleByZoom(min_xoffs, zoom));
353 int pad_top = std::max(0, cur_sprite.y_offs - UnScaleByZoom(min_yoffs, zoom));
354 int pad_right = std::max(0, UnScaleByZoom(max_width, zoom) - cur_sprite.width - pad_left);
355 int pad_bottom = std::max(0, UnScaleByZoom(max_height, zoom) - cur_sprite.height - pad_top);
356
357 if (pad_left > 0 || pad_right > 0 || pad_top > 0 || pad_bottom > 0) {
358 if (!PadSingleSprite(&cur_sprite, zoom, pad_left, pad_top, pad_right, pad_bottom)) return false;
359 }
360 }
361
362 return true;
363}
364
365static bool ResizeSprites(SpriteLoader::SpriteCollection &sprite, ZoomLevels sprite_avail, SpriteEncoder *encoder)
366{
367 /* Create a fully zoomed image if it does not exist */
368 ZoomLevel first_avail = ZoomLevel::End;
369 for (ZoomLevel zoom = ZoomLevel::Min; zoom <= ZoomLevel::Max; ++zoom) {
370 if (!sprite_avail.Test(zoom)) continue;
371 first_avail = zoom;
372 if (zoom != ZoomLevel::Min) {
373 if (!ResizeSpriteIn(sprite, zoom, ZoomLevel::Min)) return false;
374 sprite_avail.Set(ZoomLevel::Min);
375 }
376 break;
377 }
378
379 /* Pad sprites to make sizes match. */
380 if (!PadSprites(sprite, sprite_avail, encoder)) return false;
381
382 /* Create other missing zoom levels */
384 if (sprite_avail.Test(zoom)) {
385 /* Check that size and offsets match the fully zoomed image. */
386 [[maybe_unused]] const auto &root_sprite = sprite[ZoomLevel::Min];
387 [[maybe_unused]] const auto &dest_sprite = sprite[zoom];
388 assert(dest_sprite.width == UnScaleByZoom(root_sprite.width, zoom));
389 assert(dest_sprite.height == UnScaleByZoom(root_sprite.height, zoom));
390 assert(dest_sprite.x_offs == UnScaleByZoom(root_sprite.x_offs, zoom));
391 assert(dest_sprite.y_offs == UnScaleByZoom(root_sprite.y_offs, zoom));
392 } else {
393 /* Zoom level is not available, or unusable, so create it */
394 ResizeSpriteOut(sprite, zoom);
395 }
396 }
397
398 /* Replace sprites with higher resolution than the desired maximum source resolution with scaled up sprites, if not already done. */
399 if (first_avail < _settings_client.gui.sprite_zoom_min) {
400 for (ZoomLevel zoom = std::min(ZoomLevel::Normal, _settings_client.gui.sprite_zoom_min); zoom > ZoomLevel::Min; --zoom) {
401 ResizeSpriteIn(sprite, zoom, zoom - 1);
402 }
403 }
404
405 return true;
406}
407
416static void *ReadRecolourSprite(SpriteFile &file, size_t file_pos, uint num, SpriteAllocator &allocator)
417{
418 /* "Normal" recolour sprites are ALWAYS 257 bytes. Then there is a small
419 * number of recolour sprites that are 17 bytes that only exist in DOS
420 * GRFs which are the same as 257 byte recolour sprites, but with the last
421 * 240 bytes zeroed. */
422 static const uint RECOLOUR_SPRITE_SIZE = 257;
423 uint8_t *dest = allocator.Allocate<uint8_t>(std::max(RECOLOUR_SPRITE_SIZE, num));
424
425 file.SeekTo(file_pos, SEEK_SET);
426 if (file.NeedsPaletteRemap()) {
427 uint8_t *dest_tmp = new uint8_t[std::max(RECOLOUR_SPRITE_SIZE, num)];
428
429 /* Only a few recolour sprites are less than 257 bytes */
430 if (num < RECOLOUR_SPRITE_SIZE) std::fill_n(dest_tmp, RECOLOUR_SPRITE_SIZE, 0);
431 file.ReadBlock(dest_tmp, num);
432
433 /* The data of index 0 is never used; "literal 00" according to the (New)GRF specs. */
434 for (uint i = 1; i < RECOLOUR_SPRITE_SIZE; i++) {
435 dest[i] = _palmap_w2d[dest_tmp[_palmap_d2w[i - 1] + 1]];
436 }
437 delete[] dest_tmp;
438 } else {
439 file.ReadBlock(dest, num);
440 }
441
442 return dest;
443}
444
454static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_type, SpriteAllocator &allocator, SpriteEncoder *encoder)
455{
456 /* Use current blitter if no other sprite encoder is given. */
457 if (encoder == nullptr) encoder = BlitterFactory::GetCurrentBlitter();
458
459 SpriteFile &file = *sc->file;
460 size_t file_pos = sc->file_pos;
461
462 assert(sprite_type != SpriteType::Recolour);
463 assert(IsMapgenSpriteID(id) == (sprite_type == SpriteType::MapGen));
464 assert(sc->type == sprite_type);
465
466 Debug(sprite, 9, "Load sprite {}", id);
467
469 ZoomLevels sprite_avail;
470 ZoomLevels avail_8bpp;
471 ZoomLevels avail_32bpp;
472
473 SpriteLoaderGrf sprite_loader(file.GetContainerVersion());
474 if (sprite_type != SpriteType::MapGen && encoder->Is32BppSupported()) {
475 /* Try for 32bpp sprites first. */
476 sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, true, sc->control_flags, avail_8bpp, avail_32bpp);
477 }
478 if (sprite_avail.None()) {
479 sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, false, sc->control_flags, avail_8bpp, avail_32bpp);
480 if (sprite_type == SpriteType::Normal && avail_32bpp.Any() && !encoder->Is32BppSupported() && sprite_avail.None()) {
481 /* No 8bpp available, try converting from 32bpp. */
482 SpriteLoaderMakeIndexed make_indexed(sprite_loader);
483 sprite_avail = make_indexed.LoadSprite(sprite, file, file_pos, sprite_type, true, sc->control_flags, sprite_avail, avail_32bpp);
484 }
485 }
486
487 if (sprite_avail.None()) {
488 if (sprite_type == SpriteType::MapGen) return nullptr;
489 if (id == SPR_IMG_QUERY) UserError("Okay... something went horribly wrong. I couldn't load the fallback sprite. What should I do?");
490 return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, &allocator, encoder);
491 }
492
493 if (sprite_type == SpriteType::MapGen) {
494 /* Ugly hack to work around the problem that the old landscape
495 * generator assumes that those sprites are stored uncompressed in
496 * the memory, and they are only read directly by the code, never
497 * send to the blitter. So do not send it to the blitter (which will
498 * result in a data array in the format the blitter likes most), but
499 * extract the data directly and store that as sprite.
500 * Ugly: yes. Other solution: no. Blame the original author or
501 * something ;) The image should really have been a data-stream
502 * (so type = 0xFF basically). */
503 const auto &root_sprite = sprite.Root();
504 uint num = root_sprite.width * root_sprite.height;
505
506 Sprite *s = allocator.Allocate<Sprite>(sizeof(*s) + num);
507 s->width = root_sprite.width;
508 s->height = root_sprite.height;
509 s->x_offs = root_sprite.x_offs;
510 s->y_offs = root_sprite.y_offs;
511
512 SpriteLoader::CommonPixel *src = root_sprite.data;
513 uint8_t *dest = reinterpret_cast<uint8_t *>(s->data);
514 while (num-- > 0) {
515 *dest++ = src->m;
516 src++;
517 }
518
519 return s;
520 }
521
522 if (!ResizeSprites(sprite, sprite_avail, encoder)) {
523 if (id == SPR_IMG_QUERY) UserError("Okay... something went horribly wrong. I couldn't resize the fallback sprite. What should I do?");
524 return (void*)GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, &allocator, encoder);
525 }
526
527 if (sprite_type == SpriteType::Font && _font_zoom != ZoomLevel::Min) {
528 /* Make ZoomLevel::Min the desired font zoom level. */
529 sprite[ZoomLevel::Min] = sprite[_font_zoom];
530 }
531
532 return encoder->Encode(sprite_type, sprite, allocator);
533}
534
536 size_t file_pos = 0;
537 SpriteCacheCtrlFlags control_flags{};
538};
539
541static std::map<uint32_t, GrfSpriteOffset> _grf_sprite_offsets;
542
548size_t GetGRFSpriteOffset(uint32_t id)
549{
550 return _grf_sprite_offsets.find(id) != _grf_sprite_offsets.end() ? _grf_sprite_offsets[id].file_pos : SIZE_MAX;
551}
552
558{
559 _grf_sprite_offsets.clear();
560
561 if (file.GetContainerVersion() >= 2) {
562 /* Seek to sprite section of the GRF. */
563 size_t data_offset = file.ReadDword();
564 size_t old_pos = file.GetPos();
565 file.SeekTo(data_offset, SEEK_CUR);
566
567 GrfSpriteOffset offset{};
568
569 /* Loop over all sprite section entries and store the file
570 * offset for each newly encountered ID. */
571 SpriteID id, prev_id = 0;
572 while ((id = file.ReadDword()) != 0) {
573 if (id != prev_id) {
574 _grf_sprite_offsets[prev_id] = offset;
575 offset.file_pos = file.GetPos() - 4;
576 offset.control_flags.Reset();
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
612bool LoadNextSprite(SpriteID load_index, SpriteFile &file, uint file_sprite_id)
613{
614 size_t file_pos = file.GetPos();
615
616 /* Read sprite header. */
617 uint32_t num = file.GetContainerVersion() >= 2 ? file.ReadDword() : file.ReadWord();
618 if (num == 0) return false;
619 uint8_t grf_type = file.ReadByte();
620
621 SpriteType type;
622 SpriteCacheCtrlFlags control_flags;
623 if (grf_type == 0xFF) {
624 /* Some NewGRF files have "empty" pseudo-sprites which are 1
625 * byte long. Catch these so the sprites won't be displayed. */
626 if (num == 1) {
627 file.ReadByte();
628 return false;
629 }
630 file_pos = file.GetPos();
632 file.SkipBytes(num);
633 } else if (file.GetContainerVersion() >= 2 && grf_type == 0xFD) {
634 if (num != 4) {
635 /* Invalid sprite section include, ignore. */
636 file.SkipBytes(num);
637 return false;
638 }
639 /* It is not an error if no sprite with the provided ID is found in the sprite section. */
640 auto iter = _grf_sprite_offsets.find(file.ReadDword());
641 if (iter != _grf_sprite_offsets.end()) {
642 file_pos = iter->second.file_pos;
643 control_flags = iter->second.control_flags;
644 } else {
645 file_pos = SIZE_MAX;
646 }
647 type = SpriteType::Normal;
648 } else {
649 file.SkipBytes(7);
650 type = SkipSpriteData(file, grf_type, num - 8) ? SpriteType::Normal : SpriteType::Invalid;
651 /* Inline sprites are not supported for container version >= 2. */
652 if (file.GetContainerVersion() >= 2) return false;
653 }
654
655 if (type == SpriteType::Invalid) return false;
656
657 if (load_index >= MAX_SPRITES) {
658 UserError("Tried to load too many sprites (#{}; max {})", load_index, MAX_SPRITES);
659 }
660
661 bool is_mapgen = IsMapgenSpriteID(load_index);
662
663 if (is_mapgen) {
664 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?");
665 type = SpriteType::MapGen;
666 }
667
668 SpriteCache *sc = AllocateSpriteCache(load_index);
669 sc->file = &file;
670 sc->file_pos = file_pos;
671 sc->length = num;
672 sc->lru = 0;
673 sc->id = file_sprite_id;
674 sc->type = type;
675 sc->warned = false;
676 sc->control_flags = control_flags;
677
678 return true;
679}
680
681
682void DupSprite(SpriteID old_spr, SpriteID new_spr)
683{
684 SpriteCache *scnew = AllocateSpriteCache(new_spr); // may reallocate: so put it first
685 SpriteCache *scold = GetSpriteCache(old_spr);
686
687 scnew->file = scold->file;
688 scnew->file_pos = scold->file_pos;
689 scnew->ClearSpriteData();
690 scnew->id = scold->id;
691 scnew->type = scold->type;
692 scnew->warned = false;
693 scnew->control_flags = scold->control_flags;
694}
695
702static void DeleteEntriesFromSpriteCache(size_t to_remove)
703{
704 const size_t initial_in_use = _spritecache_bytes_used;
705
706 struct SpriteInfo {
707 uint32_t lru;
708 SpriteID id;
709 size_t size;
710
711 bool operator<(const SpriteInfo &other) const
712 {
713 return this->lru < other.lru;
714 }
715 };
716
717 std::vector<SpriteInfo> candidates; // max heap, ordered by LRU
718 size_t candidate_bytes = 0; // total bytes that would be released when clearing all sprites in candidates
719
720 auto push = [&](SpriteInfo info) {
721 candidates.push_back(info);
722 std::push_heap(candidates.begin(), candidates.end());
723 candidate_bytes += info.size;
724 };
725
726 auto pop = [&]() {
727 candidate_bytes -= candidates.front().size;
728 std::pop_heap(candidates.begin(), candidates.end());
729 candidates.pop_back();
730 };
731
732 SpriteID i = 0;
733 for (; i != static_cast<SpriteID>(_spritecache.size()) && candidate_bytes < to_remove; i++) {
734 const SpriteCache *sc = GetSpriteCache(i);
735 if (sc->ptr != nullptr) {
736 push({ sc->lru, i, sc->length });
737 if (candidate_bytes >= to_remove) break;
738 }
739 }
740 /* candidates now contains enough bytes to meet to_remove.
741 * only sprites with LRU values <= the maximum (i.e. the top of the heap) need to be considered */
742 for (; i != static_cast<SpriteID>(_spritecache.size()); i++) {
743 const SpriteCache *sc = GetSpriteCache(i);
744 if (sc->ptr != nullptr && sc->lru <= candidates.front().lru) {
745 push({ sc->lru, i, sc->length });
746 while (!candidates.empty() && candidate_bytes - candidates.front().size >= to_remove) {
747 pop();
748 }
749 }
750 }
751
752 for (const auto &it : candidates) {
753 GetSpriteCache(it.id)->ClearSpriteData();
754 }
755
756 Debug(sprite, 3, "DeleteEntriesFromSpriteCache, deleted: {}, freed: {}, in use: {} --> {}, requested: {}",
757 candidates.size(), candidate_bytes, initial_in_use, _spritecache_bytes_used, to_remove);
758}
759
760void IncreaseSpriteLRU()
761{
763 uint target_size = (bpp > 0 ? _sprite_cache_size * bpp / 8 : 1) * 1024 * 1024;
764 if (_spritecache_bytes_used > target_size) {
765 DeleteEntriesFromSpriteCache(_spritecache_bytes_used - target_size + 512 * 1024);
766 }
767
768 if (_sprite_lru_counter >= 0xC0000000) {
769 Debug(sprite, 3, "Fixing lru {}, inuse={}", _sprite_lru_counter, _spritecache_bytes_used);
770
771 for (SpriteCache &sc : _spritecache) {
772 if (sc.ptr != nullptr) {
773 if (sc.lru > 0x80000000) {
774 sc.lru -= 0x80000000;
775 } else {
776 sc.lru = 0;
777 }
778 }
779 }
780 _sprite_lru_counter -= 0x80000000;
781 }
782}
783
784void SpriteCache::ClearSpriteData()
785{
786 _spritecache_bytes_used -= this->length;
787 this->ptr.reset();
788}
789
791{
792 this->data = std::make_unique<std::byte[]>(size);
793 this->size = size;
794 return this->data.get();
795}
796
808static void *HandleInvalidSpriteRequest(SpriteID sprite, SpriteType requested, SpriteCache *sc, SpriteAllocator *allocator, SpriteEncoder *encoder)
809{
810 static const std::string_view sprite_types[] = {
811 "normal", // SpriteType::Normal
812 "map generator", // SpriteType::MapGen
813 "character", // SpriteType::Font
814 "recolour", // SpriteType::Recolour
815 };
816
817 SpriteType available = sc->type;
818 if (requested == SpriteType::Font && available == SpriteType::Normal) {
819 if (sc->ptr == nullptr) sc->type = SpriteType::Font;
820 return GetRawSprite(sprite, sc->type, allocator, encoder);
821 }
822
823 uint8_t warning_level = sc->warned ? 6 : 0;
824 sc->warned = true;
825 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)]);
826
827 switch (requested) {
829 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?");
830 [[fallthrough]];
831 case SpriteType::Font:
832 return GetRawSprite(SPR_IMG_QUERY, SpriteType::Normal, allocator, encoder);
834 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?");
835 return GetRawSprite(PALETTE_TO_DARK_BLUE, SpriteType::Recolour, allocator, encoder);
837 /* this shouldn't happen, overriding of SpriteType::MapGen sprites is checked in LoadNextSprite()
838 * (the only case the check fails is when these sprites weren't even loaded...) */
839 default:
840 NOT_REACHED();
841 }
842}
843
853void *GetRawSprite(SpriteID sprite, SpriteType type, SpriteAllocator *allocator, SpriteEncoder *encoder)
854{
855 assert(type != SpriteType::MapGen || IsMapgenSpriteID(sprite));
856 assert(type < SpriteType::Invalid);
857
858 if (!SpriteExists(sprite)) {
859 Debug(sprite, 1, "Tried to load non-existing sprite #{}. Probable cause: Wrong/missing NewGRFs", sprite);
860
861 /* SPR_IMG_QUERY is a BIG FAT RED ? */
862 sprite = SPR_IMG_QUERY;
863 }
864
865 SpriteCache *sc = GetSpriteCache(sprite);
866
867 if (sc->type != type) return HandleInvalidSpriteRequest(sprite, type, sc, allocator, encoder);
868
869 if (allocator == nullptr && encoder == nullptr) {
870 /* Load sprite into/from spritecache */
871
872 /* Update LRU */
873 sc->lru = ++_sprite_lru_counter;
874
875 /* Load the sprite, if it is not loaded, yet */
876 if (sc->ptr == nullptr) {
877 UniquePtrSpriteAllocator cache_allocator;
878 if (sc->type == SpriteType::Recolour) {
879 ReadRecolourSprite(*sc->file, sc->file_pos, sc->length, cache_allocator);
880 } else {
881 ReadSprite(sc, sprite, type, cache_allocator, nullptr);
882 }
883 sc->ptr = std::move(cache_allocator.data);
884 sc->length = static_cast<uint32_t>(cache_allocator.size);
885 _spritecache_bytes_used += sc->length;
886 }
887
888 return static_cast<void *>(sc->ptr.get());
889 } else {
890 /* Do not use the spritecache, but a different allocator. */
891 return ReadSprite(sc, sprite, type, *allocator, encoder);
892 }
893}
894
895void GfxInitSpriteMem()
896{
897 /* Reset the spritecache 'pool' */
898 _spritecache.clear();
899 _spritecache.shrink_to_fit();
900
901 _sprite_files.clear();
902 _spritecache_bytes_used = 0;
903}
904
910{
911 /* Clear sprite ptr for all cached items */
912 for (SpriteCache &sc : _spritecache) {
913 if (sc.ptr != nullptr) sc.ClearSpriteData();
914 }
915
917}
918
924{
925 /* Clear sprite ptr for all cached font items */
926 for (SpriteCache &sc : _spritecache) {
927 if (sc.type == SpriteType::Font && sc.ptr != nullptr) sc.ClearSpriteData();
928 }
929}
930
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 & Reset()
Reset all bits.
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:139
virtual uint8_t GetScreenDepth()=0
Get the screen depth this blitter works for.
Iterate a range of enum values.
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:359
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.
SpriteCollMap< Sprite > SpriteCollection
Type defining a collection of sprites, one for each zoom level.
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:405
@ Recolour
Recolour sprite.
Definition gfx_type.h:409
@ Font
A sprite used for fonts.
Definition gfx_type.h:408
@ MapGen
Special sprite for the map generator.
Definition gfx_type.h:407
@ Invalid
Pseudosprite or other unusable sprite, used only internally.
Definition gfx_type.h:410
@ Normal
The most basic (normal) sprite.
Definition gfx_type.h:406
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.
uint _sprite_cache_size
Default of 4MB 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.
static void * HandleInvalidSpriteRequest(SpriteID sprite, SpriteType requested, SpriteCache *sc, SpriteAllocator *allocator, SpriteEncoder *encoder)
Handles the case when a sprite of different type is requested than is present in the SpriteCache.
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.
EnumBitSet< SpriteCacheCtrlFlag, uint8_t > SpriteCacheCtrlFlags
Bitset of SpriteCacheCtrlFlag elements.
@ Palette
Sprite has palette data.
EnumBitSet< SpriteComponent, uint8_t, SpriteComponent::End > SpriteComponents
Bitset of SpriteComponent elements.
This file contains all sprite-related enums and defines.
static constexpr uint32_t MAX_SPRITES
Masks needed for sprite operations.
Definition sprites.h:1569
Definition of base types and functions in a cross-platform compatible way.
Functions related to OTTD's strings.
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
EnumBitSet< ZoomLevel, uint8_t > ZoomLevels
Bitset of ZoomLevel elements.
Definition zoom_type.h:51
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:20
@ In2x
Zoomed 2 times in.
Definition zoom_type.h:25
@ Max
Maximum zoom level.
Definition zoom_type.h:30
@ Min
Minimum zoom level.
Definition zoom_type.h:23
@ End
End for iteration.
Definition zoom_type.h:31
@ Normal
The normal zoom level.
Definition zoom_type.h:26