Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
geometry.c File Reference
#include "develop/geometry/geometry.h"
#include "common/logging.h"
#include "develop/dev_geometry.h"
#include "develop/dev_history.h"
#include "develop/develop.h"
#include "develop/imageop.h"
#include "system/mem_alloc.h"
#include <math.h>
#include <string.h>
+ Include dependency graph for geometry.c:

Go to the source code of this file.

Data Structures

struct  dt_geometry_chain_t
 

Functions

static gboolean _on_roster (const char *op)
 
static void _record_free (void *ptr)
 
static void _chain_clear (dt_geometry_chain_t *chain)
 
dt_geometry_chain_tdt_geometry_chain_new (void)
 
void dt_geometry_chain_free (dt_geometry_chain_t *chain)
 
static gint _by_iop_order (gconstpointer a, gconstpointer b)
 
static gboolean _suppressed_by_focus (const dt_geometry_chain_t *chain, const dt_geometry_record_t *record)
 Is this record disabled for the current query by the focused module?
 
static void _fold_sizes (dt_geometry_chain_t *chain)
 Fold every record's map_size in pipe order, at full resolution and scale 1.
 
static gboolean _resolve_from_history (dt_develop_t *dev, dt_iop_module_t *module, const int32_t history_end, const void **params)
 What will this module be committed with? Mirrors the pipe's own resolution.
 
void dt_geometry_chain_rebuild (dt_develop_t *dev)
 Rebuild the chain from the dev's current modules and history. GUI thread only.
 
uint64_t dt_geometry_chain_generation (const dt_geometry_chain_t *chain)
 How many times this chain has been rebuilt. A GUI cache key for anything derived from the composed geometry.
 
gboolean dt_geometry_chain_authoritative (const dt_geometry_chain_t *chain)
 Can this chain answer questions yet?
 
gboolean dt_geometry_chain_processed_size (const dt_geometry_chain_t *chain, int *width, int *height)
 The developed image's full-resolution size, from the chain's own fold.
 
const dt_geometry_record_tdt_geometry_chain_find (const dt_geometry_chain_t *chain, const char *op, const int instance)
 One module instance's record, or NULL. Use it for that module's own in/out dims.
 
static gboolean _in_bound (const dt_geometry_record_t *record, const double iop_order, const int direction)
 Does this record fall inside the requested bound?
 
static int _compose_forward (dt_geometry_chain_t *chain, const double iop_order, const int direction, float *points, const size_t points_count)
 The forward fold over a chain, shared by the public entry point and by dt_geometry_chain_compose(). Assumes the chain is usable; the callers check.
 
int dt_geometry_module_transform (dt_develop_t *dev, const dt_iop_module_t *module, float *points, const size_t points_count)
 
int dt_geometry_chain_compose (dt_geometry_chain_t *chain, const double iop_order, const int direction, float *points, const size_t points_count)
 Compose the chain over points, for a record evaluator that needs the transform stack around its own module.
 
int dt_geometry_transform (dt_develop_t *dev, const double iop_order, const int direction, float *points, const size_t points_count)
 Compose forward over the chain, in place. direction is a DT_DEV_TRANSFORM_DIR_*.
 
int dt_geometry_backtransform (dt_develop_t *dev, const double iop_order, const int direction, float *points, const size_t points_count)
 Compose backward over the chain, in place.
 
void dt_geometry_self_check (dt_develop_t *dev, const double chain_ms)
 What the shadow harness became once there was nothing left to shadow.
 

Variables

static const char *const _roster []
 

Function Documentation

◆ _by_iop_order()

static gint _by_iop_order ( gconstpointer  a,
gconstpointer  b 
)
static

Definition at line 131 of file geometry.c.

References dt_geometry_record_t::iop_order.

Referenced by dt_geometry_chain_rebuild().

◆ _chain_clear()

◆ _compose_forward()

static int _compose_forward ( dt_geometry_chain_t chain,
const double  iop_order,
const int  direction,
float *  points,
const size_t  points_count 
)
static

The forward fold over a chain, shared by the public entry point and by dt_geometry_chain_compose(). Assumes the chain is usable; the callers check.

