Files
effectcraft/crates/gpu/src/bokeh.rs
T
Christopher Linnett 280a400be4 Merge origin/main into fix/issue-2092-camera-focus-crash
Conflicts were main's new code next to this branch's additions; both kept:
- crates/effects/src/lib.rs: main's Buf::pad_sides stays as main wrote it
  (Transform's transform_grow bounds that padding itself); Buf::pad keeps
  this branch's effective-pad clamp.
- crates/raster/src/lib.rs: main's quantize_px/round_half_up and this
  branch's MAX_PADDED_SIDE (still equal to limits::MAX_SIZE on main).
2026-10-29 18:44:49 +08:00

61 lines
2.7 KiB
Rust

//! Classic 3D depth of field on the GPU (`bokeh_*` kernels): the plane buffer padded, its
//! highlights boosted, row prefix sums, then one span gather per blur level
//! ([`effectcraft_render::three_d::bokeh::kernel_spans`]) accumulated with each pixel's level
//! weight — the CPU's `progressive_blur` step for step.
use effectcraft_render::three_d::PlaneDof;
use effectcraft_render::three_d::bokeh::kernel_spans;
use crate::context::{Enc, GpuImage, Params};
/// Blur `src` (an unpadded plane buffer) as `d` says. Returns the padded, blurred image.
pub(crate) fn apply(e: &mut Enc, src: &GpuImage, d: &PlaneDof) -> Option<GpuImage> {
// Saturating, so an oversized padding is refused by `fits` below instead of wrapping around.
let (w, h) = (src.width.saturating_add(d.pad.saturating_mul(2)), src.height.saturating_add(d.pad.saturating_mul(2)));
if !e.g.fits(w.saturating_add(1), h) || d.radius.len() != w as usize * h as usize || d.levels.is_empty() {
return None;
}
let padded = e.image(w, h);
e.copy_into(src, &padded, d.pad, d.pad);
let boosted = if d.highlight.gain > 0.0 {
let out = e.scratch(w, h);
let mut p = Params::default();
p.f[0] = [d.highlight.gain as f32, d.highlight.threshold as f32, d.highlight.saturation as f32, 0.0];
e.pixels("bokeh_boost", &p, &padded, None, &out, None);
out
} else {
padded
};
let prefix = e.image(w + 1, h);
e.dispatch("bokeh_prefix", &Params::default(), crate::context::KernelImages { src: &boosted, aux: None, out: &prefix }, None, (h.div_ceil(64), 1));
// Radius map, then every level's spans.
let mut data: Vec<f32> = d.radius.as_ref().clone();
let mut levels = vec![];
for &r in &d.levels {
let (kernels, norm) = kernel_spans(&d.iris, r as f64).unwrap_or_else(|| (vec![(1.0, vec![(0, -0.5, 0.5)])], 1.0));
let off = data.len() as u32;
let mut count = 0u32;
for (wt, spans) in &kernels {
for &(dy, x0, x1) in spans {
data.extend_from_slice(&[dy as f32, x0 as f32, x1 as f32, *wt]);
count += 1;
}
}
levels.push((off, count, norm));
}
let buf = e.data(&data);
let n = d.levels.len() as u32;
let (lo, hi) = (d.levels[0], d.levels[d.levels.len() - 1]);
let mut acc = e.image(w, h);
for (k, (off, count, norm)) in levels.into_iter().enumerate() {
let out = e.scratch(w, h);
let mut p = Params::default();
p.u[0] = [off, count, k as u32, n];
p.u[1] = [u32::from(k == 0), 0, 0, 0];
p.f[0] = [1.0 / norm, lo, hi, 0.0];
e.pixels("bokeh_gather", &p, &prefix, Some(&acc), &out, Some(&buf));
acc = out;
}
Some(acc)
}