Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
dev_backbuf.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Guillaume STUTIN.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14*/
15
22#include "views/dev_backbuf.h"
23#include "common/colorspaces_inline_conversions.h" // dt_Lab_to_XYZ(), dt_XYZ_to_sRGB()
24#include "common/conf.h"
25
26#include "widgets/bauhaus.h"
28#include "system/macros.h"
29#include "system/simd.h"
30#include "gui/application.h"
31#include "control/redraw.h"
32#include "develop/develop.h"
35#include "develop/supervisor.h"
36
37#include <pango/pangocairo.h>
38
39static void _colormanage_ui_color(const float L, const float a, const float b, dt_aligned_pixel_t RGB)
40{
41 dt_aligned_pixel_t Lab = { L, a, b, 1.f };
42 dt_aligned_pixel_t XYZ = { 0.f, 0.f, 0.f, 1.f };
45}
46
48{
49 // user param is Lab lightness/brightness (which is the same for greys)
50 if(dev->iso_12646.enabled)
51 _colormanage_ui_color(50., 0., 0., bg_color);
52 else
53 _colormanage_ui_color((float)dt_conf_get_int("display/brightness"), 0., 0., bg_color);
54}
55
56void dt_dev_draw_iso12646_border(cairo_t *cr, double width, double height, int border)
57{
58 /* the white frame around the picture: the ring only, the picture is painted over the middle
59 * anyway and the middle is most of the window */
60 cairo_save(cr);
61 cairo_rectangle(cr, -border * .5f, -border * .5f, width + border, height + border);
62 cairo_rectangle(cr, 1.0, 1.0, width - 2.0, height - 2.0);
64 cairo_set_source_rgb(cr, 1., 1., 1.);
65 cairo_fill(cr);
66 cairo_restore(cr);
67}
68
70{
71 /* One snapshot: reading mode twice could print a label for a mode that has already
72 * changed between the guard and the text. */
75 if(settings.mode == DT_PROFILE_NORMAL) return;
76
77 gchar *label = settings.mode == DT_PROFILE_GAMUTCHECK ? _("gamut check") : _("soft proof");
78 cairo_set_source_rgba(cri, 0.5, 0.5, 0.5, 0.5);
79 PangoLayout *layout;
80 PangoRectangle ink;
86 pango_layout_set_text(layout, label, -1);
88 cairo_move_to(cri, ink.height * 2, height - (ink.height * 3));
90 cairo_set_source_rgb(cri, 0.7, 0.7, 0.7);
93 cairo_set_source_rgb(cri, 0.3, 0.3, 0.3);
96 g_object_unref(layout);
97}
98
100{
101 if(IS_NULL_PTR(locked)) return;
102
103 if(locked->surface)
104 {
106 locked->surface = NULL;
107 }
108
109 /* These cairo views only mirror whatever cacheline the pipeline currently exposes as backbuffer.
110 * They never own the backbuffer keepalive ref themselves: `pixelpipe_hb.c` swaps that ownership
111 * when publishing a new backbuffer. Releasing the surface must therefore only drop the GUI view. */
112 locked->entry = NULL;
113 locked->data = NULL;
115 locked->width = 0;
116 locked->height = 0;
117}
118
119static void _dev_backbuf_restart_cache_wait(gpointer user_data)
120{
121 dt_develop_t *dev = (dt_develop_t *)user_data;
122 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
124}
125
128 gboolean keep_previous_on_fail)
129{
130 if(IS_NULL_PTR(dev) || IS_NULL_PTR(pipe) || IS_NULL_PTR(locked)) return FALSE;
131 if(!IS_NULL_PTR(wait))
133
134 /* ONE read of the publication. The hash names the cacheline this surface will be built on and
135 * the dimensions say what shape its pixels are; reading them separately lets a republication
136 * land in between and pairs a cacheline with the next frame's width -- which is a wrong cairo
137 * stride, i.e. diagonal striping. See dt_backbuf_t. */
139 const uint64_t hash = published.hash;
141
142 /* Fast-path reuse is only valid if the cacheline identity/data pointer are
143 * still the same behind the hash key. This avoids reusing stale cairo views
144 * when an entry is recreated or replaced under the same key. */
146 void *live_data = NULL;
147 if(!IS_NULL_PTR(locked->surface) && locked->hash == hash
150 && live_entry == locked->entry && live_data == locked->data
151 && locked->width == (int)published.width && locked->height == (int)published.height)
152 {
153 return TRUE;
154 }
155
156 /* Note what this fast path must NOT do: assign the published dimensions to `locked' and keep
157 * the existing surface. That surface was created with a stride baked in from the dimensions it
158 * was built at, so writing new ones over it describes the cairo surface falsely -- and it also
159 * defeats the slow path's own shape check below, which compares against exactly these fields.
160 * A publication that changes the shape must build a new surface, which is what falling through
161 * to the slow path does. */
162
163 struct dt_pixel_cache_entry_t *entry = NULL;
164 /* GUI surfaces only borrow the currently published backbuffer. They rely on the backbuffer keepalive ref
165 * owned by `pixelpipe_hb.c`, so they must not take or drop their own cache refs here. */
166 void *data = NULL;
168 data = NULL;
169 if(IS_NULL_PTR(data))
170 {
171 /* Keep previous frame only while waiting for a *different* target hash.
172 * If requested hash equals the currently locked one but cache lookup fails,
173 * the cached line was likely flushed/invalidated: drop stale lock so the
174 * line can be recreated and displayed again. */
175 if(keep_previous_on_fail && !IS_NULL_PTR(locked->surface) && locked->hash != hash) return TRUE;
177 return FALSE;
178 }
179
180 const int width = (int)published.width;
181 const int height = (int)published.height;
183 const size_t required_size = (size_t)stride * (size_t)height;
184 const size_t entry_size = dt_pixel_cache_entry_get_size(entry);
185 if(width <= 0 || height <= 0 || entry_size < required_size || dt_pixel_cache_entry_get_data(entry) != data)
186 {
187 if(keep_previous_on_fail && !IS_NULL_PTR(locked->surface) && locked->hash != hash) return TRUE;
189 return FALSE;
190 }
191
192 // The widget is about to (re)bind its surface to this backbuffer cacheline.
193 // Emitted off the fast-path, so it fires when the displayed buffer changes,
194 // not on every identical expose.
197
198 if(!IS_NULL_PTR(locked->surface) && locked->data == data && locked->width == width && locked->height == height)
199 {
200 locked->hash = hash;
201 locked->entry = entry;
202 locked->data = data;
203 return TRUE;
204 }
205
207 if(IS_NULL_PTR(surface) || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS)
208 {
209 if(!IS_NULL_PTR(surface)) cairo_surface_destroy(surface);
210 if(keep_previous_on_fail && !IS_NULL_PTR(locked->surface)) return TRUE;
212 return FALSE;
213 }
214
216 locked->hash = hash;
217 locked->width = width;
218 locked->height = height;
219 locked->data = data;
220 locked->entry = entry;
221 locked->surface = surface;
222 return TRUE;
223}
224
226 const int width, const int height, const int border,
227 const dt_aligned_pixel_t bg_color)
228{
229 if(IS_NULL_PTR(cr) || IS_NULL_PTR(dev) || IS_NULL_PTR(locked) || IS_NULL_PTR(locked->surface)) return FALSE;
230 if(IS_NULL_PTR(locked->entry) || locked->hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
231
232 int wd = locked->width;
233 int ht = locked->height;
234 if(wd <= 0 || ht <= 0) return FALSE;
235
236 wd /= dt_gui_get_global()->ppd;
237 ht /= dt_gui_get_global()->ppd;
238 const double x0 = .5 * (width - wd);
239 const double y0 = .5 * (height - ht);
240
241 /* The background where the image will not cover: the four bands around it, as one even-odd
242 * fill. The hole is a pixel smaller than the image on every side so the blit overlaps it and
243 * no seam shows between the band and an antialiased image edge. A fill of the whole window
244 * under an image that covers most of it was a full pass for nothing: 5.9 ms of a frame at a
245 * 2560x1440 window on a 2x screen, against 1.3 ms for the bands. */
246 cairo_save(cr);
247 cairo_rectangle(cr, 0, 0, width, height);
248 cairo_rectangle(cr, x0 + 1.0, y0 + 1.0, wd - 2.0, ht - 2.0);
250 cairo_set_source_rgb(cr, bg_color[0], bg_color[1], bg_color[2]);
251 cairo_fill(cr);
252 cairo_restore(cr);
253
254 cairo_translate(cr, x0, y0);
255
256 if(dev->iso_12646.enabled) dt_dev_draw_iso12646_border(cr, wd, ht, border);
257
260 cairo_rectangle(cr, 0, 0, wd, ht);
261 cairo_set_source_surface(cr, locked->surface, 0, 0);
262 /* pixel for pixel on a matching target; say so, rather than leave the default filter to
263 * discover a mismatch the expensive way */
265 cairo_fill(cr);
267
268 return TRUE;
269}
270
272 const char *wait_owner_tag, cairo_t *cr, dt_develop_t *dev, int width,
273 int height, int border, const dt_aligned_pixel_t bg_color,
274 gboolean keep_previous_on_fail)
275{
276 if(IS_NULL_PTR(dev)) return FALSE;
278 return FALSE;
279 if(IS_NULL_PTR(locked->surface)) return FALSE;
280 return dt_dev_render_locked_surface(cr, dev, locked, width, height, border, bg_color);
281}
282
283// clang-format off
284// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
285// vim: shiftwidth=2 expandtab tabstop=2 cindent
286// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
287// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_colorprofiles_xyz_to_display(const dt_aligned_pixel_t XYZ, dt_aligned_pixel_t RGB)
Convert one D50 XYZ pixel to display RGB.
void dt_colorprofiles_get_settings(dt_colorprofiles_settings_t *const out)
Copy the current settings into caller-provided storage, under one lock.
The colour-profile module's API: which profiles exist, and how to apply one.
dt_Lab_to_XYZ(Lab, XYZ)
static dt_aligned_pixel_t XYZ
static dt_aligned_pixel_t Lab
static dt_aligned_pixel_t RGB
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
void dt_dev_draw_iso12646_border(cairo_t *cr, double width, double height, int border)
Definition dev_backbuf.c:56
gboolean dt_dev_lock_pipe_surface(dt_develop_t *dev, dt_dev_pixelpipe_t *pipe, dt_dev_locked_surface_t *locked, dt_dev_pixelpipe_cache_wait_t *wait, const char *wait_owner_tag, gboolean keep_previous_on_fail)
gboolean dt_dev_render_locked_surface(cairo_t *cr, const dt_develop_t *dev, dt_dev_locked_surface_t *locked, const int width, const int height, const int border, const dt_aligned_pixel_t bg_color)
static void _colormanage_ui_color(const float L, const float a, const float b, dt_aligned_pixel_t RGB)
Definition dev_backbuf.c:39
gboolean dt_dev_paint_main_backbuf(dt_dev_locked_surface_t *locked, dt_dev_pixelpipe_cache_wait_t *wait, const char *wait_owner_tag, cairo_t *cr, dt_develop_t *dev, int width, int height, int border, const dt_aligned_pixel_t bg_color, gboolean keep_previous_on_fail)
void dt_dev_get_background_color(const dt_develop_t *dev, dt_aligned_pixel_t bg_color)
Definition dev_backbuf.c:47
static void _dev_backbuf_restart_cache_wait(gpointer user_data)
void dt_dev_release_locked_surface(dt_dev_locked_surface_t *locked)
Definition dev_backbuf.c:99
void dt_dev_draw_profile_mode_label(cairo_t *cri, int height)
Definition dev_backbuf.c:69
void dt_dev_pixelpipe_cache_wait_set_owner(dt_dev_pixelpipe_cache_wait_t *wait, const char *owner_tag, gpointer owner_object)
Attach debug ownership metadata to one cache wait request.
gboolean dt_dev_pixelpipe_cache_peek_gui(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, void **data, dt_pixel_cache_entry_t **cache_entry, dt_dev_pixelpipe_cache_wait_t *wait, dt_dev_pixelpipe_cache_ready_callback_t restart, gpointer restart_data)
#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
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
void * dt_pixel_cache_entry_get_data(dt_pixel_cache_entry_t *entry)
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
size_t dt_pixel_cache_entry_get_size(dt_pixel_cache_entry_t *entry)
Peek the size (in bytes) reserved for the host buffer of a cache entry.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
static dt_backbuf_state_t dt_dev_backbuf_snapshot(const dt_backbuf_t *backbuf)
Read the whole record as one publication.
@ DT_PROFILE_GAMUTCHECK
@ DT_PROFILE_NORMAL
Asking the interface to repaint itself.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
unsigned __int64 uint64_t
Definition strptime.c:75
One coherent publication, by value.
Consistent snapshot of the display and soft-proofing settings.
dt_colorspaces_color_mode_t mode
NORMAL / SOFTPROOF / GAMUTCHECK.
struct dt_pixel_cache_entry_t * entry
Definition dev_backbuf.h:45
cairo_surface_t * surface
Definition dev_backbuf.h:46
dt_backbuf_t backbuf
dt_dev_pixelpipe_type_t type
int32_t gui_attached
Definition develop.h:167
gboolean enabled
Definition develop.h:370
struct dt_develop_t::@18 iso_12646
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
uint64_t hash
void * data
dt_atomic_uint64 age
One consumer's outstanding request, owned by that consumer, not by the queue.
void dt_supervisor_widget(const dt_sv_op_t op, const char *widget_tag, const uint64_t consumed_hash, const int pipe_type, const int32_t imgid)
@ DT_SV_READ
Definition supervisor.h:80
static gboolean dt_supervisor_active(void)
Definition supervisor.h:95
#define DT_PIXEL_APPLY_DPI(value)