use alloc::{format, string::String, vec::Vec};
use iridia_ir::interface::{InterpSampling, ResourceKind, ScalarType, Stage, SystemValue, TextureDim};
use iridia_ir::{Program, Scalar};
use naga::valid::Capabilities;
use crate::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Profile {
pub name: &'static str,
pub capabilities: Capabilities,
}
impl Profile {
pub const NAGA: Profile = Profile {
name: "naga",
capabilities: Capabilities::FLOAT64
.union(Capabilities::SHADER_FLOAT16)
.union(Capabilities::SHADER_INT16)
.union(Capabilities::SHADER_INT64)
.union(Capabilities::PRIMITIVE_INDEX)
.union(Capabilities::MULTISAMPLED_SHADING)
.union(Capabilities::CUBE_ARRAY_TEXTURES),
};
pub const BROWSER: Profile = Profile {
name: "browser WGSL",
capabilities: Capabilities::MULTISAMPLED_SHADING.union(Capabilities::CUBE_ARRAY_TEXTURES),
};
#[must_use]
pub const fn with(self, capabilities: Capabilities) -> Profile {
Profile {
name: self.name,
capabilities: self.capabilities.union(capabilities),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Requirement {
pub capability: Capabilities,
pub feature: String,
}
fn capability_of(s: Scalar) -> Option<(Capabilities, &'static str)> {
match s {
Scalar::F64 => Some((Capabilities::FLOAT64, "f64")),
Scalar::F16 => Some((Capabilities::SHADER_FLOAT16, "f16")),
Scalar::I16 | Scalar::U16 => Some((Capabilities::SHADER_INT16, "16-bit integers")),
Scalar::I64 | Scalar::U64 => Some((Capabilities::SHADER_INT64, "64-bit integers")),
_ => None,
}
}
pub fn requirements(program: &Program) -> Vec<Requirement> {
let mut found: Vec<Requirement> = Vec::new();
let mut need = |capability: Capabilities, feature: String| {
if !found.iter().any(|r| r.capability == capability) {
found.push(Requirement { capability, feature });
}
};
let f = &program.entry;
for (n, v) in f.values.iter().enumerate() {
if let Some((c, what)) = capability_of(v.ty.scalar) {
need(c, format!("value %{n} is {what}"));
}
}
for v in &f.vars {
if let Some((c, what)) = capability_of(v.ty.scalar) {
need(c, format!("variable {} is {what}", v.name));
}
}
let stage = program.interface.stage;
let inputs = program.interface.inputs.iter().map(|v| ("input", v));
let outputs = program.interface.outputs.iter().map(|v| ("output", v));
for (list, v) in inputs.chain(outputs) {
let name = format!("{list} {}{}", v.label.name, v.label.index);
if v.value.scalar == ScalarType::Double {
need(Capabilities::FLOAT64, format!("{name} is f64"));
}
let pixel_input = stage == Stage::Pixel && list == "input";
match v.system {
Some(SystemValue::PrimitiveIndex) if pixel_input => {
need(
Capabilities::PRIMITIVE_INDEX,
format!("{name} is the primitive index"),
);
}
Some(SystemValue::SampleIndex) if pixel_input => {
need(
Capabilities::MULTISAMPLED_SHADING,
format!("{name} is the sample index"),
);
}
_ => {}
}
if pixel_input && v.interp.is_some_and(|i| i.sampling == InterpSampling::Sample) {
need(
Capabilities::MULTISAMPLED_SHADING,
format!("{name} is interpolated per sample"),
);
}
}
for r in &program.interface.resources {
if let ResourceKind::Texture {
dim: TextureDim::Cube,
arrayed: true,
..
} = r.kind
{
need(
Capabilities::CUBE_ARRAY_TEXTURES,
format!("resource {} is a cube array texture", r.provenance.name),
);
}
}
found
}
pub fn preflight(program: &Program, profile: &Profile) -> Result<(), Error> {
match requirements(program)
.into_iter()
.find(|r| !profile.capabilities.contains(r.capability))
{
Some(r) => Err(Error(format!(
"{} cannot carry this program: {} (naga capability {:?})",
profile.name, r.feature, r.capability
))),
None => Ok(()),
}
}