Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
mipmap_cache.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011-2012, 2015 Edouard Gomez.
4 Copyright (C) 2011-2012 Henrik Andersson.
5 Copyright (C) 2011-2017 johannes hanika.
6 Copyright (C) 2011-2012 José Carlos García Sogo.
7 Copyright (C) 2012 Christian Tellefsen.
8 Copyright (C) 2012, 2014 Jérémy Rosen.
9 Copyright (C) 2012 Richard Wonka.
10 Copyright (C) 2012-2017, 2019-2020 Tobias Ellinghaus.
11 Copyright (C) 2012, 2014 Ulrich Pegelow.
12 Copyright (C) 2013-2014, 2019-2021 Pascal Obry.
13 Copyright (C) 2013 Simon Spannagel.
14 Copyright (C) 2014, 2016 Dan Torop.
15 Copyright (C) 2014-2015 parafin.
16 Copyright (C) 2014-2016 Pedro Côrte-Real.
17 Copyright (C) 2014-2016 Roman Lebedev.
18 Copyright (C) 2015 Matthias Gehre.
19 Copyright (C) 2016-2017 Peter Budai.
20 Copyright (C) 2017 luzpaz.
21 Copyright (C) 2019-2021 Aldric Renaudin.
22 Copyright (C) 2019, 2022-2023, 2025-2026 Aurélien PIERRE.
23 Copyright (C) 2019-2020 Heiko Bauke.
24 Copyright (C) 2019 jakubfi.
25 Copyright (C) 2019-2020 Philippe Weyland.
26 Copyright (C) 2020-2022 Hanno Schwalm.
27 Copyright (C) 2020-2021 Hubert Kowalski.
28 Copyright (C) 2021 Ralf Brown.
29 Copyright (C) 2022 Martin Bařinka.
30 Copyright (C) 2022 Miloš Komarčević.
31 Copyright (C) 2023 Ricky Moon.
32 Copyright (C) 2024 Alynx Zhou.
33 Copyright (C) 2025-2026 Guillaume Stutin.
34
35 darktable is free software: you can redistribute it and/or modify
36 it under the terms of the GNU General Public License as published by
37 the Free Software Foundation, either version 3 of the License, or
38 (at your option) any later version.
39
40 darktable is distributed in the hope that it will be useful,
41 but WITHOUT ANY WARRANTY; without even the implied warranty of
42 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43 GNU General Public License for more details.
44
45 You should have received a copy of the GNU General Public License
46 along with darktable. If not, see <http://www.gnu.org/licenses/>.
47*/
48
49#include "caches/mipmap_cache.h"
50#include "common/paths.h" // DT_PATH_MAX
54#include "common/grealpath.h"
56#include "caches/image_cache.h"
58#include "develop/supervisor.h"
62#include "control/jobs.h"
64
65#include <assert.h>
66#include <glib.h>
67#include <glib/gstdio.h>
68#include <inttypes.h>
69#include <limits.h>
70#include <stdio.h>
71#include <stdlib.h>
72#include <string.h>
73#include <strings.h>
74#include "database/database.h"
75
76/* Set once by dt_mipmap_cache_init(); see the header. */
77static gboolean _verbose = FALSE;
78
79
80/* Statement-safe: a bare `if(_verbose) dt_print(...)` swallows a following `else`. */
81#define _cache_print(channel, ...) \
82 do { \
83 if(_verbose) dt_print((channel), __VA_ARGS__); \
84 } while(0)
85
86
87/* PRIVATE. Nothing outside this file reads a field of it -- the header exposes the
88 * type as an opaque handle so that it cannot. */
90{
91 // one cache per mipmap scale!
93
94 // a few stats on usage in this run.
95 // long int to give 32-bits on old archs, so __sync* calls will work.
96 long int stats_requests; // number of total requests
97 long int stats_near_match; // served with smaller mip res
98 long int stats_misses; // nothing returned at all.
99 long int stats_fetches; // texture was fetched (either as a stand-in or as per request)
100 long int stats_standin; // texture used as stand-in
102
103
104/* PRIVATE. Nothing outside this file reads a field of it -- the header exposes the
105 * type as an opaque handle so that it cannot. */
106typedef struct dt_mipmap_cache_t
107{
108 // real width and height are stored per element
109 // (could be smaller than the max for this mip level,
110 // due to aspect ratio)
113 // size of an element inside buf
115
116 // one cache per mipmap level
120 char cachedir[DT_PATH_MAX]; // cached sha1sum filename for faster access
122
123
124#if !defined(_WIN32)
125#include <sys/statvfs.h>
126#else
127//statvfs does not exist in Windows, providing implementation
128#include "win/statvfs.h"
129#endif
130
131/* The cache instance, owned HERE. It used to be calloc'd by darktable.c and hung off the
132 * application struct, so the accessor lived in the orchestrator and the struct was reachable
133 * from anything that included darktable.h. */
135
136/* The user's choices, told to us by the application. Guarded because the GUI thread can
137 * replace them while worker threads are mid-decode, and four fields read one at a time can be
138 * a mix of old and new -- the same torn-read hazard the colour module documents. */
140static dt_pthread_mutex_t _settings_lock;
141static gsize _settings_lock_inited = 0;
142
143static inline void _settings_lock_ensure(void)
144{
145 if(g_once_init_enter(&_settings_lock_inited))
146 {
148 g_once_init_leave(&_settings_lock_inited, 1);
149 }
150}
151
160
162{
163 if(!IS_NULL_PTR(settings)) *settings = _settings_get();
164}
165
167{
168 if(IS_NULL_PTR(settings)) return;
171 _settings = *settings;
173
174 /* The LRU quota is a live field and the limit is soft: lowering it makes the next
175 * insertions evict harder rather than freeing anything now. */
178}
179
180
181#define DT_MIPMAP_CACHE_FILE_MAGIC 0xD71337
182#define DT_MIPMAP_CACHE_FILE_VERSION 23
183#define DT_MIPMAP_CACHE_DEFAULT_FILE_NAME "mipmaps"
184
186{
190 DT_MIPMAP_BUFFER_DSC_FLAG_ON_DISK = 1 << 2, // the pixels are those of the disk-cache file, as read
191 /* One generation attempt has already failed for this entry. Read together with GENERATE it gives
192 * the three states a failed thumbnail can be in: GENERATE|RETRIED is "failed once, try again on
193 * the next request", RETRIED alone is "failed twice, this is a skull and we have stopped", and
194 * neither is a thumbnail that generated normally. */
197
198// the embedded Exif data to tag thumbnails as sRGB or AdobeRGB
199static const uint8_t dt_mipmap_cache_exif_data_srgb[] = {
200 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x69,
201 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
202 0x01, 0xa0, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
203};
204static const uint8_t dt_mipmap_cache_exif_data_adobergb[] = {
205 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x69,
206 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
207 0x01, 0xa0, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
208};
213
215{
216 uint32_t width;
217 uint32_t height;
218 float iscale;
219 size_t size;
222 // why width and height are 0, kept so the GUI can say it over the skull. Never written to
223 // the disk cache, which only ever receives real thumbnails.
225
226#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
227 // do not touch!
228 // must be the last element.
229 // must be no less than 16bytes
230 char redzone[16];
231#endif
232
233 /* NB: sizeof must be a multiple of 4*sizeof(float) */
234} __attribute__((packed, aligned(DT_CACHELINE_BYTES)));
235
236#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
237static const size_t dt_mipmap_buffer_dsc_size __attribute__((unused))
238= sizeof(struct dt_mipmap_buffer_dsc) - sizeof(((struct dt_mipmap_buffer_dsc *)0)->redzone);
239#else
240static const size_t dt_mipmap_buffer_dsc_size __attribute__((unused)) = sizeof(struct dt_mipmap_buffer_dsc);
241#endif
242
243static uint8_t * _get_buffer_from_dsc(struct dt_mipmap_buffer_dsc *dsc);
244
245// The 8x8 skull a thumbnail shows when it holds no image, in DT_COLORSPACE_DISPLAY.
246#define X 0xffffffffu
247#define o 0u
248static const uint32_t _skull_8[64] DT_ALIGNED_ARRAY
249 = { o, o, o, o, o, o, o, o,
250 o, o, X, X, X, X, o, o,
251 o, X, o, X, X, o, X, o,
252 o, X, X, X, X, X, X, o,
253 o, o, X, o, o, X, o, o,
254 o, o, o, o, o, o, o, o,
255 o, o, X, X, X, X, o, o,
256 o, o, o, o, o, o, o, o };
257#undef o
258#undef X
259
260static inline void *dead_image_8(struct dt_mipmap_buffer_dsc *dsc)
261{
262 dsc->width = dsc->height = 8;
263 dsc->iscale = 1.0f;
265 assert(dsc->size > sizeof(_skull_8));
266 memcpy(_get_buffer_from_dsc(dsc), _skull_8, sizeof(_skull_8));
267 return _get_buffer_from_dsc(dsc);
268}
269
270static inline int32_t get_key(const int32_t imgid, const dt_mipmap_size_t size)
271{
272 // imgid can't be >= 2^28 (~250 million images)
273 return (((int32_t)size) << 28) | (imgid - 1);
274}
275
276static inline uint32_t get_imgid(const uint32_t key)
277{
278 return (key & 0xfffffff) + 1;
279}
280
281static inline dt_mipmap_size_t get_size(const uint32_t key)
282{
283 return (dt_mipmap_size_t)(key >> 28);
284}
285
287{
289 g_snprintf(path, sizeof(char) * DT_PATH_MAX, "%s.d" G_DIR_SEPARATOR_S "%d",
290 cache->cachedir, (int)mip);
291}
292
293void dt_mipmap_get_cache_filename(char path[DT_PATH_MAX], dt_mipmap_size_t mip, const int32_t imgid)
294{
295 gchar cache_path[DT_PATH_MAX];
296 dt_mipmap_get_cache_dir(cache_path, mip);
297
298 gchar *file = g_strdup_printf("%u.jpg", imgid);
299 dt_concat_path_file(path, cache_path, file);
300 dt_free(file);
301}
302
303static int dt_mipmap_cache_get_filename(gchar *mipmapfilename, size_t size)
304{
305 int r = -1;
306 char *abspath = NULL;
307
308 // Directory
309 char cachedir[DT_PATH_MAX] = { 0 };
310 dt_loc_get_user_cache_dir(cachedir, sizeof(cachedir));
311
312 // Build the mipmap filename fram hashing the path of the library DB
313 const gchar *dbfilename = dt_database_get_path();
314
315 if(!strcmp(dbfilename, ":memory:"))
316 {
317 mipmapfilename[0] = '\0';
318 r = 0;
319 goto exit;
320 }
321
322 abspath = g_realpath(dbfilename);
323 if(IS_NULL_PTR(abspath)) abspath = g_strdup(dbfilename);
324
325 GChecksum *chk = g_checksum_new(G_CHECKSUM_SHA1);
326 g_checksum_update(chk, (guchar *)abspath, strlen(abspath));
327 const gchar *filename = g_checksum_get_string(chk);
328
329 if(!filename || filename[0] == '\0')
330 snprintf(mipmapfilename, size, "%s/%s", cachedir, DT_MIPMAP_CACHE_DEFAULT_FILE_NAME);
331 else
332 snprintf(mipmapfilename, size, "%s/%s-%s", cachedir, DT_MIPMAP_CACHE_DEFAULT_FILE_NAME, filename);
333
334 g_checksum_free(chk);
335 r = 0;
336
337exit:
338 dt_free(abspath);
339
340 return r;
341}
342
356static void _get_thumbnail_input(const int32_t imgid, char *filename, char *ext, gboolean *input_exists,
357 gboolean *is_jpg_input, gboolean *altered, gboolean *may_be_raw)
358{
359 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
361 if(source == DT_IMAGE_PATH_NONE) filename[0] = '\0';
362 *input_exists = (source != DT_IMAGE_PATH_NONE);
363
364 const char *_ext = filename + strlen(filename);
365 while(*_ext != '.' && _ext > filename) _ext--;
366 g_strlcpy(ext, _ext, 6);
367 *is_jpg_input = !strcasecmp(_ext, ".jpg") || !strcasecmp(_ext, ".jpeg");
368
369 *altered = !IS_NULL_PTR(img) && img->history_items > 0;
370
371 // Before the first decode, an image is neither LDR nor HDR when its extension leaves that open,
372 // but the extension itself settles the question asked here: a .tif or a .heif is RGB, whether
373 // LDR or HDR; only a raw extension, like .dng, may be a raw.
374 *may_be_raw = IS_NULL_PTR(img) || dt_image_needs_rawprepare(img)
375 || (!dt_image_is_ldr(img) && !dt_image_is_hdr(img) && dt_image_ext_is_raw(_ext + 1));
376
377 if(img) dt_image_cache_read_release(img);
378}
379
380
381static void _init_f(dt_mipmap_buffer_t *mipmap_buf, float *buf, uint32_t *width, uint32_t *height, float *iscale,
382 const int32_t imgid);
383static dt_imageio_retval_t _init_8(struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid,
384 const dt_mipmap_size_t size, const int embedded_jpg_mode,
385 dt_atomic_int *shutdown);
386
420// Gets the address of the `buf->buf` pixel buffer, from the cache entry.
421static uint8_t *_get_buffer_from_dsc(struct dt_mipmap_buffer_dsc *dsc)
422{
423 if(dsc)
424 return (uint8_t *)(dsc + 1);
425 else
426 return NULL;
427}
428
429
431{
432 return (struct dt_mipmap_buffer_dsc *)entry->data;
433}
434
435// update buffer params with dsc
436static void _sync_dsc_to_buf(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid,
437 const dt_mipmap_size_t mip)
438{
439 buf->width = dsc->width;
440 buf->height = dsc->height;
441 buf->iscale = dsc->iscale;
442 buf->color_space = dsc->color_space;
443 buf->status = dsc->status;
444 buf->imgid = imgid;
445 buf->size = mip;
446 buf->buf = _get_buffer_from_dsc(dsc);
447}
448
449
450// flag a buffer as invalid and set it NULL
451// This doesn't paint skulls
453{
454 buf->width = buf->height = 0;
455 buf->iscale = 0.0f;
456 buf->buf = NULL;
457 buf->status = DT_IMAGEIO_OK;
458}
459
460
461static size_t _get_entry_size(const size_t buffer_size)
462{
463 return buffer_size + dt_mipmap_buffer_dsc_size;
464}
465
474 const size_t width, const size_t height,
475 const size_t buffer_size)
476{
477 if(IS_NULL_PTR(entry->data))
478 {
479 entry->data_size = 0;
480 *dsc = NULL;
481 return;
482 }
483
484 // Update the entry datasize
485 entry->data_size = _get_entry_size(buffer_size);
486
487 // Update the dsc parameters
488 *dsc = _get_dsc_from_entry(entry);
489 (*dsc)->width = width;
490 (*dsc)->height = height;
491 (*dsc)->iscale = 1.0f;
492 (*dsc)->color_space = DT_COLORSPACE_NONE;
493 (*dsc)->status = DT_IMAGEIO_OK;
495 (*dsc)->size = _get_entry_size(buffer_size);
496}
497
498
499// callback for the imageio core to allocate memory.
500// only needed for _FULL buffers, as they change size
501// with the input image. will allocate img->width*img->height*img->bpp bytes.
503{
504 if(IS_NULL_PTR(buf)) return NULL;
505
506 if(buf->size != DT_MIPMAP_FULL)
507 {
508 fprintf(stderr, "trying to alloc a wrong mipmap size for %s: %i (should be: %i)\n", img->filename, buf->size, DT_MIPMAP_FULL);
509 return NULL;
510 }
511
512 dt_cache_entry_t *entry = buf->cache_entry;
513
514 if(IS_NULL_PTR(entry))
515 {
516 fprintf(stderr, "trying to alloc a buffer entry that has no back-reference to cache entry\n");
517 return NULL;
518 }
519
520 /* Free and reset everything. ASan poisoning applies to live cache payloads only: after freeing
521 * the old allocation there is no valid user region left, and poisoning a NULL reset pointer makes
522 * the ASan runtime abort before it can report the real caller context. */
523 if(!IS_NULL_PTR(entry->data))
525 dt_free_align(entry->data);
526 entry->data = NULL;
527
528 // Get a new allocation
529 const int wd = img->width;
530 const int ht = img->height;
531 const size_t bpp = img->dsc.bpp;
532 const size_t buffer_size = wd * ht * bpp;
533 const size_t min_buffer_size = 64 * 4 * sizeof(float);
534 entry->data = dt_alloc_align(_get_entry_size(MAX(buffer_size, min_buffer_size)));
535 if(IS_NULL_PTR(entry->data))
536 {
537 /* The old payload is already gone, and every reader of a cache entry takes entry->data for a
538 * valid descriptor: _generate_blocking() writes dsc->status as soon as the loader returns
539 * DT_IMAGEIO_CACHE_FULL, so leaving NULL here crashes there. Fall back to the empty 0x0
540 * payload a full-size entry is created with (dt_mipmap_cache_allocate_dynamic()), so the
541 * load fails instead. */
542 entry->data = dt_alloc_align(_get_entry_size(min_buffer_size));
543 struct dt_mipmap_buffer_dsc *empty = NULL;
544 dt_mipmap_cache_update_buffer_addresses(entry, &empty, 0, 0, min_buffer_size);
545 return NULL;
546 }
547
548 // Update the references
549 struct dt_mipmap_buffer_dsc *dsc = NULL;
550 dt_mipmap_cache_update_buffer_addresses(entry, &dsc, wd, ht, buffer_size);
551
552 // Unpoison the pixel buffer region
553 ASAN_UNPOISON_MEMORY_REGION(buf->buf, dsc->size - dt_mipmap_buffer_dsc_size);
554
555 // Unpoison the dsc region
556 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
557
558 // So the padding region between dsc and buf->buf should be poisoned still
559 assert(entry->data == dsc);
560
561 if(dsc)
562 {
563 assert(entry->data_size == dsc->size);
564 }
565
566 // return pointer to start of payload
567 return _get_buffer_from_dsc(dsc);
568}
569
570// callback for the cache backend to initialize payload pointers
571// It's actually not dynamic at all, fixed size only.
573{
574 dt_mipmap_cache_t *cache = (dt_mipmap_cache_t *)data;
575 const dt_mipmap_size_t mip = get_size(entry->key);
576 const int32_t imgid = get_imgid(entry->key);
577
578 assert(mip < DT_MIPMAP_NONE);
579
580 // Free and reset everything
581 dt_free_align(entry->data);
582 entry->data = NULL;
583
584 // Get a new allocation
585 const size_t buffer_size = (mip <= DT_MIPMAP_F) ? cache->buffer_size[mip] : _get_entry_size(sizeof(float) * 4 * 64);
586 entry->data = dt_alloc_align(buffer_size);
587 if(IS_NULL_PTR(entry->data)) return;
588
589 // Update the references
590 struct dt_mipmap_buffer_dsc *dsc = NULL;
591 if(mip <= DT_MIPMAP_F)
592 dt_mipmap_cache_update_buffer_addresses(entry, &dsc, cache->max_width[mip], cache->max_height[mip], buffer_size);
593 else
594 dt_mipmap_cache_update_buffer_addresses(entry, &dsc, 0, 0, buffer_size);
595
596 if(IS_NULL_PTR(dsc)) return;
597
598 // Register the mipmap object, and the image cache object it links to. The image
599 // is usually already cached (so its allocate callback never fired), hence we
600 // register it here from the live dt_image_t; dt_supervisor_image() promotes the
601 // first sighting to a `create` so the link resolves.
603 {
604 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
605 if(img) dt_supervisor_image(DT_SV_UPDATE, imgid, img);
607 if(img) dt_image_cache_read_release(img);
608 }
609
610 if(cache->cachedir[0] && _settings_get().disk_backend && mip < DT_MIPMAP_F)
611 {
612 // try and load from disk, if successful set flag
613 char filename[DT_PATH_MAX] = {0};
614 dt_mipmap_get_cache_filename(filename, mip, get_imgid(entry->key));
615
616 gboolean io_error = FALSE;
617 gchar *error = NULL;
618 uint8_t *blob = NULL;
621 FILE *f = NULL;
622
623 f = g_fopen(filename, "rb");
624 if(IS_NULL_PTR(f))
625 {
627 "[mipmap_cache] cached file for image %d at mip size %i does not exist\n",
628 imgid, mip);
629 goto finish; // file doesn't exist
630 }
631
632 fseek(f, 0, SEEK_END);
633 long len = ftell(f);
634 if(len <= 0)
635 {
636 error = "empty file";
637 io_error = TRUE;
638 goto finish;
639 }
640
641 blob = (uint8_t *)dt_alloc_align(len);
642 if(IS_NULL_PTR(blob))
643 {
644 error = "out of memory";
645 io_error = TRUE;
646 goto finish;
647 }
648
649 fseek(f, 0, SEEK_SET);
650 const size_t rd = fread(blob, sizeof(uint8_t), len, f);
651 if(rd != len)
652 {
653 error = "corrupted file";
654 io_error = TRUE;
655 goto finish;
656 }
657
658 if(dt_imageio_jpeg_decompress_header(blob, len, &jpg))
659 {
660 error = "couldn't decompress header";
661 io_error = TRUE;
662 goto finish;
663 }
664
666
668 {
669 error = "couldn't decompress JPEG";
670 io_error = TRUE;
671 goto finish;
672 }
673
674 dsc->width = jpg.width;
675 dsc->height = jpg.height;
676 dsc->iscale = 1.0f;
679
680 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d at mip size %d (%ix%i) loaded from disk cache\n",
681 imgid, mip, jpg.width, jpg.height);
682
683finish:
684 dt_free_align(blob);
685
686 // Closed before the unlink: Windows cannot delete a file the CRT still holds open.
687 if(f)
688 {
689 fclose(f);
690 f = NULL;
691 }
692
693 if(io_error)
694 {
695 // Delete the file, we will regenerate it
696 g_unlink(filename);
697 fprintf(stderr, "[mipmap_cache] failed to open thumbnail for image %" PRIu32 " from `%s'. Reason: %s\n",
698 imgid, filename, error);
699 }
700 }
701
702 // cost is just flat one for the buffer, as the buffers might have different sizes,
703 // to make sure quota is meaningful.
704 if(mip >= DT_MIPMAP_F)
705 entry->cost = 1;
706 else
707 entry->cost = cache->buffer_size[mip];
708}
709
710static void dt_mipmap_cache_unlink_ondisk_thumbnail(void *data, int32_t imgid, dt_mipmap_size_t mip)
711{
712 dt_mipmap_cache_t *cache = (dt_mipmap_cache_t *)data;
713
714 // also remove jpg backing (always try to do that, in case user just temporarily switched it off,
715 // to avoid inconsistencies.
716 // if(_settings_get().disk_backend)
717 if(cache->cachedir[0])
718 {
719 char filename[DT_PATH_MAX] = { 0 };
720 dt_mipmap_get_cache_filename(filename, mip, imgid);
721 g_unlink(filename);
722 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %i for size %i was deleted from disk cache\n", imgid, mip);
723 }
724}
725
730static gboolean _disk_has_room_for_a_thumbnail(const char *dirname)
731{
732 struct statvfs vfsbuf;
733 if(statvfs(dirname, &vfsbuf)) return TRUE;
734
735 const int64_t free_mb = (((int64_t)vfsbuf.f_frsize * (int64_t)vfsbuf.f_bavail) >> 20);
736 if(free_mb < 100)
737 {
738 fprintf(stderr, "[mipmap_cache] not writing to the disk cache: only %" PRId64 " MB free on `%s'\n",
739 free_mb, dirname);
740 return FALSE;
741 }
742 return TRUE;
743}
744
746static void _write_thumbnail_to_disk_cache(struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid,
747 const dt_mipmap_size_t mip)
748{
749 gchar cache_path[DT_PATH_MAX] = { 0 };
750 dt_mipmap_get_cache_dir(cache_path, mip);
751 if(g_mkdir_with_parents(cache_path, 0750)) return;
752
753 if(!_disk_has_room_for_a_thumbnail(cache_path)) return;
754
755 char filename[DT_PATH_MAX] = { 0 };
756 dt_mipmap_get_cache_filename(filename, mip, imgid);
757
758 const uint8_t *exif = NULL;
759 int exif_len = 0;
761 {
764 }
765 else if(dsc->color_space == DT_COLORSPACE_ADOBERGB)
766 {
769 }
770
771 // dt_imageio_jpeg_write() opens and closes the file itself: no other handle on it, or Windows
772 // cannot unlink it below.
773 const int cache_quality = _settings_get().cache_quality;
774 if(dt_imageio_jpeg_write(filename, _get_buffer_from_dsc(dsc), dsc->width, dsc->height,
775 MIN(100, MAX(10, cache_quality)), exif, exif_len))
776 {
777 g_unlink(filename);
778 fprintf(stderr, "[mipmap_cache] failed to write image %" PRId32 " at size %d to the disk cache at `%s'\n",
779 imgid, mip, filename);
780 return;
781 }
782
783 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %" PRId32 " for size %d was written to cache at %s\n", imgid,
784 mip, filename);
785}
786
788{
789 dt_mipmap_cache_t *cache = (dt_mipmap_cache_t *)data;
790 const dt_mipmap_size_t mip = get_size(entry->key);
791
794
795 if(mip < DT_MIPMAP_F)
796 {
797 const int32_t imgid = get_imgid(entry->key);
798 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
799
801 {
802 // The pixels no longer describe the image: the file goes, and nothing may replace it.
804 }
805 // Not an entry read from its file and unchanged since: writing it would only re-encode it, at a loss.
808 "[mipmap_cache] image %" PRId32 " at mip size %d is not written to the disk cache: read from it\n",
809 imgid, mip);
810 // Neither a render that never ran (GENERATE, over an uninitialised buffer) nor a skull (8x8).
811 else if(!(dsc->flags & DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE) && dsc->width > 8 && dsc->height > 8)
812 {
813 if(cache->cachedir[0] && _settings_get().disk_backend) _write_thumbnail_to_disk_cache(dsc, imgid, mip);
814 }
815 else
816 {
817 // Logged, so that a real session says how often an aborted render leaves an entry behind.
819 "[mipmap_cache] image %" PRId32 " at mip size %d is not written to the disk cache: %s\n",
820 imgid, mip,
821 (dsc->flags & DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE) ? "never generated" : "a skull");
822 }
823 }
824
825 dt_free_align(entry->data);
826 entry->data = NULL;
827}
828
829void dt_mipmap_cache_init(const dt_mipmap_cache_settings_t *settings, const gboolean verbose)
830{
831 _verbose = verbose;
832 if(!IS_NULL_PTR(settings)) dt_mipmap_cache_set_settings(settings);
833 if(_mipmap_cache) return;
834 _mipmap_cache = (dt_mipmap_cache_t *)calloc(1, sizeof(dt_mipmap_cache_t));
835 if(IS_NULL_PTR(_mipmap_cache)) return;
837 dt_mipmap_cache_get_filename(cache->cachedir, sizeof(cache->cachedir));
838
839 // Fixed sizes for the thumbnail mip levels, selected for coverage of most screen sizes
840 // Starting at 4K, we use 3:2 ratio of camera sensor instead of 16:9/16:10 of display,
841 // in order to start caching full-resolution JPEG for 100% zoom in lighttable
842 size_t mipsizes[DT_MIPMAP_F][2] = {
843 { 360, 225 }, // mip0 - 1/2 size previous one
844 { 720, 450 }, // mip1 - 1/2 size previous one
845 { 1440, 900 }, // mip2 - covers HD, WXGA+
846 { 1920, 1200 }, // mip3 - covers 1080p and 1600x1200
847 { 2560, 1600 }, // mip4 - covers 2560x1440
848 { 3840, 2560 }, // mip5 - covers 4K and UHD
849 { 5120, 3414 }, // mip6 - covers 5K
850 { 6144, 4096 }, // mip7 - covers 6K
851 { 7680, 5120 }, // mip8 - covers 8K
852 };
853 // Set mipf for preview pipe to 1440x900
854 cache->max_width[DT_MIPMAP_F] = mipsizes[DT_MIPMAP_2][0];
855 cache->max_height[DT_MIPMAP_F] = mipsizes[DT_MIPMAP_2][1];
856 for(int k = DT_MIPMAP_F-1; k >= 0; k--)
857 {
858 cache->max_width[k] = mipsizes[k][0];
859 cache->max_height[k] = mipsizes[k][1];
860 }
861 // header + buffer
862 for(int k = DT_MIPMAP_F-1; k >= 0; k--)
863 cache->buffer_size[k] = _get_entry_size(cache->max_width[k] * cache->max_height[k] * 4);
864
865 // clear stats:
866 cache->mip_thumbs.stats_requests = 0;
867 cache->mip_thumbs.stats_near_match = 0;
868 cache->mip_thumbs.stats_misses = 0;
869 cache->mip_thumbs.stats_fetches = 0;
870 cache->mip_thumbs.stats_standin = 0;
871 cache->mip_f.stats_requests = 0;
872 cache->mip_f.stats_near_match = 0;
873 cache->mip_f.stats_misses = 0;
874 cache->mip_f.stats_fetches = 0;
875 cache->mip_f.stats_standin = 0;
876 cache->mip_full.stats_requests = 0;
877 cache->mip_full.stats_near_match = 0;
878 cache->mip_full.stats_misses = 0;
879 cache->mip_full.stats_fetches = 0;
880 cache->mip_full.stats_standin = 0;
881
882 dt_cache_init(&cache->mip_thumbs.cache, 0, _settings_get().max_memory);
885
889 cache->buffer_size[DT_MIPMAP_FULL] = 0;
890
895 = _get_entry_size(4 * sizeof(float) * cache->max_width[DT_MIPMAP_F] * cache->max_height[DT_MIPMAP_F]);
896}
897
899{
901 if(IS_NULL_PTR(cache)) return;
902
903 /* A fill ratio of 0 evicts every entry no other thread holds. The eviction walk is reused because
904 * it already skips held entries and runs the cleanup callback, which writes the thumbnail. Only
905 * mip_thumbs: DT_MIPMAP_F and DT_MIPMAP_FULL are never written to disk. */
906 const size_t reclaimed = dt_cache_gc(&cache->mip_thumbs.cache, 0.0f);
907
908 // What left RAM, not what was written: an entry read from its file is dropped unwritten. The
909 // callback logs each write on its own.
910 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] flushed to disk: %.2f MB evicted from RAM\n",
911 reclaimed / (1024.0 * 1024.0));
912}
913
915{
917 if(IS_NULL_PTR(cache)) return;
922
924 _mipmap_cache = NULL;
925}
926
928{
930 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] thumbs fill %.2f/%.2f MB (%.2f%%)\n",
931 cache->mip_thumbs.cache.cost / (1024.0 * 1024.0),
932 cache->mip_thumbs.cache.cost_quota / (1024.0 * 1024.0),
933 100.0f * (float)cache->mip_thumbs.cache.cost / (float)cache->mip_thumbs.cache.cost_quota);
934 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] float fill %"PRIu32"/%"PRIu32" slots (%.2f%%)\n",
935 (uint32_t)cache->mip_f.cache.cost, (uint32_t)cache->mip_f.cache.cost_quota,
936 100.0f * (float)cache->mip_f.cache.cost / (float)cache->mip_f.cache.cost_quota);
937 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] full fill %"PRIu32"/%"PRIu32" slots (%.2f%%)\n",
938 (uint32_t)cache->mip_full.cache.cost, (uint32_t)cache->mip_full.cache.cost_quota,
939 100.0f * (float)cache->mip_full.cache.cost / (float)cache->mip_full.cache.cost_quota);
940
941 uint64_t sum = 0;
942 uint64_t sum_fetches = 0;
943 uint64_t sum_standins = 0;
944 sum += cache->mip_thumbs.stats_requests;
945 sum_fetches += cache->mip_thumbs.stats_fetches;
946 sum_standins += cache->mip_thumbs.stats_standin;
947 sum += cache->mip_f.stats_requests;
948 sum_fetches += cache->mip_f.stats_fetches;
949 sum_standins += cache->mip_f.stats_standin;
950 sum += cache->mip_full.stats_requests;
951 sum_fetches += cache->mip_full.stats_fetches;
952 sum_standins += cache->mip_full.stats_standin;
953 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] level | near match | miss | stand-in | fetches | total rq\n");
954 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] thumb | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%%\n",
955 100.0 * cache->mip_thumbs.stats_near_match / (float)cache->mip_thumbs.stats_requests,
956 100.0 * cache->mip_thumbs.stats_misses / (float)cache->mip_thumbs.stats_requests,
957 100.0 * cache->mip_thumbs.stats_standin / (float)sum_standins,
958 100.0 * cache->mip_thumbs.stats_fetches / (float)sum_fetches,
959 100.0 * cache->mip_thumbs.stats_requests / (float)sum);
960 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] float | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%%\n",
961 100.0 * cache->mip_f.stats_near_match / (float)cache->mip_f.stats_requests,
962 100.0 * cache->mip_f.stats_misses / (float)cache->mip_f.stats_requests,
963 100.0 * cache->mip_f.stats_standin / (float)sum_standins,
964 100.0 * cache->mip_f.stats_fetches / (float)sum_fetches,
965 100.0 * cache->mip_f.stats_requests / (float)sum);
966 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] full | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%%\n",
967 100.0 * cache->mip_full.stats_near_match / (float)cache->mip_full.stats_requests,
968 100.0 * cache->mip_full.stats_misses / (float)cache->mip_full.stats_requests,
969 100.0 * cache->mip_full.stats_standin / (float)sum_standins,
970 100.0 * cache->mip_full.stats_fetches / (float)sum_fetches,
971 100.0 * cache->mip_full.stats_requests / (float)sum);
973}
974
975// Collect one sub-cache's live entries (imgid + mip + real bytes) under its lock.
977{
978 dt_pthread_mutex_lock(&c->lock);
979 GHashTableIter it;
980 gpointer key, value;
981 g_hash_table_iter_init(&it, c->hashtable);
982 while(g_hash_table_iter_next(&it, &key, &value))
983 {
984 const dt_cache_entry_t *e = (const dt_cache_entry_t *)value;
985 if(!e) continue;
987 s.imgid = get_imgid(e->key);
988 s.mip = (int)get_size(e->key);
989 s.size = e->data_size; // real allocation (cost is byte-based for thumbs, slot-based for f/full)
990 g_array_append_val(out, s);
991 }
992 dt_pthread_mutex_unlock(&c->lock);
993}
994
995void dt_mipmap_cache_get_usage(size_t *current, size_t *max)
996{
998 if(current) *current = 0;
999 if(max) *max = 0;
1000 if(IS_NULL_PTR(cache)) return;
1001
1002 // Real bytes currently held = sum of entry allocations across sub-caches.
1004 size_t used = 0;
1005 for(guint i = 0; i < entries->len; i++)
1006 used += g_array_index(entries, dt_mipmap_cache_stats_entry_t, i).size;
1007 g_array_free(entries, TRUE);
1008
1009 if(current) *current = used;
1010 if(max) *max = _settings_get().max_memory; // user-specified mipmap RAM budget
1011}
1012
1014{
1016 GArray *out = g_array_new(FALSE, FALSE, sizeof(dt_mipmap_cache_stats_entry_t));
1017 if(IS_NULL_PTR(cache)) return out;
1021 return out;
1022}
1023
1025{
1026 switch(mip)
1027 {
1028 case DT_MIPMAP_FULL:
1029 return &cache->mip_full;
1030 case DT_MIPMAP_F:
1031 return &cache->mip_f;
1032 default:
1033 return &cache->mip_thumbs;
1034 }
1035}
1036
1037// if we get a zero-sized image, paint skulls to signal a missing image
1038static void _paint_skulls(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid, const dt_mipmap_size_t mip)
1039{
1040 if(dsc->width == 0 || dsc->height == 0)
1041 {
1042 _cache_print(DT_DEBUG_CACHE, "[mipmap cache get] got a zero-sized image for img %u mip %d!\n", imgid, mip);
1043 if(mip < DT_MIPMAP_F)
1044 buf->buf = dead_image_8(dsc);
1045 else
1046 buf->buf = NULL; // full images with NULL buffer have to be handled, indicates `missing image', but still return locked slot
1047 }
1048}
1049
1050static void _validate_buffer(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid,
1051 const dt_mipmap_size_t mip)
1052{
1053 // Unpoison the dsc region
1054 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1055
1056 // May update buf->buf:
1057 _sync_dsc_to_buf(buf, dsc, imgid, mip);
1058
1059 // Unpoison the pixel buffer region
1060 ASAN_UNPOISON_MEMORY_REGION(buf->buf, dsc->size - dt_mipmap_buffer_dsc_size);
1061}
1062
1063// Grab our own local copy of the image structure
1064static gboolean _get_image_copy(const int32_t imgid, dt_image_t *buffered_image)
1065{
1066 // load the image:
1067 // make sure we access the r/w lock as shortly as possible!
1068 gboolean no_buffer = TRUE;
1069 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
1070
1071 if(cimg)
1072 {
1073 *buffered_image = *cimg;
1074 no_buffer = FALSE;
1075 }
1076
1078
1079 return no_buffer;
1080}
1081
1082// Actually do the work: produce an image
1084 const int32_t imgid, const dt_mipmap_size_t mip, dt_atomic_int *shutdown)
1085{
1086 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1087 // Unpoison the descriptor before reading any field in this function.
1088 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1089 // _generate_blocking() can write directly into the cache pixel payload
1090 // (notably through _init_8() -> _write_image()), so unpoison it up-front.
1091 ASAN_UNPOISON_MEMORY_REGION(_get_buffer_from_dsc(dsc), dsc->size - dt_mipmap_buffer_dsc_size);
1092 const uint32_t original_width = dsc->width;
1093 const uint32_t original_height = dsc->height;
1094 const float original_iscale = dsc->iscale;
1095 const dt_colorspaces_color_profile_type_t original_color_space = dsc->color_space;
1096 const dt_imageio_retval_t original_status = dsc->status;
1097 gboolean mode_changed = FALSE;
1098
1099 /* Retrying after a failure. The failed attempt left the skull's 8x8 in the descriptor, and
1100 * _init_8() opens with `if(size >= DT_MIPMAP_F || dsc->width < 16 || dsc->height < 16) return
1101 * DT_IMAGEIO_OK;` -- so re-entering with those dimensions does nothing at all, silently, and the
1102 * skull would be kept as a thumbnail that generated. Put back what the allocator asked for
1103 * (dt_mipmap_cache_update_buffer_addresses() sets exactly these). */
1106 && mip <= DT_MIPMAP_F
1108 {
1109 dsc->width = _mipmap_cache->max_width[mip];
1110 dsc->height = _mipmap_cache->max_height[mip];
1111 dsc->iscale = 1.0f;
1113 }
1114
1116 {
1117 // Already in cache, no I/O needed
1118 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d at mip size %i (%ix%i) will skip disk I/O, found in RAM cache.\n", imgid, mip,
1119 dsc->width, dsc->height);
1120 _validate_buffer(buf, dsc, imgid, mip);
1121 return;
1122 }
1123
1124 if(mip == DT_MIPMAP_FULL)
1125 {
1126 // Get the input file path, possibly from our local cache of copied images
1127 dt_image_t buffered_image;
1128 char filename[DT_PATH_MAX] = { 0 };
1129 /* Nothing to read: tell the caller why, but leave the entry flagged for generation, since a
1130 * file missing now (unplugged drive) may be back by the next request. */
1131 if(_get_image_copy(imgid, &buffered_image)
1132 || dt_image_choose_input_path(&buffered_image, filename, sizeof(filename), FALSE) == DT_IMAGE_PATH_NONE)
1133 {
1134 _invalidate_buffer(buf);
1136 return;
1137 }
1138
1140 "[mipmap_cache] fetch image %i at mip size %d float32 (%ix%i) from original file I/O\n",
1141 imgid, mip, dsc->width, dsc->height);
1142
1143 /* dt_mipmap_cache_alloc() resets the flags to GENERATE alone, which would forget that this is
1144 * already the retry: a file that never loads would then be read again on every request. */
1146
1147 // will call dt_mipmap_cache_alloc() internally:
1148 dt_imageio_retval_t ret = dt_imageio_open(&buffered_image, filename, buf);
1149
1150 // We need to update dsc because dt_imageio_open re-alloc entry->data
1151 dsc = _get_dsc_from_entry(entry);
1152 dsc->status = ret;
1153 dsc->flags |= retried;
1154
1155 if(ret == DT_IMAGEIO_OK)
1156 {
1157 // swap back new image data, may contain updated EXIF & colorspace
1158 dt_image_t *img = dt_image_cache_get(imgid, 'w');
1159 // dt_image_cache_get() returns NULL if the image vanished from the DB/cache while this
1160 // full-res I/O was in flight (e.g. removed from the library concurrently) -- nothing left
1161 // to swap the freshly-read data back into.
1162 if(!IS_NULL_PTR(img))
1163 {
1164 *img = buffered_image;
1166 }
1167 }
1168 else
1169 {
1170 dsc->width = dsc->height = 0;
1171 dsc->iscale = 0.f;
1172 }
1173 }
1174 else if(mip == DT_MIPMAP_F)
1175 {
1177 "[mipmap_cache] compute mip size %d float32 for image %i (%ix%i) from original file \n", mip,
1178 imgid, dsc->width, dsc->height);
1179 _init_f(buf, (float *)_get_buffer_from_dsc(dsc), &dsc->width, &dsc->height, &dsc->iscale, imgid);
1180 }
1181 else
1182 {
1183 // 8-bit thumbs
1185 "[mipmap_cache] compute mip size %d uint8 for image %i (%ix%i) from original file \n", mip,
1186 imgid, dsc->width, dsc->height);
1187 // The lighttable menu can change the embedded JPEG mode during the render, and its purge skips
1188 // this entry, write-locked until we return: render in one mode, and check it is still current.
1189 const int embedded_jpg_mode = _settings_get().embedded_jpg;
1190 dsc->status = _init_8(dsc, imgid, mip, embedded_jpg_mode, shutdown);
1191 const int current_mode = _settings_get().embedded_jpg;
1192 mode_changed = embedded_jpg_mode != current_mode;
1193 if(mode_changed)
1195 "[mipmap_cache] image %d at mip size %d rendered in embedded JPEG mode %d, now %d: not kept\n",
1196 imgid, mip, embedded_jpg_mode, current_mode);
1197 }
1198
1199 if((shutdown && dt_atomic_get_int(shutdown)) || mode_changed)
1200 {
1201 /* A stale GUI request stopped its thumbnail/export pipe while this cache entry
1202 * was still being produced, or the thumbnail was rendered in an embedded JPEG
1203 * mode that is no longer the current one. Keep the entry marked as "generate"
1204 * so the next request renders it again, instead of reusing a poisoned empty
1205 * thumbnail, or one in the old mode, for the rest of the session. */
1206 dsc->width = original_width;
1207 dsc->height = original_height;
1208 dsc->iscale = original_iscale;
1209 dsc->color_space = original_color_space;
1210 dsc->status = original_status;
1211 _invalidate_buffer(buf);
1212 return;
1213 }
1214
1215 /* _init_8() and _init_f() report a failure as a zero-sized image. Clearing GENERATE on it would
1216 * latch the failure: every later request takes the "already in cache" path and gets the skull
1217 * back, and dt_view_image_get_surface() reports an 8x8 buffer as DT_VIEW_SURFACE_OK, so the
1218 * widget commits it and never asks again. One transient fault -- a momentary database lock, a
1219 * network share reconnecting -- would cost an image its thumbnail for the whole session,
1220 * clearable only through "Purge selected thumbnails from cache".
1221 *
1222 * One retry is granted instead. Bounded on purpose: an image that is genuinely unreadable must not
1223 * re-run a full pipeline on every redraw, so it costs at most two attempts per cache entry. The
1224 * skull is still published for this request either way -- the caller needs something to draw. */
1225 const gboolean failed = (dsc->width == 0 || dsc->height == 0);
1226
1227 if(failed && !(dsc->flags & DT_MIPMAP_BUFFER_DSC_FLAG_RETRIED))
1228 {
1229 dsc->flags |= DT_MIPMAP_BUFFER_DSC_FLAG_RETRIED; // GENERATE deliberately left set
1230 _paint_skulls(buf, dsc, imgid, mip);
1231 _validate_buffer(buf, dsc, imgid, mip);
1233 "[mipmap_cache] image %d at mip size %d failed to generate; the next request will try"
1234 " again\n",
1235 imgid, mip);
1236 return;
1237 }
1238
1239 dsc->flags &= ~DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE;
1240 if(!failed) dsc->flags &= ~DT_MIPMAP_BUFFER_DSC_FLAG_RETRIED;
1241 _paint_skulls(buf, dsc, imgid, mip);
1242 _validate_buffer(buf, dsc, imgid, mip);
1243
1244 if(failed)
1246 "[mipmap_cache] image %d at mip size %d failed to generate twice; keeping the skull\n",
1247 imgid, mip);
1248
1249 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d at mip size %d got a new cache entry (%ix%i / %ix%i) at %p\n", imgid, mip,
1250 buf->width, buf->height, dsc->width, dsc->height, buf->buf);
1251}
1252
1253
1256 const char mode, const char *file, int line)
1257{
1258 dt_mipmap_cache_get_with_caller_and_shutdown(buf, imgid, mip, flags, mode, NULL, file, line);
1259}
1260
1262 const int32_t imgid, const dt_mipmap_size_t mip,
1263 const dt_mipmap_get_flags_t flags, const char mode,
1264 dt_atomic_int *shutdown, const char *file, int line)
1265{
1267 assert(mip <= DT_MIPMAP_NONE && mip >= DT_MIPMAP_0);
1268
1269 const int32_t key = get_key(imgid, mip);
1270
1271 // Allocation functions will check those if we run them
1272 buf->imgid = imgid;
1273 buf->size = mip;
1274
1276 {
1277 // simple case: only get and lock if it's there.
1278 dt_cache_entry_t *entry = dt_cache_testget(&_get_cache(cache, mip)->cache, key, mode);
1279 if(entry)
1280 {
1281 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1282 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1283 /* Not there yet: a render aborted mid-way leaves the entry flagged, over a buffer holding nothing.
1284 * Nor is a render that failed: its buffer is the 8x8 skull, and the _init_8() loop would
1285 * downsample it into a smaller size as if it were that image's thumbnail. */
1287 {
1288 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1289 entry = NULL;
1290 }
1291 }
1292 buf->cache_entry = entry;
1293
1294 if(entry)
1295 _validate_buffer(buf, _get_dsc_from_entry(entry), imgid, mip);
1296 else
1297 _invalidate_buffer(buf);
1298 }
1299 else if(flags == DT_MIPMAP_BLOCKING)
1300 {
1301 // simple case: blocking get
1302 dt_cache_entry_t *entry = dt_cache_get_with_caller(&_get_cache(cache, mip)->cache, key, mode, file, line);
1303
1304 /* The payload could not be allocated: CACHE_FULL for the callers that pass the status on (the
1305 * export), to whom an OK status with no buffer reads as a success. A thumbnail still shows a
1306 * skull, the shared one since no entry holds a copy: read-only, and no entry to release. */
1307 if(IS_NULL_PTR(entry))
1308 {
1309 buf->cache_entry = NULL;
1310 _invalidate_buffer(buf);
1312 if(mip < DT_MIPMAP_F)
1313 {
1314 buf->width = 8;
1315 buf->height = 8;
1316 buf->iscale = 1.0f;
1318 buf->buf = (uint8_t *)_skull_8;
1319 }
1320 fprintf(stderr, "[mipmap_cache] could not allocate a cache entry for image %" PRId32 " at mip size %d\n",
1321 imgid, mip);
1322 return;
1323 }
1324
1325 /* An entry already in the cache comes back read-locked on a 'r' get. One still flagged for
1326 * generation -- a failed render, retried -- would then be rendered into under that read lock,
1327 * by several readers at once. Take it again for writing; _generate_blocking() re-reads the flags,
1328 * so a writer that got there first leaves nothing to do. */
1329 if(mode == 'r')
1330 {
1331 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1332 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1334 {
1335 entry->_lock_demoting = 1;
1336 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1337 entry = dt_cache_get_with_caller(&_get_cache(cache, mip)->cache, key, 'w', file, line);
1338 entry->_lock_demoting = 0;
1339 }
1340 }
1341
1342 buf->cache_entry = entry;
1343 __sync_fetch_and_add(&(_get_cache(cache, mip)->stats_fetches), 1);
1344 _generate_blocking(entry, buf, imgid, mip, shutdown);
1345
1346 // image cache is leaving the write lock in place in case the image has been newly allocated.
1347 // this leads to a slight increase in thread contention, so we opt for dropping the write lock
1348 // and acquiring a read lock immediately after. since this opens a small window for other threads
1349 // to get in between, we need to take some care to re-init cache entries and dsc.
1350 // note that concurrencykit has rw locks that can be demoted from w->r without losing the lock in between.
1351 if(mode == 'r')
1352 {
1353 entry->_lock_demoting = 1;
1354 // drop the write lock
1355 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1356 // get a read lock
1357 entry = dt_cache_get(&_get_cache(cache, mip)->cache, key, mode);
1358 entry->_lock_demoting = 0;
1359 }
1360
1361 buf->cache_entry = entry;
1362
1363 }
1364 else
1365 {
1366 _invalidate_buffer(buf);
1367 }
1368}
1369
1370void dt_mipmap_cache_write_get_with_caller(dt_mipmap_buffer_t *buf, const int32_t imgid, const int mip, const char *file, int line)
1371{
1372 dt_mipmap_cache_get_with_caller(buf, imgid, mip, DT_MIPMAP_BLOCKING, 'w', file, line);
1373}
1374
1376 int line)
1377{
1379 if(buf->size == DT_MIPMAP_NONE || IS_NULL_PTR(buf->cache_entry)) return;
1380 assert(buf->imgid > 0);
1381 assert(buf->size >= DT_MIPMAP_0);
1382 assert(buf->size < DT_MIPMAP_NONE);
1383 dt_cache_release_with_caller(&_get_cache(cache, buf->size)->cache, buf->cache_entry, file, line);
1384 buf->size = DT_MIPMAP_NONE;
1385 buf->buf = NULL;
1386}
1387
1388
1389// return index dt_mipmap_size_t having at least width & height requested instead of minimum combined diff
1390// please note that the requested size is in pixels not dots.
1392 const int32_t height, const uint32_t imgid)
1393{
1395 for(int k = DT_MIPMAP_0; k < DT_MIPMAP_F; k++)
1396 {
1397 // We assume a "fit" situation, typically rectangle within square
1398 // so we don't need both dimensions to be greater than requested
1399 if((cache->max_width[k] >= width) || (cache->max_height[k] >= height))
1400 {
1401 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d will load a mip size %i (%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT ")\n", imgid, k, cache->max_width[k], cache->max_height[k]);
1402 return k;
1403 }
1404 }
1405 return DT_MIPMAP_F - 1;
1406}
1407
1409 const int32_t height, const uint32_t imgid)
1410{
1412 for(int k = DT_MIPMAP_F - 1; k >= DT_MIPMAP_0; k--)
1413 {
1414 if((cache->max_width[k] <= width) && (cache->max_height[k] <= height))
1415 {
1416 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d will fit a mip size %i (%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT ")\n", imgid, k, cache->max_width[k], cache->max_height[k]);
1417 return k;
1418 }
1419 }
1420 return DT_MIPMAP_0;
1421}
1422
1423void dt_mipmap_cache_swap_at_size(const int32_t imgid, const dt_mipmap_size_t mip, const uint8_t *const in,
1424 const int32_t width, const int32_t height, dt_colorspaces_color_profile_type_t profile)
1425{
1427 if(mip >= DT_MIPMAP_F || mip < DT_MIPMAP_0) return;
1428
1429 const uint32_t key = get_key(imgid, mip);
1430 dt_cache_entry_t *entry = dt_cache_get_with_caller(&_get_cache(cache, mip)->cache, key, 'w', __FILE__, __LINE__);
1431 if(entry)
1432 {
1433 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1434 // Unpoison descriptor and pixel payload before reading/writing either.
1435 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1436 ASAN_UNPOISON_MEMORY_REGION(_get_buffer_from_dsc(dsc), dsc->size - dt_mipmap_buffer_dsc_size);
1437 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d is synchronized from pipeline at size %i (%ix%i->%ix%i)\n",
1438 imgid, mip, width, height, dsc->width, dsc->height);
1439
1440 // Downscale
1441 dsc->iscale = 1.f;
1442 const int32_t wd = dsc->width;
1443 const int32_t ht = dsc->height;
1444 uint8_t *buf =(uint8_t *)_get_buffer_from_dsc(dsc);
1445 dt_iop_flip_and_zoom_8(in, width, height, buf, wd, ht,
1446 ORIENTATION_NONE, &dsc->width, &dsc->height);
1447
1448 /* Thumbnails are stored in AdobeRGB. In place: the conversion also swaps BGRA back
1449 * to RGBA, which is what happens even when no transform could be built. */
1450 dt_colorprofiles_bgra8_to_adobergb_rgba8(buf, buf, dsc->width, dsc->height, profile);
1451
1453 dsc->status = DT_IMAGEIO_OK;
1454 // New pixels: the file, if there is one, no longer holds them, and no failure is left to retry.
1457 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1458 }
1459}
1460
1461
1462void dt_mipmap_cache_remove_at_size(const int32_t imgid, const dt_mipmap_size_t mip, const gboolean flush_disk)
1463{
1465 if(mip >= DT_MIPMAP_F || mip < DT_MIPMAP_0) return;
1466
1467 // get rid of all ldr thumbnails:
1468 const uint32_t key = get_key(imgid, mip);
1469 dt_cache_entry_t *entry = dt_cache_testget(&_get_cache(cache, mip)->cache, key, 'w');
1470 if(entry)
1471 {
1472 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1473 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1474 if(flush_disk) dsc->flags |= DT_MIPMAP_BUFFER_DSC_FLAG_INVALIDATE;
1475 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1476 dt_cache_remove(&_get_cache(cache, mip)->cache, key);
1477 }
1478 if(flush_disk)
1479 {
1480 // directly remove the file on disk cache even if we don't have a memory entry
1481 dt_mipmap_cache_unlink_ondisk_thumbnail(cache, imgid, mip);
1482 }
1483}
1484
1485// get rid of all ldr thumbnails, and of a full-size load that failed:
1486void dt_mipmap_cache_remove(const int32_t imgid, const gboolean flush_disk)
1487{
1489 dt_mipmap_cache_remove_at_size(imgid, k, flush_disk);
1490
1491 /* The decoded raw stays: a development change does not invalidate it. A failed load holds no
1492 * raw, and the thumbnails generated next would read its failure back instead of trying the file
1493 * again, which may have been repaired, or may now fit in memory. */
1495 const uint32_t key = get_key(imgid, DT_MIPMAP_FULL);
1496 dt_cache_entry_t *entry = dt_cache_testget(full, key, 'r');
1497 if(IS_NULL_PTR(entry)) return;
1498
1499 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1500 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1501 const gboolean failed = dsc->status != DT_IMAGEIO_OK;
1502 dt_cache_release(full, entry);
1503 if(failed) dt_cache_remove(full, key);
1504}
1505
1506/* Same as _remove_at_size(), minus the "thumbnails only" guard, so the two input sizes can
1507 * be dropped by the one caller entitled to: the image is going away. DT_MIPMAP_F and
1508 * DT_MIPMAP_FULL are RAM-only -- every disk write is gated on mip < DT_MIPMAP_F -- so there
1509 * is no file to unlink for them and flush_disk has nothing to do. */
1510static void _remove_one_size(const int32_t imgid, const dt_mipmap_size_t mip)
1511{
1513 const uint32_t key = get_key(imgid, mip);
1514 dt_cache_entry_t *entry = dt_cache_testget(&_get_cache(cache, mip)->cache, key, 'w');
1515 if(entry)
1516 {
1517 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1518 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1520 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1521 dt_cache_remove(&_get_cache(cache, mip)->cache, key);
1522 }
1523}
1524
1525void dt_mipmap_cache_remove_all_sizes(const int32_t imgid, const gboolean flush_disk)
1526{
1527 dt_mipmap_cache_remove(imgid, flush_disk);
1530}
1531
1532// write thumbnail to disc if not existing there
1533void dt_mimap_cache_evict(const int32_t imgid)
1534{
1537 dt_cache_remove(&_get_cache(cache, k)->cache, get_key(imgid, k));
1538}
1539
1540static void _init_f(dt_mipmap_buffer_t *mipmap_buf, float *out, uint32_t *width, uint32_t *height, float *iscale,
1541 const int32_t imgid)
1542{
1543 const uint32_t wd = *width, ht = *height;
1544
1545 /* do not even try to process file if it isn't available */
1546 char filename[DT_PATH_MAX] = { 0 };
1548 {
1549 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1550 if(!IS_NULL_PTR(img))
1551 {
1552 source = dt_image_choose_input_path(img, filename, sizeof(filename), FALSE);
1554 }
1555 }
1556 if(source == DT_IMAGE_PATH_NONE)
1557 {
1558 *width = *height = 0;
1559 *iscale = 0.0f;
1560 return;
1561 }
1562
1565
1566 // lock image after we have the buffer, we might need to lock the image struct for
1567 // writing during raw loading, to write to width/height.
1568 const dt_image_t *image = dt_image_cache_get(imgid, 'r');
1569 if(IS_NULL_PTR(image) || IS_NULL_PTR(buf.buf) || buf.width == 0 || buf.height == 0)
1570 {
1573 *width = *height = 0;
1574 *iscale = 0.0f;
1575 return;
1576 }
1577
1578 dt_iop_roi_t roi_in, roi_out;
1579 roi_in.x = roi_in.y = 0;
1580 roi_in.width = image->width;
1581 roi_in.height = image->height;
1582 roi_in.scale = 1.0f;
1583
1584 roi_out.x = roi_out.y = 0;
1585
1586 // now let's figure out the scaling...
1587
1588 // MIP_F is 4 channels, and we do not demosaic here
1589 roi_out.scale = fminf(((float)wd) / (float)image->width, ((float)ht) / (float)image->height);
1590 roi_out.width = roi_out.scale * roi_in.width;
1591 roi_out.height = roi_out.scale * roi_in.height;
1592
1593 mipmap_buf->color_space = DT_COLORSPACE_NONE; // TODO: do we need that information in this buffer?
1594
1595 if(image->dsc.filters)
1596 {
1597 if(image->dsc.filters != 9u && image->dsc.datatype == TYPE_FLOAT)
1598 {
1599 dt_iop_clip_and_zoom_mosaic_half_size_f((float *const)out, (const float *const)buf.buf, &roi_out, &roi_in,
1600 roi_out.width, roi_in.width, image->dsc.filters);
1601 }
1602 else if(image->dsc.filters != 9u && image->dsc.datatype == TYPE_UINT16)
1603 {
1604 dt_iop_clip_and_zoom_mosaic_half_size((uint16_t * const)out, (const uint16_t *)buf.buf, &roi_out, &roi_in,
1605 roi_out.width, roi_in.width, image->dsc.filters);
1606 }
1607 else if(image->dsc.filters == 9u && image->dsc.datatype == TYPE_UINT16)
1608 {
1609 dt_iop_clip_and_zoom_mosaic_third_size_xtrans((uint16_t * const)out, (const uint16_t *)buf.buf, &roi_out,
1610 &roi_in, roi_out.width, roi_in.width, image->dsc.xtrans);
1611 }
1612 else if(image->dsc.filters == 9u && image->dsc.datatype == TYPE_FLOAT)
1613 {
1614 dt_iop_clip_and_zoom_mosaic_third_size_xtrans_f(out, (const float *)buf.buf, &roi_out, &roi_in,
1615 roi_out.width, roi_in.width, image->dsc.xtrans);
1616 }
1617 else
1618 {
1620 }
1621 }
1622 else
1623 {
1624 // downsample
1625 dt_iop_clip_and_zoom(out, (const float *)buf.buf, &roi_out, &roi_in, roi_out.width, roi_in.width);
1626 }
1627
1629
1630 *width = roi_out.width;
1631 *height = roi_out.height;
1632 *iscale = (float)image->width / (float)roi_out.width;
1633
1635}
1636
1637
1638// dummy functions for `export' to mipmap buffers:
1644
1646{
1647 return IMAGEIO_RGB | IMAGEIO_INT8;
1648}
1649
1651{
1652 return 8;
1653}
1654
1655static int _write_image(dt_imageio_module_data_t *data, const char *filename, const void *in,
1656 dt_colorspaces_color_profile_type_t over_type, const char *over_filename,
1657 void *exif, int exif_len, int32_t imgid, int num, int total, dt_dev_pixelpipe_t *pipe,
1658 const gboolean export_masks)
1659{
1660 _dummy_data_t *d = (_dummy_data_t *)data;
1661 memcpy(d->buf, in, sizeof(uint32_t) * data->width * data->height);
1662 return 0;
1663}
1664
1665static int _load_jpg(const char *filename, const int32_t imgid, const uint32_t wd, const uint32_t ht,
1666 const dt_mipmap_size_t size, const dt_image_orientation_t orientation, uint8_t *buf,
1668{
1669 int res = 1;
1671 if(!dt_imageio_jpeg_read_header(filename, &jpg))
1672 {
1674 uint8_t *tmp = (uint8_t *)dt_alloc_align(sizeof(uint8_t) * jpg.width * jpg.height * 4);
1675 if(tmp && !dt_imageio_jpeg_read(&jpg, tmp))
1676 {
1677 // scale to fit
1678 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generate mip size %d for image %d from jpeg\n", size, imgid);
1679 dt_iop_flip_and_zoom_8(tmp, jpg.width, jpg.height, buf, wd, ht, orientation, width, height);
1680 res = 0;
1681 }
1682 dt_free_align(tmp);
1683 }
1684 return res;
1685}
1686
1687static int _find_sidecar_jpg(const char *filename, const char *ext, char *sidecar)
1688{
1689 const size_t filename_len = strlen(filename) - strlen(ext);
1690 const char *exts[4] = { ".jpg", ".JPG", ".jpeg", ".JPEG" };
1691
1692 for(int i = 0; i < 4; i++)
1693 {
1694 // Damage control. Should never happen.
1695 if(filename_len + strlen(exts[i]) >= DT_PATH_MAX)
1696 continue;
1697
1698 // Construct the sidecar filename
1699 const size_t str_copy = g_snprintf(sidecar, DT_PATH_MAX, "%.*s%s", (int)filename_len, filename, exts[i]);
1700
1701 // Check if the filename was too long or if the copy failed.
1702 if (str_copy == 0 || str_copy >= DT_PATH_MAX)
1703 continue;
1704
1705 if(g_file_test(sidecar, G_FILE_TEST_EXISTS))
1706 return 1;
1707 }
1708
1709 return 0;
1710}
1711
1725static dt_imageio_retval_t _init_8(struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid,
1726 const dt_mipmap_size_t size, const int embedded_jpg_mode,
1727 dt_atomic_int *shutdown)
1728{
1729 if(size >= DT_MIPMAP_F || dsc->width < 16 || dsc->height < 16) return DT_IMAGEIO_OK;
1730
1731 uint8_t *buf = _get_buffer_from_dsc(dsc);
1732 dsc->iscale = 1.0f;
1733 const uint32_t wd = dsc->width;
1734 const uint32_t ht = dsc->height;
1735
1736 char filename[DT_PATH_MAX] = { 0 };
1737 char ext[6] = { 0 };
1738 gboolean input_exists = FALSE;
1739 gboolean is_jpg_input = FALSE;
1740 gboolean altered = FALSE;
1741 gboolean may_be_raw = TRUE;
1742 _get_thumbnail_input(imgid, filename, ext, &input_exists, &is_jpg_input, &altered, &may_be_raw);
1743
1744 // embedded JPG mode:
1745 // 0 = never use embedded thumbnail
1746 // 1 = only on unedited pics,
1747 // 2 = always use embedded thumbnail
1748 // so an edited image uses it only in mode 2, an unedited one in modes 1 and 2.
1749 const gboolean use_embedded_jpg = altered ? embedded_jpg_mode == 2 : embedded_jpg_mode >= 1;
1750
1751 /* do not even try to process file if it isn't available */
1752 if(!input_exists)
1753 {
1754 dsc->width = 0;
1755 dsc->height = 0;
1756 dsc->iscale = 0.0f;
1759 }
1760
1761 int res = 1;
1762
1763 if(!use_embedded_jpg)
1764 {
1765 // try to generate mip from larger mip
1766 // This expects that invalid mips will be flushed, so the assumption is:
1767 // if mip then it's valid (with regard to current history)
1768 for(dt_mipmap_size_t k = size + 1; k < DT_MIPMAP_F; k++)
1769 {
1771 dt_mipmap_cache_get(&tmp, imgid, k, DT_MIPMAP_TESTLOCK, 'r');
1772 if(IS_NULL_PTR(tmp.buf)) continue;
1773
1774 dsc->color_space = tmp.color_space;
1775 // downsample
1776 dt_iop_flip_and_zoom_8(tmp.buf, tmp.width, tmp.height, buf, wd, ht, ORIENTATION_NONE, &dsc->width,
1777 &dsc->height);
1778 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generate mip size %d for image %d from mip size %d (%ix%i->%ix%i)\n",
1779 size, imgid, k, tmp.width, tmp.height, dsc->width, dsc->height);
1780
1782 return DT_IMAGEIO_OK;
1783 }
1784 }
1785 else
1786 {
1787 // Orientation and camera framing are only needed when loading embedded JPEGs.
1789 dt_boundingbox_t usercrop = { 0.f, 0.f, 1.f, 1.f };
1790 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1791 if(img)
1792 {
1793 orientation = img->orientation;
1795 }
1796 if(orientation == ORIENTATION_NULL) orientation = ORIENTATION_NONE;
1797
1798 // Resolve outside the read lock: this path never decodes the raw, so the framing may still
1799 // have to be read from the file, and storing the answer needs the write lock.
1800 dt_image_resolve_usercrop(imgid, usercrop);
1801
1802 char sidecar_filename[DT_PATH_MAX] = { 0 };
1803
1804 if(is_jpg_input)
1805 {
1806 // Input file is a JPEG
1807 res = _load_jpg(filename, imgid, wd, ht, size, orientation, buf, &dsc->width, &dsc->height,
1808 &dsc->color_space);
1809 }
1810 else if(_find_sidecar_jpg(filename, ext, sidecar_filename))
1811 {
1812 // input file is a RAW but we have a companion JPEG file in the same folder:
1813 // use it in priority (it may be higher resolution/quality than embedded JPEG).
1814 res = _load_jpg(sidecar_filename, imgid, wd, ht, size, orientation, buf, &dsc->width, &dsc->height,
1815 &dsc->color_space);
1816 }
1817 else
1818 {
1819 // input file is a RAW without companion JPEG:
1820 // try to load the embedded thumbnail. Might not be large enough though.
1821 uint8_t *tmp = NULL;
1822 int32_t thumb_width, thumb_height;
1823 res = dt_imageio_large_thumbnail(filename, &tmp, &thumb_width, &thumb_height, &dsc->color_space,
1824 dsc->width, dsc->height);
1825 if(!res)
1826 {
1827 // We take the thumbnail no matter its size. It might be too small for the requested dimension,
1828 // and end up blurry. But it's less bad than the following scenario:
1829 // 1. user displays collections in grid of 5 columns (small thumbnail -> fetch embedded JPEG for performance, all good),
1830 // 2. user zooms in the grid, to 2 or 3 columns,
1831 // 3. suddently, embedded JPEG is too small so we ditch it for a full pipe recompute,
1832 // 4. but then, color/contrast/appearance unexpectedly changes and user doesn't understand WTF just happened.
1833 // Blurry is less bad than randomly inconsistent, plus user has a GUI way in lighttable to
1834 // change how thumbs are processed at runtime.
1835 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generate mip size %d for image %d from embedded jpeg\n", size, imgid);
1836 // The camera renders its embedded previews from the full default-crop rectangle even when
1837 // it recorded a narrower framing, so trim them to what the photographer actually framed.
1838 // Only here: the two branches above read a separate JPEG file, which the camera already
1839 // wrote cropped. This runs before the rotation below, hence the un-oriented box.
1840 dt_imageio_crop_thumbnail(usercrop, tmp, &thumb_width, &thumb_height);
1841 dt_iop_flip_and_zoom_8(tmp, thumb_width, thumb_height, buf, wd, ht, orientation, &dsc->width,
1842 &dsc->height);
1844 }
1845 }
1846 }
1847
1848 if(!res) return DT_IMAGEIO_OK;
1849
1850 // Mode 2 spares the cost of decoding a raw. A PNG, a TIFF or any other RGB file usually has
1851 // no embedded preview, and is itself the cheap picture that preview stands in for.
1852 if(embedded_jpg_mode == 2 && may_be_raw)
1853 {
1855 "[mipmap_cache] embedded JPEG mode forbids raw processing for image %d at mip %d\n",
1856 imgid, size);
1857 dsc->width = 0;
1858 dsc->height = 0;
1859 dsc->iscale = 0.0f;
1862 }
1863
1864 // What an embedded preview shows of a raw is the picture before any edit. An RGB file that has
1865 // none shows the same, so the embedded modes render it without its history.
1866 const gboolean ignore_history = use_embedded_jpg && !may_be_raw;
1867
1868 // try the real thing: rawspeed + pixelpipe
1870 _dummy_data_t dat;
1871 format.bpp = _bpp;
1872 format.write_image = _write_image;
1873 format.levels = _levels;
1874 dat.head.max_width = wd;
1875 dat.head.max_height = ht;
1876 dat.buf = buf;
1877 // export with flags: ignore exif (don't load from disk), don't swap byte order, don't do hq processing,
1878 // no upscaling and signal we want thumbnail export
1880 = dt_imageio_export_with_flags(imgid, "unused", &format, (dt_imageio_module_data_t *)&dat, TRUE, FALSE,
1881 FALSE, FALSE, TRUE, ignore_history, NULL, FALSE, FALSE,
1883 DT_INTENT_LAST, NULL, NULL, 1, 1, NULL, shutdown);
1884 if(status != DT_IMAGEIO_OK)
1885 {
1886 g_printerr("[mipmap_cache] could not process thumbnail for image %" PRId32 " (%s): error %d\n", imgid,
1887 filename, status);
1888 dsc->width = 0;
1889 dsc->height = 0;
1890 dsc->iscale = 0.0f;
1892 return status;
1893 }
1894
1895 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generated mip %d for image %d from scratch\n", size, imgid);
1896 // might be smaller, or have a different aspect than what we got as input.
1897 dsc->width = dat.head.width;
1898 dsc->height = dat.head.height;
1899 dsc->iscale = 1.0f;
1901 return DT_IMAGEIO_OK;
1902}
1903
1904void dt_mipmap_cache_copy_thumbnails(const uint32_t dst_imgid, const uint32_t src_imgid)
1905{
1907 if(cache->cachedir[0] && _settings_get().disk_backend)
1908 {
1909 for(dt_mipmap_size_t mip = DT_MIPMAP_0; mip < DT_MIPMAP_F; mip++)
1910 {
1911 // try and load from disk, if successful set flag
1912 char srcpath[DT_PATH_MAX] = {0};
1913 char dstpath[DT_PATH_MAX] = {0};
1914 dt_mipmap_get_cache_filename(srcpath, mip, src_imgid);
1915 dt_mipmap_get_cache_filename(dstpath, mip, dst_imgid);
1916 GFile *src = g_file_new_for_path(srcpath);
1917 GFile *dst = g_file_new_for_path(dstpath);
1918 GError *gerror = NULL;
1919 g_file_copy(src, dst, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror);
1920 // ignore errors, we tried what we could.
1921 g_object_unref(dst);
1922 g_object_unref(src);
1923 g_clear_error(&gerror);
1924 }
1925 }
1926}
1927
1928// clang-format off
1929// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1930// vim: shiftwidth=2 expandtab tabstop=2 cindent
1931// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1932// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int dt_atomic_get_int(dt_atomic_int *var)
atomic_int dt_atomic_int
Definition atomic.h:68
#define dt_cache_release(A, B)
#define dt_cache_get(A, B, C)
static void dt_cache_set_cleanup_callback(dt_cache_t *cache, dt_cache_cleanup_t cleanup_cb, void *cleanup_data)
static void dt_cache_set_allocate_callback(dt_cache_t *cache, dt_cache_allocate_t allocate_cb, void *allocate_data)
const float f
gboolean dt_colorprofiles_bgra8_to_adobergb_rgba8(const uint8_t *const in, uint8_t *const out, const int width, const int height, const dt_colorspaces_color_profile_type_t src_space)
The storage leg: convert an 8-bit plane from src_space (BGRA8) to AdobeRGB (RGBA8),...
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
gboolean dt_image_is_hdr(const dt_image_t *img)
gboolean dt_image_is_ldr(const dt_image_t *img)
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
dt_image_path_source_t dt_image_choose_input_path(const dt_image_t *img, char *pathname, size_t pathname_len, gboolean force_cache)
gboolean dt_image_resolve_usercrop(const int32_t imgid, dt_boundingbox_t box)
#define ASAN_POISON_MEMORY_REGION(addr, size)
#define ASAN_UNPOISON_MEMORY_REGION(addr, size)
int dt_worker_threads()
Definition darktable.c:2390
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:511
const gchar * dt_database_get_path(void)
Definition database.c:3795
const int res
Definition dtpthread.h:351
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
void dt_loc_get_user_cache_dir(char *cachedir, size_t bufsize)
@ TYPE_FLOAT
Definition format.h:56
@ TYPE_UINT16
Definition format.h:57
static gchar * g_realpath(const char *path)
Definition grealpath.h:49
dt_image_path_source_t
Definition image.h:310
@ DT_IMAGE_PATH_NONE
Definition image.h:311
float dt_boundingbox_t[4]
Definition image.h:83
dt_image_orientation_t
Definition image.h:225
@ ORIENTATION_NULL
Definition image.h:226
@ ORIENTATION_NONE
Definition image.h:227
dt_imageio_retval_t
Return value of image io functions: loading, exporting, and making a thumbnail.
Definition image.h:97
@ DT_IMAGEIO_OK
Definition image.h:98
@ DT_IMAGEIO_CACHE_FULL
Definition image.h:101
@ DT_IMAGEIO_NO_EMBEDDED_THUMBNAIL
Definition image.h:107
@ DT_IMAGEIO_FILE_NOT_FOUND
Definition image.h:99
void dt_image_cache_write_release(dt_image_t *img, dt_image_cache_write_mode_t mode)
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
@ DT_IMAGE_CACHE_RELAXED
Definition image_cache.h:50
gboolean dt_image_ext_is_raw(const char *ext)
int bpp
dt_imageio_retval_t dt_imageio_export_with_flags(const int32_t imgid, const char *filename, dt_imageio_module_format_t *format, dt_imageio_module_data_t *format_params, const gboolean ignore_exif, const gboolean display_byteorder, const gboolean high_quality, const double scale_factor, const gboolean thumbnail_export, const gboolean ignore_history, const char *filter, const gboolean copy_metadata, const gboolean export_masks, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_imageio_module_storage_t *storage, dt_imageio_module_data_t *storage_params, int num, int total, dt_export_metadata_t *metadata, dt_atomic_int *shutdown)
dt_imageio_retval_t dt_imageio_open(dt_image_t *img, const char *filename, dt_mipmap_buffer_t *buf)
gboolean dt_imageio_crop_thumbnail(const dt_boundingbox_t box, uint8_t *const buffer, int32_t *width, int32_t *height)
Crop an 8-bit RGBA preview buffer in place to a normalized bounding box.
int dt_imageio_large_thumbnail(const char *filename, uint8_t **buffer, int32_t *th_width, int32_t *th_height, dt_colorspaces_color_profile_type_t *color_space, const int width, const int height)
Load the thumbnail embedded into a RAW file having at least the size MAX(width, height) x MAX(width,...
@ IMAGEIO_RGB
@ IMAGEIO_INT8
int dt_imageio_jpeg_read(dt_imageio_jpeg_t *jpg, uint8_t *out)
int dt_imageio_jpeg_read_header(const char *filename, dt_imageio_jpeg_t *jpg)
dt_colorspaces_color_profile_type_t dt_imageio_jpeg_read_color_space(dt_imageio_jpeg_t *jpg)
int dt_imageio_jpeg_decompress_header(const void *in, size_t length, dt_imageio_jpeg_t *jpg)
int dt_imageio_jpeg_decompress(dt_imageio_jpeg_t *jpg, uint8_t *out)
int dt_imageio_jpeg_write(const char *filename, const uint8_t *in, const int width, const int height, const int quality, const void *exif, int exif_len)
void dt_iop_clip_and_zoom_mosaic_half_size_f(float *const out, const float *const in, const dt_iop_roi_t *const roi_out, const dt_iop_roi_t *const roi_in, const int32_t out_stride, const int32_t in_stride, const uint32_t filters)
void dt_iop_clip_and_zoom(float *out, const float *const in, const dt_iop_roi_t *const roi_out, const dt_iop_roi_t *const roi_in, const int32_t out_stride, const int32_t in_stride)
void dt_iop_flip_and_zoom_8(const uint8_t *in, int32_t iw, int32_t ih, uint8_t *out, int32_t ow, int32_t oh, const dt_image_orientation_t orientation, uint32_t *width, uint32_t *height)
void dt_iop_clip_and_zoom_mosaic_half_size(uint16_t *const out, const uint16_t *const in, const dt_iop_roi_t *const roi_out, const dt_iop_roi_t *const roi_in, const int32_t out_stride, const int32_t in_stride, const uint32_t filters)
void dt_iop_clip_and_zoom_mosaic_third_size_xtrans(uint16_t *const out, const uint16_t *const in, const dt_iop_roi_t *const roi_out, const dt_iop_roi_t *const roi_in, const int32_t out_stride, const int32_t in_stride, const uint8_t(*const xtrans)[6])
void dt_iop_clip_and_zoom_mosaic_third_size_xtrans_f(float *const out, const float *const in, const dt_iop_roi_t *const roi_out, const dt_iop_roi_t *const roi_in, const int32_t out_stride, const int32_t in_stride, const uint8_t(*const xtrans)[6])
#define DT_CTL_WORKER_RESERVED
Definition jobs.h:39
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
@ DT_DEBUG_CACHE
Definition logging.h:51
float *const restrict const size_t k
#define dt_unreachable_codepath()
Mark a branch as impossible.
Definition macros.h:141
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_CACHELINE_BYTES
Definition mem_alloc.h:66
char * key
void * dt_mipmap_cache_alloc(dt_mipmap_buffer_t *buf, const dt_image_t *img)
static void _sync_dsc_to_buf(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid, const dt_mipmap_size_t mip)
static void _paint_skulls(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid, const dt_mipmap_size_t mip)
static dt_mipmap_cache_settings_t _settings_get(void)
dt_mipmap_size_t dt_mipmap_cache_get_fitting_size(const int32_t width, const int32_t height, const uint32_t imgid)
static void _write_thumbnail_to_disk_cache(struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid, const dt_mipmap_size_t mip)
Serialise one generated thumbnail into the on-disk cache.
static const uint8_t dt_mipmap_cache_exif_data_srgb[]
void dt_mipmap_cache_get_usage(size_t *current, size_t *max)
static int _write_image(dt_imageio_module_data_t *data, const char *filename, const void *in, dt_colorspaces_color_profile_type_t over_type, const char *over_filename, void *exif, int exif_len, int32_t imgid, int num, int total, dt_dev_pixelpipe_t *pipe, const gboolean export_masks)
static int _bpp(dt_imageio_module_data_t *data)
#define X
static void _init_f(dt_mipmap_buffer_t *mipmap_buf, float *buf, uint32_t *width, uint32_t *height, float *iscale, const int32_t imgid)
static const int dt_mipmap_cache_exif_data_adobergb_length
uint32_t width
Definition mipmap_cache.c:0
static dt_pthread_mutex_t _settings_lock
void dt_mipmap_cache_update_buffer_addresses(dt_cache_entry_t *entry, struct dt_mipmap_buffer_dsc **dsc, const size_t width, const size_t height, const size_t buffer_size)
Resync all references to all references.
static int _find_sidecar_jpg(const char *filename, const char *ext, char *sidecar)
static gboolean _verbose
void dt_mipmap_cache_get_with_caller(dt_mipmap_buffer_t *buf, const int32_t imgid, const dt_mipmap_size_t mip, const dt_mipmap_get_flags_t flags, const char mode, const char *file, int line)
static void _mipmap_subcache_collect(dt_cache_t *c, GArray *out)
void dt_mipmap_cache_init(const dt_mipmap_cache_settings_t *settings, const gboolean verbose)
Initialise the cache.
void dt_mipmap_cache_cleanup(void)
static void _invalidate_buffer(dt_mipmap_buffer_t *buf)
void dt_mipmap_cache_release_with_caller(dt_mipmap_buffer_t *buf, const char *file, int line)
static const int dt_mipmap_cache_exif_data_srgb_length
#define _cache_print(channel,...)
static dt_mipmap_cache_one_t * _get_cache(dt_mipmap_cache_t *cache, const dt_mipmap_size_t mip)
void dt_mipmap_cache_write_get_with_caller(dt_mipmap_buffer_t *buf, const int32_t imgid, const int mip, const char *file, int line)
static int dt_mipmap_cache_get_filename(gchar *mipmapfilename, size_t size)
void dt_mipmap_cache_remove(const int32_t imgid, const gboolean flush_disk)
static uint8_t * _get_buffer_from_dsc(struct dt_mipmap_buffer_dsc *dsc)
static const uint8_t dt_mipmap_cache_exif_data_adobergb[]
void dt_mipmap_cache_remove_all_sizes(const int32_t imgid, const gboolean flush_disk)
dt_imageio_retval_t status
Definition mipmap_cache.c:8
static void * dead_image_8(struct dt_mipmap_buffer_dsc *dsc)
uint32_t height
Definition mipmap_cache.c:1
void dt_mipmap_cache_copy_thumbnails(const uint32_t dst_imgid, const uint32_t src_imgid)
static void _settings_lock_ensure(void)
static void _generate_blocking(dt_cache_entry_t *entry, dt_mipmap_buffer_t *buf, const int32_t imgid, const dt_mipmap_size_t mip, dt_atomic_int *shutdown)
static gboolean _get_image_copy(const int32_t imgid, dt_image_t *buffered_image)
void dt_mipmap_get_cache_dir(char path[DT_PATH_MAX], dt_mipmap_size_t mip)
float iscale
Definition mipmap_cache.c:2
void dt_mipmap_cache_get_settings(dt_mipmap_cache_settings_t *settings)
Read back the settings in force. Snapshot, taken under the same lock the setter takes,...
void dt_mipmap_cache_remove_at_size(const int32_t imgid, const dt_mipmap_size_t mip, const gboolean flush_disk)
static void _validate_buffer(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid, const dt_mipmap_size_t mip)
static size_t _get_entry_size(const size_t buffer_size)
static uint32_t get_imgid(const uint32_t key)
size_t size
Definition mipmap_cache.c:3
void dt_mipmap_cache_print(void)
void dt_mipmap_cache_deallocate_dynamic(void *data, dt_cache_entry_t *entry)
void dt_mipmap_cache_get_with_caller_and_shutdown(dt_mipmap_buffer_t *buf, const int32_t imgid, const dt_mipmap_size_t mip, const dt_mipmap_get_flags_t flags, const char mode, dt_atomic_int *shutdown, const char *file, int line)
void dt_mipmap_cache_set_settings(const dt_mipmap_cache_settings_t *settings)
Apply new settings to a running cache.
void dt_mipmap_cache_allocate_dynamic(void *data, dt_cache_entry_t *entry)
static int _load_jpg(const char *filename, const int32_t imgid, const uint32_t wd, const uint32_t ht, const dt_mipmap_size_t size, const dt_image_orientation_t orientation, uint8_t *buf, uint32_t *width, uint32_t *height, dt_colorspaces_color_profile_type_t *color_space)
void dt_mimap_cache_evict(const int32_t imgid)
static int32_t get_key(const int32_t imgid, const dt_mipmap_size_t size)
static dt_mipmap_cache_settings_t _settings
struct dt_mipmap_buffer_dsc * _get_dsc_from_entry(dt_cache_entry_t *entry)
#define DT_MIPMAP_CACHE_DEFAULT_FILE_NAME
static gboolean _disk_has_room_for_a_thumbnail(const char *dirname)
Is there room on the filesystem holding dirname for one more thumbnail?
void dt_mipmap_cache_flush_to_disk(void)
Write the resident thumbnails to the disk cache and drop them from RAM; the cache stays usable.
GArray * dt_mipmap_cache_get_entries_stats(void)
dt_mipmap_size_t dt_mipmap_cache_get_matching_size(const int32_t width, const int32_t height, const uint32_t imgid)
#define o
dt_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
static void _get_thumbnail_input(const int32_t imgid, char *filename, char *ext, gboolean *input_exists, gboolean *is_jpg_input, gboolean *altered, gboolean *may_be_raw)
Return what choosing the source of a thumbnail needs: the input file, whether it exists on the filesy...
static dt_mipmap_size_t get_size(const uint32_t key)
static const uint32_t _skull_8[64] DT_ALIGNED_ARRAY
void dt_mipmap_get_cache_filename(char path[DT_PATH_MAX], dt_mipmap_size_t mip, const int32_t imgid)
dt_mipmap_buffer_dsc_flags
@ DT_MIPMAP_BUFFER_DSC_FLAG_NONE
@ DT_MIPMAP_BUFFER_DSC_FLAG_RETRIED
@ DT_MIPMAP_BUFFER_DSC_FLAG_INVALIDATE
@ DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE
@ DT_MIPMAP_BUFFER_DSC_FLAG_ON_DISK
static void dt_mipmap_cache_unlink_ondisk_thumbnail(void *data, int32_t imgid, dt_mipmap_size_t mip)
static dt_mipmap_cache_t * _mipmap_cache
static dt_imageio_retval_t _init_8(struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid, const dt_mipmap_size_t size, const int embedded_jpg_mode, dt_atomic_int *shutdown)
Render an 8-bit thumbnail into the buffer of dsc, from a larger cached thumbnail, an embedded or comp...
static void _remove_one_size(const int32_t imgid, const dt_mipmap_size_t mip)
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
void dt_mipmap_cache_swap_at_size(const int32_t imgid, const dt_mipmap_size_t mip, const uint8_t *const in, const int32_t width, const int32_t height, dt_colorspaces_color_profile_type_t profile)
static gsize _settings_lock_inited
static int _levels(dt_imageio_module_data_t *data)
dt_mipmap_get_flags_t
@ DT_MIPMAP_BLOCKING
@ DT_MIPMAP_TESTLOCK
dt_mipmap_size_t
@ DT_MIPMAP_F
@ DT_MIPMAP_0
@ DT_MIPMAP_2
@ DT_MIPMAP_NONE
@ DT_MIPMAP_FULL
#define dt_mipmap_cache_get(B, C, D, E, F)
#define dt_mipmap_cache_release(B)
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
void dt_concat_path_file(char destination[4096], const char path[4096], const char *const file)
Append a constant filename to a variable, stack-based, fixed-sized, directory, and add a / in-between...
Definition darktable.c:2683
#define dt_pixelpipe_cache_free_align(mem)
@ DT_INTENT_LAST
Count of real intents, and the codebase's "unset" marker – never a user choice.
dt_colorspaces_color_profile_type_t
@ DT_COLORSPACE_ADOBERGB
@ DT_COLORSPACE_DISPLAY
The monitor profile – the one list entry that mutates after init.
@ DT_COLORSPACE_SRGB
sRGB – registered TWICE, and the two entries are not interchangeable.
@ DT_COLORSPACE_NONE
No profile / "take it from the image settings". Never matches a list entry.
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
void dt_cache_release_with_caller(dt_cache_t *cache, dt_cache_entry_t *entry, const char *file, int line)
dt_cache_entry_t * dt_cache_testget(dt_cache_t *cache, const uint32_t key, char mode)
void dt_cache_init(dt_cache_t *cache, size_t entry_size, size_t cost_quota)
dt_cache_entry_t * dt_cache_get_with_caller(dt_cache_t *cache, const uint32_t key, char mode, const char *file, int line)
size_t dt_cache_gc(dt_cache_t *cache, const float fill_ratio)
int dt_cache_remove(dt_cache_t *cache, const uint32_t key)
void dt_cache_cleanup(dt_cache_t *cache)
const float r
unsigned __int64 uint64_t
Definition strptime.c:75
dt_imageio_module_data_t head
uint32_t key
size_t data_size
int _lock_demoting
void * data
size_t cost
size_t cost
size_t cost_quota
int32_t height
Definition image.h:406
dt_image_orientation_t orientation
Definition image.h:375
int32_t width
Definition image.h:406
uint32_t history_items
Definition image.h:412
dt_iop_buffer_dsc_t dsc
Definition image.h:428
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:395
uint32_t filters
Definition format.h:89
uint8_t xtrans[6][6]
Definition format.h:99
dt_iop_buffer_type_t datatype
Definition format.h:85
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_colorspaces_color_profile_type_t color_space
dt_mipmap_buffer_dsc_flags flags
dt_imageio_retval_t status
dt_colorspaces_color_profile_type_t color_space
dt_cache_entry_t * cache_entry
NULL with a non-NULL buf when a blocking get could not allocate the thumbnail's entry: buf is then a ...
dt_mipmap_size_t size
dt_imageio_retval_t status
Why the entry holds no image: a thumbnail drawn as a skull, or a full-size input with no pixels....
Everything about the mipmap cache that the USER decides.
size_t max_memory
RAM budget for the thumbnail LRU, in bytes. A soft quota, not a hard limit.
int embedded_jpg
Whether to prefer the embedded JPEG over decoding (lighttable/embedded_jpg).
int cache_quality
JPEG quality for thumbnails written to disk (database_cache_quality).
int mip
size_t size
int32_t imgid
dt_mipmap_cache_one_t mip_full
size_t max_height[DT_MIPMAP_NONE]
dt_mipmap_cache_one_t mip_f
dt_mipmap_cache_one_t mip_thumbs
char cachedir[DT_PATH_MAX]
size_t max_width[DT_MIPMAP_NONE]
size_t buffer_size[DT_MIPMAP_NONE]
fsblkcnt_t f_bavail
Definition statvfs.h:56
unsigned long f_frsize
Definition statvfs.h:53
void dt_supervisor_mipmap(const dt_sv_op_t op, const int32_t imgid, const int mip)
GHashTable * entries
Definition supervisor.c:120
void dt_supervisor_image(const dt_sv_op_t op, const int32_t imgid, const dt_image_t *img)
@ DT_SV_UPDATE
Definition supervisor.h:79
@ DT_SV_CREATE
Definition supervisor.h:78
@ DT_SV_DELETE
Definition supervisor.h:81
static gboolean dt_supervisor_active(void)
Definition supervisor.h:95
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29