Skip to main content

iridia_exec/
tessellation.rs

1//! A tessellated patch on the CPU: the hull program for each output control point and for the patch's constants, the
2//! fixed-function tessellator ([`super::tessellator`]) over the factors the hull program wrote, and the domain program
3//! for each point it made. WGSL has no tessellation stages, so this is how to draw a patch from lifted programs.
4//!
5//! The programs are the function-form [`Program`]s of [`iridia::stage_functions`], run one invocation at a time with
6//! [`Executable::function`]. A control point (the vertex program's output, the hull program's input) is a
7//! [`Vertex`]: one register per input varying of the hull program, in the order of its
8//! interface ([`TessellationShader::inputs`]). The hull program's outputs reach the domain program by label (the
9//! control points' varyings and the patch constants), so the two programs need not number them alike.
10
11use super::geometry::Vertex;
12use super::pixel::SlotBackend;
13use super::stage::{Bindings, Constants};
14use super::tessellator::{
15    Factors, OutputPrimitive, Tessellation, tessellate_isolines, tessellate_quad, tessellate_triangle,
16};
17use super::{Bound, Executable, Runner, Scratch};
18use crate::Sampler;
19use iridia::{
20    DomainFacts, FunctionSig, HullFacts, Program, StageFacts, TessDomain, TessTopology, stage_functions,
21};
22
23/// What the tessellator made of a patch, with the programs' results.
24#[derive(Clone, Debug, PartialEq)]
25pub struct Patch {
26    /// The hull program's output control points: one register per output varying ([`TessellationShader::control_outputs`]).
27    pub control_points: Vec<Vertex>,
28    /// The tessellation factors the hull program wrote.
29    pub factors: Factors,
30    /// The domain program's result for each point the tessellator made: one register per output varying of the domain
31    /// program ([`TessellationShader::outputs`]).
32    pub vertices: Vec<Vertex>,
33    /// The domain locations of the points (u, v; a triangle's third coordinate is `1 - u - v`).
34    pub locations: Vec<[f32; 2]>,
35    /// What `indices` make: points, lines or triangles (clockwise or counter-clockwise in the domain, as the hull
36    /// program says).
37    pub topology: TessTopology,
38    /// Point numbers, one per point of a point list, two per line, three per triangle.
39    pub indices: Vec<u32>,
40}
41
42/// A hull program and the domain program it feeds, ready to run.
43#[derive(Debug)]
44pub struct TessellationShader {
45    hull: Program,
46    hull_bindings: Bindings,
47    control: Executable,
48    control_sig: FunctionSig,
49    constant: Executable,
50    hull_facts: HullFacts,
51    domain: Program,
52    domain_bindings: Bindings,
53    domain_exe: Executable,
54    domain_sig: FunctionSig,
55    domain_facts: DomainFacts,
56    /// The patch constants (hull results of the constant phase) that are the edge factors, then the inside factors.
57    edge_factors: Vec<usize>,
58    inside_factors: Vec<usize>,
59    /// For each patch constant the domain program reads, the hull result it is.
60    constant_map: Vec<usize>,
61    /// For each input varying of the domain program, the output varying of the hull program it is.
62    control_map: Vec<usize>,
63}
64
65fn find(sig: &FunctionSig, name: &str) -> Option<usize> {
66    sig.result.iter().position(|r| r.name == name)
67}
68
69impl TessellationShader {
70    /// Prepare `hull` and the `domain` program it feeds. The programs must agree on the domain and on the number of
71    /// control points between them, and the domain program's inputs must be varyings the hull program writes.
72    pub fn new(hull: Program, domain: Program) -> Result<Self, String> {
73        let StageFacts::Hull(hull_facts) = hull.interface.facts.clone() else {
74            return Err(format!("a {:?} program is not a hull program", hull.stage));
75        };
76        let StageFacts::Domain(domain_facts) = domain.interface.facts.clone() else {
77            return Err(format!("a {:?} program is not a domain program", domain.stage));
78        };
79        if hull_facts.domain != domain_facts.domain {
80            return Err(format!(
81                "the hull program tessellates a {:?} domain, the domain program is for a {:?} one",
82                hull_facts.domain, domain_facts.domain
83            ));
84        }
85        if hull_facts.output_control_points != domain_facts.input_control_points {
86            return Err(format!(
87                "the hull program writes {} control points, the domain program reads {}",
88                hull_facts.output_control_points, domain_facts.input_control_points
89            ));
90        }
91        let mut hull_sigs =
92            stage_functions(&hull.interface)?.ok_or("a hull program without its functions")?;
93        let constant_sig = hull_sigs
94            .iter()
95            .position(|s| s.name == "patch_constant")
96            .map(|i| hull_sigs.swap_remove(i))
97            .ok_or("a hull program without its patch-constant function")?;
98        let control_sig = hull_sigs
99            .into_iter()
100            .find(|s| s.name == "main")
101            .ok_or("a hull program without its control-point function")?;
102        let domain_sig = stage_functions(&domain.interface)?
103            .and_then(|mut f| f.pop())
104            .ok_or("a domain program without its function")?;
105        let control = Executable::function(&hull, "main").map_err(|e| e.to_string())?;
106        let constant = Executable::function(&hull, "patch_constant").map_err(|e| e.to_string())?;
107        let domain_exe = Executable::function(&domain, "main").map_err(|e| e.to_string())?;
108
109        let (edges, insides) = match hull_facts.domain {
110            TessDomain::Triangle => (3, 1),
111            TessDomain::Quad => (4, 2),
112            TessDomain::Isoline => (2, 0),
113        };
114        let factor = |name: &str, k: usize| {
115            let name = format!("{name}{k}");
116            find(&constant_sig, &name)
117                .ok_or_else(|| format!("the hull program's patch constants have no {name}"))
118        };
119        let edge_factors = (0..edges)
120            .map(|k| factor("SV_TessFactor", k))
121            .collect::<Result<Vec<_>, _>>()?;
122        let inside_factors = (0..insides)
123            .map(|k| factor("SV_InsideTessFactor", k))
124            .collect::<Result<Vec<_>, _>>()?;
125
126        // the domain program's arguments: its patch constants, then its input varyings as arrays of control points
127        let constants_read = domain_facts.patch_constants.len();
128        let constant_map = domain_sig
129            .arguments
130            .iter()
131            .take(constants_read)
132            .map(|a| {
133                find(&constant_sig, &a.name).ok_or_else(|| {
134                    format!(
135                        "the domain program reads the patch constant {} the hull program does not write",
136                        a.name
137                    )
138                })
139            })
140            .collect::<Result<Vec<_>, _>>()?;
141        let control_map = domain_sig
142            .arguments
143            .iter()
144            .skip(constants_read)
145            .take(domain.interface.inputs.len())
146            .map(|a| {
147                find(&control_sig, &a.name).ok_or_else(|| {
148                    format!("the domain program reads the control-point varying {} the hull program does not write", a.name)
149                })
150            })
151            .collect::<Result<Vec<_>, _>>()?;
152
153        Ok(Self {
154            hull_bindings: Bindings::new(&hull)?,
155            domain_bindings: Bindings::new(&domain)?,
156            hull,
157            control,
158            control_sig,
159            constant,
160            hull_facts,
161            domain,
162            domain_exe,
163            domain_sig,
164            domain_facts,
165            edge_factors,
166            inside_factors,
167            constant_map,
168            control_map,
169        })
170    }
171
172    /// The hull program.
173    pub fn hull(&self) -> &Program {
174        &self.hull
175    }
176
177    /// The domain program.
178    pub fn domain(&self) -> &Program {
179        &self.domain
180    }
181
182    /// What the hull program states: the domain, the partitioning, the output, the control point counts.
183    pub fn hull_facts(&self) -> &HullFacts {
184        &self.hull_facts
185    }
186
187    /// What the domain program states.
188    pub fn domain_facts(&self) -> &DomainFacts {
189        &self.domain_facts
190    }
191
192    /// The names of the hull program's input varyings: the registers of the control points given to
193    /// [`TessellationShader::tessellate`], in order.
194    pub fn inputs(&self) -> impl Iterator<Item = &str> {
195        let n = self.hull.interface.inputs.len();
196        self.control_sig.arguments.iter().take(n).map(|a| a.name.as_str())
197    }
198
199    /// The names of the hull program's output varyings: the registers of [`Patch::control_points`].
200    pub fn control_outputs(&self) -> impl Iterator<Item = &str> {
201        let n = self.hull.interface.outputs.len();
202        self.control_sig.result.iter().take(n).map(|a| a.name.as_str())
203    }
204
205    /// The names of the domain program's output varyings: the registers of [`Patch::vertices`].
206    pub fn outputs(&self) -> impl Iterator<Item = &str> {
207        self.domain_sig.result.iter().map(|a| a.name.as_str())
208    }
209
210    /// Tessellate the patch of `control_points` (as many as the hull program reads), the patch numbered `primitive`
211    /// of the draw. `hull_constants` and `domain_constants` hold the rows of the constant buffers each program reads,
212    /// by (register, space); image and buffer access of both go to `sampler` by bind slot.
213    pub fn tessellate(
214        &self,
215        hull_constants: &Constants,
216        domain_constants: &Constants,
217        sampler: &mut dyn Sampler,
218        control_points: &[Vertex],
219        primitive: u32,
220    ) -> Result<Patch, String> {
221        let input_count = self.hull_facts.input_control_points as usize;
222        if control_points.len() != input_count {
223            return Err(format!(
224                "{} control points given, the hull program reads {input_count}",
225                control_points.len()
226            ));
227        }
228        let inputs = self.hull.interface.inputs.len();
229        let mut inputs_as_arrays = Vec::with_capacity(inputs.saturating_mul(input_count));
230        for varying in 0..inputs {
231            for (k, point) in control_points.iter().enumerate() {
232                inputs_as_arrays.push(*point.get(varying).ok_or_else(|| {
233                    format!(
234                        "control point {k} has {} registers, the hull program reads {inputs} ({})",
235                        point.len(),
236                        self.inputs().collect::<Vec<_>>().join(", ")
237                    )
238                })?);
239            }
240        }
241
242        // the hull program: each output control point, then the patch constants
243        let hull_bound = self.hull_bindings.bind(&self.control, hull_constants)?;
244        let constant_bound = self.hull_bindings.bind(&self.constant, hull_constants)?;
245        let mut hull_backend = self.hull_bindings.backend(sampler);
246        let output_count = self.hull_facts.output_control_points as usize;
247        let outputs = self.hull.interface.outputs.len();
248        let mut control_outputs: Vec<Vertex> = Vec::with_capacity(output_count);
249        let mut scratch = Scratch::new(&self.control);
250        for id in 0..output_count {
251            let mut arguments = inputs_as_arrays.clone();
252            arguments.push([u32::try_from(id).unwrap_or(u32::MAX), 0, 0, 0]);
253            let (values, kept) = call(&self.control, &hull_bound, &mut hull_backend, scratch, &arguments)?;
254            scratch = kept;
255            let members = values.get(..outputs).ok_or_else(|| {
256                format!(
257                    "the hull program returned {} registers for a control point, its signature has {outputs}",
258                    values.len()
259                )
260            })?;
261            control_outputs.push(members.to_vec());
262        }
263        let mut arguments = inputs_as_arrays;
264        for varying in 0..outputs {
265            for point in &control_outputs {
266                arguments.push(output_of(point, varying)?);
267            }
268        }
269        arguments.push([primitive, 0, 0, 0]);
270        let (constants, _) = call(
271            &self.constant,
272            &constant_bound,
273            &mut hull_backend,
274            Scratch::new(&self.constant),
275            &arguments,
276        )?;
277        let float = |k: usize| -> Result<f32, String> { Ok(f32::from_bits(output_of(&constants, k)?[0])) };
278        let mut factors = Factors {
279            edges: [0.0; 4],
280            inside: [0.0; 2],
281        };
282        for (slot, &k) in factors.edges.iter_mut().zip(&self.edge_factors) {
283            *slot = float(k)?;
284        }
285        for (slot, &k) in factors.inside.iter_mut().zip(&self.inside_factors) {
286            *slot = float(k)?;
287        }
288
289        // the tessellator
290        let partitioning = self.hull_facts.partitioning;
291        let output = match self.hull_facts.topology {
292            TessTopology::Point => OutputPrimitive::Point,
293            TessTopology::Line => OutputPrimitive::Line,
294            TessTopology::TriangleCw => OutputPrimitive::TriangleCw,
295            TessTopology::TriangleCcw => OutputPrimitive::TriangleCcw,
296        };
297        let Tessellation { points, indices } = match self.hull_facts.domain {
298            TessDomain::Triangle => tessellate_triangle(partitioning, output, factors),
299            TessDomain::Quad => tessellate_quad(partitioning, output, factors),
300            TessDomain::Isoline => {
301                tessellate_isolines(partitioning, output, factors.edges[0], factors.edges[1])
302            }
303        };
304
305        // the domain program for each point
306        let domain_bound = self.domain_bindings.bind(&self.domain_exe, domain_constants)?;
307        let mut domain_backend = self.domain_bindings.backend(sampler);
308        let mut fixed: Vec<[u32; 4]> = self
309            .constant_map
310            .iter()
311            .map(|&k| output_of(&constants, k))
312            .collect::<Result<_, _>>()?;
313        for varying in &self.control_map {
314            for point in &control_outputs {
315                fixed.push(output_of(point, *varying)?);
316            }
317        }
318        let triangle = self.domain_facts.domain == TessDomain::Triangle;
319        let mut vertices = Vec::with_capacity(points.len());
320        let mut scratch = Scratch::new(&self.domain_exe);
321        for &[u, v] in &points {
322            let mut arguments = fixed.clone();
323            let w = if triangle { 1.0 - u - v } else { 0.0 };
324            arguments.push([u.to_bits(), v.to_bits(), w.to_bits(), 0]);
325            arguments.push([primitive, 0, 0, 0]);
326            let (values, kept) = call(
327                &self.domain_exe,
328                &domain_bound,
329                &mut domain_backend,
330                scratch,
331                &arguments,
332            )?;
333            scratch = kept;
334            vertices.push(values);
335        }
336        Ok(Patch {
337            control_points: control_outputs,
338            factors,
339            vertices,
340            locations: points,
341            topology: self.hull_facts.topology,
342            indices,
343        })
344    }
345}
346
347/// Register `k` of a program's result.
348fn output_of(values: &[[u32; 4]], k: usize) -> Result<[u32; 4], String> {
349    values.get(k).copied().ok_or_else(|| {
350        format!(
351            "the program returned {} registers, past register {k}",
352            values.len()
353        )
354    })
355}
356
357/// Run `executable` once on `arguments`, returning its result's registers and the registers kept for the next run.
358fn call(
359    executable: &Executable,
360    bound: &Bound<'_>,
361    backend: &mut SlotBackend<'_>,
362    scratch: Scratch<1>,
363    arguments: &[[u32; 4]],
364) -> Result<(Vec<[u32; 4]>, Scratch<1>), String> {
365    let mut runner = Runner::<1>::with_scratch(executable, bound, backend, None, scratch);
366    let [out] = runner.run([arguments])?;
367    Ok((out.values, runner.into_scratch()))
368}