dev-doc/pixelpipe_architecture.md
The pixelpipe is the core image processing engine of darktable. It is responsible for taking an input image (RAW or raster), passing it through a series of modules (IOPs), and producing an output for display (darkroom, thumbnail) or export.
dt_dev_pixelpipe_tDefined in src/develop/pixelpipe_hb.h.
This structure represents a single instance of a processing pipeline. A dt_develop_t (the main development state) holds several pipes:
dev->full.pipe: Main darkroom center view (via dt_dev_viewport_t full).dev->preview_pipe: Navigation/overview preview (direct member of dt_develop_t).dev->preview2.pipe: Second darkroom window (via dt_dev_viewport_t preview2).Key members include:
nodes: A GList of dt_dev_pixelpipe_iop_t representing the processing chain.image: The dt_image_t being processed.input: The source image data (float buffer).cache: The hash-based pixel cache (dt_dev_pixelpipe_cache_t).input_timestamp: Timestamp of the input data, used for invalidation.bypass_blendif: (boolean) If true, blending is bypassed (e.g., for mask display).mask_display: Controls how/if masks are displayed in the UI.dt_dev_pixelpipe_iop_tDefined in src/develop/pixelpipe_hb.h.
Represents a specific instance of a module within a pipe. While dt_iop_module_t represents the module's global state and settings, dt_dev_pixelpipe_iop_t ("piece") holds the state specific to one execution context.
Key members:
module: Pointer to the logic definition (dt_iop_module_t).data: Pointer to the module's parameters (the C struct).enabled: Whether this node is active in this run.roi_in, roi_out: Regions of Interest (see below).blendop_data: Pointer to the blending parameters for this instance.histogram: Histogram data for this module.process_cl_ready: (boolean) Flag indicating if OpenCL processing is ready/possible.process_tiling_ready: (boolean) Flag indicating if tiled processing is ready/possible.dt_dev_pixelpipe_change() checks flags to see what changed (history, params, zoom).dt_dev_pixelpipe_synch_all(): Iterates over the history stack.commit_params() on modules to copy parameters from the global module state to the pipe-specific piece->data.dt_dev_pixelpipe_process() is the main driver.
modify_roi_in() on each module to ask: "If I need this output area, what input area do you need?".
pixelpipe_cache). If a hash matches, it reuses the buffer.process() (or process_cl for OpenCL).Darktable employs a sophisticated caching mechanism to avoid redundant processing. This is implemented in src/develop/pixelpipe_cache.c.
Each module instance in the pipeline (dt_dev_pixelpipe_iop_t) maintains a hash that represents its state.
The hash is computed cumulatively. For a given module at position $N$, the hash depends on:
pipe->input_profile_info, pipe->work_profile_info, pipe->output_profile_info). Because colour profile changes are committed globally rather than per-module, they cannot be tracked in individual piece->hash values and are instead included in the base hash for every cache lookup.piece->hash, which covers operation name, instance, params, and blending).When dt_iop_commit_params is called (usually after a param change), the piece->hash is updated. This hash change propagates down the pipeline.
dt_dev_pixelpipe_cache_t)The cache stores processed buffers keyed by these hashes. When the pipeline runs, it checks if a valid buffer exists for the current hash at each stage.
process() method is executed.Cache invalidation is handled via dt_dev_pixelpipe_cache_invalidate_later and dt_dev_pixelpipe_cache_flush.
invalidate_later(pipe, order): Invalidates all cache lines for modules with iop_order >= order.flush(pipe): Invalidates everything.Debugging flags:
-d pipe shows cache hits/misses.-d memory shows cache memory usage.Darktable processes images in chunks (tiles) or just the visible area to save memory and improve performance.
roi_out: The region the module must produce.roi_in: The region the module needs from the previous module.For simple point operations (exposure, curves), roi_in == roi_out.
For geometric operations (lens correction, rotate), roi_in is a transformed version of roi_out.
For neighborhood operations (blur, sharpen), roi_in is slightly larger than roi_out (padding).
The pixelpipe is designed to be threaded.
DT_OMP_FOR) within their process() function to parallelize loops.process_cl() callbacks. The pipe handles data transfer to/from the GPU.Two key pipeline operations iterate modules in different orders:
commit_params() runs in forward pipe order (e.g., temperature before channelmixerrgb). This is the normal processing direction._dt_dev_load_pipeline_defaults() runs in reverse pipe order (e.g., channelmixerrgb before temperature). This happens during history reset and default loading.This asymmetry matters for modules that communicate via shared state. For example, temperature.c writes white balance coefficients into dev->chroma.wb_coeffs, and channelmixerrgb.c reads them during commit_params(). During forward processing, temperature commits first and the data is available. But during reverse-order default loading, channelmixerrgb runs first — before temperature has written its values. This caused a bug where stale values from a previous image or history state influenced the defaults.
Consequence: Shared state (like dev->chroma) must be properly reset before reverse-order iteration to prevent this class of ordering-dependent bug.
The parameters in dt_dev_pixelpipe_iop_t->data are binary blobs defined by the module's params_t struct. The introspection system allows the core to copy, hash, and store these blobs without knowing their internal structure.