Merge #1968: Make Gaussian Blur and Fast Box Blur continuous at small values (#1939) (@MarekSpodBiedry)

This commit is contained in:
aiongg
2026-10-24 01:20:43 +00:00
8 changed files with 162 additions and 16 deletions
+10 -1
View File
@@ -261,6 +261,10 @@ fn gaussian(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
return Some(b);
}
let (kx, ky) = dims_xy(ctx.params.e("dimensions"));
// Small sigmas use the CPU's sampled kernel (#1939).
if effectcraft_raster::gaussian_is_exact(s * kx, s * ky) {
return None;
}
let repeat = ctx.params.b("repeatEdge");
if !repeat && !ctx.adjustment {
b.pad(e, (s * 3.0).ceil() as u32)?;
@@ -270,7 +274,12 @@ fn gaussian(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
}
fn box_blur(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
let r = (ctx.params.f("radius") * b.scale).round().max(0.0) as usize;
let radius = (ctx.params.f("radius") * b.scale).max(0.0);
// Fractional radii blend two integer radii on the CPU (#1939).
if radius - radius.floor() >= 1e-4 {
return None;
}
let r = radius.floor() as usize;
if r == 0 {
return Some(b);
}
+4
View File
@@ -188,6 +188,10 @@ fn gaussian_legacy(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
2 => (0.0, 1.0),
_ => (1.0, 1.0),
};
// Small sigmas use the CPU's sampled kernel (#1939).
if effectcraft_raster::gaussian_is_exact(s * kx, s * ky) {
return None;
}
if !ctx.adjustment {
b.pad(e, (s * 3.0).ceil() as u32)?;
}
+19 -4
View File
@@ -524,12 +524,27 @@ pub(crate) fn c(r: f64, g: f64, b: f64) -> Value {
Value::Color([r, g, b, 1.0])
}
#[test]
fn small_blurs_fall_back_to_the_cpu() {
// #1939: small Gaussian sigmas and fractional box radii render on the CPU kernel.
for (id, vals) in [("ec.blur.gaussian", [("blurriness", n(0.9))]), ("ec.blur.fastbox", [("radius", n(1.5))])] {
let mut s = Scene::new(BitDepth::Bpc32);
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
s.push(bg);
let mut l = s.footage(70, 44);
s.effect(&mut l, id, &vals);
s.push(l);
check(&format!("{id} {vals:?}"), compare_at(&s, opts(), Tick::ZERO), 0.0);
}
}
#[test]
fn gpu_effects_match_cpu() {
effect_case("ec.blur.gaussian", &[("blurriness", n(9.0))]);
effect_case("ec.blur.gaussian", &[("blurriness", n(4.0)), ("dimensions", Value::Enum(1)), ("repeatEdge", Value::Bool(true))]);
effect_case("ec.blur.fastbox", &[("radius", n(5.0)), ("iterations", n(2.0))]);
effect_case("ec.blur.fastbox", &[("radius", n(3.0)), ("dimensions", Value::Enum(2)), ("repeatEdge", Value::Bool(true))]);
// Sigma 3 px and up and even radii, so the half-scale pass stays on the GPU too (#1939).
effect_case("ec.blur.gaussian", &[("blurriness", n(14.0))]);
effect_case("ec.blur.gaussian", &[("blurriness", n(12.0)), ("dimensions", Value::Enum(1)), ("repeatEdge", Value::Bool(true))]);
effect_case("ec.blur.fastbox", &[("radius", n(6.0)), ("iterations", n(2.0))]);
effect_case("ec.blur.fastbox", &[("radius", n(4.0)), ("dimensions", Value::Enum(2)), ("repeatEdge", Value::Bool(true))]);
effect_case("ec.blur.directional", &[("direction", n(30.0)), ("length", n(12.0))]);
effect_case("ec.stylize.glow", &[("threshold", n(40.0)), ("radius", n(14.0)), ("intensity", n(1.5))]);
effect_case("ec.stylize.glow", &[("colors", Value::Enum(1)), ("operation", Value::Enum(1)), ("colorA", c(1.0, 0.8, 0.1))]);
+3 -2
View File
@@ -46,6 +46,7 @@ fn bevels_and_legacy_gaussian() {
effect_case("ec.perspective.bevelalpha", &[("edgeThickness", n(2.0)), ("lightAngle", n(200.0)), ("lightColor", c(1.0, 0.8, 0.5))]);
effect_case("ec.perspective.beveledges", &[("edgeThickness", n(0.1)), ("lightIntensity", n(0.5))]);
effect_case("ec.perspective.beveledges", &[("edgeThickness", n(0.3)), ("lightAngle", n(130.0))]);
effect_case("ec.obsolete.gaussianlegacy", &[("blurriness", n(7.0))]);
effect_case("ec.obsolete.gaussianlegacy", &[("blurriness", n(5.0)), ("blurDimensions", e(2))]);
// Sigma 3 px and up at half scale too: smaller ones render on the CPU (#1939).
effect_case("ec.obsolete.gaussianlegacy", &[("blurriness", n(14.0))]);
effect_case("ec.obsolete.gaussianlegacy", &[("blurriness", n(12.0)), ("blurDimensions", e(2))]);
}