use super::geometry::Vertex;
use super::stage::{Bindings, Constants};
use super::{Executable, error};
use crate::{Error, Sampler};
use iridia::{IoType, MeshFacts, MeshTopology, Program, StageFacts, mesh_vertex_io};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Meshlet {
pub group: [u32; 3],
pub vertices: Vec<Vertex>,
pub primitives: Vec<Vec<u32>>,
}
#[derive(Debug)]
pub struct MeshShader {
program: Program,
executable: Executable,
facts: MeshFacts,
outputs: Vec<(String, usize)>,
bindings: Bindings,
}
impl MeshShader {
pub fn new(program: Program) -> Result<Self, Error> {
let StageFacts::Mesh(facts) = program.interface.facts else {
return Err(error(format!(
"a {:?} program is not a mesh program",
program.stage
)));
};
let outputs = mesh_vertex_io(&program.interface)
.map_err(error)?
.into_iter()
.map(|item| match item.ty {
IoType::Value(v) => Ok((item.name, usize::from(v.width))),
_ => Err(error(format!(
"{}: a vertex output that is not a value",
item.name
))),
})
.collect::<Result<Vec<_>, _>>()?;
if outputs.len() != program.interface.outputs.len() {
return Err(error(
"a mesh program whose clip or cull distances are carried together",
));
}
let executable = Executable::new(&program)?;
let (_, words) = executable
.mesh_output
.ok_or_else(|| error("a mesh program without its output variable"))?;
let shader = Self {
bindings: Bindings::new(&program).map_err(error)?,
program,
executable,
facts,
outputs,
};
let layout = shader.layout();
if words as usize != layout.total {
return Err(error(format!(
"the output variable has {words} words, its vertices and primitives make {}",
layout.total
)));
}
Ok(shader)
}
pub fn program(&self) -> &Program {
&self.program
}
pub fn facts(&self) -> MeshFacts {
self.facts
}
pub fn group_size(&self) -> [u32; 3] {
self.executable.workgroup.unwrap_or([1, 1, 1])
}
pub fn outputs(&self) -> impl Iterator<Item = &str> {
self.outputs.iter().map(|(name, _)| name.as_str())
}
fn layout(&self) -> Layout {
let vertex: usize = self.outputs.iter().map(|(_, width)| width).sum();
let index = match self.facts.topology {
MeshTopology::Triangle => 3,
MeshTopology::Line => 2,
};
let (max_vertices, max_primitives) = (
self.facts.max_vertices as usize,
self.facts.max_primitives as usize,
);
Layout {
vertex,
index,
vertices_at: 2,
primitives_at: 2usize.saturating_add(max_vertices.saturating_mul(vertex)),
total: 2usize
.saturating_add(max_vertices.saturating_mul(vertex))
.saturating_add(max_primitives.saturating_mul(index)),
}
}
pub fn draw(
&self,
constants: &Constants,
sampler: &mut dyn Sampler,
groups: [u32; 3],
wave: u32,
) -> Result<Vec<Meshlet>, Error> {
let bound = self.bindings.bind(&self.executable, constants).map_err(error)?;
let mut backend = self.bindings.backend(sampler);
let layout = self.layout();
let mut meshlets = Vec::new();
self.executable
.dispatch_mesh(&bound, &mut backend, groups, wave, &mut |group, words| {
meshlets.push(self.read(&layout, group, words)?);
Ok(())
})
.map_err(error)?;
Ok(meshlets)
}
fn read(&self, layout: &Layout, group: [u32; 3], words: &[u32]) -> Result<Meshlet, String> {
if words.len() != layout.total {
return Err(format!(
"{} words of output, expected {}",
words.len(),
layout.total
));
}
let (Some(&vertex_count), Some(&primitive_count)) = (words.first(), words.get(1)) else {
return Err("an output variable without its counts".into());
};
let (vertex_count, primitive_count) = (vertex_count as usize, primitive_count as usize);
if vertex_count > self.facts.max_vertices as usize {
return Err(format!(
"the group set {vertex_count} vertices, past the {} it declares",
self.facts.max_vertices
));
}
if primitive_count > self.facts.max_primitives as usize {
return Err(format!(
"the group set {primitive_count} primitives, past the {} it declares",
self.facts.max_primitives
));
}
let mut vertices = Vec::with_capacity(vertex_count);
for k in 0..vertex_count {
let mut at = layout.vertices_at.saturating_add(k.saturating_mul(layout.vertex));
let mut registers = Vec::with_capacity(self.outputs.len());
for &(_, width) in &self.outputs {
let mut register = [0u32; 4];
let (Some(to), Some(from)) =
(register.get_mut(..width), words.get(at..at.saturating_add(width)))
else {
return Err(format!(
"vertex {k}: {width} words at word {at} of {}",
words.len()
));
};
to.copy_from_slice(from);
at = at.saturating_add(width);
registers.push(register);
}
vertices.push(registers);
}
let mut primitives = Vec::with_capacity(primitive_count);
for k in 0..primitive_count {
let at = layout
.primitives_at
.saturating_add(k.saturating_mul(layout.index));
let Some(indices) = words
.get(at..at.saturating_add(layout.index))
.map(<[u32]>::to_vec)
else {
return Err(format!(
"primitive {k}: {} words at word {at} of {}",
layout.index,
words.len()
));
};
if let Some(bad) = indices.iter().find(|&&i| i as usize >= vertex_count) {
return Err(format!(
"primitive {k} names vertex {bad}, past the {vertex_count} the group set"
));
}
primitives.push(indices);
}
Ok(Meshlet {
group,
vertices,
primitives,
})
}
}
struct Layout {
vertex: usize,
index: usize,
vertices_at: usize,
primitives_at: usize,
total: usize,
}