The fused pass hands a fragment a colour and no coordinate. That is what buys one dispatch for a whole edit, and it is also a wall: sharpening, noise reduction, clarity, texture, dehaze and spot removal are each defined by what the neighbours are doing, and FR-DEV-3 and FR-DEV-8 ask for all six. None of them could be written at any price. So there is now a detail stage. An operation implements `Operation` for its parameters exactly as before — the panel, the sidecar, the history and the presets all work unchanged — and additionally returns `Affects::Detail` and a `DetailStage` yielding one pass per dispatch. `Affects` grows the third variant `docs/requirements.md:250` designed and nothing had cut. Where the stage sits is a colour-science decision, not an arrangement of convenience. It runs after every point operation and every mask layer, so an amount chosen against a tone curve survives the curve moving; in linear sRGB after the camera matrix, because camera RGB has no luminance to sharpen against; and before the output transform and the clip, because FR-DEV-2 allows one quantisation and a highlight clipped before a convolution grows a dark ring. The fused pass therefore ends one of two ways, and when a detail stage follows it hands on unclipped f16 and the last detail pass encodes. At render resolution rather than on the source, which is the whole of FR-DSP-1: a pass before the framing prologue would cost 24 MP to draw a 2 MP preview. `RenderScale` is what makes that survivable — a radius is stored as a fraction of the frame's shorter edge, exactly as a mask feather already is, or as a count of source pixels, and converted per render. It also reports when a radius is smaller than a proxy pixel rather than drawing a plausible lie; zooming to 1:1 makes the preview exact with no second path. `Invalidation` gives FR-DEV-3d something to mean. Moving a detail parameter leaves the colour key alone, so `AdjustPass` keeps the linear intermediate and skips the fused dispatch: dragging a sharpening slider costs a convolution. Moving exposure does re-run the detail passes, because they read what the colour pass wrote, and there is no arrangement of keys that avoids it while keeping sharpening after tone. Validated by a separable box blur that is not a develop operation, behind the `detail-probe` feature and absent from a shipping build. An abstraction with no consumer is a guess; a box blur's answer is known in closed form, so the tests assert every byte of the ramp rather than that the edge got softer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
2.7 KiB
TOML
74 lines
2.7 KiB
TOML
[package]
|
|
name = "dr-gpu"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
license.workspace = true
|
|
|
|
[dependencies]
|
|
dr-types.workspace = true
|
|
dr-decode.workspace = true
|
|
dr-pipeline.workspace = true
|
|
# The watershed's pixel passes are here because they are shaders; everything
|
|
# that reasons about regions rather than pixels lives there, where it is
|
|
# testable with no adapter present. No features: this half needs neither the
|
|
# inference runtime nor the weights, and the workspace declaration defaults
|
|
# them off so that stays true.
|
|
dr-segment.workspace = true
|
|
wgpu.workspace = true
|
|
thiserror.workspace = true
|
|
log.workspace = true
|
|
bytemuck.workspace = true
|
|
# Needed outside tests: shader compilation errors are collected through an
|
|
# async error scope, which must be resolved before the pipeline is returned.
|
|
pollster.workspace = true
|
|
|
|
[dev-dependencies]
|
|
env_logger.workspace = true
|
|
# The detail stage's test consumer — a box blur that is not a develop operation
|
|
# and never reaches the panel. An abstraction with no consumers is a guess, and
|
|
# this is the one that proves the neighbourhood passes compile, ping-pong,
|
|
# encode once, and scale between a proxy and an export. A dev-dependency, so a
|
|
# shipping `dr-gpu` does not carry it.
|
|
dr-pipeline = { workspace = true, features = ["detail-probe"] }
|
|
# The local-adjustment example needs the model, which the library half of this
|
|
# crate deliberately does not: `dr-gpu` holds the shaders, and the inference
|
|
# runtime belongs to whoever is asking a question about the picture.
|
|
dr-segment = { workspace = true, features = ["semantic", "embedded-model"] }
|
|
|
|
[[example]]
|
|
name = "bench"
|
|
required-features = ["readback"]
|
|
|
|
[[example]]
|
|
name = "develop"
|
|
# No `readback` needed since S1: this writes a file, so it goes through
|
|
# `export_pixels`, which is ungated precisely because an export is not the
|
|
# round-trip AC-8 forbids.
|
|
|
|
[features]
|
|
default = []
|
|
# Exposes read_pixels outside tests. Production must not enable this.
|
|
readback = []
|
|
# Exposes `Segmentation::read_field`, which builds the region adjacency graph
|
|
# on the CPU. Separate from `readback` on purpose — see `segment.rs`. Once per
|
|
# image on a worker, not the per-frame display round-trip AC-8 forbids; still a
|
|
# full-resolution transfer, and still F3's open gap.
|
|
segment-readback = []
|
|
|
|
[[example]]
|
|
name = "segment"
|
|
required-features = ["segment-readback"]
|
|
|
|
[[example]]
|
|
name = "local"
|
|
# No `segment-readback`: this reads the *rendered* proxy back through
|
|
# `export_pixels` to feed the model, which is the ungated export path. The
|
|
# watershed, and the region-graph transfer that needs the gate, is not involved.
|
|
|
|
|
|
[[test]]
|
|
name = "masked_outputs"
|
|
# Needs the distance transform, which lives with the model half of dr-segment.
|
|
required-features = []
|