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:
+53
-34
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user