use crate::shader_layout::programs::Target;
use crate::shader_layout::reflect::ShaderStruct;
pub(super) struct RustField {
pub name: &'static str,
pub offset: usize,
pub size: usize,
}
pub(super) struct Lane {
pub(crate) rust: Vec<RustField>,
pub shader: Vec<&'static str>,
}
pub(super) struct Mirror {
pub(crate) rust_name: &'static str,
pub(crate) rust_size: usize,
pub(crate) shader_name: &'static str,
pub lanes: Vec<Lane>,
}
macro_rules! rust_field {
($ty:ty, $($field:ident).+) => {{
fn width<F>(_: fn(&$ty) -> &F) -> usize {
::core::mem::size_of::<F>()
}
$crate::shader_layout::mirror::RustField {
name: stringify!($($field).+),
offset: ::core::mem::offset_of!($ty, $($field).+),
size: width(|v| &v.$($field).+),
}
}};
}
macro_rules! lanes {
($out:ident, $ty:ty,) => {};
($out:ident, $ty:ty, [$($($r:ident).+),+] => [$($s:literal),*], $($rest:tt)*) => {
$out.push($crate::shader_layout::mirror::Lane {
rust: vec![$($crate::shader_layout::mirror::rust_field!($ty, $($r).+)),+],
shader: vec![$($s),*],
});
$crate::shader_layout::mirror::lanes!($out, $ty, $($rest)*);
};
($out:ident, $ty:ty, $r:ident, $($rest:tt)*) => {
$out.push($crate::shader_layout::mirror::Lane {
rust: vec![$crate::shader_layout::mirror::rust_field!($ty, $r)],
shader: vec![stringify!($r)],
});
$crate::shader_layout::mirror::lanes!($out, $ty, $($rest)*);
};
}
macro_rules! mirror {
($ty:ty => $shader:literal { $($lanes:tt)* }) => {{
let mut lanes = Vec::new();
$crate::shader_layout::mirror::lanes!(lanes, $ty, $($lanes)*);
$crate::shader_layout::mirror::Mirror {
rust_name: stringify!($ty),
rust_size: ::core::mem::size_of::<$ty>(),
shader_name: $shader,
lanes,
}
}};
}
pub(super) use {lanes, mirror, rust_field};
pub(super) fn drift(mirror: &Mirror, shader: &ShaderStruct) -> Vec<String> {
let mut out = Vec::new();
check_tiling(mirror, &mut out);
check_lanes(mirror, shader, &mut out);
check_coverage(mirror, shader, &mut out);
check_size(mirror, shader, &mut out);
out
}
fn check_tiling(mirror: &Mirror, out: &mut Vec<String>) {
let mut cursor = 0;
for field in mirror.lanes.iter().flat_map(|lane| &lane.rust) {
if field.offset != cursor {
out.push(format!(
"{}.{} sits at {} but the previous field ends at {cursor}: the lane list \
must name every field in order",
mirror.rust_name, field.name, field.offset,
));
}
cursor = field.offset + field.size;
}
if cursor != mirror.rust_size {
out.push(format!(
"{} is {} bytes but its lanes account for {cursor}",
mirror.rust_name, mirror.rust_size,
));
}
}
fn check_lanes(mirror: &Mirror, shader: &ShaderStruct, out: &mut Vec<String>) {
let declared = shader.extent();
for lane in &mirror.lanes {
let rust_offset = lane.rust[0].offset;
let rust_size: usize = lane.rust.iter().map(|f| f.size).sum();
let names = lane
.rust
.iter()
.map(|f| f.name)
.collect::<Vec<_>>()
.join(" + ");
let Some(first) = lane.shader.first() else {
if rust_offset < declared {
out.push(format!(
"{}.{names} is marked as bytes the shader does not cover, but {} declares \
members through byte {declared}",
mirror.rust_name, mirror.shader_name,
));
}
continue;
};
let mut shader_offset = None;
let mut shader_size = 0;
let mut cursor = None;
for name in &lane.shader {
let Some(field) = shader.fields.iter().find(|f| f.name == *name) else {
out.push(format!(
"{}.{names} maps to `{name}`, which {} does not declare",
mirror.rust_name, mirror.shader_name,
));
continue;
};
if let Some(end) = cursor
&& field.offset != end
{
out.push(format!(
"{} members `{first}`..`{name}` are not contiguous: `{name}` sits at {} \
after byte {end}",
mirror.shader_name, field.offset,
));
}
shader_offset.get_or_insert(field.offset);
shader_size += field.size;
cursor = Some(field.offset + field.size);
}
let Some(shader_offset) = shader_offset else {
continue;
};
if shader_offset != rust_offset || shader_size != rust_size {
out.push(format!(
"{}.{names} covers bytes {rust_offset}..{} but {}.{} covers {shader_offset}..{}",
mirror.rust_name,
rust_offset + rust_size,
mirror.shader_name,
lane.shader.join(" + "),
shader_offset + shader_size,
));
}
}
}
fn check_coverage(mirror: &Mirror, shader: &ShaderStruct, out: &mut Vec<String>) {
for field in &shader.fields {
if !mirror
.lanes
.iter()
.any(|lane| lane.shader.contains(&field.name.as_str()))
{
out.push(format!(
"{}.{} is declared in the shader but no {} field claims it",
mirror.shader_name, field.name, mirror.rust_name,
));
}
}
}
fn check_size(mirror: &Mirror, shader: &ShaderStruct, out: &mut Vec<String>) {
let Some(block) = shader.block_size else {
return;
};
let rounds_up = block == shader.extent() && mirror.rust_size > block;
if mirror.rust_size != block && !rounds_up {
out.push(format!(
"{} is {} bytes but the shader binds {} as a {block}-byte block",
mirror.rust_name, mirror.rust_size, mirror.shader_name,
));
}
}
pub(super) struct Case {
pub mirror: Mirror,
pub targets: &'static [Target],
}
pub(super) fn everywhere(mirror: Mirror) -> Case {
Case {
mirror,
targets: &Target::ALL,
}
}
pub(super) fn on(targets: &'static [Target], mirror: Mirror) -> Case {
Case { mirror, targets }
}