Merge branch 'worktree-agent-a75c901968abfa183' into integration

# Conflicts:
#	core/dr-gpu/src/adjust.rs
#	core/dr-pipeline/ops/README.md
#	core/dr-pipeline/src/lib.rs
#	core/dr-pipeline/src/ops/mod.rs
#	ui/dr-ui/src/develop.rs
This commit is contained in:
2026-08-22 19:29:39 +02:00
10 changed files with 1783 additions and 77 deletions
+53 -34
View File
@@ -1064,21 +1064,28 @@ mod tests {
g.set_param(cap.id, p.id, value);
let shader = g.compose();
// Through the detail stage rather than through `render`,
// because a neighbourhood operation contributes no fused
// fragment: its WGSL is generated per resolution and lives
// in dispatches of its own. Compiling only the fused half
// would leave every kernel in the chain untested here —
// and worse, `render` refuses a shader composed to hand on
// linear working values, so the omission would arrive as
// "invalid WGSL" against a shader that is perfectly valid.
// A neighbourhood operation compiles as a *chain*, not
// as a fragment: it contributes nothing to the fused pass,
// and the fused pass in turn stops short of the output
// transform so the last detail pass can perform it. Going
// through `render_detailed` covers both kinds with one
// loop, which is the property that makes this test extend
// itself when an operation is added.
//
// The scale comes from the graph, so the kernel really is
// converted the way a render converts it. The image is
// 16×16 and so is the target, which puts the ratio at 1.0
// and keeps an acutance operation from declining to draw
// (`RenderScale::resolves`) and compiling its pass-through
// instead of the kernel this test exists to check.
// Compiling only the fused half would leave every kernel
// untested here — and worse, `render` refuses a shader
// composed to hand on linear working values, so the
// omission would arrive as "invalid WGSL" against a shader
// that is perfectly valid.
//
// The scale comes from the graph, so the kernel is
// converted the way a real render converts it. Mind the
// size: a radius stated in source pixels can decide there
// is nothing to draw at sixteen pixels
// (`RenderScale::resolves`) and compile its pass-through
// instead of the kernel under test. The chain still
// carries the resolve pass that finishes the render, and
// that generated source is worth compiling too.
let scale = g.render_scale(img.size(), (16, 16));
let detail = g.compose_detail(scale);
let key = g.invalidation().through(dr_pipeline::Affects::Colour);
@@ -1442,38 +1449,50 @@ mod tests {
}
}
let shader = g.compose();
// Cropped, so the render is against an output size that is not the
// source size — the case where a wrong dispatch or a wrong texture
// allocation would show up.
let (w, h) = g.output_size(32, 32);
let shader = g.compose();
let scale = g.render_scale(img.size(), (w, h));
let detail = g.compose_detail(scale);
// A neighbourhood operation is active and yet emits no fused block: it
// reads pixels it is not writing, so it is a dispatch of its own. The
// ones that are come from the detail chain rather than from a list
// here, which keeps the count exact as sharpening, noise reduction and
// clarity arrive instead of loosening it to an inequality.
let neighbourhood: std::collections::BTreeSet<&str> = detail
.passes
.iter()
.map(|p| p.label.split('/').next().expect("<op id>/<pass label>"))
.collect();
assert_eq!(
shader.source.matches("---- ").count(),
// Every fusable operation, plus framing — which emits a stage of
// its own rather than an operation block, and is not in
// `descriptors` — less the ones that run after this shader.
g.descriptors().len() + 1 - neighbourhood.len(),
"every fusable operation and the framing should be active"
// Every operation has to reach the pipeline, but they do not all reach
// the same half of it, and which half is not this test's business to
// know: a point operation is a block in the fused shader, and a
// neighbourhood operation is one or more passes of the detail chain
// (`dr_pipeline::detail`) and contributes *no* fused block, because a
// fused fragment is handed a colour with no way back to a coordinate.
//
// Asserted as an exclusive or over the chain rather than as a count,
// so that adding either kind of operation extends this test on its own
// — and so that an operation which somehow managed both, or neither,
// is named rather than showing up as an arithmetic mismatch.
let mut fused_blocks = 0;
for desc in g.descriptors() {
let id = desc.id.0;
let point = shader.source.contains(&format!("---- {id} ----"));
let neighbourhood = detail
.passes
.iter()
.any(|p| p.label.starts_with(&format!("{id}/")));
assert!(
point ^ neighbourhood,
"{id} reaches {} of the two stages; every active operation \
belongs to exactly one",
if point { "both" } else { "neither" }
);
fused_blocks += usize::from(point);
}
);
assert!(
shader.source.contains("---- framing ----"),
"framing must reach the shader alongside the colour operations"
);
assert!(
!detail.is_empty(),
"with every operation active the detail stage must run"
);
// Both halves, from the one graph: with a detail stage present the
// fused pass stops at linear working values and the last detail pass