use std::{
cell::RefCell,
sync::atomic::{AtomicBool, Ordering},
};
use crate::{
Color, RenderEffect, RuntimeShader, SubstrateSpec, render_effect::ShaderSpecializationCache,
};
#[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_DIRECTIONAL_REFRACTION_OFF",
slots: &[GLASS_REFRACTION_MODE_UNIFORM],
inactive: |u| slot(u, GLASS_REFRACTION_MODE_UNIFORM) <= 0.5,
},
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_PROJECTION_OFF",
slots: &[168, 169],
inactive: |u| {
slot(u, 168) <= 0.0
|| slot(u, 169) <= 0.0
|| (slot(u, 168) == 1.0 && slot(u, 169) == 1.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_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 glass_material_folds_enabled() -> bool {
GLASS_MATERIAL_FOLDS.load(Ordering::Relaxed)
}
pub fn set_glass_material_folds(enabled: bool) {
GLASS_MATERIAL_FOLDS.store(enabled, Ordering::Relaxed);
}
static GLASS_MATERIAL_FOLDS: AtomicBool = AtomicBool::new(cfg!(target_os = "android"));
pub fn specialize_liquid_glass(shader: &mut RuntimeShader) {
specialize_liquid_glass_with_folds(shader, glass_material_folds_enabled());
}
pub fn specialize_liquid_glass_with_folds(shader: &mut RuntimeShader, folds: bool) {
const _: () = assert!(LIQUID_GLASS_SPECIALIZATIONS.len() <= u32::BITS as usize);
const CACHE_CAPACITY: usize = 32;
type SpecializationKey = (
u32,
Option<u32>,
bool,
bool,
bool,
Option<u32>,
u8,
Option<u32>,
);
thread_local! {
static CACHE: RefCell<ShaderSpecializationCache<SpecializationKey, CACHE_CAPACITY>> =
const { RefCell::new(ShaderSpecializationCache::new()) };
}
let uniforms = shader.uniforms();
let flags = if folds {
LIQUID_GLASS_SPECIALIZATIONS
.iter()
.enumerate()
.fold(0, |flags, (index, specialization)| {
flags | (u32::from((specialization.inactive)(uniforms)) << index)
})
} else {
0
};
let substrate_radius = (slot(uniforms, GLASS_ADAPTIVE_FROST_UNIFORM) > 0.0).then(|| {
(GLASS_ADAPTIVE_NEIGHBOURHOOD_DP * slot(uniforms, GLASS_EFFECT_DENSITY_UNIFORM).max(1.0))
.to_bits()
});
let mean_tone = slot(uniforms, GLASS_ADAPTIVE_TONE_UNIFORM) > 0.5;
let pane_radius = (slot(uniforms, GLASS_PANE_BLEND_UNIFORM) > 0.0)
.then(|| slot(uniforms, GLASS_PANE_BLEND_UNIFORM).to_bits());
let projection = [
slot(uniforms, GLASS_OPTICAL_PROJECTION_UNIFORM),
slot(uniforms, GLASS_OPTICAL_PROJECTION_UNIFORM + 1),
];
let projected = projection.iter().all(|v| *v > 0.0) && projection != [1.0, 1.0];
let stage = slot(uniforms, GLASS_OPTICAL_STAGE_UNIFORM) as u8;
let backdrop_radius = (stage == 2 && slot(uniforms, GLASS_BACKDROP_BLUR_UNIFORM) > 0.0)
.then(|| slot(uniforms, GLASS_BACKDROP_BLUR_UNIFORM).to_bits());
CACHE.with_borrow_mut(|cache| {
cache.apply(
shader,
(
flags,
substrate_radius,
folds,
mean_tone,
projected,
pane_radius,
stage,
backdrop_radius,
),
|shader, &(flags, radius, folds, mean_tone, projected, pane_radius, stage, backdrop_radius)| {
for (index, specialization) in LIQUID_GLASS_SPECIALIZATIONS.iter().enumerate() {
if flags & (1 << index) != 0 {
shader.set_override(specialization.flag, 1.0);
} else {
shader.clear_override(specialization.flag);
}
}
shader.set_draw_split((folds && !projected).then_some(GLASS_RIM_DRAW_OVERRIDE));
let substrate = radius.map(|radius| SubstrateSpec::Blur {
radius_px: f32::from_bits(radius),
});
let mut substrates = arrayvec::ArrayVec::<SubstrateSpec, 3>::new();
substrates.extend(substrate);
if mean_tone {
substrates.push(SubstrateSpec::Mean);
}
if let Some(radius) = pane_radius {
substrates.push(SubstrateSpec::Blur {
radius_px: f32::from_bits(radius),
});
}
if matches!(stage, 1 | 2) {
substrates.clear();
if let Some(radius) = backdrop_radius {
substrates.push(SubstrateSpec::Blur { radius_px: f32::from_bits(radius) });
}
}
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(shader: RuntimeShader) -> RenderEffect {
if slot(shader.uniforms(), GLASS_REFRACTION_MODE_UNIFORM) >= 1.5
&& slot(shader.uniforms(), 112) <= 0.5
&& slot(shader.uniforms(), GLASS_OPTICAL_STAGE_UNIFORM) == 0.0
{
let stage = |index: u8| {
let mut pass = shader.clone();
pass.set_float(GLASS_OPTICAL_STAGE_UNIFORM, f32::from(index));
if index < 3 {
pass.set_output_support(None);
pass.set_output_padding(0.0);
}
glass_shader_effect(pass)
};
stage(1).then(stage(2)).then(stage(3))
} else {
glass_shader_effect(shader)
}
}
fn glass_shader_effect(mut shader: RuntimeShader) -> RenderEffect {
specialize_liquid_glass(&mut shader);
if matches!(
slot(shader.uniforms(), GLASS_OPTICAL_STAGE_UNIFORM),
1.0 | 2.0
) {
shader.set_draw_split(None);
}
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_REFRACTION_MODE_UNIFORM: usize = 130;
pub const GLASS_EDGE_REFRACTION_REACH_UNIFORM: usize = 131;
pub const GLASS_FACE_LIGHTING_OFF_UNIFORM: usize = 132;
pub const GLASS_TOUCH_RADIUS_UNIFORM: usize = 133;
pub const GLASS_LAYER_CLIPPED_UNIFORM: usize = 134;
pub const GLASS_KEY_FILL_UNIFORM: usize = 135;
pub const GLASS_FACE_RESPONSE_UNIFORM: usize = 142;
pub const GLASS_CAPSULE_SMOOTHING_UNIFORM: usize = 146;
pub const GLASS_OPTICAL_STAGE_UNIFORM: usize = 147;
pub const GLASS_EDGE_SPECTRUM_UNIFORM: usize = 148;
pub const GLASS_INNER_SHADOW_UNIFORM: usize = 156;
pub const GLASS_EDGE_RETURN_DEPTH_UNIFORM: usize = 164;
pub const GLASS_OPTICAL_PROJECTION_UNIFORM: usize = 168;
pub const GLASS_ADAPTIVE_TONE_UNIFORM: usize = 166;
pub const GLASS_PANE_BLEND_UNIFORM: usize = 170;
pub const GLASS_BACKDROP_BLUR_UNIFORM: usize = 172;
pub const GLASS_FOREGROUND_CONTENT_UNIFORM: usize = 174;
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 = concat!(
include_str!("../shaders/glass_geometry.wgsl"),
include_str!("../shaders/liquid_glass.wgsl"),
);
pub const LIQUID_GLASS_GEOMETRY_WGSL: &str = include_str!("../shaders/glass_geometry.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 projected_optics_disable_rim_scissors_and_restore_them_at_identity() {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float2(GLASS_OPTICAL_PROJECTION_UNIFORM, 1.2, 0.75);
specialize_liquid_glass_with_folds(&mut shader, true);
assert_eq!(shader.draw_split(), None);
shader.set_float2(GLASS_OPTICAL_PROJECTION_UNIFORM, 1.0, 1.0);
specialize_liquid_glass_with_folds(&mut shader, true);
assert_eq!(shader.draw_split(), Some(GLASS_RIM_DRAW_OVERRIDE));
}
#[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");
};
let mut shader = (**shader).clone();
specialize_liquid_glass_with_folds(&mut shader, true);
shader.overrides().iter().map(|(flag, _)| *flag).collect()
}
#[test]
fn glass_substrates_refresh_for_frost_tone_and_pane_combinations() {
for folds in [false, true] {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
for radius in [0.0, 8.0, 24.0, 0.0] {
for frost in [false, true] {
for tone in [false, true] {
shader.set_float(GLASS_PANE_BLEND_UNIFORM, radius);
shader.set_float(GLASS_ADAPTIVE_FROST_UNIFORM, f32::from(frost));
shader.set_float(GLASS_ADAPTIVE_TONE_UNIFORM, f32::from(tone));
specialize_liquid_glass_with_folds(&mut shader, folds);
let mut expected = Vec::new();
if frost {
expected.push(SubstrateSpec::Blur {
radius_px: GLASS_ADAPTIVE_NEIGHBOURHOOD_DP,
});
}
if tone {
expected.push(SubstrateSpec::Mean);
}
if radius > 0.0 {
expected.push(SubstrateSpec::Blur { radius_px: radius });
}
assert_eq!(shader.substrates(), expected);
}
}
}
}
}
#[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_with_folds(&mut shader, true);
assert_eq!(
shader.substrates().len(),
1,
"the declaration sets the capture geometry, which must not follow activity"
);
}
#[test]
fn edge_lens_stages_preserve_intermediate_images_and_final_support() {
fn shaders(effect: &RenderEffect) -> Vec<&RuntimeShader> {
match effect {
RenderEffect::Shader { shader } => vec![shader],
RenderEffect::Chain { first, second } => {
let mut passes = shaders(first);
passes.extend(shaders(second));
passes
}
_ => panic!("glass must contain shader stages"),
}
}
let support = crate::Rect {
x: -2.0,
y: -3.0,
width: 120.0,
height: 80.0,
};
for activity in [0.0, 0.5, 1.0] {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
shader.set_float(GLASS_REFRACTION_MODE_UNIFORM, 2.0);
shader.set_float(GLASS_ACTIVITY_UNIFORM, activity);
shader.set_input_padding(30.0);
shader.set_output_padding(8.0);
shader.set_output_support(Some(support));
let effect = liquid_glass_runtime_effect(shader.clone());
let passes = shaders(&effect);
assert_eq!(passes.len(), 3);
for (index, pass) in passes.iter().enumerate() {
assert_eq!(
pass.uniforms()[GLASS_OPTICAL_STAGE_UNIFORM],
(index + 1) as f32
);
assert_eq!(pass.uniforms()[GLASS_ACTIVITY_UNIFORM], activity);
assert!(pass.batched_source());
assert_eq!(pass.input_padding(), 30.0);
if index < 2 {
assert_eq!(pass.output_support(), None);
assert_eq!(pass.output_padding(), 0.0);
assert_eq!(pass.draw_split(), None);
} else {
assert_eq!(pass.output_support(), Some(support));
assert_eq!(pass.output_padding(), 8.0);
}
}
for (mode, mask, stage) in [
(0.0, 0.0, 0.0),
(1.0, 0.0, 0.0),
(2.0, 1.0, 0.0),
(2.0, 0.0, 1.0),
(2.0, 0.0, 2.0),
(2.0, 0.0, 3.0),
] {
let mut single = shader.clone();
single.set_float(GLASS_REFRACTION_MODE_UNIFORM, mode);
single.set_float(112, mask);
single.set_float(GLASS_OPTICAL_STAGE_UNIFORM, stage);
let effect = liquid_glass_runtime_effect(single);
let passes = shaders(&effect);
assert_eq!(passes.len(), 1);
assert_eq!(passes[0].uniforms()[GLASS_OPTICAL_STAGE_UNIFORM], stage);
}
}
}
#[test]
fn cached_glass_specialization_tracks_flags_and_substrate_radius() {
let mut template = RuntimeShader::new(LIQUID_GLASS_WGSL);
template.set_override("CALLER", 7.0);
specialize_liquid_glass_with_folds(&mut template, true);
for (activity, frost, density, rim) in [
(1.0, 0.5, 1.0, 0.0),
(1.0, 0.5, 2.0, 0.0),
(0.0, 0.5, 2.0, 1.0),
(0.5, 0.0, 3.0, 0.0),
(1.0, 0.5, 1.0, 0.0),
] {
for _ in 0..2 {
let mut shader = template.clone();
shader.set_float(GLASS_ACTIVITY_UNIFORM, activity);
shader.set_float(GLASS_ADAPTIVE_FROST_UNIFORM, frost);
shader.set_float(GLASS_EFFECT_DENSITY_UNIFORM, density);
shader.set_float(GLASS_RIM_STYLE_UNIFORM, rim);
specialize_liquid_glass_with_folds(&mut shader, true);
for (flag, raised) in [
("GLASS_ADAPTIVE_FROST_OFF", frost <= 0.0),
("GLASS_RIM_STYLE_OFF", rim <= 0.0),
] {
assert_eq!(shader.overrides().contains(&(flag, 1.0)), raised, "{flag}");
}
let substrate = (frost > 0.0).then_some(SubstrateSpec::Blur {
radius_px: GLASS_ADAPTIVE_NEIGHBOURHOOD_DP * density,
});
assert_eq!(shader.substrates(), substrate.as_slice());
assert_eq!(shader.draw_split(), Some(GLASS_RIM_DRAW_OVERRIDE));
assert!(shader.overrides().contains(&("CALLER", 7.0)));
assert_eq!(shader.uniforms()[GLASS_EFFECT_DENSITY_UNIFORM], density);
}
}
assert!(template.substrates().is_empty());
assert!(
template
.overrides()
.contains(&("GLASS_ADAPTIVE_FROST_OFF", 1.0))
);
}
#[test]
fn animating_a_material_end_to_end_asks_for_one_pipeline() {
let mut shader = RuntimeShader::new(LIQUID_GLASS_WGSL);
let mut sets: Vec<Vec<(&'static str, f64)>> = Vec::new();
for step in 0..=40u16 {
let value = f32::from(step) / 40.0;
shader.set_float(GLASS_ACTIVITY_UNIFORM, value);
shader.set_float(GLASS_TRANSMISSION_REFRACTION_UNIFORM, value);
specialize_liquid_glass_with_folds(&mut shader, true);
let set = shader.overrides().to_vec();
if !sets.contains(&set) {
sets.push(set);
}
}
assert_eq!(
sets.len(),
1,
"one press walks through {} override sets, and every one of them is a pipeline \
the backend compiles inside the frame that first needs it: {sets:?}",
sets.len()
);
}
#[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_with_folds(&mut shader, true);
shader.set_float(GLASS_RIM_STYLE_UNIFORM, 1.0);
specialize_liquid_glass_with_folds(&mut shader, true);
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_with_folds(&mut fresh, true);
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_with_folds(&mut shader, true);
let mut inactive = RuntimeShader::new(LIQUID_GLASS_WGSL);
inactive.set_override("CALLER_OVERRIDE", 7.0);
specialize_liquid_glass_with_folds(&mut inactive, true);
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.as_ref() 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 { .. })
));
}
}