Definition at line 372 of file geometry.c.

References _in_bound(), _suppressed_by_focus(), dt_geometry_record_t::data, IS_NULL_PTR, dt_geometry_chain_t::records, dt_geometry_vtable_t::transform, and dt_geometry_record_t::vtable.

Referenced by dt_geometry_chain_compose(), and dt_geometry_transform().

◆ _fold_sizes()

static void _fold_sizes ( dt_geometry_chain_t chain)
static

Fold every record's map_size in pipe order, at full resolution and scale 1.

Reproduces dt_dev_pixelpipe_get_roi_out(): seed the input rect from the raw dimensions at scale 1, hand each enabled module its input and take its output as the next module's input, and record both on the way through. Those per-record rects are a product in their own right – consumers read their own module's input/output dimensions off them, and not only the geometric modules do (graduatednd has no geometry callbacks and reads its output rect for its overlay).

A record with no vtable, or one suppressed by the focused module, is identity. The suppression matters for SIZE and not only for coordinates: the fold this mirrors clears piece->enabled for such modules, so the developed size genuinely changes when the user enters crop's edit mode.

Definition at line 179 of file geometry.c.

References _suppressed_by_focus(), dt_geometry_record_t::data, dt_geometry_record_t::in, IS_NULL_PTR, dt_geometry_vtable_t::map_size, dt_geometry_record_t::out, dt_geometry_chain_t::processed_height, dt_geometry_chain_t::processed_width, dt_geometry_chain_t::raw_height, dt_geometry_chain_t::raw_width, dt_geometry_chain_t::records, dt_geometry_chain_t::sized, dt_geometry_record_t::vtable, and rect::width.

Referenced by dt_geometry_chain_rebuild().

◆ _in_bound()

static gboolean _in_bound ( const dt_geometry_record_t record,
const double  iop_order,
const int  direction 
)
static

Does this record fall inside the requested bound?

The five modes are dt_dev_distort_transform_plus()'s, reproduced exactly. Every existing caller's bound has to keep meaning what it meant – the mask GUI in particular composes BACK_EXCL up to a module, shifts, then FORW_INCL back out, and a bound that shifted by one module would move every clone source by that module's transform.

Definition at line 357 of file geometry.c.

References DT_DEV_TRANSFORM_DIR_ALL, DT_DEV_TRANSFORM_DIR_BACK_EXCL, DT_DEV_TRANSFORM_DIR_BACK_INCL, DT_DEV_TRANSFORM_DIR_FORW_EXCL, DT_DEV_TRANSFORM_DIR_FORW_INCL, dt_geometry_record_t::iop_order, and TRUE.

Referenced by _compose_forward(), and dt_geometry_backtransform().

◆ _on_roster()

static gboolean _on_roster ( const char *  op)
static

Definition at line 90 of file geometry.c.

References _roster, FALSE, i, IS_NULL_PTR, and TRUE.

Referenced by dt_geometry_chain_rebuild().

◆ _record_free()

static void _record_free ( void ptr)
static

Definition at line 98 of file geometry.c.

References dt_geometry_record_t::data, dt_free, dt_geometry_record_t::free_data, and IS_NULL_PTR.

Referenced by _chain_clear().

◆ _resolve_from_history()

static gboolean _resolve_from_history ( dt_develop_t dev,
dt_iop_module_t module,
const int32_t  history_end,
const void **  params 
)
static

What will this module be committed with? Mirrors the pipe's own resolution.

dt_dev_pixelpipe_change() decides a piece's parameters and its enabled state in two ways, and the chain has to reproduce both or it describes a geometry the pipes are not rendering:

  • IOP_FLAGS_NO_HISTORY_STACK modules are committed from their DEFAULTS (dt_dev_pixelpipe_sync_no_history), because they enable and disable themselves;
  • every other module takes the last history item at or before history_end, falling back to its defaults when it has none (_sync_pipe_nodes_from_history).

