From 3a9188e0fb6192e84c141799d25abb48c2ffb5b0 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Thu, 5 Nov 2026 18:11:29 +0100 Subject: [PATCH] HDR Highlight Compression on the GPU: a NaN Amount keeps the layer, as on the CPU Doc comments on the touched helpers. Co-Authored-By: Claude Opus 5.5 --- crates/effects/src/utility.rs | 3 +++ crates/gpu/src/fx_pixel2.rs | 5 ++++- crates/gpu/src/tests/fx_pixel2.rs | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/effects/src/utility.rs b/crates/effects/src/utility.rs index 65de35cf6..667b90242 100644 --- a/crates/effects/src/utility.rs +++ b/crates/effects/src/utility.rs @@ -60,6 +60,7 @@ const HDR_LUMA: [f32; 3] = [0.30, 0.59, 0.11]; /// Exponent of the domain the Amount blend happens in (out^0.4 is mixed linearly). const HDR_BLEND_GAMMA: f32 = 0.4; +/// `v^e` with the sign of `v` kept (negative input stays negative, as on the GPU). #[inline] fn hdr_spow(v: f32, e: f32) -> f32 { v.signum() * v.abs().powf(e) @@ -82,6 +83,8 @@ fn hdr_compress_pixel(c: [f32; 3], t: f32) -> [f32; 3] { }) } +/// HDR Highlight Compression: every straight-colour pixel through [`hdr_compress_pixel`] at +/// Amount / 100; Amount 0 or NaN returns the layer unchanged. fn hdr_compression(ctx: &EffectCtx, mut b: Buf) -> Buf { let t = (ctx.params.f("amount") / 100.0).clamp(0.0, 1.0) as f32; if t <= 0.0 || !t.is_finite() { diff --git a/crates/gpu/src/fx_pixel2.rs b/crates/gpu/src/fx_pixel2.rs index 1fe1fec72..5b20976f4 100644 --- a/crates/gpu/src/fx_pixel2.rs +++ b/crates/gpu/src/fx_pixel2.rs @@ -215,6 +215,8 @@ fn composite_mode(m: u32) -> BlendMode { } } +/// The per-pixel effects of `fp2_point` (Cineon, HDR Compander and Highlight Compression, CC +/// Overbrights, CC Composite, Color Link): their parameters packed for the kernel, or `None`. fn point(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option { let pr = ctx.params; let mut p = Params::default(); @@ -238,7 +240,8 @@ fn point(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option { } "ec.utility.hdrcompression" => { let t = (pr.f("amount") / 100.0).clamp(0.0, 1.0) as f32; - if t <= 0.0 { + // A NaN Amount keeps the layer, as on the CPU. + if t <= 0.0 || !t.is_finite() { return Some(b); } p.u[0][0] = 2; diff --git a/crates/gpu/src/tests/fx_pixel2.rs b/crates/gpu/src/tests/fx_pixel2.rs index 9660592e7..16f2bd392 100644 --- a/crates/gpu/src/tests/fx_pixel2.rs +++ b/crates/gpu/src/tests/fx_pixel2.rs @@ -185,6 +185,8 @@ fn composites_and_colour() { effect_case("ec.utility.hdrcompression", &[]); effect_case("ec.utility.hdrcompression", &[("amount", n(40.0))]); effect_case("ec.utility.hdrcompression", &[("amount", n(90.0))]); + // A NaN Amount keeps the layer (the CPU returns it unchanged). + effect_case("ec.utility.hdrcompression", &[("amount", n(f64::NAN))]); effect_case("ec.utility.growbounds", &[("pixels", n(9.0))]); effect_case("ec.utility.ccoverbrights", &[]); for ch in 1..=5 {