use super::pixel::SlotBackend;
use super::stage::{Bindings, Constants};
use super::{Bound, Executable, Runner, Scratch};
use crate::Sampler;
use iridia::{FunctionSig, GeometryFacts, GsInput, GsOutput, Program, StageFacts, stage_functions};
pub type Vertex = Vec<[u32; 4]>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Topology {
PointList,
LineList,
LineStrip,
TriangleList,
TriangleStrip,
LineListAdjacency,
LineStripAdjacency,
TriangleListAdjacency,
TriangleStripAdjacency,
}
impl Topology {
pub fn input(self) -> GsInput {
match self {
Self::PointList => GsInput::Point,
Self::LineList | Self::LineStrip => GsInput::Line,
Self::TriangleList | Self::TriangleStrip => GsInput::Triangle,
Self::LineListAdjacency | Self::LineStripAdjacency => GsInput::LineAdjacency,
Self::TriangleListAdjacency | Self::TriangleStripAdjacency => GsInput::TriangleAdjacency,
}
}
#[allow(clippy::arithmetic_side_effects)] pub fn primitives(self, vertices: usize) -> Vec<Vec<usize>> {
let list = |size: usize| -> Vec<Vec<usize>> {
(0..vertices / size)
.map(|p| (p * size..(p + 1) * size).collect())
.collect()
};
let strip = |size: usize| -> Vec<Vec<usize>> {
(0..(vertices + 1).saturating_sub(size))
.map(|p| (p..p + size).collect())
.collect()
};
match self {
Self::PointList => list(1),
Self::LineList => list(2),
Self::LineStrip => strip(2),
Self::TriangleList => list(3),
Self::TriangleStrip => strip(3)
.into_iter()
.enumerate()
.map(|(p, mut t)| {
if p % 2 == 1 {
t.swap(1, 2);
}
t
})
.collect(),
Self::LineListAdjacency => list(4),
Self::LineStripAdjacency => strip(4),
Self::TriangleListAdjacency => list(6),
Self::TriangleStripAdjacency => {
let count = vertices.saturating_sub(4) / 2;
(0..count)
.map(|i| {
let last = i + 1 == count;
let after = if last { 2 * i + 5 } else { 2 * i + 6 };
if i % 2 == 0 {
vec![
2 * i,
if i == 0 { 1 } else { 2 * i - 2 },
2 * i + 2,
after,
2 * i + 4,
2 * i + 3,
]
} else {
vec![2 * i, 2 * i + 3, 2 * i + 4, after, 2 * i + 2, 2 * i - 2]
}
})
.collect()
}
}
}
}
impl From<GsOutput> for Topology {
fn from(output: GsOutput) -> Self {
match output {
GsOutput::PointList => Self::PointList,
GsOutput::LineStrip => Self::LineStrip,
GsOutput::TriangleStrip => Self::TriangleStrip,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Strip {
pub primitive: u32,
pub instance: u32,
pub vertices: Vec<Vertex>,
}
impl Strip {
pub fn primitives(&self, output: GsOutput) -> Vec<Vec<usize>> {
Topology::from(output).primitives(self.vertices.len())
}
}
#[derive(Debug)]
pub struct GeometryShader {
program: Program,
executable: Executable,
signature: FunctionSig,
facts: GeometryFacts,
inputs: usize,
outputs: usize,
bindings: Bindings,
}
impl GeometryShader {
pub fn new(program: Program) -> Result<Self, String> {
let StageFacts::Geometry(facts) = program.interface.facts else {
return Err(format!("a {:?} program is not a geometry program", program.stage));
};
let signature = stage_functions(&program.interface)?
.and_then(|mut f| f.pop())
.ok_or("a geometry program without its function")?;
let executable = Executable::function(&program, "main").map_err(|e| e.to_string())?;
let (inputs, outputs) = (program.interface.inputs.len(), program.interface.outputs.len());
let bindings = Bindings::new(&program)?;
Ok(Self {
program,
executable,
signature,
facts,
inputs,
outputs,
bindings,
})
}
pub fn program(&self) -> &Program {
&self.program
}
pub fn facts(&self) -> GeometryFacts {
self.facts
}
pub fn inputs(&self) -> impl Iterator<Item = &str> {
self.signature
.arguments
.iter()
.take(self.inputs)
.map(|a| a.name.as_str())
}
pub fn outputs(&self) -> impl Iterator<Item = &str> {
self.signature
.result
.iter()
.take(self.outputs)
.map(|a| a.name.as_str())
}
pub fn run(
&self,
constants: &Constants,
sampler: &mut dyn Sampler,
vertices: &[&Vertex],
primitive: u32,
instance: u32,
) -> Result<Vec<Strip>, String> {
let bound = self.bind(constants)?;
let mut backend = self.backend(sampler);
self.invoke(
&bound,
&mut backend,
Scratch::new(&self.executable),
vertices,
primitive,
instance,
)
.map(|(strips, _)| strips)
}
pub fn draw(
&self,
constants: &Constants,
sampler: &mut dyn Sampler,
vertices: &[Vertex],
topology: Topology,
) -> Result<Vec<Strip>, String> {
if topology.input() != self.facts.input {
return Err(format!(
"a draw of {topology:?} feeds the geometry program primitives of {:?}, which reads {:?}",
topology.input(),
self.facts.input
));
}
let bound = self.bind(constants)?;
let mut backend = self.backend(sampler);
let mut scratch = Scratch::new(&self.executable);
let mut strips = Vec::new();
for (primitive, indices) in topology.primitives(vertices.len()).iter().enumerate() {
let primitive_vertices: Vec<&Vertex> = indices
.iter()
.map(|&i| {
vertices.get(i).ok_or_else(|| {
format!(
"primitive {primitive} names vertex {i}, past the {} given",
vertices.len()
)
})
})
.collect::<Result<_, _>>()?;
for instance in 0..self.facts.instances.max(1) {
let (emitted, kept) = self.invoke(
&bound,
&mut backend,
scratch,
&primitive_vertices,
u32::try_from(primitive).map_err(|e| e.to_string())?,
instance,
)?;
scratch = kept;
strips.extend(emitted);
}
}
Ok(strips)
}
pub fn draw_indexed(
&self,
constants: &Constants,
sampler: &mut dyn Sampler,
vertices: &[Vertex],
indices: &[u32],
restart: Option<u32>,
topology: Topology,
) -> Result<Vec<Strip>, String> {
if let Some(cut) = restart
&& let Some(at) = indices.iter().position(|&i| i == cut)
{
return Err(format!(
"index {at} is the restart index {cut:#x}: a draw with a strip restart is not assembled (the \
primitives around a cut are not measured on the D3D reference)"
));
}
let drawn = indices
.iter()
.map(|&i| {
vertices
.get(i as usize)
.cloned()
.ok_or_else(|| format!("index {i} names a vertex past the {} given", vertices.len()))
})
.collect::<Result<Vec<_>, _>>()?;
self.draw(constants, sampler, &drawn, topology)
}
fn bind<'a>(&self, constants: &'a Constants) -> Result<Bound<'a>, String> {
self.bindings.bind(&self.executable, constants)
}
fn backend<'a>(&'a self, sampler: &'a mut dyn Sampler) -> SlotBackend<'a> {
self.bindings.backend(sampler)
}
fn invoke(
&self,
bound: &Bound<'_>,
backend: &mut SlotBackend<'_>,
scratch: Scratch<1>,
vertices: &[&Vertex],
primitive: u32,
instance: u32,
) -> Result<(Vec<Strip>, Scratch<1>), String> {
let size = self.facts.input.vertices() as usize;
if vertices.len() != size {
return Err(format!(
"{} vertices given for a primitive of {:?}, which has {size}",
vertices.len(),
self.facts.input
));
}
let mut arguments = Vec::with_capacity(self.inputs.saturating_mul(size).saturating_add(2));
for varying in 0..self.inputs {
for (k, vertex) in vertices.iter().enumerate() {
arguments.push(*vertex.get(varying).ok_or_else(|| {
format!(
"vertex {k} has {} registers, the program reads {} ({})",
vertex.len(),
self.inputs,
self.inputs().collect::<Vec<_>>().join(", ")
)
})?);
}
}
arguments.push([primitive, 0, 0, 0]);
arguments.push([instance, 0, 0, 0]);
let mut runner = Runner::<1>::with_scratch(&self.executable, bound, backend, None, scratch);
let [out] = runner.run([&arguments])?;
let scratch = runner.into_scratch();
let max = self.facts.max_vertices as usize;
let count_at = self.outputs.saturating_mul(max);
let want = count_at.saturating_add(1).saturating_add(max);
if out.values.len() != want {
return Err(format!(
"the program returned {} values, its signature has {want}",
out.values.len()
));
}
let count = out.values.get(count_at).map_or(0, |r| r[0]) as usize;
if count > max {
return Err(format!(
"the invocation emitted {count} vertices, past the {max} it declares"
));
}
let mut strips: Vec<Strip> = Vec::new();
for k in 0..count {
let cut = out
.values
.get(count_at.saturating_add(1).saturating_add(k))
.ok_or_else(|| format!("the program returned no strip flag for vertex {k}"))?;
if k == 0 || cut[0] != 0 {
strips.push(Strip {
primitive,
instance,
vertices: Vec::new(),
});
}
let vertex = (0..self.outputs)
.map(|o| {
out.values
.get(o.saturating_mul(max).saturating_add(k))
.copied()
.ok_or_else(|| format!("the program returned no output {o} for vertex {k}"))
})
.collect::<Result<_, _>>()?;
if let Some(strip) = strips.last_mut() {
strip.vertices.push(vertex);
}
}
Ok((strips, scratch))
}
}