use iridia::build::{Builder, Kind, Val, Width};
use iridia::{IoType, ScalarType, Stage, SystemValue};
use naga::{Expression as E, Handle, Statement, TypeInner};
use super::resources::{Resources, lanes_of};
use super::setup::Setup;
use super::{Frame, Lift};
use crate::Result;
use dxbc_reader::decode::{Declared, Decoded, Register};
fn kind_of(s: ScalarType) -> Kind {
Kind::of(s)
}
impl<'a> Lift<'a> {
pub(super) fn new(decoded: &'a Decoded, stage: Stage, setup: Setup) -> Result<Self> {
let mut lift = Lift {
b: Builder::new(),
decoded,
stage,
temps: Vec::new(),
inputs: Default::default(),
outputs: Default::default(),
indexable: Default::default(),
ids: Default::default(),
sig: None,
control_points: None,
output_points: None,
patch_in: None,
domain_point: None,
point_id: None,
fork_id: None,
join_id: None,
gs: None,
gs_instance: None,
in_phase: false,
pending_decl: None,
primitive_id: None,
input_coverage: None,
depth: None,
coverage: None,
resources: Resources::default(),
frames: Vec::<Frame>::new(),
notes: setup.notes.clone(),
word: 0,
returned: false,
arguments: Vec::new(),
result_type: None,
setup,
};
lift.declare_resources()?;
if lift.setup.sigs.is_none() {
lift.declare_locals();
}
Ok(lift)
}
pub(super) fn declare_locals(&mut self) {
let decoded = self.decoded;
let lift = self;
lift.temps.clear();
lift.inputs.clear();
lift.outputs.clear();
lift.indexable.clear();
lift.ids.clear();
lift.primitive_id = None;
lift.control_points = None;
lift.output_points = None;
lift.patch_in = None;
lift.domain_point = None;
lift.point_id = None;
lift.fork_id = None;
lift.join_id = None;
lift.gs = None;
lift.gs_instance = None;
lift.input_coverage = None;
lift.depth = None;
lift.coverage = None;
let u4 = lift.u4();
for n in 0..lift.setup.temps {
let local = lift.b.local(&format!("r{n}"), u4);
lift.temps.push(local);
}
for (register, size) in lift.setup.indexable.clone() {
let Some(len) = std::num::NonZeroU32::new(size) else {
continue;
};
let ty = lift.b.ty(TypeInner::Array {
base: u4,
size: naga::ArraySize::Constant(len),
stride: 16,
});
let local = lift.b.local(&format!("x{register}"), ty);
lift.indexable.insert(register, (local, size));
}
for register in lift.setup.declared_inputs.keys().copied().collect::<Vec<_>>() {
let local = lift.b.local(&format!("v{register}"), u4);
lift.inputs.insert(register, local);
}
for d in &decoded.declarations {
if let Declared::Input { operand, .. } = &d.kind {
match operand.register {
Register::PrimitiveId if lift.primitive_id.is_none() => {
lift.primitive_id = Some(lift.b.local("vPrim", u4));
}
Register::InputCoverage if lift.input_coverage.is_none() => {
lift.input_coverage = Some(lift.b.local("vCoverage", u4));
}
Register::ThreadId
| Register::ThreadGroupId
| Register::ThreadIdInGroup
| Register::ThreadIdInGroupFlattened => {
let (system, name) = match operand.register {
Register::ThreadId => (SystemValue::DispatchThreadId, "vThreadID"),
Register::ThreadGroupId => (SystemValue::GroupId, "vThreadGroupID"),
Register::ThreadIdInGroup => (SystemValue::GroupThreadId, "vThreadIDInGroup"),
_ => (SystemValue::GroupIndex, "vThreadIDInGroupFlattened"),
};
lift.ids.entry(system).or_insert_with(|| lift.b.local(name, u4));
}
_ => {}
}
}
if let Declared::Output { operand, .. } = &d.kind {
match operand.register {
Register::Output => {
let register = operand.indices.first().map_or(0, |i| i.base);
if !lift.outputs.contains_key(®ister) {
let local = lift.b.local(&format!("o{register}"), u4);
lift.outputs.insert(register, local);
}
}
Register::OutputDepth
| Register::OutputDepthGreaterEqual
| Register::OutputDepthLessEqual
if lift.depth.is_none() =>
{
lift.depth = Some(lift.b.local("oDepth", u4));
}
Register::OutputCoverage if lift.coverage.is_none() => {
lift.coverage = Some(lift.b.local("oMask", u4));
}
_ => {}
}
}
}
for (output, first, count) in lift.setup.index_ranges.clone() {
for r in first..first.saturating_add(count) {
if output && !lift.outputs.contains_key(&r) {
let local = lift.b.local(&format!("o{r}"), u4);
lift.outputs.insert(r, local);
}
}
}
for v in &lift.result_varyings() {
if let Some(d3d) = v.provenance.d3d
&& d3d.register != u32::MAX
&& !lift.outputs.contains_key(&d3d.register)
{
let local = lift.b.local(&format!("o{}", d3d.register), u4);
lift.outputs.insert(d3d.register, local);
}
}
}
pub(super) fn prologue(&mut self) -> Result<()> {
let io = self.setup.io.clone();
let declared = match self.b.declare_entry(&io) {
Ok(d) => d,
Err(detail) => return self.refuse(detail),
};
self.arguments = declared.arguments;
self.result_type = declared.result;
for (i, item) in io.arguments.iter().enumerate() {
let IoType::Value(v) = item.ty else {
return self.refuse("an array-typed input");
};
let Ok(argument) = u32::try_from(i) else {
return self.refuse("more than 4294967295 inputs");
};
let expr = self.b.app(E::FunctionArgument(argument));
let mut value = Val {
expr,
kind: kind_of(v.scalar),
width: match Width::try_from(v.width) {
Ok(width) => width,
Err(e) => return self.refuse(format!("an input of {e}")),
},
};
let Some(varying) = item
.varyings
.first()
.and_then(|&v| self.setup.interface.inputs.get(v))
.cloned()
else {
return self.refuse(format!("the input {} names no varying", item.name));
};
if value.kind == Kind::B {
let ones = self.b.lit_u(u32::MAX);
let zero = self.b.lit_u(0);
value = self.b.select(value, ones, zero);
} else {
value = self.b.bitcast(value, Kind::U);
}
if let Some(d3d) = varying.provenance.d3d {
let declared = self
.setup
.declared_inputs
.get(&d3d.register)
.copied()
.unwrap_or(0);
if declared & d3d.mask != 0 {
let Some(&local) = self.inputs.get(&d3d.register) else {
return self.refuse(format!("input register v{} has no local", d3d.register));
};
let pointer = self.b.local_ptr(local);
for (j, lane) in lanes_of(d3d.mask).into_iter().enumerate() {
let component = self.b.lane(value, super::operands::lane_u8(j));
let target = self.b.app(E::AccessIndex {
base: pointer,
index: u32::from(lane),
});
self.b.store(target, component);
}
}
}
let special = match varying.system {
Some(SystemValue::PrimitiveIndex) => self.primitive_id,
Some(SystemValue::Coverage) => self.input_coverage,
Some(
SystemValue::DispatchThreadId
| SystemValue::GroupId
| SystemValue::GroupThreadId
| SystemValue::GroupIndex,
) => {
let system = varying.system.expect("matched");
let Some(&local) = self.ids.get(&system) else {
continue;
};
let pointer = self.b.local_ptr(local);
for lane in value.width.lanes() {
let component = self.b.lane(value, lane);
let target = self.b.app(E::AccessIndex {
base: pointer,
index: u32::from(lane),
});
self.b.store(target, component);
}
None
}
_ => None,
};
if let Some(local) = special {
let pointer = self.b.local_ptr(local);
let target = self.b.app(E::AccessIndex {
base: pointer,
index: 0,
});
self.b.store(target, value);
}
}
Ok(())
}
pub(super) fn epilogue(&mut self) -> Result<()> {
if self.sig.is_some() {
return self.function_epilogue();
}
let Some(result) = self.result_type else {
self.b.push(Statement::Return { value: None });
return Ok(());
};
let io = self.setup.io.clone();
let mut parts = Vec::new();
for item in &io.results {
let part = match item.ty {
IoType::Array(..) | IoType::Memory => return self.refuse("an array-typed output"),
IoType::ClipDistances(_) => {
let mut components = Vec::new();
for &i in &item.varyings {
let Some(v) = self.setup.interface.outputs.get(i).cloned() else {
return self.refuse("a clip distance past the interface's outputs");
};
let Some(d3d) = v.provenance.d3d else {
return self.refuse(format!("clip distance {} has no register", item.name));
};
let Some(&local) = self.outputs.get(&d3d.register) else {
return self.refuse(format!(
"clip distance {} has no register o{}",
item.name, d3d.register
));
};
let p = self.b.local_ptr(local);
let row = self.b.load(p, Kind::U, Width::Four);
for lane in lanes_of(d3d.mask) {
let c = self.b.lane(row, lane);
components.push(self.b.bitcast(c, Kind::F).expr);
}
}
let ty = self.array_type(&item.ty);
self.b.app(E::Compose { ty, components })
}
IoType::Value(carried) => {
let Some(v) = item
.varyings
.first()
.and_then(|&v| self.setup.interface.outputs.get(v))
.cloned()
else {
return self.refuse(format!("the output {} names no varying", item.name));
};
let (local, lanes): (Handle<naga::LocalVariable>, Vec<u8>) = match v.system {
Some(
SystemValue::Depth | SystemValue::DepthGreaterEqual | SystemValue::DepthLessEqual,
) => (self.depth.expect("a depth output has its local"), vec![0]),
Some(SystemValue::Coverage) => {
(self.coverage.expect("a coverage output has its local"), vec![0])
}
Some(SystemValue::ClipPosition | SystemValue::Target(_)) => {
let d3d = v.provenance.d3d.expect("a signature element has a register");
match self.outputs.get(&d3d.register) {
Some(&l) => (l, vec![0, 1, 2, 3]),
None => {
return self.refuse(format!(
"output register o{} is not declared",
d3d.register
));
}
}
}
_ => {
let d3d = v.provenance.d3d.expect("a signature element has a register");
match self.outputs.get(&d3d.register) {
Some(&l) => (l, lanes_of(d3d.mask)),
None => {
return self.refuse(format!(
"output register o{} is not declared",
d3d.register
));
}
}
}
};
let p = self.b.local_ptr(local);
let row = self.b.load(p, Kind::U, Width::Four);
let picked = self.b.swizzle(row, &lanes);
let value = self.b.bitcast(picked, kind_of(carried.scalar));
debug_assert_eq!(value.width.get(), carried.width);
value.expr
}
};
parts.push(part);
}
let composed = self.b.app(E::Compose {
ty: result,
components: parts,
});
self.b.push(Statement::Return {
value: Some(composed),
});
Ok(())
}
fn array_type(&mut self, ty: &IoType) -> Handle<naga::Type> {
let IoType::ClipDistances(n) = ty else {
unreachable!("an array type")
};
let f = self.b.ty_of(Kind::F, Width::One);
self.b.ty(TypeInner::Array {
base: f,
size: naga::ArraySize::Constant(std::num::NonZeroU32::new(*n).expect("clip distances")),
stride: 4,
})
}
}