use crate::geometry::Region;
pub const MAX_GAUSSIAN_RADIUS: i32 = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PingPongKey {
pub size: [u32; 2],
pub levels: u32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GaussianKernel {
pub sigma: f32,
pub tap_radius: i32,
}
pub fn resolve_gaussian(physical_radius: f32) -> GaussianKernel {
let tap_radius = (physical_radius.round() as i32).clamp(0, MAX_GAUSSIAN_RADIUS);
let sigma = (physical_radius / 3.0).max(0.5);
GaussianKernel { sigma, tap_radius }
}
pub const KAWASE_THRESHOLD_PX: f32 = 16.0;
pub const MAX_KAWASE_LEVELS: u32 = 6;
pub fn use_dual_kawase(physical_radius: f32) -> bool {
physical_radius >= KAWASE_THRESHOLD_PX
}
pub fn resolve_kawase_levels(physical_radius: f32) -> u32 {
let levels = physical_radius.max(2.0).log2().round() as i32;
levels.clamp(1, MAX_KAWASE_LEVELS as i32) as u32
}
pub fn kawase_level_size(base: [u32; 2], level: u32) -> [u32; 2] {
[(base[0] >> level).max(1), (base[1] >> level).max(1)]
}
pub fn kawase_halfpixel(size: [u32; 2]) -> [f32; 2] {
[0.5 / size[0] as f32, 0.5 / size[1] as f32]
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TargetEncoding {
Linear,
Srgb,
}
pub fn backdrop_uv_remap(source_region: &Region, clipped: &Region) -> ([f32; 2], [f32; 2]) {
let [sx, sy] = [
source_region.origin[0] as f32,
source_region.origin[1] as f32,
];
let [sw, sh] = [source_region.size[0] as f32, source_region.size[1] as f32];
let [cx, cy] = [clipped.origin[0] as f32, clipped.origin[1] as f32];
let [cw, ch] = [clipped.size[0] as f32, clipped.size[1] as f32];
([(sx - cx) / cw, (sy - cy) / ch], [sw / cw, sh / ch])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Scale;
fn close(a: f32, b: f32) -> bool {
(a - b).abs() < 1e-4
}
#[test]
fn resolve_gaussian_zero_radius_is_single_tap_passthrough() {
let k = resolve_gaussian(0.0);
assert_eq!(k.tap_radius, 0);
assert!(
k.sigma > 0.0,
"sigma must stay positive so i/sigma is finite"
);
}
#[test]
fn resolve_gaussian_sets_sigma_to_a_third_of_radius() {
let k = resolve_gaussian(15.0);
assert_eq!(k.tap_radius, 15);
assert!(close(k.sigma, 5.0));
}
#[test]
fn resolve_gaussian_clamps_tap_radius_to_max() {
let k = resolve_gaussian(1000.0);
assert_eq!(k.tap_radius, MAX_GAUSSIAN_RADIUS);
}
#[test]
fn use_dual_kawase_switches_at_the_threshold() {
assert!(!use_dual_kawase(KAWASE_THRESHOLD_PX - 0.1));
assert!(use_dual_kawase(KAWASE_THRESHOLD_PX));
assert!(use_dual_kawase(40.0));
}
#[test]
fn resolve_kawase_levels_grows_logarithmically_and_clamps() {
assert_eq!(resolve_kawase_levels(16.0), 4); assert_eq!(resolve_kawase_levels(32.0), 5);
assert_eq!(resolve_kawase_levels(10000.0), MAX_KAWASE_LEVELS);
assert_eq!(resolve_kawase_levels(2.0), 1); assert_eq!(resolve_kawase_levels(22.0), 4);
assert_eq!(resolve_kawase_levels(23.0), 5);
}
#[test]
fn kawase_level_size_halves_and_floors_at_one() {
assert_eq!(kawase_level_size([200, 100], 0), [200, 100]);
assert_eq!(kawase_level_size([200, 100], 1), [100, 50]);
assert_eq!(kawase_level_size([200, 100], 2), [50, 25]);
assert_eq!(kawase_level_size([200, 1], 4), [12, 1]);
}
#[test]
fn kawase_halfpixel_is_half_a_texel() {
assert_eq!(kawase_halfpixel([100, 50]), [0.5 / 100.0, 0.5 / 50.0]);
}
fn region(origin: [u32; 2], size: [u32; 2]) -> Region {
Region {
origin,
size,
scale: Scale::new(1.0),
}
}
#[test]
fn backdrop_uv_remap_is_identity_when_unclipped() {
let r = region([50, 50], [100, 100]);
let (offset, scale) = backdrop_uv_remap(&r, &r);
assert!(close(offset[0], 0.0) && close(offset[1], 0.0));
assert!(close(scale[0], 1.0) && close(scale[1], 1.0));
}
#[test]
fn backdrop_uv_remap_insets_a_right_clipped_region() {
let source = region([100, 50], [100, 80]);
let clipped = region([100, 50], [60, 80]);
let (offset, scale) = backdrop_uv_remap(&source, &clipped);
assert!(close(offset[0], 0.0) && close(offset[1], 0.0));
assert!(close(scale[0], 100.0 / 60.0) && close(scale[1], 1.0));
}
#[test]
fn ping_pong_key_distinguishes_size_and_levels() {
let a = PingPongKey {
size: [100, 80],
levels: 1,
};
let b = PingPongKey {
size: [100, 80],
levels: 2,
};
let c = PingPongKey {
size: [80, 100],
levels: 1,
};
assert_ne!(a, b);
assert_ne!(a, c);
assert_eq!(
a,
PingPongKey {
size: [100, 80],
levels: 1
}
);
}
}