What it must NOT read is module->params and module->enabled. Those are the GUI thread's live values, and dt_dev_add_history_item() is throttled, so between an edit and its commit they are ahead of what any pipe has been told – which shadow mode caught as a size divergence while the crop module's piece was mid-transition.

Parameters
paramsout: the blob to hand geometry_record(). Borrowed, valid while history_mutex is held.
Returns
the enabled state the pipe will use.

Definition at line 223 of file geometry.c.

References dt_dev_history_item_t::enabled, enabled, dt_develop_t::history, IOP_FLAGS_NO_HISTORY_STACK, IS_NULL_PTR, and dt_dev_history_item_t::params.

Referenced by dt_geometry_chain_rebuild().

◆ _suppressed_by_focus()

static gboolean _suppressed_by_focus ( const dt_geometry_chain_t chain,
const dt_geometry_record_t record 
)
static

Is this record disabled for the current query by the focused module?

The chain's copy of dt_dev_pixelpipe_activemodule_disables_currentmodule(). It is evaluated HERE, per query, and never stored in a record: the inputs are the focused module's identity, its tag filter, and whether it is in editing mode – all of which change with no history commit, which is precisely why records cannot carry them.

Definition at line 148 of file geometry.c.

References dt_geometry_chain_t::dev, dt_iop_get_cache_bypass(), FALSE, dt_develop_t::gui_attached, dt_develop_t::gui_module, dt_geometry_record_t::instance, IS_NULL_PTR, dt_iop_module_t::multi_priority, dt_geometry_record_t::op, dt_iop_module_t::op, and dt_geometry_record_t::operation_tags.

Referenced by _compose_forward(), _fold_sizes(), dt_geometry_backtransform(), and dt_geometry_module_transform().

◆ dt_geometry_backtransform()

int dt_geometry_backtransform ( dt_develop_t dev,
const double  iop_order,
const int  direction,
float *  points,
const size_t  points_count 
)

◆ dt_geometry_chain_authoritative()

gboolean dt_geometry_chain_authoritative ( const dt_geometry_chain_t chain)

Can this chain answer questions yet?

TRUE only when every enabled module the roster names has published a record. Authority is WHOLESALE: composing some modules from records and the rest from pipeline pieces would interleave two states, and the result would be wrong in a way that looks plausible.

There is no longer anything to fall back TO – the pipe this replaced is deleted – so a FALSE here is not a degraded mode, it is the GUI declining to answer: sizes come back FALSE, the transforms return 0 and leave their points untouched. Before an image is loaded that is simply the truth. After one is, it is a defect, and dt_geometry_self_check names which module owes a record.

Definition at line 324 of file geometry.c.

References dt_geometry_chain_t::authoritative, and IS_NULL_PTR.

Referenced by dt_dev_module_geometry_gui(), dt_dev_processed_size_gui(), gui_post_expose(), and main().

◆ dt_geometry_chain_compose()

int dt_geometry_chain_compose ( dt_geometry_chain_t chain,
double  iop_order,
int  direction,
float *  points,
size_t  points_count 
)

Compose the chain over points, for a record evaluator that needs the transform stack around its own module.

The nested case, and the reason dt_geometry_vtable_t hands every evaluator the chain. iop/liquify.c is the one that needs it: its warps are stored in RAW sensor coordinates, so before it can rasterise anything it has to push its own path nodes through everything upstream of itself. On the pixel pipe it does that by re-entering the pipe walker mid-walk; here it re-enters this.

Bounded exactly like the walkers, so BACK_EXCL of the caller's own iop_order excludes the caller and the recursion terminates. Do not call it with a bound that includes the caller.

Definition at line 412 of file geometry.c.

References _compose_forward(), and IS_NULL_PTR.

Referenced by _distort_paths().

◆ dt_geometry_chain_find()

const dt_geometry_record_t * dt_geometry_chain_find ( const dt_geometry_chain_t chain,
const char *  op,
const int  instance 
)

One module instance's record, or NULL. Use it for that module's own in/out dims.

Definition at line 337 of file geometry.c.

References dt_geometry_record_t::data, dt_geometry_record_t::instance, IS_NULL_PTR, dt_geometry_record_t::op, and dt_geometry_chain_t::records.

