use std::borrow::Cow;
use super::strips::PaintClass;
pub const HELPERS: &str = include_str!("../../shaders/helpers.wgsl");
pub const STRIP: &str = concat!(
include_str!("../../shaders/helpers.wgsl"),
include_str!("../../shaders/strip.wgsl")
);
pub const CLEAR: &str = concat!(
include_str!("../../shaders/helpers.wgsl"),
include_str!("../../shaders/clear.wgsl")
);
pub const COPY: &str = concat!(
include_str!("../../shaders/helpers.wgsl"),
include_str!("../../shaders/copy.wgsl")
);
pub const FILTER_KERNELS: &str = include_str!("../../shaders/filters_blur.wgsl");
pub const DROP_SHADOW_KERNELS: &str = include_str!("../../shaders/filters_drop_shadow.wgsl");
pub const FILTER: &str = concat!(
include_str!("../../shaders/helpers.wgsl"),
include_str!("../../shaders/filters_blur.wgsl"),
include_str!("../../shaders/filters_drop_shadow.wgsl"),
include_str!("../../shaders/filter.wgsl")
);
pub const STRIP_NAME: &str = "frust-engine strip";
pub const STRIP_FEATURES: [&str; 6] = [
"IMAGE",
"IMAGE_FILTERED",
"GRADIENT",
"BLURRED_RECT",
"LAYER",
"GLYPH",
];
pub const IMAGE_SAMPLER: &str = "IMAGE_SAMPLER";
#[must_use]
pub const fn class_features(class: PaintClass) -> &'static [&'static str] {
match class {
PaintClass::SolidCoverage => &[],
PaintClass::Gradient => &["GRADIENT"],
PaintClass::Image => &["IMAGE"],
PaintClass::ImageFiltered => &["IMAGE", "IMAGE_FILTERED"],
PaintClass::BlurredRect => &["BLURRED_RECT"],
PaintClass::LayerComposite => &["LAYER"],
PaintClass::Glyph => &["GLYPH"],
PaintClass::Uber => &STRIP_FEATURES,
}
}
#[must_use]
pub const fn strip_class_name(class: PaintClass) -> &'static str {
match class {
PaintClass::SolidCoverage => "frust-engine strip/solid",
PaintClass::Gradient => "frust-engine strip/gradient",
PaintClass::Image => "frust-engine strip/image",
PaintClass::ImageFiltered => "frust-engine strip/image-filtered",
PaintClass::BlurredRect => "frust-engine strip/blurred-rect",
PaintClass::LayerComposite => "frust-engine strip/layer",
PaintClass::Glyph => "frust-engine strip/glyph",
PaintClass::Uber => STRIP_NAME,
}
}
#[must_use]
pub fn strip_class_source(class: PaintClass) -> Cow<'static, str> {
strip_program_source(class, false)
}
#[must_use]
pub fn samples_images(class: PaintClass) -> bool {
class_features(class).contains(&"IMAGE")
}
#[must_use]
pub fn strip_program_features(class: PaintClass, image_sampler: bool) -> Vec<&'static str> {
let mut features = class_features(class).to_vec();
if image_sampler && samples_images(class) {
features.push(IMAGE_SAMPLER);
}
features
}
#[must_use]
pub fn strip_program_source(class: PaintClass, image_sampler: bool) -> Cow<'static, str> {
Cow::Owned(specialise(
STRIP,
&strip_program_features(class, image_sampler),
))
}
#[must_use]
pub const fn strip_program_name(class: PaintClass, image_sampler: bool) -> &'static str {
if !image_sampler {
return strip_class_name(class);
}
match class {
PaintClass::Image => "frust-engine strip/image+sampler",
PaintClass::ImageFiltered => "frust-engine strip/image-filtered+sampler",
PaintClass::Uber => "frust-engine strip+sampler",
_ => strip_class_name(class),
}
}
const DIRECTIVE_IF: &str = "//#if ";
const DIRECTIVE_ENDIF: &str = "//#endif";
const DIRECTIVE_ELSE: &str = "//#else";
#[must_use]
pub fn specialise(source: &str, enabled: &[&str]) -> String {
let mut out = String::with_capacity(source.len());
let mut open: Vec<bool> = Vec::new();
for line in source.split_inclusive('\n') {
let trimmed = line.trim();
let newline = if line.ends_with('\n') { "\n" } else { "" };
if let Some(feature) = trimmed.strip_prefix(DIRECTIVE_IF) {
let feature = feature.trim();
open.push(enabled.contains(&feature));
out.push_str(newline);
} else if trimmed == DIRECTIVE_ELSE {
if let Some(live) = open.last_mut() {
*live = !*live;
}
out.push_str(newline);
} else if trimmed == DIRECTIVE_ENDIF {
open.pop();
out.push_str(newline);
} else if open.iter().all(|live| *live) {
out.push_str(line);
} else {
out.push_str(newline);
}
}
out
}
pub const CLEAR_NAME: &str = "frust-engine clear";
pub const COPY_NAME: &str = "frust-engine copy";
pub const FILTER_NAME: &str = "frust-engine filter";
pub const MODULES: [(&str, &str); 4] = [
(STRIP_NAME, STRIP),
(CLEAR_NAME, CLEAR),
(COPY_NAME, COPY),
(FILTER_NAME, FILTER),
];
#[cfg(test)]
mod tests {
use super::*;
use crate::gpu::{GpuConfig, GpuStrip};
use wgpu::naga;
fn validate(name: &str, src: &str) -> naga::Module {
let module = naga::front::wgsl::parse_str(src)
.unwrap_or_else(|err| panic!("{name} failed to parse: {err:?}"));
naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::empty(),
)
.validate(&module)
.unwrap_or_else(|err| panic!("{name} failed validation: {err:?}"));
module
}
fn struct_span(module: &naga::Module, name: &str) -> u32 {
module
.types
.iter()
.find_map(|(_, ty)| match (&ty.name, &ty.inner) {
(Some(ty_name), naga::TypeInner::Struct { span, .. }) if ty_name == name => {
Some(*span)
}
_ => None,
})
.unwrap_or_else(|| panic!("the module declares no struct named {name}"))
}
#[test]
fn every_module_parses_and_validates() {
for (name, src) in MODULES {
validate(name, src);
}
for image_sampler in [false, true] {
for class in PaintClass::ALL {
validate(
strip_program_name(class, image_sampler),
&strip_program_source(class, image_sampler),
);
}
}
}
fn is_directive(line: &str) -> bool {
let trimmed = line.trim();
trimmed.starts_with(DIRECTIVE_IF) || trimmed == DIRECTIVE_ELSE || trimmed == DIRECTIVE_ENDIF
}
#[test]
fn the_manual_uber_program_is_the_strip_source_without_the_sampler_arm() {
let uber = strip_class_source(PaintClass::Uber);
let sampled = strip_program_source(PaintClass::Uber, true);
assert_eq!(strip_class_name(PaintClass::Uber), STRIP_NAME);
assert_eq!(uber.lines().count(), STRIP.lines().count());
let mut blanked = 0;
for ((source, kept), sampled) in STRIP.lines().zip(uber.lines()).zip(sampled.lines()) {
if kept == source {
continue;
}
assert!(kept.is_empty(), "a kept line was rewritten: `{source}`");
if is_directive(source) || source.trim().is_empty() {
continue;
}
assert_eq!(
sampled, source,
"the manual Uber program blanked `{source}`, which is not the sampler arm"
);
blanked += 1;
}
assert!(blanked > 0, "the sampler arm is in the source");
assert!(!uber.contains("atlas_sampler"));
assert!(!uber.contains("textureSampleLevel("));
assert!(sampled.contains("var atlas_sampler: sampler;"));
}
#[test]
fn the_families_differ_only_in_the_image_programs() {
for class in PaintClass::ALL {
let default = strip_program_source(class, false);
let sampled = strip_program_source(class, true);
assert_eq!(
default == sampled,
!samples_images(class),
"{}",
class.label()
);
assert_eq!(
strip_program_name(class, false) == strip_program_name(class, true),
!samples_images(class),
"{}",
class.label()
);
assert_eq!(strip_program_name(class, false), strip_class_name(class));
assert_eq!(default, strip_class_source(class));
}
assert!(samples_images(PaintClass::Image));
assert!(samples_images(PaintClass::ImageFiltered));
assert!(samples_images(PaintClass::Uber));
for class in [
PaintClass::Image,
PaintClass::ImageFiltered,
PaintClass::Uber,
] {
let default = strip_program_source(class, false);
let sampled = strip_program_source(class, true);
let fs =
|src: &str| src[src.find("fn fs_main(").expect("a fragment stage")..].to_string();
assert!(fs(&default).contains("sample_color = bilinear_sample("));
assert!(!fs(&default).contains("sampled_bilinear("));
assert!(fs(&sampled).contains("sample_color = sampled_bilinear("));
assert!(!fs(&sampled).contains("bilinear_sample("));
}
}
#[test]
fn every_directive_names_a_known_feature_and_every_block_closes() {
let mut depth = 0_usize;
let mut seen = Vec::new();
for (number, line) in STRIP.lines().enumerate() {
let trimmed = line.trim();
if let Some(feature) = trimmed.strip_prefix(DIRECTIVE_IF) {
assert!(
STRIP_FEATURES.contains(&feature.trim()) || feature.trim() == IMAGE_SAMPLER,
"line {number} names an unknown feature `{feature}`"
);
seen.push(feature.trim().to_string());
depth += 1;
} else if trimmed == DIRECTIVE_ELSE {
assert!(
depth > 0,
"line {number} switches a block that was never opened"
);
} else if trimmed == DIRECTIVE_ENDIF {
assert!(
depth > 0,
"line {number} closes a block that was never opened"
);
depth -= 1;
} else {
assert!(
!trimmed.starts_with("//#"),
"line {number} looks like a directive but is not one: `{trimmed}`"
);
}
}
assert_eq!(
depth, 0,
"a feature block is left open at the end of the source"
);
for feature in STRIP_FEATURES.into_iter().chain([IMAGE_SAMPLER]) {
assert!(
seen.iter().any(|f| f == feature),
"feature {feature} gates nothing — a class enabling it would equal one that does not"
);
}
}
#[test]
fn the_pre_pass_keeps_line_numbers_and_only_drops_disabled_blocks() {
let src = "a\n//#if X\nb\n//#if Y\nc\n//#endif\n//#endif\nd";
assert_eq!(specialise(src, &["X", "Y"]), "a\n\nb\n\nc\n\n\nd");
assert_eq!(specialise(src, &["X"]), "a\n\nb\n\n\n\n\nd");
assert_eq!(specialise(src, &["Y"]), "a\n\n\n\n\n\n\nd");
assert_eq!(specialise(src, &[]), "a\n\n\n\n\n\n\nd");
let src = "a\n//#if X\nb\n//#if Y\nc\n//#else\ne\n//#endif\n//#endif\nd";
assert_eq!(specialise(src, &["X", "Y"]), "a\n\nb\n\nc\n\n\n\n\nd");
assert_eq!(specialise(src, &["X"]), "a\n\nb\n\n\n\ne\n\n\nd");
assert_eq!(specialise(src, &["Y"]), "a\n\n\n\n\n\n\n\n\nd");
for image_sampler in [false, true] {
for class in PaintClass::ALL {
assert_eq!(
strip_program_source(class, image_sampler).lines().count(),
STRIP.lines().count(),
"{} must keep the source's line numbering",
class.label()
);
}
}
}
#[test]
fn each_class_keeps_only_its_own_branches() {
let branch = |src: &str, needle: &str| src.contains(needle);
let image = "paint_type == PAINT_TYPE_IMAGE {";
let bicubic = "sample_color = bicubic_sample(";
let external = "sample_color = sample_external_image(";
let bilinear = "sample_color = bilinear_sample(";
let linear = "paint_type == PAINT_TYPE_LINEAR_GRADIENT {";
let radial = "paint_type == PAINT_TYPE_RADIAL_GRADIENT {";
let sweep = "paint_type == PAINT_TYPE_SWEEP_GRADIENT {";
let blurred = "paint_type == PAINT_TYPE_BLURRED_ROUNDED_RECT {";
let layer = "} else if color_source == COLOR_SOURCE_LAYER {\n let layer_opacity";
for class in PaintClass::ALL {
let src = strip_class_source(class);
let fs = &src[src.find("fn fs_main(").expect("a fragment entry point")..];
let keeps = |feature: &str| class_features(class).contains(&feature);
assert!(
branch(fs, "paint_type == PAINT_TYPE_SOLID {"),
"{}",
class.label()
);
assert!(
branch(fs, "if is_rect && rect_frac != 0u {"),
"{}",
class.label()
);
assert_eq!(branch(fs, image), keeps("IMAGE"), "{}", class.label());
assert_eq!(branch(fs, bilinear), keeps("IMAGE"), "{}", class.label());
for filtered in [bicubic, external] {
assert_eq!(
branch(fs, filtered),
keeps("IMAGE_FILTERED"),
"{}",
class.label()
);
}
for gradient in [linear, radial, sweep] {
assert_eq!(branch(fs, gradient), keeps("GRADIENT"), "{}", class.label());
}
assert_eq!(
branch(fs, blurred),
keeps("BLURRED_RECT"),
"{}",
class.label()
);
assert_eq!(branch(&src, layer), keeps("LAYER"), "{}", class.label());
}
}
#[test]
fn the_strip_program_addresses_its_resource_textures_by_config_shifts() {
assert_eq!(STRIP.matches("textureDimensions(").count(), 1);
assert!(STRIP.contains("vec2<f32>(textureDimensions(tex))"));
for class in PaintClass::ALL {
assert!(!strip_class_source(class).contains("textureDimensions("));
let sampled = strip_program_source(class, true);
assert_eq!(
sampled.contains("textureDimensions("),
samples_images(class),
"{}",
class.label()
);
}
assert!(STRIP.contains("(1u << config.alphas_tex_width_bits) - 1u"));
assert_eq!(
STRIP
.matches(
"get_gradient_texture_width(gradient_texel0),\n \
config.encoded_paints_tex_width_bits\n"
)
.count(),
3,
"each gradient kind passes the LUT width shift"
);
assert!(HELPERS.contains("flat_coord & ((1u << lut_width_bits) - 1u)"));
assert!(HELPERS.contains("flat_coord >> lut_width_bits"));
}
#[test]
fn the_glyph_class_keeps_only_the_glyph_branch() {
let glyph_arm = "paint_type == PAINT_TYPE_GLYPH {";
for image_sampler in [false, true] {
let src = strip_program_source(PaintClass::Glyph, image_sampler);
let fs = &src[src.find("fn fs_main(").expect("a fragment entry point")..];
assert!(fs.contains(glyph_arm));
assert!(!fs.contains("paint_type == PAINT_TYPE_IMAGE {"));
assert!(!fs.contains("sampled_bilinear("));
assert!(!fs.contains("bilinear_sample("));
assert!(!src.contains("var atlas_sampler: sampler;"));
assert!(fs.contains("paint_type == PAINT_TYPE_SOLID {"));
}
assert_eq!(
strip_program_source(PaintClass::Glyph, false),
strip_program_source(PaintClass::Glyph, true)
);
for class in PaintClass::ALL {
let src = strip_class_source(class);
let fs = &src[src.find("fn fs_main(").expect("a fragment entry point")..];
assert_eq!(
fs.contains(glyph_arm),
class_features(class).contains(&"GLYPH"),
"{}",
class.label()
);
}
assert!(strip_class_source(PaintClass::Uber).contains(glyph_arm));
}
#[test]
fn the_helper_prelude_validates_on_its_own() {
validate("helpers", HELPERS);
}
#[test]
fn every_module_is_the_helper_prelude_followed_by_its_own_source() {
for (name, src) in MODULES {
assert!(
src.starts_with(HELPERS),
"{name} must be assembled as the helper prelude followed by its own source"
);
assert!(
src.len() > HELPERS.len(),
"{name} must contribute source of its own"
);
}
}
#[test]
fn the_helper_prelude_declares_no_bindings() {
for (name, prelude) in [
("helpers", HELPERS),
("filter kernels", FILTER_KERNELS),
("drop shadow kernels", DROP_SHADOW_KERNELS),
] {
let declaration = prelude
.lines()
.find(|line| !line.trim_start().starts_with("//") && line.contains("@group"));
assert!(
declaration.is_none(),
"a prelude that declared a binding would change the bind-group layout of every \
module it is prepended to ({name}): {declaration:?}"
);
}
}
#[test]
fn the_filter_module_is_every_prelude_followed_by_its_own_source() {
assert!(FILTER.starts_with(HELPERS));
assert!(FILTER[HELPERS.len()..].starts_with(FILTER_KERNELS));
assert!(FILTER[HELPERS.len() + FILTER_KERNELS.len()..].starts_with(DROP_SHADOW_KERNELS));
assert!(FILTER.len() > HELPERS.len() + FILTER_KERNELS.len() + DROP_SHADOW_KERNELS.len());
}
#[test]
fn the_filter_instance_matches_its_rust_layout() {
let module = validate(FILTER_NAME, FILTER);
assert_eq!(
struct_span(&module, "FilterInstanceData") as usize,
size_of::<crate::filters::blur::FilterInstanceData>(),
"the shader's `FilterInstanceData` must stay byte-identical to the Rust one"
);
}
#[test]
fn the_strip_config_uniform_matches_its_rust_layout() {
let module = validate(STRIP_NAME, STRIP);
assert_eq!(
u64::from(struct_span(&module, "Config")),
GpuConfig::SIZE,
"the shader's `Config` must stay byte-identical to `GpuConfig`"
);
}
#[test]
fn the_strip_instance_matches_its_rust_layout() {
let module = validate(STRIP_NAME, STRIP);
assert_eq!(
struct_span(&module, "StripInstance") as usize,
size_of::<GpuStrip>(),
"the shader's `StripInstance` must stay byte-identical to `GpuStrip`"
);
}
}