fn float_literals(src: &str) -> Vec<String> {
let no_comments: String = src
.lines()
.map(|l| l.split("//").next().unwrap_or(""))
.collect::<Vec<_>>()
.join("\n");
let bytes: Vec<char> = no_comments.chars().collect();
let mut out = Vec::new();
let mut i = 0;
while i < bytes.len() {
if !bytes[i].is_ascii_digit() {
i += 1;
continue;
}
if i > 0 && (bytes[i - 1].is_alphanumeric() || bytes[i - 1] == '_') {
while i < bytes.len() && (bytes[i].is_alphanumeric() || bytes[i] == '_') {
i += 1;
}
continue;
}
let start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
let mut is_float = false;
if i < bytes.len() && bytes[i] == '.' {
is_float = true;
i += 1;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
}
if i < bytes.len() && (bytes[i] == 'e' || bytes[i] == 'E') {
let save = i;
i += 1;
if i < bytes.len() && (bytes[i] == '-' || bytes[i] == '+') {
i += 1;
}
if i < bytes.len() && bytes[i].is_ascii_digit() {
is_float = true;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
} else {
i = save;
}
}
let text: String = bytes[start..i].iter().collect();
if i < bytes.len() && bytes[i] == 'f' && is_float {
i += 1;
}
if i < bytes.len() && bytes[i] == 'u' && !is_float {
i += 1;
}
if is_float {
out.push(text);
}
}
out
}
fn assert_parity(name: &str, msl: &str, wgsl: &str) {
let a = float_literals(msl);
let b = float_literals(wgsl);
for (i, (x, y)) in a.iter().zip(b.iter()).enumerate() {
assert_eq!(
x, y,
"{name}: constant #{i} differs โ MSL has {x}, WGSL has {y}. \
The two kernels have drifted; make the change in both."
);
}
assert_eq!(
a.len(),
b.len(),
"{name}: MSL has {} float constants, WGSL has {}. \
Extra on one side: MSL {:?} / WGSL {:?}",
a.len(),
b.len(),
&a[b.len().min(a.len())..],
&b[a.len().min(b.len())..],
);
}
#[test]
fn paint_kernels_agree() {
assert_parity(
"paint",
mir::frame::paint::PAINT_MSL,
mir::frame::paint::PAINT_WGSL,
);
}
#[test]
fn cull_kernels_agree() {
assert_parity(
"bvh_cull",
mir::frame::cull::BVH_CULL_MSL,
mir::frame::cull::BVH_CULL_WGSL,
);
}
#[test]
fn camera_uniform_declared_the_same_in_both() {
for field in [
"view_proj", "planes", "viewport", "near", "far",
"cam_pos", "cam_right", "cam_up", "cam_fwd",
] {
for (name, src) in [
("PAINT_MSL", mir::frame::paint::PAINT_MSL),
("PAINT_WGSL", mir::frame::paint::PAINT_WGSL),
("BVH_CULL_MSL", mir::frame::cull::BVH_CULL_MSL),
("BVH_CULL_WGSL", mir::frame::cull::BVH_CULL_WGSL),
] {
assert!(
src.contains(field),
"{name} does not declare Camera::{field}; the struct it binds \
no longer matches the one the host writes."
);
}
}
}
#[test]
fn both_kernels_are_lit_by_the_same_light() {
for (name, src) in [
("PAINT_MSL", mir::frame::paint::PAINT_MSL),
("PAINT_WGSL", mir::frame::paint::PAINT_WGSL),
] {
assert!(
src.contains("0.36370") && src.contains("0.72739") && src.contains("0.58191"),
"{name} no longer carries the shared light direction"
);
}
let n: f32 = 0.36370f32.powi(2) + 0.72739f32.powi(2) + 0.58191f32.powi(2);
assert!((n - 1.0).abs() < 1e-4, "light is not normalized: |L|^2 = {n}");
}