use naga::Binding;
use crate::{Interface, Label, ScalarType, Stage, StageFacts, SystemValue, Value, Varying};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IoType {
Value(Value),
ClipDistances(u32),
Array(Value, u32),
Memory,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IoItem {
pub name: String,
pub ty: IoType,
pub binding: Binding,
pub varyings: Vec<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EntryIo {
pub arguments: Vec<IoItem>,
pub results: Vec<IoItem>,
}
pub fn varying_name(label: &Label) -> String {
format!("{}{}", label.name, label.index)
}
pub fn carried(stage: Stage, output: bool, v: &Varying) -> Value {
let (scalar, width) = match v.system {
Some(SystemValue::ClipPosition | SystemValue::FragCoord) => (ScalarType::Float, 4),
Some(SystemValue::FrontFacing) => (ScalarType::Bool, 1),
Some(
SystemValue::VertexIndex
| SystemValue::InstanceIndex
| SystemValue::PrimitiveIndex
| SystemValue::SampleIndex
| SystemValue::Coverage,
) => (ScalarType::Uint, 1),
Some(SystemValue::GroupId | SystemValue::GroupThreadId | SystemValue::DispatchThreadId) => {
(ScalarType::Uint, 3)
}
Some(SystemValue::GroupIndex | SystemValue::LaneIndex | SystemValue::LaneCount) => {
(ScalarType::Uint, 1)
}
Some(SystemValue::DispatchRaysIndex | SystemValue::DispatchRaysDimensions) => (ScalarType::Uint, 3),
Some(
SystemValue::WorldRayOrigin
| SystemValue::WorldRayDirection
| SystemValue::ObjectRayOrigin
| SystemValue::ObjectRayDirection,
) => (ScalarType::Float, 3),
Some(SystemValue::RayTMin | SystemValue::RayTCurrent) => (ScalarType::Float, 1),
Some(SystemValue::ObjectToWorld | SystemValue::WorldToObject) => (ScalarType::Float, 4),
Some(
SystemValue::RayFlags
| SystemValue::InstanceId
| SystemValue::GeometryIndex
| SystemValue::HitKind,
) => (ScalarType::Uint, 1),
Some(SystemValue::Depth | SystemValue::DepthGreaterEqual | SystemValue::DepthLessEqual) => {
(ScalarType::Float, 1)
}
Some(SystemValue::Target(_)) if stage == Stage::Pixel && output => (v.value.scalar, 4),
_ => (v.value.scalar, v.value.width),
};
Value { scalar, width }
}
fn builtin(stage: Stage, output: bool, system: SystemValue) -> Option<naga::BuiltIn> {
use SystemValue::*;
Some(match (stage, output, system) {
(Stage::Vertex, true, ClipPosition) | (Stage::Pixel, false, FragCoord) => {
naga::BuiltIn::Position { invariant: false }
}
(Stage::Pixel, false, FrontFacing) => naga::BuiltIn::FrontFacing,
(Stage::Vertex, false, VertexIndex) => naga::BuiltIn::VertexIndex,
(Stage::Vertex, false, InstanceIndex) => naga::BuiltIn::InstanceIndex,
(Stage::Pixel, false, PrimitiveIndex) => naga::BuiltIn::PrimitiveIndex,
(Stage::Pixel, false, SampleIndex) => naga::BuiltIn::SampleIndex,
(Stage::Pixel, _, Coverage) => naga::BuiltIn::SampleMask,
(Stage::Compute | Stage::Mesh, false, GroupId) => naga::BuiltIn::WorkGroupId,
(Stage::Compute | Stage::Mesh, false, GroupThreadId) => naga::BuiltIn::LocalInvocationId,
(Stage::Compute | Stage::Mesh, false, DispatchThreadId) => naga::BuiltIn::GlobalInvocationId,
(Stage::Compute | Stage::Mesh, false, GroupIndex) => naga::BuiltIn::LocalInvocationIndex,
(Stage::Compute | Stage::Pixel, false, LaneIndex) => naga::BuiltIn::SubgroupInvocationId,
(Stage::Compute | Stage::Pixel, false, LaneCount) => naga::BuiltIn::SubgroupSize,
(Stage::Pixel, true, Depth | DepthGreaterEqual | DepthLessEqual) => naga::BuiltIn::FragDepth,
_ => return None,
})
}
fn location(stage: Stage, output: bool, v: &Varying, at: u32) -> Binding {
let float = v.value.scalar == ScalarType::Float;
let (interpolation, sampling) = match (stage, output) {
(Stage::Pixel, false) => {
let declared = v.interp.map(|i| {
(
match i.mode {
crate::InterpMode::Perspective => naga::Interpolation::Perspective,
crate::InterpMode::Linear => naga::Interpolation::Linear,
crate::InterpMode::Flat => naga::Interpolation::Flat,
},
match i.sampling {
crate::InterpSampling::Center => None,
crate::InterpSampling::Centroid => Some(naga::Sampling::Centroid),
crate::InterpSampling::Sample => Some(naga::Sampling::Sample),
},
)
});
match declared {
Some((mode, sampling)) if float && mode != naga::Interpolation::Flat => {
(Some(mode), sampling)
}
Some(_) => (Some(naga::Interpolation::Flat), None),
None if float => (Some(naga::Interpolation::Perspective), None),
None => (Some(naga::Interpolation::Flat), None),
}
}
(Stage::Vertex, true) => (
Some(if float {
naga::Interpolation::Perspective
} else {
naga::Interpolation::Flat
}),
None,
),
_ => (None, None),
};
Binding::Location {
location: at,
interpolation,
sampling,
blend_src: None,
per_primitive: false,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FnItem {
pub name: String,
pub ty: IoType,
pub varying: Option<usize>,
pub list: ItemList,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ItemList {
Inputs,
Outputs,
PatchConstants,
Pipeline,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FunctionSig {
pub name: &'static str,
pub arguments: Vec<FnItem>,
pub result: Vec<FnItem>,
}
pub fn stage_functions(interface: &Interface) -> Result<Option<Vec<FunctionSig>>, String> {
let stage = interface.stage;
let values = |list: &[Varying], output: bool, of: ItemList| -> Vec<FnItem> {
list.iter()
.enumerate()
.map(|(i, v)| FnItem {
name: varying_name(&v.label),
ty: IoType::Value(carried(stage, output, v)),
varying: Some(i),
list: of,
})
.collect()
};
let arrays = |list: &[Varying], output: bool, prefix: &str, n: u32, of: ItemList| -> Vec<FnItem> {
list.iter()
.enumerate()
.map(|(i, v)| FnItem {
name: format!("{prefix}{}", varying_name(&v.label)),
ty: IoType::Array(carried(stage, output, v), n),
varying: Some(i),
list: of,
})
.collect()
};
let scalar = |name: &str, width: u8, scalar: ScalarType| FnItem {
name: name.into(),
ty: IoType::Value(Value { scalar, width }),
varying: None,
list: ItemList::Pipeline,
};
let (inputs, outputs, patch) = (ItemList::Inputs, ItemList::Outputs, ItemList::PatchConstants);
Ok(match (&interface.stage, &interface.facts) {
(Stage::Vertex, StageFacts::VertexWithoutPosition) => Some(vec![FunctionSig {
name: "main",
arguments: values(&interface.inputs, false, inputs),
result: values(&interface.outputs, true, outputs),
}]),
(Stage::Hull, StageFacts::Hull(h)) => {
let mut main_args = arrays(&interface.inputs, false, "", h.input_control_points, inputs);
main_args.push(scalar("SV_OutputControlPointID", 1, ScalarType::Uint));
let mut constant_args = arrays(&interface.inputs, false, "", h.input_control_points, inputs);
constant_args.extend(arrays(
&interface.outputs,
true,
"out_",
h.output_control_points,
outputs,
));
constant_args.push(scalar("SV_PrimitiveID", 1, ScalarType::Uint));
Some(vec![
FunctionSig {
name: "main",
arguments: main_args,
result: values(&interface.outputs, true, outputs),
},
FunctionSig {
name: "patch_constant",
arguments: constant_args,
result: values(&h.patch_constants, true, patch),
},
])
}
(Stage::Domain, StageFacts::Domain(d)) => {
let mut args = values(&d.patch_constants, false, patch);
args.extend(arrays(
&interface.inputs,
false,
"",
d.input_control_points,
inputs,
));
args.push(scalar("SV_DomainLocation", 3, ScalarType::Float));
args.push(scalar("SV_PrimitiveID", 1, ScalarType::Uint));
Some(vec![FunctionSig {
name: "main",
arguments: args,
result: values(&interface.outputs, true, outputs),
}])
}
(Stage::Hull | Stage::Domain, _) => {
return Err(format!("a {stage:?} program without its patch facts"));
}
(Stage::Geometry, StageFacts::Geometry(g)) => {
let mut args = arrays(&interface.inputs, false, "", g.input.vertices(), inputs);
args.push(scalar("SV_PrimitiveID", 1, ScalarType::Uint));
args.push(scalar("SV_GSInstanceID", 1, ScalarType::Uint));
let mut result = arrays(&interface.outputs, true, "", g.max_vertices, outputs);
result.push(scalar("SV_VertexCount", 1, ScalarType::Uint));
result.push(FnItem {
name: "SV_StripStart".into(),
ty: IoType::Array(
Value {
scalar: ScalarType::Uint,
width: 1,
},
g.max_vertices,
),
varying: None,
list: ItemList::Pipeline,
});
Some(vec![FunctionSig {
name: "main",
arguments: args,
result,
}])
}
(Stage::Geometry, _) => return Err("a geometry program without its facts".into()),
(stage, StageFacts::None | StageFacts::RayTracing(_)) if stage.is_ray_tracing() => {
let mut arguments = values(&interface.inputs, false, inputs);
let memory = |name: &str| FnItem {
name: name.into(),
ty: IoType::Memory,
varying: None,
list: ItemList::Pipeline,
};
if matches!(
stage,
Stage::AnyHit | Stage::ClosestHit | Stage::Miss | Stage::Callable
) {
arguments.push(memory("payload"));
}
if matches!(stage, Stage::AnyHit | Stage::ClosestHit) {
arguments.push(memory("attributes"));
}
Some(vec![FunctionSig {
name: "main",
arguments,
result: Vec::new(),
}])
}
(stage, facts) if stage.is_ray_tracing() => {
return Err(format!("a {stage:?} program with {facts:?}"));
}
_ => None,
})
}
pub fn mesh_vertex_io(interface: &Interface) -> Result<Vec<IoItem>, String> {
let mut vertex = interface.clone();
vertex.stage = Stage::Vertex;
vertex.inputs.clear();
vertex.facts = StageFacts::None;
Ok(entry_io(&vertex)?.results)
}
pub fn entry_io(interface: &Interface) -> Result<EntryIo, String> {
let stage = interface.stage;
if stage == Stage::Mesh {
let mut ids = interface.clone();
ids.stage = Stage::Compute;
ids.outputs.clear();
ids.facts = StageFacts::None;
return Ok(EntryIo {
arguments: entry_io(&ids)?.arguments,
results: Vec::new(),
});
}
let mut io = EntryIo {
arguments: Vec::new(),
results: Vec::new(),
};
for output in [false, true] {
let list = if output {
&interface.outputs
} else {
&interface.inputs
};
let items = if output {
&mut io.results
} else {
&mut io.arguments
};
let mut next_location = 0u32;
let mut clip: Option<usize> = None;
for (i, v) in list.iter().enumerate() {
let name = varying_name(&v.label);
let direction = if output { "output" } else { "input" };
if v.system == Some(SystemValue::ClipDistance) {
if stage != Stage::Vertex || !output {
if !v.used {
continue;
}
return Err(format!(
"{name}: clip distances as a {stage:?} {direction} have no built-in binding"
));
}
let width = u32::from(v.value.width);
match clip {
Some(at) => {
let Some(item) = items.get_mut(at) else {
return Err(format!("{name}: the clip distances item is missing"));
};
item.varyings.push(i);
let IoType::ClipDistances(n) = &mut item.ty else {
return Err(format!("{name}: the clip distances item is not clip distances"));
};
*n = n.saturating_add(width);
}
None => {
clip = Some(items.len());
items.push(IoItem {
name: "clip_distances".into(),
ty: IoType::ClipDistances(width),
binding: Binding::BuiltIn(naga::BuiltIn::ClipDistances),
varyings: vec![i],
});
}
}
continue;
}
let binding = match v.system {
Some(SystemValue::Target(n)) if stage == Stage::Pixel && output => Binding::Location {
location: n,
interpolation: None,
sampling: None,
blend_src: None,
per_primitive: false,
},
Some(SystemValue::RenderTargetArrayIndex | SystemValue::ViewportArrayIndex) => {
let at = next_location;
next_location = at.saturating_add(1);
location(stage, output, v, at)
}
Some(SystemValue::CullDistance) if stage == Stage::Vertex && output => {
let at = next_location;
next_location = at.saturating_add(1);
location(stage, output, v, at)
}
Some(system) => match builtin(stage, output, system) {
Some(built_in) => Binding::BuiltIn(built_in),
None if !v.used => continue,
None => {
return Err(format!(
"{name}: {system:?} as a {stage:?} {direction} has no built-in binding"
));
}
},
None => {
let at = next_location;
next_location = at.saturating_add(1);
location(stage, output, v, at)
}
};
items.push(IoItem {
name,
ty: IoType::Value(carried(stage, output, v)),
binding,
varyings: vec![i],
});
}
}
Ok(io)
}