Skip to main content

iridia_exec/
mesh.rs

1//! A mesh program on the CPU: a thread group that writes one meshlet. [`MeshShader::draw`] runs the thread groups of a
2//! dispatch one after another and returns the meshlet each wrote: its vertices (one register per output varying, as a
3//! geometry program's emitted vertices are, [`MeshShader::outputs`] names them) and its primitives (the indices of their
4//! vertices). What the rasterizer does with them is the pipeline's.
5//!
6//! The program is the entry point of [`iridia::build::Builder::finish_mesh`]: the group's invocations run in lockstep
7//! like a compute program's, and what they write is the module's output variable, thread-group memory that is read when
8//! the group has finished. The counts the program set are the group's (D3D requires every invocation to set the same),
9//! and a count past the most the program declares, or an index past the vertices it counted, is an error of the run:
10//! D3D leaves both undefined.
11
12use super::geometry::Vertex;
13use super::stage::{Bindings, Constants};
14use super::{Executable, error};
15use crate::{Error, Sampler};
16use iridia::{IoType, MeshFacts, MeshTopology, Program, StageFacts, mesh_vertex_io};
17
18/// What one thread group wrote.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct Meshlet {
21    /// The thread group's position in the dispatch.
22    pub group: [u32; 3],
23    /// The vertices the program counted, one register per output varying.
24    pub vertices: Vec<Vertex>,
25    /// The primitives the program counted, each as the indices of its vertices (three for a triangle, two for a line).
26    pub primitives: Vec<Vec<u32>>,
27}
28
29/// A mesh program ready to run.
30#[derive(Debug)]
31pub struct MeshShader {
32    program: Program,
33    executable: Executable,
34    facts: MeshFacts,
35    /// The output varyings: their names and widths, in the order of the registers of a vertex.
36    outputs: Vec<(String, usize)>,
37    bindings: Bindings,
38}
39
40impl MeshShader {
41    /// Prepare `program`, a mesh program, to run.
42    pub fn new(program: Program) -> Result<Self, Error> {
43        let StageFacts::Mesh(facts) = program.interface.facts else {
44            return Err(error(format!(
45                "a {:?} program is not a mesh program",
46                program.stage
47            )));
48        };
49        let outputs = mesh_vertex_io(&program.interface)
50            .map_err(error)?
51            .into_iter()
52            .map(|item| match item.ty {
53                IoType::Value(v) => Ok((item.name, usize::from(v.width))),
54                _ => Err(error(format!(
55                    "{}: a vertex output that is not a value",
56                    item.name
57                ))),
58            })
59            .collect::<Result<Vec<_>, _>>()?;
60        if outputs.len() != program.interface.outputs.len() {
61            return Err(error(
62                "a mesh program whose clip or cull distances are carried together",
63            ));
64        }
65        let executable = Executable::new(&program)?;
66        let (_, words) = executable
67            .mesh_output
68            .ok_or_else(|| error("a mesh program without its output variable"))?;
69        let shader = Self {
70            bindings: Bindings::new(&program).map_err(error)?,
71            program,
72            executable,
73            facts,
74            outputs,
75        };
76        let layout = shader.layout();
77        if words as usize != layout.total {
78            return Err(error(format!(
79                "the output variable has {words} words, its vertices and primitives make {}",
80                layout.total
81            )));
82        }
83        Ok(shader)
84    }
85
86    /// The mesh program the shader runs.
87    pub fn program(&self) -> &Program {
88        &self.program
89    }
90
91    /// What the program states: the kind of primitive it writes and the most vertices and primitives a group writes.
92    pub fn facts(&self) -> MeshFacts {
93        self.facts
94    }
95
96    /// The thread group's invocations along each axis.
97    pub fn group_size(&self) -> [u32; 3] {
98        self.executable.workgroup.unwrap_or([1, 1, 1])
99    }
100
101    /// The names of the output varyings: the registers of the vertices of a [`Meshlet`], in order.
102    pub fn outputs(&self) -> impl Iterator<Item = &str> {
103        self.outputs.iter().map(|(name, _)| name.as_str())
104    }
105
106    /// Where the parts of the output variable lie, in the executor's packed words.
107    fn layout(&self) -> Layout {
108        let vertex: usize = self.outputs.iter().map(|(_, width)| width).sum();
109        let index = match self.facts.topology {
110            MeshTopology::Triangle => 3,
111            MeshTopology::Line => 2,
112        };
113        let (max_vertices, max_primitives) = (
114            self.facts.max_vertices as usize,
115            self.facts.max_primitives as usize,
116        );
117        Layout {
118            vertex,
119            index,
120            vertices_at: 2,
121            primitives_at: 2usize.saturating_add(max_vertices.saturating_mul(vertex)),
122            total: 2usize
123                .saturating_add(max_vertices.saturating_mul(vertex))
124                .saturating_add(max_primitives.saturating_mul(index)),
125        }
126    }
127
128    /// Run `groups` thread groups (`wave` the wave width the wave operations report) and return the meshlet each wrote,
129    /// in the order of the dispatch (x, then y, then z). `constants` holds the rows of the constant buffers by
130    /// (register, space); image and buffer access go to `sampler` by bind slot.
131    pub fn draw(
132        &self,
133        constants: &Constants,
134        sampler: &mut dyn Sampler,
135        groups: [u32; 3],
136        wave: u32,
137    ) -> Result<Vec<Meshlet>, Error> {
138        let bound = self.bindings.bind(&self.executable, constants).map_err(error)?;
139        let mut backend = self.bindings.backend(sampler);
140        let layout = self.layout();
141        let mut meshlets = Vec::new();
142        self.executable
143            .dispatch_mesh(&bound, &mut backend, groups, wave, &mut |group, words| {
144                meshlets.push(self.read(&layout, group, words)?);
145                Ok(())
146            })
147            .map_err(error)?;
148        Ok(meshlets)
149    }
150
151    /// The meshlet in the words of the output variable of a finished group.
152    fn read(&self, layout: &Layout, group: [u32; 3], words: &[u32]) -> Result<Meshlet, String> {
153        if words.len() != layout.total {
154            return Err(format!(
155                "{} words of output, expected {}",
156                words.len(),
157                layout.total
158            ));
159        }
160        let (Some(&vertex_count), Some(&primitive_count)) = (words.first(), words.get(1)) else {
161            return Err("an output variable without its counts".into());
162        };
163        let (vertex_count, primitive_count) = (vertex_count as usize, primitive_count as usize);
164        if vertex_count > self.facts.max_vertices as usize {
165            return Err(format!(
166                "the group set {vertex_count} vertices, past the {} it declares",
167                self.facts.max_vertices
168            ));
169        }
170        if primitive_count > self.facts.max_primitives as usize {
171            return Err(format!(
172                "the group set {primitive_count} primitives, past the {} it declares",
173                self.facts.max_primitives
174            ));
175        }
176        let mut vertices = Vec::with_capacity(vertex_count);
177        for k in 0..vertex_count {
178            let mut at = layout.vertices_at.saturating_add(k.saturating_mul(layout.vertex));
179            let mut registers = Vec::with_capacity(self.outputs.len());
180            for &(_, width) in &self.outputs {
181                let mut register = [0u32; 4];
182                let (Some(to), Some(from)) =
183                    (register.get_mut(..width), words.get(at..at.saturating_add(width)))
184                else {
185                    return Err(format!(
186                        "vertex {k}: {width} words at word {at} of {}",
187                        words.len()
188                    ));
189                };
190                to.copy_from_slice(from);
191                at = at.saturating_add(width);
192                registers.push(register);
193            }
194            vertices.push(registers);
195        }
196        let mut primitives = Vec::with_capacity(primitive_count);
197        for k in 0..primitive_count {
198            let at = layout
199                .primitives_at
200                .saturating_add(k.saturating_mul(layout.index));
201            let Some(indices) = words
202                .get(at..at.saturating_add(layout.index))
203                .map(<[u32]>::to_vec)
204            else {
205                return Err(format!(
206                    "primitive {k}: {} words at word {at} of {}",
207                    layout.index,
208                    words.len()
209                ));
210            };
211            if let Some(bad) = indices.iter().find(|&&i| i as usize >= vertex_count) {
212                return Err(format!(
213                    "primitive {k} names vertex {bad}, past the {vertex_count} the group set"
214                ));
215            }
216            primitives.push(indices);
217        }
218        Ok(Meshlet {
219            group,
220            vertices,
221            primitives,
222        })
223    }
224}
225
226/// The output variable's words: its two counts, then the vertices and the primitives, each packed.
227struct Layout {
228    /// Words of a vertex.
229    vertex: usize,
230    /// Words of a primitive.
231    index: usize,
232    vertices_at: usize,
233    primitives_at: usize,
234    total: usize,
235}