use crate::gpu::scene::Blur;
use bytemuck::{Pod, Zeroable};
pub(super) const PARAMS_SIZE: u64 = 32;
pub(super) const DOWNSCALE: f32 = 2.0;
pub(super) const MAX_TAPS: f32 = 24.0;
pub(super) const IDLE_FRAMES: u32 = 120;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub(super) struct Params {
pub(super) inv_size: [f32; 2],
pub(super) direction: [f32; 2],
pub(super) sigma: f32,
pub(super) stride: f32,
pub(super) taps: f32,
pub(super) padding: f32,
}
pub(super) type Scissor = Option<[u32; 4]>;
pub(super) fn plan(blur: &Blur, scale: f32, low: (u32, u32)) -> (Scissor, Scissor, [Params; 2]) {
let sigma_low = (blur.sigma * scale / DOWNSCALE).max(0.0);
let stride = (sigma_low / (MAX_TAPS / 3.0)).max(1.0);
let sigma = sigma_low / stride;
let taps = (sigma * 3.0).ceil().clamp(1.0, MAX_TAPS);
let reach = (taps * stride).ceil() as i64 + 1;
let (left, top) = (blur.bounds[0].max(blur.clip[0]), blur.bounds[1].max(blur.clip[1]));
let right = (blur.bounds[0] + blur.bounds[2]).min(blur.clip[0] + blur.clip[2]);
let bottom = (blur.bounds[1] + blur.bounds[3]).min(blur.clip[1] + blur.clip[3]);
let to_low = |value: f32| (value * scale / DOWNSCALE).floor() as i64;
let rect = (
to_low(left),
to_low(top),
(right * scale / DOWNSCALE).ceil() as i64,
(bottom * scale / DOWNSCALE).ceil() as i64,
);
let clamp = |(x0, y0, x1, y1): (i64, i64, i64, i64)| -> Scissor {
let (x0, y0) = (x0.clamp(0, low.0 as i64), y0.clamp(0, low.1 as i64));
let (x1, y1) = (x1.clamp(0, low.0 as i64), y1.clamp(0, low.1 as i64));
(x1 > x0 && y1 > y0).then(|| [x0 as u32, y0 as u32, (x1 - x0) as u32, (y1 - y0) as u32])
};
let first = clamp((rect.0 - 1, rect.1 - reach, rect.2 + 1, rect.3 + reach));
let second = clamp((rect.0 - 1, rect.1 - 1, rect.2 + 1, rect.3 + 1));
let inv_size = [1.0 / low.0 as f32, 1.0 / low.1 as f32];
let params = |direction: [f32; 2]| Params { inv_size, direction, sigma, stride, taps, padding: 0.0 };
(first, second, [params([1.0, 0.0]), params([0.0, 1.0])])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Bounds, Corners, Point, Size, px};
fn blur(x: f32, y: f32, w: f32, h: f32, sigma: f32) -> Blur {
let area = Bounds::new(Point::new(px(x), px(y)), Size::new(px(w), px(h)));
Blur::new(
area,
Bounds::new(Point::new(px(0.0), px(0.0)), Size::new(px(1000.0), px(1000.0))),
Corners::all(px(0.0)),
sigma,
)
}
#[test]
fn el_recorte_de_la_vertical_es_el_de_la_caja_y_el_de_la_horizontal_lo_amplia() {
let (first, second, params) = plan(&blur(100.0, 100.0, 200.0, 100.0, 8.0), 2.0, (500, 500));
let (first, second) = (first.unwrap(), second.unwrap());
assert_eq!((second[0], second[1], second[2], second[3]), (99, 99, 202, 102), "caja en media resolución ± 1");
assert!(first[1] < second[1] && first[3] > second[3], "la horizontal cubre más filas");
assert_eq!(first[0], second[0], "pero las mismas columnas");
assert_eq!(params[0].direction, [1.0, 0.0]);
assert_eq!(params[1].direction, [0.0, 1.0]);
}
#[test]
fn mucha_desviacion_separa_las_muestras_en_vez_de_anadir_mas() {
let (_, _, small) = plan(&blur(0.0, 0.0, 10.0, 10.0, 4.0), 1.0, (100, 100));
let (_, _, large) = plan(&blur(0.0, 0.0, 10.0, 10.0, 64.0), 1.0, (100, 100));
assert_eq!(small[0].stride, 1.0);
assert!(large[0].stride > 1.0 && large[0].taps <= MAX_TAPS);
let reach = |p: &Params| p.stride * p.sigma;
assert!((reach(&large[0]) - 32.0).abs() < 0.01, "σ en media resolución = 64 / 2");
}
#[test]
fn un_rectangulo_fuera_de_la_pantalla_no_pinta_nada() {
let (first, second, _) = plan(&blur(5000.0, 5000.0, 100.0, 100.0, 8.0), 1.0, (100, 100));
assert!(first.is_none() && second.is_none());
}
}