Files
DarkRoom/core/dr-gpu/Cargo.toml
T
dtourolleandClaude Opus 5 4a2fcb6d22 Render a film stock on the GPU, and let it take over the rendering
The stock model landed in dr-film with no way to see it. This is the
pipeline node, the two texture bindings it reads, and the end-to-end test
that proves the shader agrees with the model.

The design point is that a film simulation is not an adjustment. Every
other node changes a picture; this one makes it. A stock's characteristic
curve does the camera profile's base curve's job -- from measurements
rather than from a curve somebody drew -- so running both renders the
scene twice: the camera's rendering, and then a film's rendering of that.
It looks like neither, and it reads as a colour-management bug with no
colour-management bug to find.

So `Operation::renders` is new. A node declaring it takes camera RGB and
hands back linear sRGB, and the composer emits neither the base curve nor
the conversion out of camera space. Both halves move together, and the
composer keeps them as one string precisely so that getting half of it
right is impossible.

The tables are not parameters, for the reason vignetting's coefficients
are not: they are measurements. dr-pipeline declares the layout as a plain
struct and keeps its no-dependency property; the two crates share no types
on purpose. `EditGraph::set_film_tables` offers them to every node rather
than to the one that wants them, because knowing which concrete type is
which is what the graph is organised not to know.

Bindings 4 and 5 follow the masks precedent: declared unconditionally so
one bind group layout serves every generated shader, bound to 1x1
placeholders when no stock is loaded. Both are interpolated by hand with
textureLoad -- this pipeline binds no sampler, and adding one for two
lookups would cost a binding in every shader. Uploads are keyed on content
so an unchanged stock does not push half a megabyte across the bus per
frame.

The end-to-end test earned its place immediately: it found the density
lookup being filled z-fastest while a 3D texture upload wants x-fastest,
so the red and blue axes were transposed. Green matched exactly, which is
what that bug looks like -- a plausible photograph of the wrong colour,
and one that every unit test on either side of the seam passes. dr-film
now pins the layout in a test that needs no device, and states it where
the field is declared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 15:12:56 +02:00

78 lines
2.9 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 film stocks, for the end-to-end test only. The library half deliberately
# does not link them: `dr-gpu` binds tables it is handed and has no opinion on
# where a stock comes from.
dr-film.workspace = true
# 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 = []