Run the view transform after the detail stage, in a pass of its own
The fused pass stops at "linear working values" when a sharpener, a blur or a repair follows, and the detail passes convolve what it hands on. Until now it handed on the rendering: the base curve, and since the last commit the view transform, ran before the store. So every kernel worked on display-referred values while its comments promised the opposite — D19's second finding. A fused pass composed for a detail stage now stops before the view transform, and carries a second shader, `ComposedShader::view`, composed from the same inputs. It runs the same prologue, for the positions a fragment reads (a film's grain seeds from `source_px`) and the corners it blacks out, takes its colour from the detail stage's result bound where the sample cache would be, and runs the view transform, the output transform and the mask reveal. `render_detailed` dispatches it after the last detail pass, in the same encoder. So no detail pass encodes any more. Every pass writes an intermediate, the last one included, which retires three things that existed only to make the last pass encode: `writes_output` and the runner's second layout, the body-less resolve pass for an active kernel with nothing to draw at this scale, and capture sharpening's pass-through, which now emits no pass at all. An empty chain is a whole render: the view pass reads the fused result directly. The detail stage no longer takes an output space either, so `compose_detail_for` folds into `compose_detail` and the space is named once, on the fused half. The cost is one full-render read and write per frame when a detail stage exists, and a third intermediate for a one-pass chain.
This commit is contained in:
@@ -109,8 +109,7 @@ fn row_rgb(pixels: &[u8], size: u32, y: u32) -> Vec<[u8; 3]> {
|
||||
fn render(pass: &mut AdjustPass, graph: &EditGraph, source: &DemosaicedImage, out: u32) -> Vec<u8> {
|
||||
let shader = graph.compose_for(ColourSpace::Srgb);
|
||||
let scale = graph.render_scale(source.size(), (out, out));
|
||||
let detail =
|
||||
graph.compose_detail_for(scale.full_size(), scale.render_size(), ColourSpace::Srgb);
|
||||
let detail = graph.compose_detail(scale.full_size(), scale.render_size());
|
||||
let key = graph.invalidation().through(Affects::Colour);
|
||||
pass.render_detailed(source, &shader, out, out, None, &detail, key)
|
||||
.expect("render");
|
||||
@@ -683,28 +682,21 @@ fn texture_contributes_nothing_where_its_scale_does_not_exist() {
|
||||
// `render_masked`, and was rejected for handing a linear-working shader
|
||||
// to the plain path — so texture alone on a thumbnail did not render.
|
||||
//
|
||||
// The seam was closed where that note said it would have to be, at the
|
||||
// composition boundary: `compose_detail` now emits a bodyless
|
||||
// `detail/resolve` pass in exactly this case, which reads only the pixel
|
||||
// it writes and performs the output transform the fused pass declined to
|
||||
// do. So the chain is no longer empty — it carries precisely the one pass
|
||||
// that finishes the render and no kernel at all, which is the honest
|
||||
// description of "a two-pixel surface structure is not present in a
|
||||
// 128-pixel rendering".
|
||||
// The seam was closed at the composition boundary, and closed again,
|
||||
// more simply, by D19: no detail pass encodes any more, the fused pass's
|
||||
// view pass performs the output transform whatever the chain holds, and
|
||||
// so the empty chain is a whole render. That is the honest description of
|
||||
// "a two-pixel surface structure is not present in a 128-pixel
|
||||
// rendering".
|
||||
let scale = graph.render_scale(source.size(), (128, 128));
|
||||
let composed =
|
||||
graph.compose_detail_for(scale.full_size(), scale.render_size(), ColourSpace::Srgb);
|
||||
assert_eq!(
|
||||
composed.len(),
|
||||
1,
|
||||
"the chain must carry the resolve pass and nothing else"
|
||||
);
|
||||
assert_eq!(composed.passes[0].label, "detail/resolve");
|
||||
assert_eq!(
|
||||
composed.radius(),
|
||||
0,
|
||||
let composed = graph.compose_detail(scale.full_size(), scale.render_size());
|
||||
assert!(
|
||||
composed.is_empty(),
|
||||
"texture claimed a kernel it cannot draw"
|
||||
);
|
||||
let mut pass = AdjustPass::new(&ctx);
|
||||
render(&mut pass, &graph, &source, 128);
|
||||
assert_eq!(pass.view_dispatches(), 1, "the view pass still finishes it");
|
||||
|
||||
// With clarity on as well the edit is renderable again, and the dispatch
|
||||
// count says what the assertion above says: two passes, not four. Texture
|
||||
|
||||
Reference in New Issue
Block a user