Referenced by _flip_swaps_axes(), and dt_dev_module_geometry_gui().

◆ dt_geometry_chain_free()

void dt_geometry_chain_free ( dt_geometry_chain_t chain)

Definition at line 124 of file geometry.c.

References _chain_clear(), dt_free, and IS_NULL_PTR.

Referenced by dt_dev_cleanup().

◆ dt_geometry_chain_generation()

uint64_t dt_geometry_chain_generation ( const dt_geometry_chain_t chain)

How many times this chain has been rebuilt. A GUI cache key for anything derived from the composed geometry.

A consumer that caches something it composed through this service – a mask outline in image coordinates, say – needs to know when to throw that cache away. The answer is "when the geometry moved", and this counter is that, cheaply: it advances once per rebuild, and a rebuild happens exactly where a pipe flag is raised, i.e. where the module stack or the history changed.

It is deliberately NOT a content hash. A rebuild that lands on identical geometry still advances it, which costs a consumer one redundant recompute; the alternative – hashing each record's module-owned data blob, whose size this service does not know – would have to guess, and a missed change here is an overlay drawn in the wrong place. Over-invalidating is the safe direction for a key.

What it must NOT be replaced by is a PIXEL identity. Keying an outline cache on a pipe's backbuffer hash, which is what iop/masks did before, ties a geometric fact to a rendering event: every republished preview frame – continuous while a brush is being dragged – then invalidates outlines whose inputs did not change. Measured on the report in #1158: 566 rebuilds of two brush outlines in 80 seconds, ~2 s of coordinate transform, gravity centres recomputed bit-identical every time, and a darkroom expose growing from 23 ms to 137 ms as strokes accumulated.

Returns
0 for a chain that has never been built, which no live generation can equal.

Definition at line 319 of file geometry.c.

References dt_geometry_chain_t::generation, and IS_NULL_PTR.

Referenced by _masks_draw_creation_session_forms(), dt_masks_gui_form_create(), dt_masks_gui_form_create_throttled(), and dt_masks_gui_form_test_create().

◆ dt_geometry_chain_new()

dt_geometry_chain_t * dt_geometry_chain_new ( void  )

Definition at line 119 of file geometry.c.

Referenced by dt_dev_init(), and main().

◆ dt_geometry_chain_processed_size()

gboolean dt_geometry_chain_processed_size ( const dt_geometry_chain_t chain,
int *  width,
int *  height 
)

The developed image's full-resolution size, from the chain's own fold.

Definition at line 329 of file geometry.c.

References FALSE, height, IS_NULL_PTR, dt_geometry_chain_t::processed_height, dt_geometry_chain_t::processed_width, dt_geometry_chain_t::sized, TRUE, and width.

Referenced by dt_dev_get_thumbnail_size(), and dt_dev_processed_size_gui().

◆ dt_geometry_chain_rebuild()

void dt_geometry_chain_rebuild ( struct dt_develop_t dev)

◆ dt_geometry_module_transform()

◆ dt_geometry_self_check()

void dt_geometry_self_check ( dt_develop_t dev,
const double  chain_ms 
)

What the shadow harness became once there was nothing left to shadow.

Check the chain against itself, and report what the rebuild cost.

It used to compare every answer against the pixel-less pipe, which was the right check while the pipe still owned them – it is what caught the chain composing modules the pipe was suppressing, and it is why the focus exception is evaluated live. That reference is gone with the pipe, so what is left has to test the chain against ITSELF, and only identities that a wrong chain can actually fail are worth printing.

Two of them are:

  • the round trip. transform then backtransform over the whole chain must return the point it started from. Every evaluator's inverse is exercised, and an inverse derived in the wrong frame fails it – which is the question flip's 90-degree orientations pose.
  • the partition. The bounds are a cut of the same ordered list, so composing the two halves must equal composing all of it: FORW_INCL(x) after BACK_EXCL(x) is DIR_ALL, and so is FORW_EXCL(x) after BACK_INCL(x), for x at every module's own iop_order. This is the bound bookkeeping the module GUIs depend on – they ask for their own iop_order, never DIR_ALL – and an off-by-one in _in_bound() drops or repeats exactly one module here.

