use crate::{Color, RenderEffect, RuntimeShader, SubstrateSpec};
#[derive(Clone, Copy, Debug)]
pub struct LiquidGlassSpecialization {
pub flag: &'static str,
pub slots: &'static [usize],
pub inactive: fn(&[f32]) -> bool,
}
fn slot(uniforms: &[f32], index: usize) -> f32 {
uniforms.get(index).copied().unwrap_or(0.0)
}
pub const LIQUID_GLASS_SPECIALIZATIONS: &[LiquidGlassSpecialization] = &[
LiquidGlassSpecialization {
flag: "GLASS_LOUPE_OFF",
slots: &[80],
inactive: |u| slot(u, 80) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_FOLD_OFF",
slots: &[GLASS_FOLD_DEPTH_UNIFORM],
inactive: |u| slot(u, GLASS_FOLD_DEPTH_UNIFORM) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_SCENE_SHAPES_OFF",
slots: &[30],
inactive: |u| slot(u, 30) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_WOBBLE_OFF",
slots: &[32, 26],
inactive: |u| slot(u, 32) == 0.0 && slot(u, 26) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_ELLIPSE_BLEND_OFF",
slots: &[110],
inactive: |u| slot(u, 110) == 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_STRAIN_OFF",
slots: &[106, 107, 108, 109],
inactive: |u| {
let axis_identity = (slot(u, 106) == 0.0 && slot(u, 107) == 0.0)
|| (slot(u, 106) == 1.0 && slot(u, 107) == 0.0);
let ratio_identity = slot(u, 108) <= 0.0
|| slot(u, 109) <= 0.0
|| (slot(u, 108) == 1.0 && slot(u, 109) == 1.0);
axis_identity && ratio_identity
},
},
LiquidGlassSpecialization {
flag: "GLASS_ZOOM_ANCHOR_OFF",
slots: &[
GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM,
GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM + 1,
],
inactive: |u| {
slot(u, GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM) == 0.0
&& slot(u, GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM + 1) == 0.0
},
},
LiquidGlassSpecialization {
flag: "GLASS_TOUCH_OFF",
slots: &[120],
inactive: |u| slot(u, 120) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_CONTENT_MASK_OFF",
slots: &[112],
inactive: |u| slot(u, 112) <= 0.5,
},
LiquidGlassSpecialization {
flag: "GLASS_OPTICAL_BLUR_OFF",
slots: &[GLASS_BLUR_RADIUS_UNIFORM],
inactive: |u| slot(u, GLASS_BLUR_RADIUS_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_SHADOW_OFF",
slots: &[102],
inactive: |u| slot(u, 102) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_ZOOM_OFF",
slots: &[GLASS_OPTICAL_ZOOM_UNIFORM],
inactive: |u| slot(u, GLASS_OPTICAL_ZOOM_UNIFORM) <= 1.0,
},
LiquidGlassSpecialization {
flag: GLASS_PHYSICAL_REFRACTION_OFF_FLAG,
slots: &[GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM],
inactive: |u| slot(u, GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM) <= 0.5,
},
LiquidGlassSpecialization {
flag: "GLASS_FULL_TRANSMISSION",
slots: &[GLASS_TRANSMISSION_REFRACTION_UNIFORM],
inactive: |u| slot(u, GLASS_TRANSMISSION_REFRACTION_UNIFORM) >= 1.0,
},
LiquidGlassSpecialization {
flag: GLASS_DISPERSION_OFF_FLAG,
slots: &[GLASS_DISPERSION_UNIFORM],
inactive: |u| slot(u, GLASS_DISPERSION_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_ADAPTIVE_FROST_OFF",
slots: &[GLASS_ADAPTIVE_FROST_UNIFORM],
inactive: |u| slot(u, GLASS_ADAPTIVE_FROST_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_INK_OFF",
slots: &[127],
inactive: |u| slot(u, 127) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_RIM_STYLE_OFF",
slots: &[GLASS_RIM_STYLE_UNIFORM],
inactive: |u| slot(u, GLASS_RIM_STYLE_UNIFORM) <= 0.0,
},
LiquidGlassSpecialization {
flag: "GLASS_INTERIOR_GUARD",
slots: &[],
inactive: |_| true,
},
];
pub fn specialize_liquid_glass(shader: &mut RuntimeShader) {
let uniforms: Vec<f32> = shader.uniforms().to_vec();
for specialization in LIQUID_GLASS_SPECIALIZATIONS {
if (specialization.inactive)(&uniforms) {
shader.set_override(specialization.flag, 1.0);
} else {
shader.clear_override(specialization.flag);
}
}
shader.set_draw_split(Some(GLASS_RIM_DRAW_OVERRIDE));
let substrates = if slot(&uniforms, GLASS_ADAPTIVE_FROST_UNIFORM) > 0.0 {
vec![SubstrateSpec::Blur {
radius_px: GLASS_ADAPTIVE_NEIGHBOURHOOD_DP
* slot(&uniforms, GLASS_EFFECT_DENSITY_UNIFORM).max(1.0),
}]
} else {
Vec::new()
};
shader.set_substrates(substrates);
}
pub const GLASS_RIM_DRAW_OVERRIDE: &str = "GLASS_RIM_DRAW";
pub const GLASS_DISPERSION_OFF_FLAG: &str = "GLASS_DISPERSION_OFF";
pub const GLASS_PHYSICAL_REFRACTION_OFF_FLAG: &str = "GLASS_PHYSICAL_REFRACTION_OFF";
pub const GLASS_ADAPTIVE_NEIGHBOURHOOD_DP: f32 = 16.0;
pub fn liquid_glass_runtime_effect(mut shader: RuntimeShader) -> RenderEffect {
specialize_liquid_glass(&mut shader);
shader.set_batched_source(true);
RenderEffect::runtime_shader(shader)
}
pub const GLASS_ADAPTIVE_FROST_UNIFORM: usize = 91;
pub const GLASS_BLUR_RADIUS_UNIFORM: usize = 93;
pub const GLASS_REFRACTION_CURVE_UNIFORM: usize = 94;
pub const GLASS_DISPERSION_UNIFORM: usize = 95;
pub const GLASS_TRANSMISSION_REFRACTION_UNIFORM: usize = 96;
pub const GLASS_PHYSICAL_REFRACTION_DEPTH_UNIFORM: usize = 98;
pub const GLASS_EFFECT_DENSITY_UNIFORM: usize = 99;
pub const GLASS_MENISCUS_ABSORPTION_UNIFORM: usize = 100;
pub const GLASS_PHYSICAL_REFRACTION_DEPTH_ENABLED_UNIFORM: usize = 101;
pub const GLASS_FOLD_DEPTH_UNIFORM: usize = 88;
pub const GLASS_OPTICAL_ZOOM_UNIFORM: usize = 89;
pub const GLASS_OPTICAL_ZOOM_ANCHOR_UNIFORM: usize = 128;
pub const GLASS_RIM_STYLE_UNIFORM: usize = 28;
pub const GLASS_ACTIVITY_UNIFORM: usize = 111;
pub const GLASS_RESTING_TINT_UNIFORM: usize = 113;
pub const LIQUID_GLASS_WGSL: &str = include_str!("../shaders/liquid_glass.wgsl");
pub const GLASS_LIGHT_DIRECTION_UNIFORM: usize = 122;
#[derive(Clone, Debug, PartialEq)]
pub struct LiquidGlassSpec {
pub corner_radius: f32,
pub refraction_depth: f32,
pub refraction_curve: f32,
pub blur_radius: f32,
pub highlight: f32,
pub saturation: f32,
pub lift: f32,
pub contrast: f32,
pub meniscus_absorption: f32,
pub dither: f32,
}
impl Default for LiquidGlassSpec {
fn default() -> Self {
Self {
corner_radius: 28.0,
refraction_depth: 0.34,
refraction_curve: 0.25,
blur_radius: 0.0,
highlight: 0.7,
saturation: 1.0,
lift: 0.0,
contrast: 1.0,
meniscus_absorption: 1.0,
dither: 0.5,
}
}
}
#[derive(Clone, Debug)]
pub struct LiquidGlassRect {
pub left: f32,
pub top: f32,
pub width: f32,
pub height: f32,
pub tint_color: Color,
}
pub fn liquid_glass_effect(
rect: &LiquidGlassRect,
spec: &LiquidGlassSpec,
area_width: f32,
area_height: f32,
) -> RenderEffect {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
let cx = rect.left + rect.width * 0.5;
let cy = rect.top + rect.height * 0.5;
shader.set_float2(0, area_width, area_height);
shader.set_float2(2, cx, cy);
shader.set_float2(4, rect.width, rect.height);
shader.set_float(6, spec.corner_radius);
shader.set_float(9, spec.refraction_depth.clamp(0.0, 2.0));
shader.set_float(
GLASS_REFRACTION_CURVE_UNIFORM,
spec.refraction_curve.clamp(0.05, 1.0),
);
shader.set_float(11, spec.highlight);
shader.set_float4(
14,
rect.tint_color.r(),
rect.tint_color.g(),
rect.tint_color.b(),
rect.tint_color.a(),
);
shader.set_float(18, spec.saturation);
shader.set_float(20, spec.lift);
shader.set_float(21, spec.dither);
shader.set_float(24, spec.contrast);
shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, spec.blur_radius.max(0.0));
shader.set_float(GLASS_TRANSMISSION_REFRACTION_UNIFORM, 1.0);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, 1.0);
shader.set_float(
GLASS_MENISCUS_ABSORPTION_UNIFORM,
spec.meniscus_absorption.clamp(0.0, 1.0),
);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
shader.set_input_padding(liquid_glass_input_padding(spec));
liquid_glass_runtime_effect(shader)
}
fn liquid_glass_input_padding(spec: &LiquidGlassSpec) -> f32 {
spec.blur_radius.max(2.0).ceil()
}
#[derive(Clone, Debug, PartialEq)]
pub struct LiquidLoupeSpec {
pub magnification: f32,
pub focus_offset: (f32, f32),
pub dispersion: f32,
pub highlight: f32,
pub activity: f32,
pub corner_radius: f32,
}
impl Default for LiquidLoupeSpec {
fn default() -> Self {
Self {
magnification: 1.25,
focus_offset: (0.0, 75.0),
dispersion: 0.36,
highlight: 0.42,
activity: 1.0,
corner_radius: 0.0,
}
}
}
pub fn liquid_loupe_effect(node_size: (f32, f32), spec: &LiquidLoupeSpec) -> RenderEffect {
let (w, h) = (node_size.0.max(1.0), node_size.1.max(1.0));
let activity = spec.activity.clamp(0.0, 1.0);
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float2(0, w, h);
shader.set_float2(2, w * 0.5, h * 0.5);
shader.set_float2(4, w, h);
if spec.corner_radius > 0.0 {
shader.set_float(6, spec.corner_radius.min(0.5 * h.min(w)));
} else {
shader.set_float(6, -1.0);
}
shader.set_float(9, 0.34 * activity);
shader.set_float(GLASS_REFRACTION_CURVE_UNIFORM, 0.25);
shader.set_float(
GLASS_DISPERSION_UNIFORM,
spec.dispersion.clamp(0.0, 1.0) * activity,
);
shader.set_float(GLASS_TRANSMISSION_REFRACTION_UNIFORM, 1.0);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, 1.0);
shader.set_float(GLASS_MENISCUS_ABSORPTION_UNIFORM, 1.0);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
shader.set_float(11, spec.highlight * activity);
shader.set_float4(14, 1.0, 1.0, 1.0, 0.0);
shader.set_float(18, 1.0);
shader.set_float(20, 0.0);
shader.set_float(21, 0.5);
shader.set_float(24, 1.0);
shader.set_float(28, activity);
shader.set_float(80, 1.0);
shader.set_float2(81, spec.focus_offset.0, spec.focus_offset.1);
shader.set_float(83, 1.0 + (spec.magnification.max(0.2) - 1.0) * activity);
shader.set_float(90, activity);
let focus_reach = (spec.focus_offset.0.powi(2) + spec.focus_offset.1.powi(2)).sqrt();
shader.set_input_padding((focus_reach + 8.0).ceil());
liquid_glass_runtime_effect(shader)
}
pub fn liquid_menu_glass_effect(
node_size: (f32, f32),
blur_radius_px: f32,
progress: f32,
) -> RenderEffect {
let (w, h) = (node_size.0.max(1.0), node_size.1.max(1.0));
let p = progress.clamp(0.0, 1.0);
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float2(0, w, h);
shader.set_float2(2, w * 0.5, h * 0.5);
shader.set_float2(4, w, h);
shader.set_float(6, -1.0);
shader.set_float(9, 0.10 * p);
shader.set_float(GLASS_REFRACTION_CURVE_UNIFORM, 0.25);
shader.set_float(GLASS_TRANSMISSION_REFRACTION_UNIFORM, 1.0);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, 1.0);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 1.0);
shader.set_float(11, 0.19 * p);
shader.set_float4(14, 0.0, 0.0, 0.0, 0.04 * p);
shader.set_float(18, 1.0 + 0.10 * p);
shader.set_float(20, -0.06 * p);
shader.set_float(24, 1.0 + 0.05 * p);
shader.set_float(21, 0.5);
let requested_blur = if blur_radius_px > 0.5 {
blur_radius_px * (1.0 - 0.4 * p)
} else {
0.0
};
const WCKSRD_OPTICAL_BLUR_RADIUS_PX: f32 = 2.0;
let (wcksrd_blur, gaussian_blur) = if requested_blur > WCKSRD_OPTICAL_BLUR_RADIUS_PX {
(0.0, requested_blur)
} else {
(requested_blur, 0.0)
};
shader.set_float(GLASS_BLUR_RADIUS_UNIFORM, wcksrd_blur);
shader.set_input_padding(12.0 + requested_blur);
let optical = liquid_glass_runtime_effect(shader);
if gaussian_blur > f32::EPSILON {
RenderEffect::blur_with_edge_treatment(gaussian_blur, crate::TileMode::Mirror).then(optical)
} else {
optical
}
}
pub fn liquid_glass_effect_multi(
rects: &[LiquidGlassRect],
spec: &LiquidGlassSpec,
area_width: f32,
area_height: f32,
) -> Option<RenderEffect> {
let mut result: Option<RenderEffect> = None;
for rect in rects {
let effect = liquid_glass_effect(rect, spec, area_width, area_height);
result = Some(match result {
Some(existing) => existing.then(effect),
None => effect,
});
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn rect() -> LiquidGlassRect {
LiquidGlassRect {
left: 100.0,
top: 50.0,
width: 200.0,
height: 100.0,
tint_color: Color(0.5, 0.5, 1.0, 0.1),
}
}
fn shader_lines_reading(slot: usize) -> Vec<&'static str> {
let needles = [format!("get_float({slot}u)"), format!("get_vec2({slot}u)")];
LIQUID_GLASS_WGSL
.lines()
.filter(|line| needles.iter().any(|needle| line.contains(needle)))
.collect()
}
#[test]
fn every_specialization_flag_is_a_shader_override_and_guards_all_of_its_slot_reads() {
for specialization in LIQUID_GLASS_SPECIALIZATIONS {
let declaration = format!("override {}: bool = false;", specialization.flag);
assert!(
LIQUID_GLASS_WGSL.contains(&declaration),
"`{declaration}` missing from liquid_glass.wgsl"
);
let mut guarded_reads = 0;
for slot in specialization.slots {
for line in shader_lines_reading(*slot) {
assert!(
line.contains(specialization.flag),
"slot {slot} is read outside its `{}` guard: `{}`",
specialization.flag,
line.trim()
);
guarded_reads += 1;
}
}
assert!(
guarded_reads > 0 || specialization.slots.is_empty(),
"`{}` guards no uniform read; the flag would fold nothing",
specialization.flag
);
}
let declared: Vec<&str> = LIQUID_GLASS_WGSL
.lines()
.filter_map(|line| line.strip_prefix("override "))
.filter(|rest| rest.contains(": bool"))
.filter_map(|rest| rest.split(':').next())
.collect();
for flag in &declared {
assert!(
LIQUID_GLASS_SPECIALIZATIONS
.iter()
.any(|specialization| specialization.flag == *flag),
"shader override `{flag}` is missing from LIQUID_GLASS_SPECIALIZATIONS, so \
nothing ever raises it"
);
}
assert_eq!(declared.len(), LIQUID_GLASS_SPECIALIZATIONS.len());
}
fn raised_flags(effect: &RenderEffect) -> Vec<&'static str> {
let RenderEffect::Shader { shader } = effect else {
panic!("liquid glass must be one runtime shader");
};
shader.overrides().iter().map(|(flag, _)| *flag).collect()
}
#[test]
fn a_resting_glass_keeps_its_substrate_declaration() {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float(GLASS_ADAPTIVE_FROST_UNIFORM, 0.42);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, 2.0);
shader.set_float(GLASS_ACTIVITY_UNIFORM, 0.0);
specialize_liquid_glass(&mut shader);
assert_eq!(
shader.substrates().len(),
1,
"the declaration sets the capture geometry, which must not follow activity"
);
}
#[test]
fn a_plain_pane_raises_every_flag() {
let flags = raised_flags(&liquid_glass_effect(
&rect(),
&LiquidGlassSpec::default(),
800.0,
600.0,
));
let every_flag: Vec<&str> = LIQUID_GLASS_SPECIALIZATIONS
.iter()
.map(|specialization| specialization.flag)
.collect();
let mut sorted = every_flag.clone();
sorted.sort_unstable();
assert_eq!(
flags, sorted,
"a plain pane uses no optional feature, so every flag folds"
);
}
#[test]
fn respecializing_mutated_uniforms_matches_fresh_shader_and_preserves_caller_override() {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_override("CALLER_OVERRIDE", 7.0);
specialize_liquid_glass(&mut shader);
shader.set_float(GLASS_RIM_STYLE_UNIFORM, 1.0);
specialize_liquid_glass(&mut shader);
let mut fresh = RuntimeShader::new(LIQUID_GLASS_WGSL);
fresh.set_override("CALLER_OVERRIDE", 7.0);
fresh.set_float(GLASS_RIM_STYLE_UNIFORM, 1.0);
specialize_liquid_glass(&mut fresh);
assert_eq!(shader.overrides(), fresh.overrides());
assert_eq!(shader.overrides_hash(), fresh.overrides_hash());
assert!(shader.overrides().contains(&("CALLER_OVERRIDE", 7.0)));
shader.set_float(GLASS_RIM_STYLE_UNIFORM, 0.0);
specialize_liquid_glass(&mut shader);
let mut inactive = RuntimeShader::new(LIQUID_GLASS_WGSL);
inactive.set_override("CALLER_OVERRIDE", 7.0);
specialize_liquid_glass(&mut inactive);
assert_eq!(shader.overrides(), inactive.overrides());
assert_eq!(shader.overrides_hash(), inactive.overrides_hash());
assert!(shader.overrides().contains(&("CALLER_OVERRIDE", 7.0)));
}
#[test]
fn a_blurred_dispersive_loupe_keeps_its_features_live() {
let flags = raised_flags(&liquid_loupe_effect(
(200.0, 120.0),
&LiquidLoupeSpec::default(),
));
for live in ["GLASS_LOUPE_OFF", "GLASS_DISPERSION_OFF"] {
assert!(
!flags.contains(&live),
"{live} must stay live for a loupe: {flags:?}"
);
}
assert!(flags.contains(&"GLASS_FOLD_OFF"));
assert!(flags.contains(&"GLASS_SCENE_SHAPES_OFF"));
let blurred = liquid_glass_effect(
&rect(),
&LiquidGlassSpec {
blur_radius: 1.5,
..LiquidGlassSpec::default()
},
800.0,
600.0,
);
assert!(
!raised_flags(&blurred).contains(&"GLASS_OPTICAL_BLUR_OFF"),
"an optical blur radius keeps the wcKSRD footprint live"
);
}
#[test]
fn liquid_glass_spec_defaults_match_wcksrd() {
let spec = LiquidGlassSpec::default();
assert_eq!(spec.corner_radius, 28.0);
assert_eq!(spec.refraction_depth, 0.34);
assert_eq!(spec.refraction_curve, 0.25);
assert_eq!(spec.blur_radius, 0.0);
assert_eq!(spec.saturation, 1.0);
assert_eq!(spec.lift, 0.0);
assert_eq!(spec.contrast, 1.0);
assert_eq!(spec.meniscus_absorption, 1.0);
}
#[test]
fn liquid_glass_effect_packs_the_single_optical_program() {
let spec = LiquidGlassSpec {
refraction_depth: 0.72,
refraction_curve: 0.8,
blur_radius: 6.0,
saturation: 1.6,
lift: 0.12,
contrast: 1.05,
meniscus_absorption: 0.3,
dither: 1.0,
..LiquidGlassSpec::default()
};
let RenderEffect::Shader { shader } = liquid_glass_effect(&rect(), &spec, 800.0, 600.0)
else {
panic!("liquid glass must be one runtime shader");
};
let uniforms = shader.uniforms();
assert_eq!(&uniforms[0..6], &[800.0, 600.0, 200.0, 100.0, 200.0, 100.0]);
assert_eq!(uniforms[9], 0.72);
assert_eq!(uniforms[GLASS_REFRACTION_CURVE_UNIFORM], 0.8);
assert_eq!(uniforms[GLASS_TRANSMISSION_REFRACTION_UNIFORM], 1.0);
assert_eq!(uniforms[GLASS_EFFECT_DENSITY_UNIFORM], 1.0);
assert_eq!(uniforms[GLASS_MENISCUS_ABSORPTION_UNIFORM], 0.3);
assert_eq!(uniforms[18], 1.6);
assert_eq!(uniforms[20], 0.12);
assert_eq!(uniforms[21], 1.0);
assert_eq!(uniforms[24], 1.05);
assert_eq!(uniforms[GLASS_BLUR_RADIUS_UNIFORM], 6.0);
assert_eq!(shader.input_padding(), 6.0);
}
#[test]
fn refraction_depth_is_clamped_at_the_shader_boundary() {
for (input, expected) in [(-1.0, 0.0), (0.8, 0.8), (3.0, 2.0)] {
let spec = LiquidGlassSpec {
refraction_depth: input,
..LiquidGlassSpec::default()
};
let RenderEffect::Shader { shader } = liquid_glass_effect(&rect(), &spec, 800.0, 600.0)
else {
panic!("liquid glass must be one runtime shader");
};
assert_eq!(shader.uniforms()[9], expected);
}
}
#[test]
fn refraction_curve_is_clamped_at_the_shader_boundary() {
for (input, expected) in [(-1.0, 0.05), (0.8, 0.8), (3.0, 1.0)] {
let spec = LiquidGlassSpec {
refraction_curve: input,
..LiquidGlassSpec::default()
};
let RenderEffect::Shader { shader } = liquid_glass_effect(&rect(), &spec, 800.0, 600.0)
else {
panic!("liquid glass must be one runtime shader");
};
assert_eq!(shader.uniforms()[GLASS_REFRACTION_CURVE_UNIFORM], expected);
}
}
#[test]
fn loupe_and_menu_use_the_same_wcksrd_program() {
let loupe_spec = LiquidLoupeSpec::default();
let RenderEffect::Shader { shader: loupe } =
liquid_loupe_effect((117.0, 82.0), &loupe_spec)
else {
panic!("loupe must use the shared runtime shader");
};
assert_eq!(loupe.uniforms()[9], 0.34);
assert_eq!(loupe.uniforms()[GLASS_REFRACTION_CURVE_UNIFORM], 0.25);
assert_eq!(
loupe.uniforms()[GLASS_DISPERSION_UNIFORM],
loupe_spec.dispersion
);
assert_eq!(loupe.uniforms()[80], 1.0);
assert!(loupe.input_padding() >= 75.0);
let RenderEffect::Chain { first, second } =
liquid_menu_glass_effect((240.0, 44.0), 8.0, 1.0)
else {
panic!("a heavy settled blur must chain a Gaussian into the shader");
};
let RenderEffect::Blur { radius_x, .. } = *first else {
panic!("the chain's first stage is the Gaussian remainder");
};
assert!(radius_x > 0.0);
let RenderEffect::Shader { shader: menu } = *second else {
panic!("the chain's second stage is the wcKSRD program");
};
assert_eq!(menu.uniforms()[9], 0.10);
assert_eq!(menu.uniforms()[GLASS_REFRACTION_CURVE_UNIFORM], 0.25);
assert!(menu.uniforms()[GLASS_BLUR_RADIUS_UNIFORM] <= 2.0 + 1.0e-6);
}
#[test]
fn liquid_glass_effect_multi_handles_empty_and_multiple_rects() {
let spec = LiquidGlassSpec::default();
assert!(liquid_glass_effect_multi(&[], &spec, 800.0, 600.0).is_none());
let rects = [rect(), rect()];
assert!(matches!(
liquid_glass_effect_multi(&rects, &spec, 800.0, 600.0),
Some(RenderEffect::Chain { .. })
));
}
}