Say that a mask layer's settings add to the photograph's

fc54523 (0.18.1) stopped running a layer as a second chain after every
global operation and made its settings offsets applied at each
operation's own place, and nothing outside the code said so. The
manual still read as though a mask's slider were a setting of its own,
frame-budget.md described the masks as "a separate chain per layer",
and no document said how a layer is blended at all.

architecture.md §5.2 now says it: the offset, the blend by the layer's
weighted difference from the global result, what a photograph with no
layers composes to, and the film as the one operation whose settings
are averaged instead (6b99f67). FR-DEV-3 records the change as resolved
beside its local-adjustments bullet, frame-budget.md's note on what it
does not measure describes the cost as it now is, and the manual gives
the arithmetic in one sentence: -20 in a mask over -30 is -50 there.
The bundled manual is regenerated to match.
This commit is contained in:
2026-09-27 07:59:29 -04:00
parent 6bccc2db13
commit 7558053932
5 changed files with 33 additions and 5 deletions
+12
View File
@@ -390,6 +390,18 @@ keeps a star: real light arrives through a lens and lights a patch, so its neigh
6×6 sensor-anchored colour tile serves Bayer and X-Trans alike, every path that demosaics gets it, 6×6 sensor-anchored colour tile serves Bayer and X-Trans alike, every path that demosaics gets it,
and there is no setting. and there is no setting.
**A mask layer runs inside this chain, not after it.** Its settings are offsets to the global ones
(`mask::offset_onto`), and each operation a layer touches is composed at the operation's own place:
the global fragment and each layer's combined fragment read the same input, and the pixel moves by
each layer's weighted difference, `c_g + Σ wᵢ(cᵢ − c_g)`. At full weight that is the combined
setting exactly and at zero the global result exactly, so global contrast −30 under a layer at −20
is contrast −50 where contrast runs, never −30 now and −20 again later — which is what the layer
chain did before 0.18.1, and how the shadows of a night shot went magenta. A photograph with no
layers composes to the same shader byte for byte. The film is the one exception, because it is a
rendering rather than an adjustment and cross-fading two developments is not what a region of a
pushed negative looks like: an operation that `blends_settings` has its uniforms averaged by mask
weight instead, and runs once (FR-DEV-3f).
There is a second reduction that does not hang off the bottom of this chain. The raw histogram There is a second reduction that does not hang off the bottom of this chain. The raw histogram
(FR-CULL-3) taps the demosaiced scene-linear texture directly — the demosaic box's output — (FR-CULL-3) taps the demosaiced scene-linear texture directly — the demosaic box's output —
because what it measures is the file rather than the render. See §5.5. because what it measures is the file rather than the render. See §5.5.
+6 -3
View File
@@ -325,9 +325,12 @@ without measuring them:
Stated because §7 of [display-and-extension.md](display-and-extension.md) asks Stated because §7 of [display-and-extension.md](display-and-extension.md) asks
for it, and because each of these could move the numbers. for it, and because each of these could move the numbers.
- **Local adjustments.** The mask stack is a separate chain per layer and is not - **Local adjustments.** Not in any row above. Since 0.18.1 a layer is no longer
in any row above. `render_masked` takes them and the fused shader addresses a separate chain after the global one: each operation a layer touches runs a
them per layer, so a heavily masked edit costs more than `all`. second fragment at its own place in the chain, blended by the layer's mask
([architecture.md §5.2](architecture.md)), and `render_masked` binds the mask
array the fused shader samples. A heavily masked edit therefore costs more
than `all`, by roughly one fragment per touched operation per layer.
- **Spot repairs.** These add detail passes, and their cost is per spot. - **Spot repairs.** These add detail passes, and their cost is per spot.
- **Lens corrections.** Not part of `EditGraph::default_chain` — they are built - **Lens corrections.** Not part of `EditGraph::default_chain` — they are built
from a matched profile — so the `point` row does not include the warp chain. from a matched profile — so the `point` row does not include the warp chain.
+7
View File
@@ -326,6 +326,13 @@ once, at the final export or display stage.
- Crop, straighten, rotate, flip - Crop, straighten, rotate, flip
- Local adjustments: linear gradient, radial gradient, and brush masks - Local adjustments: linear gradient, radial gradient, and brush masks
*Resolved 2026-09-26:* a mask layer's settings are **offsets to the photograph's**, applied at each
operation's own place in the chain — global contrast −30 under a layer at −20 is −50 inside the
mask, applied once. A moved switch or choice replaces the global one, and an offset that brings an
operation back to neutral undoes the global setting inside the mask. Layers used to run as a second
chain after every global operation, which compounded the two edits in ways neither slider showed
([architecture.md §5.2](architecture.md); `core/dr-gpu/tests/local_adjustments.rs`).
**FR-DEV-3a — Self-describing operations.** Every processing operation shall declare its own **FR-DEV-3a — Self-describing operations.** Every processing operation shall declare its own
parameters through a descriptor, so that adding an operation requires no changes to frontend code. parameters through a descriptor, so that adding an operation requires no changes to frontend code.
An operation declares *what* its parameters are; the frontend decides *how* to present them. An operation declares *what* its parameters are; the frontend decides *how* to present them.
+4 -1
View File
@@ -235,7 +235,10 @@ take the crop back or keep it.
`Local` in the rail turns the column into a mask stack. `Find subjects` runs a `Local` in the rail turns the column into a mask stack. `Find subjects` runs a
segmentation model over the photograph; what it recognises appears as a list segmentation model over the photograph; what it recognises appears as a list
of categories with how much of the frame each covers. Click one and it is a of categories with how much of the frame each covers. Click one and it is a
mask — then every slider below edits only that region. mask — then every slider below edits only that region. A mask's slider adds to
the photograph's own rather than repeating it: contrast −20 in the mask over
−30 on the whole frame is −50 there, and +30 in the mask cancels the frame's
−30 inside it.
![What the model found in an urban scene: ground, architecture, sky, vegetation](media/local-categories.png) ![What the model found in an urban scene: ground, architecture, sky, vegetation](media/local-categories.png)
+4 -1
View File
@@ -306,7 +306,10 @@ take the crop back or keep it.</p>
<p><code>Local</code> in the rail turns the column into a mask stack. <code>Find subjects</code> runs a <p><code>Local</code> in the rail turns the column into a mask stack. <code>Find subjects</code> runs a
segmentation model over the photograph; what it recognises appears as a list segmentation model over the photograph; what it recognises appears as a list
of categories with how much of the frame each covers. Click one and it is a of categories with how much of the frame each covers. Click one and it is a
mask — then every slider below edits only that region.</p> mask — then every slider below edits only that region. A mask's slider adds to
the photograph's own rather than repeating it: contrast −20 in the mask over
−30 on the whole frame is −50 there, and +30 in the mask cancels the frame's
−30 inside it.</p>
<figure><img loading="lazy" src="media/local-categories.png" alt="What the model found in an urban scene: ground, architecture, sky, vegetation"><figcaption>What the model found in an urban scene: ground, architecture, sky, vegetation</figcaption></figure> <figure><img loading="lazy" src="media/local-categories.png" alt="What the model found in an urban scene: ground, architecture, sky, vegetation"><figcaption>What the model found in an urban scene: ground, architecture, sky, vegetation</figcaption></figure>
<figure><img loading="lazy" src="media/local-segment.png" alt="The sky chosen: tinted on the photograph, and the column now scoped to it"><figcaption>The sky chosen: tinted on the photograph, and the column now scoped to it</figcaption></figure> <figure><img loading="lazy" src="media/local-segment.png" alt="The sky chosen: tinted on the photograph, and the column now scoped to it"><figcaption>The sky chosen: tinted on the photograph, and the column now scoped to it</figcaption></figure>
<p>A mask is a stack of parts. Paint into it, subtract a gradient from it, grow <p>A mask is a stack of parts. Paint into it, subtract a gradient from it, grow