What it can no longer catch is a chain that is self-consistently wrong: both halves of a partition composing the same wrong subset still add up. That check needed a second implementation, and keeping a whole pipeline alive to be one was the cost this service exists to remove. Under ‘-d dev’; never changes behaviour.

Definition at line 473 of file geometry.c.

References dt_geometry_chain_t::authoritative, dt_geometry_record_t::data, DT_DEBUG_DEV, DT_DEV_TRANSFORM_DIR_ALL, DT_DEV_TRANSFORM_DIR_BACK_EXCL, DT_DEV_TRANSFORM_DIR_BACK_INCL, DT_DEV_TRANSFORM_DIR_FORW_EXCL, DT_DEV_TRANSFORM_DIR_FORW_INCL, dt_free, dt_geometry_backtransform(), dt_geometry_transform(), dt_get_debug_flags(), dt_print(), dt_geometry_record_t::enabled, dt_develop_t::geometry_chain, i, dt_geometry_record_t::instance, dt_geometry_record_t::iop_order, IS_NULL_PTR, dt_geometry_chain_t::missing, names, dt_geometry_record_t::op, dt_geometry_chain_t::processed_height, dt_geometry_chain_t::processed_width, dt_geometry_chain_t::raw_height, dt_geometry_chain_t::raw_width, dt_geometry_chain_t::records, and dt_geometry_record_t::vtable.

Referenced by dt_dev_get_thumbnail_size().

◆ dt_geometry_transform()

int dt_geometry_transform ( dt_develop_t dev,
const double  iop_order,
const int  direction,
float *  points,
const size_t  points_count 
)

Compose forward over the chain, in place. direction is a DT_DEV_TRANSFORM_DIR_*.

Definition at line 419 of file geometry.c.

References _compose_forward(), dt_geometry_chain_t::authoritative, dt_develop_t::geometry_chain, and IS_NULL_PTR.

Referenced by dt_dev_coordinates_raw_abs_to_image_abs(), dt_dev_distort_transform_gui(), and dt_geometry_self_check().

Variable Documentation

◆ _roster

const char* const _roster[]
static
Initial value:
= {
"rawprepare", "basebuffer", "demosaic", "lens", "ashift", "liquify", "rotatepixels",
"scalepixels", "flip", "clipping", "crop", "borders",
}

THE ROSTER.

Which modules owe this service a record. It is a hand-maintained list and cannot be anything else: dt_iop_module_t's geometry callbacks are bound through the DEFAULT() macros in common/module_api.h, so EVERY module has a non-NULL, no-op distort_transform() and an identity modify_roi_out(). There is no runtime predicate for "is this a geometry module" to test.

Membership is by source audit (doc/geometry-service.md ยง1): the 15 modules that implement modify_roi_out plus the 11 that implement distort_transform, minus the two that implement neither in a way this service is concerned with.

NOT on the roster, deliberately:

  • retouch, spots: identity modify_roi_out and no point transform at all. Their modify_roi_in expands the read window for source patches, which is a RENDERING concern – it reads live dev->forms on the pipeline thread and exists so a clone source is inside the tile. Nothing about it belongs to GUI geometry.
  • initialscale, finalscale: no modify_roi_out whatsoever, so identity in a scale-1 fold, and no point transform. finalscale's enabled state additionally depends on pipe type and zoom, which is exactly the kind of per-pipe fact a pipe-less service must not try to own.
  • useless: the module template, not built.

A module added to this list without a geometry_record() implementation keeps the chain non-authoritative forever. That used to be the safe direction because consumers fell back to the pixel-less pipe; there is no fallback now, so it is instead the LOUD direction – sizes come back FALSE and the darkroom cannot lay itself out, with the missing modules named under ‘-d dev’. Wholesale authority is still the rule it enforces: composing some modules from records and the rest from somewhere else interleaves two states, and the result is wrong in a way that looks plausible.

Definition at line 61 of file geometry.c.

Referenced by _on_roster().