mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
Drafts keep the wide planes across a noise-reduction change (CPU NR drag)
A noise-reduction change invalidated the scene-linear stage and with it every spatial plane (base, clarity, dehaze, chromaticity), so each tick of an NR drag on the CPU rebuilt all of them. The plan now also carries the linear key without the NR settings; a draft render whose image differs from the cached one only in NR keeps those wide blurs and redoes only log luminance and the fine texture band. Full-quality renders never take them. Draft NR drag, single thread: prepare 18.7 -> 7 ms (render ~135 -> ~125 ms CPU). Sources: own design. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
0ba118de92
commit
6873aab6eb
3 files changed
+59
-5
No files matched your search
@@ -358,6 +358,9 @@ pub struct Plan<'a> {
|
||||
pub geo: u64,
|
||||
/// Key of the white-balanced, retouched, denoised image (and of the planes computed from it).
|
||||
pub lin_key: u64,
|
||||
/// [`Self::lin_key`] without the noise-reduction settings: draft renders reuse the smooth
|
||||
/// planes of an image that differs from this one only in its noise reduction.
|
||||
pub pre_nr_key: u64,
|
||||
/// Red eye / pet eye corrections with their detected pupils, in output pixels.
|
||||
pub eyes: Vec<redeye::EyeK>,
|
||||
/// The whole frame's dehaze airlight, for a windowed render (which can't see the whole frame).
|
||||
@@ -418,7 +421,7 @@ pub fn plan<'a>(src: &Rgb32f, info: &SourceInfo, s: &'a DevelopSettings, req: &R
|
||||
let (wb_t, wb_tint) = local::effective_wb(info, s);
|
||||
let eyes = redeye::resolve(src, &s.red_eye, s.orientation, &frame, w, h, px_per_long);
|
||||
let d = &s.detail;
|
||||
let lin_key = hash_of((
|
||||
let pre_nr_key = hash_of((
|
||||
geo,
|
||||
[wb_t, wb_tint, info.as_shot_temp, info.as_shot_tint].map(f64::to_bits),
|
||||
format!("{:?}", s.spots),
|
||||
@@ -427,10 +430,10 @@ pub fn plan<'a>(src: &Rgb32f, info: &SourceInfo, s: &'a DevelopSettings, req: &R
|
||||
format!("{:?}", s.optics),
|
||||
// and the local tone mapping
|
||||
local_tone::enabled(info, s),
|
||||
[d.nr_luminance, d.nr_detail, d.nr_color, d.nr_color_detail, d.nr_color_smoothness].map(f64::to_bits),
|
||||
src_long,
|
||||
));
|
||||
Plan { settings, frame, w, h, px_per_long, src_long, geo, lin_key, eyes, fixed_air, keep, mattes: info.mattes.clone() }
|
||||
let lin_key = hash_of((pre_nr_key, [d.nr_luminance, d.nr_detail, d.nr_color, d.nr_color_detail, d.nr_color_smoothness].map(f64::to_bits)));
|
||||
Plan { settings, frame, w, h, px_per_long, src_long, geo, lin_key, pre_nr_key, eyes, fixed_air, keep, mattes: info.mattes.clone() }
|
||||
}
|
||||
|
||||
/// Whether the scene-linear stage needs work only the CPU does (defringe, spot removal).
|
||||
@@ -521,7 +524,12 @@ fn render_impl(src: Src<'_>, info: &SourceInfo, s: &DevelopSettings, req: &Rende
|
||||
lap("wb/spots/nr", &mut t);
|
||||
let mut planes = match cached.map(|e| e.planes) {
|
||||
Some(p) if p.key == lin_key => p,
|
||||
_ => local::Planes { key: lin_key, ..Default::default() },
|
||||
// A draft while noise reduction changes (a slider drag): the wide, edge-aware planes of the
|
||||
// image before the change stand in (noise reduction barely moves a blur that wide); the
|
||||
// per-pixel log luminance and the fine texture band are redone. Keyed so a full-quality
|
||||
// render never takes them.
|
||||
Some(p) if req.quality == Quality::Draft && p.pre_nr == plan.pre_nr_key => p.for_other_nr(),
|
||||
_ => local::Planes { key: lin_key, pre_nr: plan.pre_nr_key, ..Default::default() },
|
||||
};
|
||||
let mut prep = local::prepare(lin.clone(), s, frame, px_per_long, req.quality, &mut planes, plan.mattes.as_deref());
|
||||
if let Some(a) = plan.fixed_air {
|
||||
|
||||
@@ -210,6 +210,8 @@ pub fn log_lum(c: [f32; 3]) -> f32 {
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct Planes {
|
||||
pub key: u64,
|
||||
/// The image's key without its noise reduction (`Plan::pre_nr_key`).
|
||||
pub pre_nr: u64,
|
||||
pub log_l: Option<Arc<Plane>>,
|
||||
pub base: Option<(u32, Arc<Plane>)>,
|
||||
pub clarity: Option<(u32, Arc<Plane>)>,
|
||||
@@ -220,6 +222,15 @@ pub(crate) struct Planes {
|
||||
pub chroma: Option<(u32, Arc<Rgb32f>)>,
|
||||
}
|
||||
|
||||
impl Planes {
|
||||
/// The planes that still fit an image differing from this one only in noise reduction, close
|
||||
/// enough for a draft: the wide blurs (base, clarity, dehaze, chromaticity). Its `key` matches
|
||||
/// no image, so only another draft takes them.
|
||||
pub fn for_other_nr(self) -> Planes {
|
||||
Planes { key: 0, pre_nr: self.pre_nr, log_l: None, texture: None, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reuse `slot` if it was computed at `sigma`, else compute and store it.
|
||||
fn plane_at(slot: &mut Option<(u32, Arc<Plane>)>, sigma: f32, f: impl FnOnce() -> Plane) -> Arc<Plane> {
|
||||
match slot {
|
||||
|
||||
@@ -206,7 +206,13 @@ fn stage_cache_matches_uncached_render_through_a_slider_session() {
|
||||
for req in [&full, &draft] {
|
||||
let a = render_cached(&src, &info, &s, req, &cache).image;
|
||||
let b = render(&src, &info, &s, req).image;
|
||||
assert_eq!(a, b, "step {i} {:?}", req.quality);
|
||||
if req.quality == Quality::Draft && i == 6 {
|
||||
// a draft while noise reduction changes keeps the wide planes of the image before it
|
||||
let d = max_diff(&a, &b);
|
||||
assert!(d <= 3, "step {i} draft: {d}");
|
||||
} else {
|
||||
assert_eq!(a, b, "step {i} {:?}", req.quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(cache.len(), 2);
|
||||
@@ -220,6 +226,35 @@ fn stage_cache_matches_uncached_render_through_a_slider_session() {
|
||||
assert_eq!(render_cached(&other, &info, &s, &full, &cache).image, render(&other, &info, &s, &full).image);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drafts_keep_wide_planes_across_a_noise_reduction_change_and_full_renders_do_not() {
|
||||
use crate::{Quality, StageCache, render_cached};
|
||||
use std::sync::Arc;
|
||||
let src = Arc::new(scene());
|
||||
let info = SourceInfo { raw: true, ..Default::default() };
|
||||
let cache = StageCache::default();
|
||||
let draft = RenderRequest { quality: Quality::Draft, ..RenderRequest::fit(160, 160) };
|
||||
let full = RenderRequest { quality: Quality::Full, ..draft };
|
||||
let mut s = typical_edits();
|
||||
s.effects.clarity = 60.0;
|
||||
render_cached(&src, &info, &s, &draft, &cache);
|
||||
let clarity_plane = |c: &StageCache| c.entries.lock().unwrap().first().and_then(|e| e.planes.clarity.clone()).unwrap().1;
|
||||
let first = clarity_plane(&cache);
|
||||
s.detail.nr_luminance = 50.0;
|
||||
render_cached(&src, &info, &s, &draft, &cache);
|
||||
assert!(Arc::ptr_eq(&first, &clarity_plane(&cache)), "a draft redid the clarity plane for a noise-reduction change");
|
||||
for v in [10.0, 45.0, 80.0] {
|
||||
s.detail.nr_luminance = v;
|
||||
s.detail.nr_color = v / 2.0;
|
||||
let a = render_cached(&src, &info, &s, &draft, &cache).image;
|
||||
let d = max_diff(&a, &render(&src, &info, &s, &draft).image);
|
||||
// close to the exact draft (a few levels at edges, where clarity is strong)
|
||||
assert!(d <= 8, "nr {v}: {d}");
|
||||
// a full-quality render after the drafts is exact (it never takes the stand-in planes)
|
||||
assert_eq!(render_cached(&src, &info, &s, &full, &cache).image, render(&src, &info, &s, &full).image, "nr {v}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exposure_after_spatial_filters_equals_exposing_the_source() {
|
||||
// The pipeline filters the image before exposure and applies exposure per pixel; filters on
|
||||
|
||||
Reference in new issue
Block a user