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 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"
55#include "caches/image_cache.h"
57#include "develop/supervisor.h"
61#include "control/jobs.h"
63
64#include <assert.h>
65#include <glib.h>
66#include <glib/gstdio.h>
67#include <inttypes.h>
68#include <limits.h>
69#include <stdio.h>
70#include <stdlib.h>
71#include <string.h>
72#include <strings.h>
73#include "database/database.h"
74
75/* Set once by dt_mipmap_cache_init(); see the header. */
76static gboolean _verbose = FALSE;
77
78
79/* Statement-safe: a bare `if(_verbose) dt_print(...)` swallows a following `else`. */
80#define _cache_print(channel, ...) \
81 do { \
82 if(_verbose) dt_print((channel), __VA_ARGS__); \
83 } while(0)
84
85
86/* PRIVATE. Nothing outside this file reads a field of it -- the header exposes the
87 * type as an opaque handle so that it cannot. */
89{
90 // one cache per mipmap scale!
92
93 // a few stats on usage in this run.
94 // long int to give 32-bits on old archs, so __sync* calls will work.
95 long int stats_requests; // number of total requests
96 long int stats_near_match; // served with smaller mip res
97 long int stats_misses; // nothing returned at all.
98 long int stats_fetches; // texture was fetched (either as a stand-in or as per request)
99 long int stats_standin; // texture used as stand-in
101
102
103/* PRIVATE. Nothing outside this file reads a field of it -- the header exposes the
104 * type as an opaque handle so that it cannot. */
105typedef struct dt_mipmap_cache_t
106{
107 // real width and height are stored per element
108 // (could be smaller than the max for this mip level,
109 // due to aspect ratio)
112 // size of an element inside buf
114
115 // one cache per mipmap level
119 char cachedir[DT_PATH_MAX]; // cached sha1sum filename for faster access
121
122
123#if !defined(_WIN32)
124#include <sys/statvfs.h>
125#else
126//statvfs does not exist in Windows, providing implementation
127#include "win/statvfs.h"
128#endif
129
130/* The cache instance, owned HERE. It used to be calloc'd by darktable.c and hung off the
131 * application struct, so the accessor lived in the orchestrator and the struct was reachable
132 * from anything that included darktable.h. */
134
135/* The user's choices, told to us by the application. Guarded because the GUI thread can
136 * replace them while worker threads are mid-decode, and four fields read one at a time can be
137 * a mix of old and new -- the same torn-read hazard the colour module documents. */
139static dt_pthread_mutex_t _settings_lock;
140static gsize _settings_lock_inited = 0;
141
142static inline void _settings_lock_ensure(void)
143{
144 if(g_once_init_enter(&_settings_lock_inited))
145 {
147 g_once_init_leave(&_settings_lock_inited, 1);
148 }
149}
150
159
161{
162 if(!IS_NULL_PTR(settings)) *settings = _settings_get();
163}
164
166{
167 if(IS_NULL_PTR(settings)) return;
170 _settings = *settings;
172
173 /* The LRU quota is a live field and the limit is soft: lowering it makes the next
174 * insertions evict harder rather than freeing anything now. */
177}
178
179
180#define DT_MIPMAP_CACHE_FILE_MAGIC 0xD71337
181#define DT_MIPMAP_CACHE_FILE_VERSION 23
182#define DT_MIPMAP_CACHE_DEFAULT_FILE_NAME "mipmaps"
183
190
191// the embedded Exif data to tag thumbnails as sRGB or AdobeRGB
192static const uint8_t dt_mipmap_cache_exif_data_srgb[] = {
193 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x69,
194 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
195 0x01, 0xa0, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
196};
197static const uint8_t dt_mipmap_cache_exif_data_adobergb[] = {
198 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x69,
199 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
200 0x01, 0xa0, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
201};
206
208{
209 uint32_t width;
210 uint32_t height;
211 float iscale;
212 size_t size;
215
216#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
217 // do not touch!
218 // must be the last element.
219 // must be no less than 16bytes
220 char redzone[16];
221#endif
222
223 /* NB: sizeof must be a multiple of 4*sizeof(float) */
224} __attribute__((packed, aligned(DT_CACHELINE_BYTES)));
225
226#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
227static const size_t dt_mipmap_buffer_dsc_size __attribute__((unused))
228= sizeof(struct dt_mipmap_buffer_dsc) - sizeof(((struct dt_mipmap_buffer_dsc *)0)->redzone);
229#else
230static const size_t dt_mipmap_buffer_dsc_size __attribute__((unused)) = sizeof(struct dt_mipmap_buffer_dsc);
231#endif
232
233static uint8_t * _get_buffer_from_dsc(struct dt_mipmap_buffer_dsc *dsc);
234
235static inline void *dead_image_8(struct dt_mipmap_buffer_dsc *dsc)
236{
237 dsc->width = dsc->height = 8;
238 dsc->iscale = 1.0f;
240 assert(dsc->size > 64 * sizeof(uint32_t));
241 const uint32_t X = 0xffffffffu;
242 const uint32_t o = 0u;
243 const uint32_t image[]
244 = { o, o, o, o, o, o, o, o,
245 o, o, X, X, X, X, o, o,
246 o, X, o, X, X, o, X, o,
247 o, X, X, X, X, X, X, o,
248 o, o, X, o, o, X, o, o,
249 o, o, o, o, o, o, o, o,
250 o, o, X, X, X, X, o, o,
251 o, o, o, o, o, o, o, o };
252 memcpy(_get_buffer_from_dsc(dsc), image, sizeof(uint32_t) * 64);
253 return _get_buffer_from_dsc(dsc);
254}
255
256static inline int32_t get_key(const int32_t imgid, const dt_mipmap_size_t size)
257{
258 // imgid can't be >= 2^28 (~250 million images)
259 return (((int32_t)size) << 28) | (imgid - 1);
260}
261
262static inline uint32_t get_imgid(const uint32_t key)
263{
264 return (key & 0xfffffff) + 1;
265}
266
267static inline dt_mipmap_size_t get_size(const uint32_t key)
268{
269 return (dt_mipmap_size_t)(key >> 28);
270}
271
273{
275 g_snprintf(path, sizeof(char) * DT_PATH_MAX, "%s.d" G_DIR_SEPARATOR_S "%d",
276 cache->cachedir, (int)mip);
277}
278
279void dt_mipmap_get_cache_filename(char path[DT_PATH_MAX], dt_mipmap_size_t mip, const int32_t imgid)
280{
281 gchar cache_path[DT_PATH_MAX];
282 dt_mipmap_get_cache_dir(cache_path, mip);
283
284 gchar *file = g_strdup_printf("%u.jpg", imgid);
285 dt_concat_path_file(path, cache_path, file);
286 dt_free(file);
287}
288
289static int dt_mipmap_cache_get_filename(gchar *mipmapfilename, size_t size)
290{
291 int r = -1;
292 char *abspath = NULL;
293
294 // Directory
295 char cachedir[DT_PATH_MAX] = { 0 };
296 dt_loc_get_user_cache_dir(cachedir, sizeof(cachedir));
297
298 // Build the mipmap filename fram hashing the path of the library DB
299 const gchar *dbfilename = dt_database_get_path();
300
301 if(!strcmp(dbfilename, ":memory:"))
302 {
303 mipmapfilename[0] = '\0';
304 r = 0;
305 goto exit;
306 }
307
308 abspath = g_realpath(dbfilename);
309 if(IS_NULL_PTR(abspath)) abspath = g_strdup(dbfilename);
310
311 GChecksum *chk = g_checksum_new(G_CHECKSUM_SHA1);
312 g_checksum_update(chk, (guchar *)abspath, strlen(abspath));
313 const gchar *filename = g_checksum_get_string(chk);
314
315 if(!filename || filename[0] == '\0')
316 snprintf(mipmapfilename, size, "%s/%s", cachedir, DT_MIPMAP_CACHE_DEFAULT_FILE_NAME);
317 else
318 snprintf(mipmapfilename, size, "%s/%s-%s", cachedir, DT_MIPMAP_CACHE_DEFAULT_FILE_NAME, filename);
319
320 g_checksum_free(chk);
321 r = 0;
322
323exit:
324 dt_free(abspath);
325
326 return r;
327}
328
343static void _write_mipmap_to_disk(const int32_t imgid, char *filename, char *ext, gboolean *input_exists,
344 gboolean *is_jpg_input, gboolean *use_embedded_jpg, gboolean *write_to_disk)
345{
346 // Get file name
347 char _filename[DT_PATH_MAX] = { 0 };
348 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
349 if(filename || ext || input_exists || is_jpg_input)
350 {
351 const dt_image_path_source_t source = dt_image_choose_input_path(img, _filename, sizeof(_filename), FALSE);
352 if(source == DT_IMAGE_PATH_NONE) _filename[0] = '\0';
353 if(filename) strncpy(filename, _filename, DT_PATH_MAX);
354 if(input_exists) *input_exists = (source != DT_IMAGE_PATH_NONE);
355 }
356
357 // Get the file extension
358 if(ext || is_jpg_input)
359 {
360 char *_ext = _filename + strlen(_filename);
361 while(*_ext != '.' && _ext > _filename) _ext--;
362 if(ext) strncpy(ext, _ext, 5);
363 if(is_jpg_input) *is_jpg_input = !strcasecmp(_ext, ".jpg") || !strcasecmp(_ext, ".jpeg");
364 }
365
366 // embedded JPG mode:
367 // 0 = never use embedded thumbnail
368 // 1 = only on unedited pics,
369 // 2 = always use embedded thumbnail
370 int mode = _settings_get().embedded_jpg;
371 gboolean altered = FALSE;
372 if(mode == 1)
373 {
374 if(img) altered = img->history_items > 0;
375 }
376 const gboolean _use_embedded_jpg
377 = (mode == 2 // always use embedded
378 || (mode == 1 && !altered)); // use embedded only on unaltered images
379 if(use_embedded_jpg) *use_embedded_jpg = _use_embedded_jpg;
380
381 // Save to cache only if the option is enabled by user and the thumbnail is not the embedded thumbnail
382 // The rationale is getting the un-processed JPEG thumbnail is cheap and not worth saving on disk.
383 // This allows fast toggling between JPEG and processed RAW thumbnail from GUI.
384 if(write_to_disk)
385 {
386 *write_to_disk = _settings_get().disk_backend;
387 }
388
389 if(img)
391}
392
393
394static void _init_f(dt_mipmap_buffer_t *mipmap_buf, float *buf, uint32_t *width, uint32_t *height, float *iscale,
395 const int32_t imgid);
396static void _init_8(uint8_t *buf, uint32_t *width, uint32_t *height, float *iscale,
398 const dt_mipmap_size_t size, dt_atomic_int *shutdown);
399
433// Gets the address of the `buf->buf` pixel buffer, from the cache entry.
434static uint8_t *_get_buffer_from_dsc(struct dt_mipmap_buffer_dsc *dsc)
435{
436 if(dsc)
437 return (uint8_t *)(dsc + 1);
438 else
439 return NULL;
440}
441
442
444{
445 return (struct dt_mipmap_buffer_dsc *)entry->data;
446}
447
448// update buffer params with dsc
449static void _sync_dsc_to_buf(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid,
450 const dt_mipmap_size_t mip)
451{
452 buf->width = dsc->width;
453 buf->height = dsc->height;
454 buf->iscale = dsc->iscale;
455 buf->color_space = dsc->color_space;
456 buf->imgid = imgid;
457 buf->size = mip;
458 buf->buf = _get_buffer_from_dsc(dsc);
459}
460
461
462// flag a buffer as invalid and set it NULL
463// This doesn't paint skulls
465{
466 buf->width = buf->height = 0;
467 buf->iscale = 0.0f;
468 buf->buf = NULL;
469}
470
471
472static size_t _get_entry_size(const size_t buffer_size)
473{
474 return buffer_size + dt_mipmap_buffer_dsc_size;
475}
476
485 const size_t width, const size_t height,
486 const size_t buffer_size)
487{
488 if(IS_NULL_PTR(entry->data))
489 {
490 entry->data_size = 0;
491 *dsc = NULL;
492 return;
493 }
494
495 // Update the entry datasize
496 entry->data_size = _get_entry_size(buffer_size);
497
498 // Update the dsc parameters
499 *dsc = _get_dsc_from_entry(entry);
500 (*dsc)->width = width;
501 (*dsc)->height = height;
502 (*dsc)->iscale = 1.0f;
503 (*dsc)->color_space = DT_COLORSPACE_NONE;
505 (*dsc)->size = _get_entry_size(buffer_size);
506}
507
508
509// callback for the imageio core to allocate memory.
510// only needed for _FULL buffers, as they change size
511// with the input image. will allocate img->width*img->height*img->bpp bytes.
513{
514 if(IS_NULL_PTR(buf)) return NULL;
515
516 if(buf->size != DT_MIPMAP_FULL)
517 {
518 fprintf(stderr, "trying to alloc a wrong mipmap size for %s: %i (should be: %i)\n", img->filename, buf->size, DT_MIPMAP_FULL);
519 return NULL;
520 }
521
522 dt_cache_entry_t *entry = buf->cache_entry;
523
524 if(IS_NULL_PTR(entry))
525 {
526 fprintf(stderr, "trying to alloc a buffer entry that has no back-reference to cache entry\n");
527 return NULL;
528 }
529
530 /* Free and reset everything. ASan poisoning applies to live cache payloads only: after freeing
531 * the old allocation there is no valid user region left, and poisoning a NULL reset pointer makes
532 * the ASan runtime abort before it can report the real caller context. */
533 if(!IS_NULL_PTR(entry->data))
535 dt_free_align(entry->data);
536 entry->data = NULL;
537
538 // Get a new allocation
539 const int wd = img->width;
540 const int ht = img->height;
541 const size_t bpp = img->dsc.bpp;
542 const size_t buffer_size = wd * ht * bpp;
543 const size_t min_buffer_size = 64 * 4 * sizeof(float);
544 entry->data = dt_alloc_align(_get_entry_size(MAX(buffer_size, min_buffer_size)));
545 if(IS_NULL_PTR(entry->data)) return NULL;
546
547 // Update the references
548 struct dt_mipmap_buffer_dsc *dsc = NULL;
549 dt_mipmap_cache_update_buffer_addresses(entry, &dsc, wd, ht, buffer_size);
550
551 // Unpoison the pixel buffer region
552 ASAN_UNPOISON_MEMORY_REGION(buf->buf, dsc->size - dt_mipmap_buffer_dsc_size);
553
554 // Unpoison the dsc region
555 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
556
557 // So the padding region between dsc and buf->buf should be poisoned still
558 assert(entry->data == dsc);
559
560 if(dsc)
561 {
562 assert(entry->data_size == dsc->size);
563 }
564
565 // return pointer to start of payload
566 return _get_buffer_from_dsc(dsc);
567}
568
569// callback for the cache backend to initialize payload pointers
570// It's actually not dynamic at all, fixed size only.
572{
573 dt_mipmap_cache_t *cache = (dt_mipmap_cache_t *)data;
574 const dt_mipmap_size_t mip = get_size(entry->key);
575 const int32_t imgid = get_imgid(entry->key);
576
577 assert(mip < DT_MIPMAP_NONE);
578
579 // Free and reset everything
580 dt_free_align(entry->data);
581 entry->data = NULL;
582
583 // Get a new allocation
584 const size_t buffer_size = (mip <= DT_MIPMAP_F) ? cache->buffer_size[mip] : _get_entry_size(sizeof(float) * 4 * 64);
585 entry->data = dt_alloc_align(buffer_size);
586 if(IS_NULL_PTR(entry->data)) return;
587
588 // Update the references
589 struct dt_mipmap_buffer_dsc *dsc = NULL;
590 if(mip <= DT_MIPMAP_F)
591 dt_mipmap_cache_update_buffer_addresses(entry, &dsc, cache->max_width[mip], cache->max_height[mip], buffer_size);
592 else
593 dt_mipmap_cache_update_buffer_addresses(entry, &dsc, 0, 0, buffer_size);
594
595 if(IS_NULL_PTR(dsc)) return;
596
597 // Register the mipmap object, and the image cache object it links to. The image
598 // is usually already cached (so its allocate callback never fired), hence we
599 // register it here from the live dt_image_t; dt_supervisor_image() promotes the
600 // first sighting to a `create` so the link resolves.
602 {
603 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
604 if(img) dt_supervisor_image(DT_SV_UPDATE, imgid, img);
606 if(img) dt_image_cache_read_release(img);
607 }
608
609 gboolean write_to_disk;
610 _write_mipmap_to_disk(imgid, NULL, NULL, NULL, NULL, NULL, &write_to_disk);
611
612 if(cache->cachedir[0] && write_to_disk && mip < DT_MIPMAP_F)
613 {
614 // try and load from disk, if successful set flag
615 char filename[DT_PATH_MAX] = {0};
616 dt_mipmap_get_cache_filename(filename, mip, get_imgid(entry->key));
617
618 gboolean io_error = FALSE;
619 gchar *error = NULL;
620 uint8_t *blob = NULL;
623 FILE *f = NULL;
624
625 f = g_fopen(filename, "rb");
626 if(IS_NULL_PTR(f))
627 {
629 "[mipmap_cache] cached file for image %d at mip size %i does not exist\n",
630 imgid, mip);
631 goto finish; // file doesn't exist
632 }
633
634 fseek(f, 0, SEEK_END);
635 long len = ftell(f);
636 if(len <= 0)
637 {
638 error = "empty file";
639 io_error = TRUE;
640 goto finish;
641 }
642
643 blob = (uint8_t *)dt_alloc_align(len);
644 if(IS_NULL_PTR(blob))
645 {
646 error = "out of memory";
647 io_error = TRUE;
648 goto finish;
649 }
650
651 fseek(f, 0, SEEK_SET);
652 const size_t rd = fread(blob, sizeof(uint8_t), len, f);
653 if(rd != len)
654 {
655 error = "corrupted file";
656 io_error = TRUE;
657 goto finish;
658 }
659
660 if(dt_imageio_jpeg_decompress_header(blob, len, &jpg))
661 {
662 error = "couldn't decompress header";
663 io_error = TRUE;
664 goto finish;
665 }
666
668
670 {
671 error = "couldn't decompress JPEG";
672 io_error = TRUE;
673 goto finish;
674 }
675
676 dsc->width = jpg.width;
677 dsc->height = jpg.height;
678 dsc->iscale = 1.0f;
680 dsc->flags = 0;
681
682 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d at mip size %d (%ix%i) loaded from disk cache\n",
683 imgid, mip, jpg.width, jpg.height);
684
685finish:
686 if(f && io_error)
687 {
688 // Delete the file, we will regenerate it
689 g_unlink(filename);
690 fprintf(stderr, "[mipmap_cache] failed to open thumbnail for image %" PRIu32 " from `%s'. Reason: %s\n",
691 imgid, filename, error);
692 }
693
694 dt_free_align(blob);
695 if(f) fclose(f);
696 }
697
698 // cost is just flat one for the buffer, as the buffers might have different sizes,
699 // to make sure quota is meaningful.
700 if(mip >= DT_MIPMAP_F)
701 entry->cost = 1;
702 else
703 entry->cost = cache->buffer_size[mip];
704}
705
706static void dt_mipmap_cache_unlink_ondisk_thumbnail(void *data, int32_t imgid, dt_mipmap_size_t mip)
707{
708 dt_mipmap_cache_t *cache = (dt_mipmap_cache_t *)data;
709
710 // also remove jpg backing (always try to do that, in case user just temporarily switched it off,
711 // to avoid inconsistencies.
712 // if(_settings_get().disk_backend)
713 if(cache->cachedir[0])
714 {
715 char filename[DT_PATH_MAX] = { 0 };
716 dt_mipmap_get_cache_filename(filename, mip, imgid);
717 g_unlink(filename);
718 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %i for size %i was deleted from disk cache\n", imgid, mip);
719 }
720}
721
723{
724 dt_mipmap_cache_t *cache = (dt_mipmap_cache_t *)data;
725 const dt_mipmap_size_t mip = get_size(entry->key);
726
729 if(mip < DT_MIPMAP_F)
730 {
731 const int32_t imgid = get_imgid(entry->key);
732 gboolean write_to_disk;
733 _write_mipmap_to_disk(imgid, NULL, NULL, NULL, NULL, NULL, &write_to_disk);
734
735 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
736 // don't write skulls:
737 if(dsc->width > 8 && dsc->height > 8)
738 {
740 {
742 }
743 if(cache->cachedir[0] && write_to_disk && mip < DT_MIPMAP_F)
744 {
745 // serialize to disk
746 gchar cache_path[DT_PATH_MAX];
747 dt_mipmap_get_cache_dir(cache_path, mip);
748 const int mkd = g_mkdir_with_parents(cache_path, 0750);
749
750 if(!mkd)
751 {
752 char filename[DT_PATH_MAX] = {0};
753 dt_mipmap_get_cache_filename(filename, mip, get_imgid(entry->key));
754 // Don't write existing files as both performance and quality (lossy jpg) suffer
755 // FIXME: actually, yes, we write existing files too. See FIXME above.
756 FILE *f = NULL;
757 if((f = g_fopen(filename, "wb"))) // !g_file_test(filename, G_FILE_TEST_EXISTS)
758 {
759 // first check the disk isn't full
760 struct statvfs vfsbuf;
761 if (!statvfs(filename, &vfsbuf))
762 {
763 const int64_t free_mb = ((vfsbuf.f_frsize * vfsbuf.f_bavail) >> 20);
764 if (free_mb < 100)
765 {
766 fprintf(stderr, "Aborting image write as only %" PRId64 " MB free to write %s\n", free_mb, filename);
767 goto write_error;
768 }
769 }
770 else
771 {
772 fprintf(stderr, "Aborting image write since couldn't determine free space available to write %s\n", filename);
773 goto write_error;
774 }
775
776 const int cache_quality = _settings_get().cache_quality;
777 const uint8_t *exif = NULL;
778 int exif_len = 0;
780 {
783 }
784 else if(dsc->color_space == DT_COLORSPACE_ADOBERGB)
785 {
788 }
789 if(dt_imageio_jpeg_write(filename, _get_buffer_from_dsc(dsc), dsc->width, dsc->height,
790 MIN(100, MAX(10, cache_quality)), exif, exif_len))
791 {
792write_error:
793 g_unlink(filename);
794 }
795 else
796 {
797 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %i for size %i was written to cache at %s\n", imgid, mip, filename);
798 }
799 }
800 if(f) fclose(f);
801 }
802 }
803 }
804 }
805 dt_free_align(entry->data);
806 entry->data = NULL;
807}
808
809void dt_mipmap_cache_init(const dt_mipmap_cache_settings_t *settings, const gboolean verbose)
810{
811 _verbose = verbose;
812 if(!IS_NULL_PTR(settings)) dt_mipmap_cache_set_settings(settings);
813 if(_mipmap_cache) return;
814 _mipmap_cache = (dt_mipmap_cache_t *)calloc(1, sizeof(dt_mipmap_cache_t));
815 if(IS_NULL_PTR(_mipmap_cache)) return;
817 dt_mipmap_cache_get_filename(cache->cachedir, sizeof(cache->cachedir));
818
819 // Fixed sizes for the thumbnail mip levels, selected for coverage of most screen sizes
820 // Starting at 4K, we use 3:2 ratio of camera sensor instead of 16:9/16:10 of display,
821 // in order to start caching full-resolution JPEG for 100% zoom in lighttable
822 size_t mipsizes[DT_MIPMAP_F][2] = {
823 { 360, 225 }, // mip0 - 1/2 size previous one
824 { 720, 450 }, // mip1 - 1/2 size previous one
825 { 1440, 900 }, // mip2 - covers HD, WXGA+
826 { 1920, 1200 }, // mip3 - covers 1080p and 1600x1200
827 { 2560, 1600 }, // mip4 - covers 2560x1440
828 { 3840, 2560 }, // mip5 - covers 4K and UHD
829 { 5120, 3414 }, // mip6 - covers 5K
830 { 6144, 4096 }, // mip7 - covers 6K
831 { 7680, 5120 }, // mip8 - covers 8K
832 };
833 // Set mipf for preview pipe to 1440x900
834 cache->max_width[DT_MIPMAP_F] = mipsizes[DT_MIPMAP_2][0];
835 cache->max_height[DT_MIPMAP_F] = mipsizes[DT_MIPMAP_2][1];
836 for(int k = DT_MIPMAP_F-1; k >= 0; k--)
837 {
838 cache->max_width[k] = mipsizes[k][0];
839 cache->max_height[k] = mipsizes[k][1];
840 }
841 // header + buffer
842 for(int k = DT_MIPMAP_F-1; k >= 0; k--)
843 cache->buffer_size[k] = _get_entry_size(cache->max_width[k] * cache->max_height[k] * 4);
844
845 // clear stats:
846 cache->mip_thumbs.stats_requests = 0;
847 cache->mip_thumbs.stats_near_match = 0;
848 cache->mip_thumbs.stats_misses = 0;
849 cache->mip_thumbs.stats_fetches = 0;
850 cache->mip_thumbs.stats_standin = 0;
851 cache->mip_f.stats_requests = 0;
852 cache->mip_f.stats_near_match = 0;
853 cache->mip_f.stats_misses = 0;
854 cache->mip_f.stats_fetches = 0;
855 cache->mip_f.stats_standin = 0;
856 cache->mip_full.stats_requests = 0;
857 cache->mip_full.stats_near_match = 0;
858 cache->mip_full.stats_misses = 0;
859 cache->mip_full.stats_fetches = 0;
860 cache->mip_full.stats_standin = 0;
861
862 dt_cache_init(&cache->mip_thumbs.cache, 0, _settings_get().max_memory);
865
869 cache->buffer_size[DT_MIPMAP_FULL] = 0;
870
875 = _get_entry_size(4 * sizeof(float) * cache->max_width[DT_MIPMAP_F] * cache->max_height[DT_MIPMAP_F]);
876}
877
879{
881 if(IS_NULL_PTR(cache)) return;
886
888 _mipmap_cache = NULL;
889}
890
892{
894 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] thumbs fill %.2f/%.2f MB (%.2f%%)\n",
895 cache->mip_thumbs.cache.cost / (1024.0 * 1024.0),
896 cache->mip_thumbs.cache.cost_quota / (1024.0 * 1024.0),
897 100.0f * (float)cache->mip_thumbs.cache.cost / (float)cache->mip_thumbs.cache.cost_quota);
898 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] float fill %"PRIu32"/%"PRIu32" slots (%.2f%%)\n",
899 (uint32_t)cache->mip_f.cache.cost, (uint32_t)cache->mip_f.cache.cost_quota,
900 100.0f * (float)cache->mip_f.cache.cost / (float)cache->mip_f.cache.cost_quota);
901 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] full fill %"PRIu32"/%"PRIu32" slots (%.2f%%)\n",
902 (uint32_t)cache->mip_full.cache.cost, (uint32_t)cache->mip_full.cache.cost_quota,
903 100.0f * (float)cache->mip_full.cache.cost / (float)cache->mip_full.cache.cost_quota);
904
905 uint64_t sum = 0;
906 uint64_t sum_fetches = 0;
907 uint64_t sum_standins = 0;
908 sum += cache->mip_thumbs.stats_requests;
909 sum_fetches += cache->mip_thumbs.stats_fetches;
910 sum_standins += cache->mip_thumbs.stats_standin;
911 sum += cache->mip_f.stats_requests;
912 sum_fetches += cache->mip_f.stats_fetches;
913 sum_standins += cache->mip_f.stats_standin;
914 sum += cache->mip_full.stats_requests;
915 sum_fetches += cache->mip_full.stats_fetches;
916 sum_standins += cache->mip_full.stats_standin;
917 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] level | near match | miss | stand-in | fetches | total rq\n");
918 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] thumb | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%%\n",
919 100.0 * cache->mip_thumbs.stats_near_match / (float)cache->mip_thumbs.stats_requests,
920 100.0 * cache->mip_thumbs.stats_misses / (float)cache->mip_thumbs.stats_requests,
921 100.0 * cache->mip_thumbs.stats_standin / (float)sum_standins,
922 100.0 * cache->mip_thumbs.stats_fetches / (float)sum_fetches,
923 100.0 * cache->mip_thumbs.stats_requests / (float)sum);
924 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] float | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%%\n",
925 100.0 * cache->mip_f.stats_near_match / (float)cache->mip_f.stats_requests,
926 100.0 * cache->mip_f.stats_misses / (float)cache->mip_f.stats_requests,
927 100.0 * cache->mip_f.stats_standin / (float)sum_standins,
928 100.0 * cache->mip_f.stats_fetches / (float)sum_fetches,
929 100.0 * cache->mip_f.stats_requests / (float)sum);
930 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] full | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%% | %6.2f%%\n",
931 100.0 * cache->mip_full.stats_near_match / (float)cache->mip_full.stats_requests,
932 100.0 * cache->mip_full.stats_misses / (float)cache->mip_full.stats_requests,
933 100.0 * cache->mip_full.stats_standin / (float)sum_standins,
934 100.0 * cache->mip_full.stats_fetches / (float)sum_fetches,
935 100.0 * cache->mip_full.stats_requests / (float)sum);
937}
938
939// Collect one sub-cache's live entries (imgid + mip + real bytes) under its lock.
941{
942 dt_pthread_mutex_lock(&c->lock);
943 GHashTableIter it;
944 gpointer key, value;
945 g_hash_table_iter_init(&it, c->hashtable);
946 while(g_hash_table_iter_next(&it, &key, &value))
947 {
948 const dt_cache_entry_t *e = (const dt_cache_entry_t *)value;
949 if(!e) continue;
951 s.imgid = get_imgid(e->key);
952 s.mip = (int)get_size(e->key);
953 s.size = e->data_size; // real allocation (cost is byte-based for thumbs, slot-based for f/full)
954 g_array_append_val(out, s);
955 }
956 dt_pthread_mutex_unlock(&c->lock);
957}
958
959void dt_mipmap_cache_get_usage(size_t *current, size_t *max)
960{
962 if(current) *current = 0;
963 if(max) *max = 0;
964 if(IS_NULL_PTR(cache)) return;
965
966 // Real bytes currently held = sum of entry allocations across sub-caches.
968 size_t used = 0;
969 for(guint i = 0; i < entries->len; i++)
970 used += g_array_index(entries, dt_mipmap_cache_stats_entry_t, i).size;
971 g_array_free(entries, TRUE);
972
973 if(current) *current = used;
974 if(max) *max = _settings_get().max_memory; // user-specified mipmap RAM budget
975}
976
978{
980 GArray *out = g_array_new(FALSE, FALSE, sizeof(dt_mipmap_cache_stats_entry_t));
981 if(IS_NULL_PTR(cache)) return out;
985 return out;
986}
987
989{
990 switch(mip)
991 {
992 case DT_MIPMAP_FULL:
993 return &cache->mip_full;
994 case DT_MIPMAP_F:
995 return &cache->mip_f;
996 default:
997 return &cache->mip_thumbs;
998 }
999}
1000
1001// if we get a zero-sized image, paint skulls to signal a missing image
1002static void _paint_skulls(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid, const dt_mipmap_size_t mip)
1003{
1004 if(dsc->width == 0 || dsc->height == 0)
1005 {
1006 _cache_print(DT_DEBUG_CACHE, "[mipmap cache get] got a zero-sized image for img %u mip %d!\n", imgid, mip);
1007 if(mip < DT_MIPMAP_F)
1008 buf->buf = dead_image_8(dsc);
1009 else
1010 buf->buf = NULL; // full images with NULL buffer have to be handled, indicates `missing image', but still return locked slot
1011 }
1012}
1013
1014static void _validate_buffer(dt_mipmap_buffer_t *buf, struct dt_mipmap_buffer_dsc *dsc, const int32_t imgid,
1015 const dt_mipmap_size_t mip)
1016{
1017 // Unpoison the dsc region
1018 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1019
1020 // May update buf->buf:
1021 _sync_dsc_to_buf(buf, dsc, imgid, mip);
1022
1023 // Unpoison the pixel buffer region
1024 ASAN_UNPOISON_MEMORY_REGION(buf->buf, dsc->size - dt_mipmap_buffer_dsc_size);
1025}
1026
1027// Grab our own local copy of the image structure
1028static gboolean _get_image_copy(const int32_t imgid, dt_image_t *buffered_image)
1029{
1030 // load the image:
1031 // make sure we access the r/w lock as shortly as possible!
1032 gboolean no_buffer = TRUE;
1033 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
1034
1035 if(cimg)
1036 {
1037 *buffered_image = *cimg;
1038 no_buffer = FALSE;
1039 }
1040
1042
1043 return no_buffer;
1044}
1045
1046// Actually do the work: produce an image
1048 const int32_t imgid, const dt_mipmap_size_t mip, dt_atomic_int *shutdown)
1049{
1050 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1051 // Unpoison the descriptor before reading any field in this function.
1052 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1053 // _generate_blocking() can write directly into the cache pixel payload
1054 // (notably through _init_8() -> _write_image()), so unpoison it up-front.
1055 ASAN_UNPOISON_MEMORY_REGION(_get_buffer_from_dsc(dsc), dsc->size - dt_mipmap_buffer_dsc_size);
1056 const uint32_t original_width = dsc->width;
1057 const uint32_t original_height = dsc->height;
1058 const float original_iscale = dsc->iscale;
1059 const dt_colorspaces_color_profile_type_t original_color_space = dsc->color_space;
1060
1062 {
1063 // Already in cache, no I/O needed
1064 _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,
1065 dsc->width, dsc->height);
1066 _validate_buffer(buf, dsc, imgid, mip);
1067 return;
1068 }
1069
1070 if(mip == DT_MIPMAP_FULL)
1071 {
1072 dt_image_t buffered_image;
1073 if(_get_image_copy(imgid, &buffered_image)) return;
1074
1075 // Get the input file path, possibly from our local cache of copied images
1076 char filename[DT_PATH_MAX] = { 0 };
1077 dt_image_path_source_t source = dt_image_choose_input_path(&buffered_image, filename, sizeof(filename), FALSE);
1078 if(source == DT_IMAGE_PATH_NONE) return;
1079
1081 "[mipmap_cache] fetch image %i at mip size %d float32 (%ix%i) from original file I/O\n",
1082 imgid, mip, dsc->width, dsc->height);
1083
1084 // will call dt_mipmap_cache_alloc() internally:
1085 dt_imageio_retval_t ret = dt_imageio_open(&buffered_image, filename, buf);
1086
1087 // We need to update dsc because dt_imageio_open re-alloc entry->data
1088 dsc = _get_dsc_from_entry(entry);
1089
1090 if(ret == DT_IMAGEIO_OK)
1091 {
1092 // swap back new image data, may contain updated EXIF & colorspace
1093 dt_image_t *img = dt_image_cache_get(imgid, 'w');
1094 // dt_image_cache_get() returns NULL if the image vanished from the DB/cache while this
1095 // full-res I/O was in flight (e.g. removed from the library concurrently) -- nothing left
1096 // to swap the freshly-read data back into.
1097 if(!IS_NULL_PTR(img))
1098 {
1099 *img = buffered_image;
1101 }
1102 }
1103 else
1104 {
1105 dsc->width = dsc->height = 0;
1106 dsc->iscale = 0.f;
1107 }
1108 }
1109 else if(mip == DT_MIPMAP_F)
1110 {
1112 "[mipmap_cache] compute mip size %d float32 for image %i (%ix%i) from original file \n", mip,
1113 imgid, dsc->width, dsc->height);
1114 _init_f(buf, (float *)_get_buffer_from_dsc(dsc), &dsc->width, &dsc->height, &dsc->iscale, imgid);
1115 }
1116 else
1117 {
1118 // 8-bit thumbs
1120 "[mipmap_cache] compute mip size %d uint8 for image %i (%ix%i) from original file \n", mip,
1121 imgid, dsc->width, dsc->height);
1122 _init_8((uint8_t *)_get_buffer_from_dsc(dsc), &dsc->width, &dsc->height, &dsc->iscale, &dsc->color_space, imgid,
1123 mip, shutdown);
1124 }
1125
1126 if(shutdown && dt_atomic_get_int(shutdown))
1127 {
1128 /* A stale GUI request stopped its thumbnail/export pipe while this cache entry
1129 * was still being produced. Keep the entry marked as "generate" so the next
1130 * request can restart from a clean state instead of reusing a poisoned empty
1131 * thumbnail for the rest of the session. */
1132 dsc->width = original_width;
1133 dsc->height = original_height;
1134 dsc->iscale = original_iscale;
1135 dsc->color_space = original_color_space;
1136 _invalidate_buffer(buf);
1137 return;
1138 }
1139
1140 dsc->flags &= ~DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE;
1141 _paint_skulls(buf, dsc, imgid, mip);
1142 _validate_buffer(buf, dsc, imgid, mip);
1143
1144 _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,
1145 buf->width, buf->height, dsc->width, dsc->height, buf->buf);
1146}
1147
1148
1151 const char mode, const char *file, int line)
1152{
1153 dt_mipmap_cache_get_with_caller_and_shutdown(buf, imgid, mip, flags, mode, NULL, file, line);
1154}
1155
1157 const int32_t imgid, const dt_mipmap_size_t mip,
1158 const dt_mipmap_get_flags_t flags, const char mode,
1159 dt_atomic_int *shutdown, const char *file, int line)
1160{
1162 assert(mip <= DT_MIPMAP_NONE && mip >= DT_MIPMAP_0);
1163
1164 const int32_t key = get_key(imgid, mip);
1165
1166 // Allocation functions will check those if we run them
1167 buf->imgid = imgid;
1168 buf->size = mip;
1169
1171 {
1172 // simple case: only get and lock if it's there.
1173 dt_cache_entry_t *entry = dt_cache_testget(&_get_cache(cache, mip)->cache, key, mode);
1174 buf->cache_entry = entry;
1175
1176 if(entry)
1177 _validate_buffer(buf, _get_dsc_from_entry(entry), imgid, mip);
1178 else
1179 _invalidate_buffer(buf);
1180 }
1181 else if(flags == DT_MIPMAP_BLOCKING)
1182 {
1183 // simple case: blocking get
1184 dt_cache_entry_t *entry = dt_cache_get_with_caller(&_get_cache(cache, mip)->cache, key, mode, file, line);
1185 buf->cache_entry = entry;
1186 __sync_fetch_and_add(&(_get_cache(cache, mip)->stats_fetches), 1);
1187 _generate_blocking(entry, buf, imgid, mip, shutdown);
1188
1189 // image cache is leaving the write lock in place in case the image has been newly allocated.
1190 // this leads to a slight increase in thread contention, so we opt for dropping the write lock
1191 // and acquiring a read lock immediately after. since this opens a small window for other threads
1192 // to get in between, we need to take some care to re-init cache entries and dsc.
1193 // note that concurrencykit has rw locks that can be demoted from w->r without losing the lock in between.
1194 if(mode == 'r')
1195 {
1196 entry->_lock_demoting = 1;
1197 // drop the write lock
1198 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1199 // get a read lock
1200 entry = dt_cache_get(&_get_cache(cache, mip)->cache, key, mode);
1201 entry->_lock_demoting = 0;
1202 }
1203
1204 buf->cache_entry = entry;
1205
1206 }
1207 else
1208 {
1209 _invalidate_buffer(buf);
1210 }
1211}
1212
1213void dt_mipmap_cache_write_get_with_caller(dt_mipmap_buffer_t *buf, const int32_t imgid, const int mip, const char *file, int line)
1214{
1215 dt_mipmap_cache_get_with_caller(buf, imgid, mip, DT_MIPMAP_BLOCKING, 'w', file, line);
1216}
1217
1219 int line)
1220{
1222 if(buf->size == DT_MIPMAP_NONE || IS_NULL_PTR(buf->cache_entry)) return;
1223 assert(buf->imgid > 0);
1224 assert(buf->size >= DT_MIPMAP_0);
1225 assert(buf->size < DT_MIPMAP_NONE);
1226 dt_cache_release_with_caller(&_get_cache(cache, buf->size)->cache, buf->cache_entry, file, line);
1227 buf->size = DT_MIPMAP_NONE;
1228 buf->buf = NULL;
1229}
1230
1231
1232// return index dt_mipmap_size_t having at least width & height requested instead of minimum combined diff
1233// please note that the requested size is in pixels not dots.
1235 const int32_t height, const uint32_t imgid)
1236{
1238 for(int k = DT_MIPMAP_0; k < DT_MIPMAP_F; k++)
1239 {
1240 // We assume a "fit" situation, typically rectangle within square
1241 // so we don't need both dimensions to be greater than requested
1242 if((cache->max_width[k] >= width) || (cache->max_height[k] >= height))
1243 {
1244 _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]);
1245 return k;
1246 }
1247 }
1248 return DT_MIPMAP_F - 1;
1249}
1250
1252 const int32_t height, const uint32_t imgid)
1253{
1255 for(int k = DT_MIPMAP_F - 1; k >= DT_MIPMAP_0; k--)
1256 {
1257 if((cache->max_width[k] <= width) && (cache->max_height[k] <= height))
1258 {
1259 _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]);
1260 return k;
1261 }
1262 }
1263 return DT_MIPMAP_0;
1264}
1265
1266void dt_mipmap_cache_swap_at_size(const int32_t imgid, const dt_mipmap_size_t mip, const uint8_t *const in,
1267 const int32_t width, const int32_t height, dt_colorspaces_color_profile_type_t profile)
1268{
1270 if(mip >= DT_MIPMAP_F || mip < DT_MIPMAP_0) return;
1271
1272 const uint32_t key = get_key(imgid, mip);
1273 dt_cache_entry_t *entry = dt_cache_get_with_caller(&_get_cache(cache, mip)->cache, key, 'w', __FILE__, __LINE__);
1274 if(entry)
1275 {
1276 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1277 // Unpoison descriptor and pixel payload before reading/writing either.
1278 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1279 ASAN_UNPOISON_MEMORY_REGION(_get_buffer_from_dsc(dsc), dsc->size - dt_mipmap_buffer_dsc_size);
1280 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] image %d is synchronized from pipeline at size %i (%ix%i->%ix%i)\n",
1281 imgid, mip, width, height, dsc->width, dsc->height);
1282
1283 // Downscale
1284 dsc->iscale = 1.f;
1285 const int32_t wd = dsc->width;
1286 const int32_t ht = dsc->height;
1287 uint8_t *buf =(uint8_t *)_get_buffer_from_dsc(dsc);
1288 dt_iop_flip_and_zoom_8(in, width, height, buf, wd, ht,
1289 ORIENTATION_NONE, &dsc->width, &dsc->height);
1290
1291 /* Thumbnails are stored in AdobeRGB. In place: the conversion also swaps BGRA back
1292 * to RGBA, which is what happens even when no transform could be built. */
1293 dt_colorprofiles_bgra8_to_adobergb_rgba8(buf, buf, dsc->width, dsc->height, profile);
1294
1296 dsc->flags &= ~DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE;
1297 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1298 }
1299}
1300
1301
1302void dt_mipmap_cache_remove_at_size(const int32_t imgid, const dt_mipmap_size_t mip, const gboolean flush_disk)
1303{
1305 if(mip >= DT_MIPMAP_F || mip < DT_MIPMAP_0) return;
1306
1307 // get rid of all ldr thumbnails:
1308 const uint32_t key = get_key(imgid, mip);
1309 dt_cache_entry_t *entry = dt_cache_testget(&_get_cache(cache, mip)->cache, key, 'w');
1310 if(entry)
1311 {
1312 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1313 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1314 if(flush_disk) dsc->flags |= DT_MIPMAP_BUFFER_DSC_FLAG_INVALIDATE;
1315 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1316 dt_cache_remove(&_get_cache(cache, mip)->cache, key);
1317 }
1318 if(flush_disk)
1319 {
1320 // directly remove the file on disk cache even if we don't have a memory entry
1321 dt_mipmap_cache_unlink_ondisk_thumbnail(cache, imgid, mip);
1322 }
1323}
1324
1325// get rid of all ldr thumbnails:
1326void dt_mipmap_cache_remove(const int32_t imgid, const gboolean flush_disk)
1327{
1329 dt_mipmap_cache_remove_at_size(imgid, k, flush_disk);
1330}
1331
1332/* Same as _remove_at_size(), minus the "thumbnails only" guard, so the two input sizes can
1333 * be dropped by the one caller entitled to: the image is going away. DT_MIPMAP_F and
1334 * DT_MIPMAP_FULL are RAM-only -- every disk write is gated on mip < DT_MIPMAP_F -- so there
1335 * is no file to unlink for them and flush_disk has nothing to do. */
1336static void _remove_one_size(const int32_t imgid, const dt_mipmap_size_t mip)
1337{
1339 const uint32_t key = get_key(imgid, mip);
1340 dt_cache_entry_t *entry = dt_cache_testget(&_get_cache(cache, mip)->cache, key, 'w');
1341 if(entry)
1342 {
1343 struct dt_mipmap_buffer_dsc *dsc = _get_dsc_from_entry(entry);
1344 ASAN_UNPOISON_MEMORY_REGION(dsc, dt_mipmap_buffer_dsc_size);
1346 dt_cache_release(&_get_cache(cache, mip)->cache, entry);
1347 dt_cache_remove(&_get_cache(cache, mip)->cache, key);
1348 }
1349}
1350
1351void dt_mipmap_cache_remove_all_sizes(const int32_t imgid, const gboolean flush_disk)
1352{
1353 dt_mipmap_cache_remove(imgid, flush_disk);
1356}
1357
1358// write thumbnail to disc if not existing there
1359void dt_mimap_cache_evict(const int32_t imgid)
1360{
1363 dt_cache_remove(&_get_cache(cache, k)->cache, get_key(imgid, k));
1364}
1365
1366static void _init_f(dt_mipmap_buffer_t *mipmap_buf, float *out, uint32_t *width, uint32_t *height, float *iscale,
1367 const int32_t imgid)
1368{
1369 const uint32_t wd = *width, ht = *height;
1370
1371 /* do not even try to process file if it isn't available */
1372 char filename[DT_PATH_MAX] = { 0 };
1374 {
1375 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1376 if(img)
1377 {
1378 source = dt_image_choose_input_path(img, filename, sizeof(filename), FALSE);
1380 }
1381 }
1382 if(source == DT_IMAGE_PATH_NONE)
1383 {
1384 *width = *height = 0;
1385 *iscale = 0.0f;
1386 return;
1387 }
1388
1391
1392 // lock image after we have the buffer, we might need to lock the image struct for
1393 // writing during raw loading, to write to width/height.
1394 const dt_image_t *image = dt_image_cache_get(imgid, 'r');
1395
1396 dt_iop_roi_t roi_in, roi_out;
1397 roi_in.x = roi_in.y = 0;
1398 roi_in.width = image->width;
1399 roi_in.height = image->height;
1400 roi_in.scale = 1.0f;
1401
1402 roi_out.x = roi_out.y = 0;
1403
1404 // now let's figure out the scaling...
1405
1406 // MIP_F is 4 channels, and we do not demosaic here
1407 roi_out.scale = fminf(((float)wd) / (float)image->width, ((float)ht) / (float)image->height);
1408 roi_out.width = roi_out.scale * roi_in.width;
1409 roi_out.height = roi_out.scale * roi_in.height;
1410
1411 if(IS_NULL_PTR(buf.buf) || buf.width == 0 || buf.height == 0)
1412 {
1414 *width = *height = 0;
1415 *iscale = 0.0f;
1416 return;
1417 }
1418
1419 mipmap_buf->color_space = DT_COLORSPACE_NONE; // TODO: do we need that information in this buffer?
1420
1421 if(image->dsc.filters)
1422 {
1423 if(image->dsc.filters != 9u && image->dsc.datatype == TYPE_FLOAT)
1424 {
1425 dt_iop_clip_and_zoom_mosaic_half_size_f((float *const)out, (const float *const)buf.buf, &roi_out, &roi_in,
1426 roi_out.width, roi_in.width, image->dsc.filters);
1427 }
1428 else if(image->dsc.filters != 9u && image->dsc.datatype == TYPE_UINT16)
1429 {
1430 dt_iop_clip_and_zoom_mosaic_half_size((uint16_t * const)out, (const uint16_t *)buf.buf, &roi_out, &roi_in,
1431 roi_out.width, roi_in.width, image->dsc.filters);
1432 }
1433 else if(image->dsc.filters == 9u && image->dsc.datatype == TYPE_UINT16)
1434 {
1435 dt_iop_clip_and_zoom_mosaic_third_size_xtrans((uint16_t * const)out, (const uint16_t *)buf.buf, &roi_out,
1436 &roi_in, roi_out.width, roi_in.width, image->dsc.xtrans);
1437 }
1438 else if(image->dsc.filters == 9u && image->dsc.datatype == TYPE_FLOAT)
1439 {
1440 dt_iop_clip_and_zoom_mosaic_third_size_xtrans_f(out, (const float *)buf.buf, &roi_out, &roi_in,
1441 roi_out.width, roi_in.width, image->dsc.xtrans);
1442 }
1443 else
1444 {
1446 }
1447 }
1448 else
1449 {
1450 // downsample
1451 dt_iop_clip_and_zoom(out, (const float *)buf.buf, &roi_out, &roi_in, roi_out.width, roi_in.width);
1452 }
1453
1455
1456 *width = roi_out.width;
1457 *height = roi_out.height;
1458 *iscale = (float)image->width / (float)roi_out.width;
1459
1461}
1462
1463
1464// dummy functions for `export' to mipmap buffers:
1470
1472{
1473 return IMAGEIO_RGB | IMAGEIO_INT8;
1474}
1475
1477{
1478 return 8;
1479}
1480
1481static int _write_image(dt_imageio_module_data_t *data, const char *filename, const void *in,
1482 dt_colorspaces_color_profile_type_t over_type, const char *over_filename,
1483 void *exif, int exif_len, int32_t imgid, int num, int total, dt_dev_pixelpipe_t *pipe,
1484 const gboolean export_masks)
1485{
1486 _dummy_data_t *d = (_dummy_data_t *)data;
1487 memcpy(d->buf, in, sizeof(uint32_t) * data->width * data->height);
1488 return 0;
1489}
1490
1491static int _load_jpg(const char *filename, const int32_t imgid, const uint32_t wd, const uint32_t ht,
1492 const dt_mipmap_size_t size, const dt_image_orientation_t orientation, uint8_t *buf,
1494{
1495 int res = 1;
1497 if(!dt_imageio_jpeg_read_header(filename, &jpg))
1498 {
1500 uint8_t *tmp = (uint8_t *)dt_alloc_align(sizeof(uint8_t) * jpg.width * jpg.height * 4);
1501 if(tmp && !dt_imageio_jpeg_read(&jpg, tmp))
1502 {
1503 // scale to fit
1504 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generate mip size %d for image %d from jpeg\n", size, imgid);
1505 dt_iop_flip_and_zoom_8(tmp, jpg.width, jpg.height, buf, wd, ht, orientation, width, height);
1506 res = 0;
1507 }
1508 dt_free_align(tmp);
1509 }
1510 return res;
1511}
1512
1513static int _find_sidecar_jpg(const char *filename, const char *ext, char *sidecar)
1514{
1515 const size_t filename_len = strlen(filename) - strlen(ext);
1516 const char *exts[4] = { ".jpg", ".JPG", ".jpeg", ".JPEG" };
1517
1518 for(int i = 0; i < 4; i++)
1519 {
1520 // Damage control. Should never happen.
1521 if(filename_len + strlen(exts[i]) >= DT_PATH_MAX)
1522 continue;
1523
1524 // Construct the sidecar filename
1525 const size_t str_copy = g_snprintf(sidecar, DT_PATH_MAX, "%.*s%s", (int)filename_len, filename, exts[i]);
1526
1527 // Check if the filename was too long or if the copy failed.
1528 if (str_copy == 0 || str_copy >= DT_PATH_MAX)
1529 continue;
1530
1531 if(g_file_test(sidecar, G_FILE_TEST_EXISTS))
1532 return 1;
1533 }
1534
1535 return 0;
1536}
1537
1538static void _init_8(uint8_t *buf, uint32_t *width, uint32_t *height, float *iscale,
1540 const dt_mipmap_size_t size, dt_atomic_int *shutdown)
1541{
1542 if(size >= DT_MIPMAP_F || *width < 16 || *height < 16) return;
1543
1544 *iscale = 1.0f;
1545 const uint32_t wd = *width, ht = *height;
1546
1547 char filename[DT_PATH_MAX] = { 0 };
1548 char ext[6] = { 0 };
1549 gboolean input_exists, is_jpg_input, use_embedded_jpg;
1550 const int embedded_jpg_mode = _settings_get().embedded_jpg;
1551 _write_mipmap_to_disk(imgid, filename, ext, &input_exists, &is_jpg_input, &use_embedded_jpg, NULL);
1552
1553 /* do not even try to process file if it isn't available */
1554 if(!input_exists)
1555 {
1556 *width = *height = 0;
1557 *iscale = 0.0f;
1559 return;
1560 }
1561
1562 int res = 1;
1563
1564 // try to generate mip from larger mip
1565 // This expects that invalid mips will be flushed, so the assumption is:
1566 // if mip then it's valid (with regard to current history)
1567 if(res && !use_embedded_jpg && size < DT_MIPMAP_F - 1)
1568 {
1569 for(dt_mipmap_size_t k = size + 1; k < DT_MIPMAP_F; k++)
1570 {
1572 dt_mipmap_cache_get(&tmp, imgid, k, DT_MIPMAP_TESTLOCK, 'r');
1573 if(IS_NULL_PTR(tmp.buf)) continue;
1574
1575 *color_space = tmp.color_space;
1576 // downsample
1577 dt_iop_flip_and_zoom_8(tmp.buf, tmp.width, tmp.height, buf, wd, ht, ORIENTATION_NONE, width, height);
1578 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generate mip size %d for image %d from mip size %d (%ix%i->%ix%i)\n",
1579 size, imgid, k, tmp.width, tmp.height, *width, *height);
1580
1582 res = 0;
1583 break;
1584 }
1585 }
1586
1587 // Orientation and camera framing are only needed when loading embedded JPEGs.
1589 dt_boundingbox_t usercrop = { 0.f, 0.f, 1.f, 1.f };
1590 if(use_embedded_jpg)
1591 {
1592 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1593 if(img)
1594 {
1595 orientation = (img->orientation != ORIENTATION_NULL) ? img->orientation : ORIENTATION_NONE;
1597 }
1598
1599 // Resolve outside the read lock: this path never decodes the raw, so the framing may still
1600 // have to be read from the file, and storing the answer needs the write lock.
1601 dt_image_resolve_usercrop(imgid, usercrop);
1602 }
1603
1604 if(res && use_embedded_jpg)
1605 {
1606 char sidecar_filename[DT_PATH_MAX] = { 0 };
1607
1608 if(is_jpg_input)
1609 {
1610 // Input file is a JPEG
1611 res = _load_jpg(filename, imgid, wd, ht, size, orientation, buf, width, height, color_space);
1612 }
1613 else if(_find_sidecar_jpg(filename, ext, sidecar_filename))
1614 {
1615 // input file is a RAW but we have a companion JPEG file in the same folder:
1616 // use it in priority (it may be higher resolution/quality than embedded JPEG).
1617 res = _load_jpg(sidecar_filename, imgid, wd, ht, size, orientation, buf, width, height, color_space);
1618 }
1619 else
1620 {
1621 // input file is a RAW without companion JPEG:
1622 // try to load the embedded thumbnail. Might not be large enough though.
1623 uint8_t *tmp = NULL;
1624 int32_t thumb_width, thumb_height;
1625 res = dt_imageio_large_thumbnail(filename, &tmp, &thumb_width, &thumb_height, color_space, *width, *height);
1626 if(!res)
1627 {
1628 // We take the thumbnail no matter its size. It might be too small for the requested dimension,
1629 // and end up blurry. But it's less bad than the following scenario:
1630 // 1. user displays collections in grid of 5 columns (small thumbnail -> fetch embedded JPEG for performance, all good),
1631 // 2. user zooms in the grid, to 2 or 3 columns,
1632 // 3. suddently, embedded JPEG is too small so we ditch it for a full pipe recompute,
1633 // 4. but then, color/contrast/appearance unexpectedly changes and user doesn't understand WTF just happened.
1634 // Blurry is less bad than randomly inconsistent, plus user has a GUI way in lighttable to
1635 // change how thumbs are processed at runtime.
1636 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generate mip size %d for image %d from embedded jpeg\n", size, imgid);
1637 // The camera renders its embedded previews from the full default-crop rectangle even when
1638 // it recorded a narrower framing, so trim them to what the photographer actually framed.
1639 // Only here: the two branches above read a separate JPEG file, which the camera already
1640 // wrote cropped. This runs before the rotation below, hence the un-oriented box.
1641 dt_imageio_crop_thumbnail(usercrop, tmp, &thumb_width, &thumb_height);
1642 dt_iop_flip_and_zoom_8(tmp, thumb_width, thumb_height, buf, wd, ht, orientation, width, height);
1644 }
1645 }
1646 }
1647
1648 if(res)
1649 {
1650 if(embedded_jpg_mode == 2)
1651 {
1653 "[mipmap_cache] embedded JPEG mode forbids raw processing for image %d at mip %d\n",
1654 imgid, size);
1655 *width = *height = 0;
1656 *iscale = 0.0f;
1658 return;
1659 }
1660
1661 // try the real thing: rawspeed + pixelpipe
1663 _dummy_data_t dat;
1664 format.bpp = _bpp;
1665 format.write_image = _write_image;
1666 format.levels = _levels;
1667 dat.head.max_width = wd;
1668 dat.head.max_height = ht;
1669 dat.buf = buf;
1670 // export with flags: ignore exif (don't load from disk), don't swap byte order, don't do hq processing,
1671 // no upscaling and signal we want thumbnail export
1672 res = dt_imageio_export_with_flags(imgid, "unused", &format, (dt_imageio_module_data_t *)&dat, TRUE, FALSE, FALSE,
1674 NULL, 1, 1, NULL, shutdown);
1675 if(!res)
1676 {
1677 _cache_print(DT_DEBUG_CACHE, "[mipmap_cache] generated mip %d for image %d from scratch\n", size, imgid);
1678 // might be smaller, or have a different aspect than what we got as input.
1679 *width = dat.head.width;
1680 *height = dat.head.height;
1681 *iscale = 1.0f;
1683 }
1684 }
1685
1686 // any errors?
1687 if(res)
1688 {
1689 fprintf(stderr, "[mipmap_cache] could not process thumbnail!\n");
1690 *width = *height = 0;
1691 *iscale = 0.0f;
1693 }
1694}
1695
1696void dt_mipmap_cache_copy_thumbnails(const uint32_t dst_imgid, const uint32_t src_imgid)
1697{
1699 gboolean write_to_disk_src, write_to_disk_dst;
1700 _write_mipmap_to_disk(src_imgid, NULL, NULL, NULL, NULL, NULL, &write_to_disk_src);
1701 _write_mipmap_to_disk(dst_imgid, NULL, NULL, NULL, NULL, NULL, &write_to_disk_dst);
1702
1703 if(cache->cachedir[0] && write_to_disk_src && write_to_disk_dst)
1704 {
1705 for(dt_mipmap_size_t mip = DT_MIPMAP_0; mip < DT_MIPMAP_F; mip++)
1706 {
1707 // try and load from disk, if successful set flag
1708 char srcpath[DT_PATH_MAX] = {0};
1709 char dstpath[DT_PATH_MAX] = {0};
1710 dt_mipmap_get_cache_filename(srcpath, mip, src_imgid);
1711 dt_mipmap_get_cache_filename(dstpath, mip, dst_imgid);
1712 GFile *src = g_file_new_for_path(srcpath);
1713 GFile *dst = g_file_new_for_path(dstpath);
1714 GError *gerror = NULL;
1715 g_file_copy(src, dst, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror);
1716 // ignore errors, we tried what we could.
1717 g_object_unref(dst);
1718 g_object_unref(src);
1719 g_clear_error(&gerror);
1720 }
1721 }
1722}
1723
1724// clang-format off
1725// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1726// vim: shiftwidth=2 expandtab tabstop=2 cindent
1727// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1728// 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
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:2381
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
const gchar * dt_database_get_path(void)
Definition database.c:3787
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:301
@ DT_IMAGE_PATH_NONE
Definition image.h:302
float dt_boundingbox_t[4]
Definition image.h:83
dt_image_orientation_t
Definition image.h:216
@ ORIENTATION_NULL
Definition image.h:217
@ ORIENTATION_NONE
Definition image.h:218
dt_imageio_retval_t
Definition image.h:91
@ DT_IMAGEIO_OK
Definition image.h:92
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
int bpp
int 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, gboolean is_scaling, const gboolean thumbnail_export, 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:38
@ 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 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)
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
static void _init_8(uint8_t *buf, uint32_t *width, uint32_t *height, float *iscale, dt_colorspaces_color_profile_type_t *color_space, const int32_t imgid, const dt_mipmap_size_t size, dt_atomic_int *shutdown)
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 void _write_mipmap_to_disk(const int32_t imgid, char *filename, char *ext, gboolean *input_exists, gboolean *is_jpg_input, gboolean *use_embedded_jpg, gboolean *write_to_disk)
Check if an image should be written to disk, if the thumbnail should be computed from embedded JPEG,...
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)
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
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)
dt_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
static dt_mipmap_size_t get_size(const uint32_t key)
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_INVALIDATE
@ DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE
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 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:2674
#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)
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:397
dt_image_orientation_t orientation
Definition image.h:366
int32_t width
Definition image.h:397
uint32_t history_items
Definition image.h:403
dt_iop_buffer_dsc_t dsc
Definition image.h:419
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
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_colorspaces_color_profile_type_t color_space
dt_cache_entry_t * cache_entry
dt_mipmap_size_t size
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).
gboolean disk_backend
Write generated thumbnails to the on-disk cache (cache_disk_backend).
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