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 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);
}
}
#[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`"
);
}
}