use super::geometry::Vertex;
use super::pixel::SlotBackend;
use super::stage::{Bindings, Constants};
use super::tessellator::{
Factors, OutputPrimitive, Tessellation, tessellate_isolines, tessellate_quad, tessellate_triangle,
};
use super::{Bound, Executable, Runner, Scratch};
use crate::Sampler;
use iridia::{
DomainFacts, FunctionSig, HullFacts, Program, StageFacts, TessDomain, TessTopology, stage_functions,
};
#[derive(Clone, Debug, PartialEq)]
pub struct Patch {
pub control_points: Vec<Vertex>,
pub factors: Factors,
pub vertices: Vec<Vertex>,
pub locations: Vec<[f32; 2]>,
pub topology: TessTopology,
pub indices: Vec<u32>,
}
#[derive(Debug)]
pub struct TessellationShader {
hull: Program,
hull_bindings: Bindings,
control: Executable,
control_sig: FunctionSig,
constant: Executable,
hull_facts: HullFacts,
domain: Program,
domain_bindings: Bindings,
domain_exe: Executable,
domain_sig: FunctionSig,
domain_facts: DomainFacts,
edge_factors: Vec<usize>,
inside_factors: Vec<usize>,
constant_map: Vec<usize>,
control_map: Vec<usize>,
}
fn find(sig: &FunctionSig, name: &str) -> Option<usize> {
sig.result.iter().position(|r| r.name == name)
}
impl TessellationShader {
pub fn new(hull: Program, domain: Program) -> Result<Self, String> {
let StageFacts::Hull(hull_facts) = hull.interface.facts.clone() else {
return Err(format!("a {:?} program is not a hull program", hull.stage));
};
let StageFacts::Domain(domain_facts) = domain.interface.facts.clone() else {
return Err(format!("a {:?} program is not a domain program", domain.stage));
};
if hull_facts.domain != domain_facts.domain {
return Err(format!(
"the hull program tessellates a {:?} domain, the domain program is for a {:?} one",
hull_facts.domain, domain_facts.domain
));
}
if hull_facts.output_control_points != domain_facts.input_control_points {
return Err(format!(
"the hull program writes {} control points, the domain program reads {}",
hull_facts.output_control_points, domain_facts.input_control_points
));
}
let mut hull_sigs =
stage_functions(&hull.interface)?.ok_or("a hull program without its functions")?;
let constant_sig = hull_sigs
.iter()
.position(|s| s.name == "patch_constant")
.map(|i| hull_sigs.swap_remove(i))
.ok_or("a hull program without its patch-constant function")?;
let control_sig = hull_sigs
.into_iter()
.find(|s| s.name == "main")
.ok_or("a hull program without its control-point function")?;
let domain_sig = stage_functions(&domain.interface)?
.and_then(|mut f| f.pop())
.ok_or("a domain program without its function")?;
let control = Executable::function(&hull, "main").map_err(|e| e.to_string())?;
let constant = Executable::function(&hull, "patch_constant").map_err(|e| e.to_string())?;
let domain_exe = Executable::function(&domain, "main").map_err(|e| e.to_string())?;
let (edges, insides) = match hull_facts.domain {
TessDomain::Triangle => (3, 1),
TessDomain::Quad => (4, 2),
TessDomain::Isoline => (2, 0),
};
let factor = |name: &str, k: usize| {
let name = format!("{name}{k}");
find(&constant_sig, &name)
.ok_or_else(|| format!("the hull program's patch constants have no {name}"))
};
let edge_factors = (0..edges)
.map(|k| factor("SV_TessFactor", k))
.collect::<Result<Vec<_>, _>>()?;
let inside_factors = (0..insides)
.map(|k| factor("SV_InsideTessFactor", k))
.collect::<Result<Vec<_>, _>>()?;
let constants_read = domain_facts.patch_constants.len();
let constant_map = domain_sig
.arguments
.iter()
.take(constants_read)
.map(|a| {
find(&constant_sig, &a.name).ok_or_else(|| {
format!(
"the domain program reads the patch constant {} the hull program does not write",
a.name
)
})
})
.collect::<Result<Vec<_>, _>>()?;
let control_map = domain_sig
.arguments
.iter()
.skip(constants_read)
.take(domain.interface.inputs.len())
.map(|a| {
find(&control_sig, &a.name).ok_or_else(|| {
format!("the domain program reads the control-point varying {} the hull program does not write", a.name)
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
hull_bindings: Bindings::new(&hull)?,
domain_bindings: Bindings::new(&domain)?,
hull,
control,
control_sig,
constant,
hull_facts,
domain,
domain_exe,
domain_sig,
domain_facts,
edge_factors,
inside_factors,
constant_map,
control_map,
})
}
pub fn hull(&self) -> &Program {
&self.hull
}
pub fn domain(&self) -> &Program {
&self.domain
}
pub fn hull_facts(&self) -> &HullFacts {
&self.hull_facts
}
pub fn domain_facts(&self) -> &DomainFacts {
&self.domain_facts
}
pub fn inputs(&self) -> impl Iterator<Item = &str> {
let n = self.hull.interface.inputs.len();
self.control_sig.arguments.iter().take(n).map(|a| a.name.as_str())
}
pub fn control_outputs(&self) -> impl Iterator<Item = &str> {
let n = self.hull.interface.outputs.len();
self.control_sig.result.iter().take(n).map(|a| a.name.as_str())
}
pub fn outputs(&self) -> impl Iterator<Item = &str> {
self.domain_sig.result.iter().map(|a| a.name.as_str())
}
pub fn tessellate(
&self,
hull_constants: &Constants,
domain_constants: &Constants,
sampler: &mut dyn Sampler,
control_points: &[Vertex],
primitive: u32,
) -> Result<Patch, String> {
let input_count = self.hull_facts.input_control_points as usize;
if control_points.len() != input_count {
return Err(format!(
"{} control points given, the hull program reads {input_count}",
control_points.len()
));
}
let inputs = self.hull.interface.inputs.len();
let mut inputs_as_arrays = Vec::with_capacity(inputs.saturating_mul(input_count));
for varying in 0..inputs {
for (k, point) in control_points.iter().enumerate() {
inputs_as_arrays.push(*point.get(varying).ok_or_else(|| {
format!(
"control point {k} has {} registers, the hull program reads {inputs} ({})",
point.len(),
self.inputs().collect::<Vec<_>>().join(", ")
)
})?);
}
}
let hull_bound = self.hull_bindings.bind(&self.control, hull_constants)?;
let constant_bound = self.hull_bindings.bind(&self.constant, hull_constants)?;
let mut hull_backend = self.hull_bindings.backend(sampler);
let output_count = self.hull_facts.output_control_points as usize;
let outputs = self.hull.interface.outputs.len();
let mut control_outputs: Vec<Vertex> = Vec::with_capacity(output_count);
let mut scratch = Scratch::new(&self.control);
for id in 0..output_count {
let mut arguments = inputs_as_arrays.clone();
arguments.push([u32::try_from(id).unwrap_or(u32::MAX), 0, 0, 0]);
let (values, kept) = call(&self.control, &hull_bound, &mut hull_backend, scratch, &arguments)?;
scratch = kept;
let members = values.get(..outputs).ok_or_else(|| {
format!(
"the hull program returned {} registers for a control point, its signature has {outputs}",
values.len()
)
})?;
control_outputs.push(members.to_vec());
}
let mut arguments = inputs_as_arrays;
for varying in 0..outputs {
for point in &control_outputs {
arguments.push(output_of(point, varying)?);
}
}
arguments.push([primitive, 0, 0, 0]);
let (constants, _) = call(
&self.constant,
&constant_bound,
&mut hull_backend,
Scratch::new(&self.constant),
&arguments,
)?;
let float = |k: usize| -> Result<f32, String> { Ok(f32::from_bits(output_of(&constants, k)?[0])) };
let mut factors = Factors {
edges: [0.0; 4],
inside: [0.0; 2],
};
for (slot, &k) in factors.edges.iter_mut().zip(&self.edge_factors) {
*slot = float(k)?;
}
for (slot, &k) in factors.inside.iter_mut().zip(&self.inside_factors) {
*slot = float(k)?;
}
let partitioning = self.hull_facts.partitioning;
let output = match self.hull_facts.topology {
TessTopology::Point => OutputPrimitive::Point,
TessTopology::Line => OutputPrimitive::Line,
TessTopology::TriangleCw => OutputPrimitive::TriangleCw,
TessTopology::TriangleCcw => OutputPrimitive::TriangleCcw,
};
let Tessellation { points, indices } = match self.hull_facts.domain {
TessDomain::Triangle => tessellate_triangle(partitioning, output, factors),
TessDomain::Quad => tessellate_quad(partitioning, output, factors),
TessDomain::Isoline => {
tessellate_isolines(partitioning, output, factors.edges[0], factors.edges[1])
}
};
let domain_bound = self.domain_bindings.bind(&self.domain_exe, domain_constants)?;
let mut domain_backend = self.domain_bindings.backend(sampler);
let mut fixed: Vec<[u32; 4]> = self
.constant_map
.iter()
.map(|&k| output_of(&constants, k))
.collect::<Result<_, _>>()?;
for varying in &self.control_map {
for point in &control_outputs {
fixed.push(output_of(point, *varying)?);
}
}
let triangle = self.domain_facts.domain == TessDomain::Triangle;
let mut vertices = Vec::with_capacity(points.len());
let mut scratch = Scratch::new(&self.domain_exe);
for &[u, v] in &points {
let mut arguments = fixed.clone();
let w = if triangle { 1.0 - u - v } else { 0.0 };
arguments.push([u.to_bits(), v.to_bits(), w.to_bits(), 0]);
arguments.push([primitive, 0, 0, 0]);
let (values, kept) = call(
&self.domain_exe,
&domain_bound,
&mut domain_backend,
scratch,
&arguments,
)?;
scratch = kept;
vertices.push(values);
}
Ok(Patch {
control_points: control_outputs,
factors,
vertices,
locations: points,
topology: self.hull_facts.topology,
indices,
})
}
}
fn output_of(values: &[[u32; 4]], k: usize) -> Result<[u32; 4], String> {
values.get(k).copied().ok_or_else(|| {
format!(
"the program returned {} registers, past register {k}",
values.len()
)
})
}
fn call(
executable: &Executable,
bound: &Bound<'_>,
backend: &mut SlotBackend<'_>,
scratch: Scratch<1>,
arguments: &[[u32; 4]],
) -> Result<(Vec<[u32; 4]>, Scratch<1>), String> {
let mut runner = Runner::<1>::with_scratch(executable, bound, backend, None, scratch);
let [out] = runner.run([arguments])?;
Ok((out.values, runner.into_scratch()))
}