use std::collections::BTreeMap;
use iridia::SystemValue;
use iridia::build::{Kind, Val, Width};
use naga::{AddressSpace, Expression as E, FunctionArgument, Handle, Statement, TypeInner};
use super::setup::{const_int, ray_system_value};
use super::{Lift, Lv};
use crate::Result;
use dxil_reader::module::{Instruction, Type, TypeId, ValueId};
#[derive(Default)]
pub(super) struct Operations {
made: BTreeMap<(&'static str, Option<Handle<naga::Type>>), Handle<naga::Function>>,
}
impl Lift<'_> {
fn pointee(&self, v: ValueId) -> Result<TypeId> {
let ty = match v.checked_sub(self.f.first) {
None => self.m.value_types.get(v).copied(),
Some(local) => self.f.local_types.get(local).copied(),
};
match ty.and_then(|t| self.m.types.get(t)) {
Some(Type::Pointer(t, _)) => Ok(*t),
_ => self.refuse("a ray tracing operation on something that is not a pointer to a structure"),
}
}
fn operation(
&mut self,
name: &'static str,
structure: Option<Handle<naga::Type>>,
arguments: impl FnOnce(&mut Self) -> Vec<(&'static str, Handle<naga::Type>)>,
returns_bool: bool,
) -> Handle<naga::Function> {
if let Some(&f) = self.operations.made.get(&(name, structure)) {
return f;
}
let arguments = arguments(self)
.into_iter()
.map(|(name, ty)| FunctionArgument {
name: Some(name.to_owned()),
ty,
binding: None,
})
.collect();
let f = self.b.declare_operation(name, arguments, returns_bool);
self.operations.made.insert((name, structure), f);
f
}
fn pointer_type(&mut self, structure: Handle<naga::Type>) -> Handle<naga::Type> {
self.b.ty(TypeInner::Pointer {
base: structure,
space: AddressSpace::Function,
})
}
fn memory_pointer(&mut self, v: ValueId) -> Result<(Handle<E>, Handle<naga::Type>)> {
let structure = self.pointee(v)?;
let (structure, _) = self.naga_type(structure)?;
let Lv::Pointer(p) = self.get(v)? else {
return self.refuse("a ray tracing operation on something that is not a pointer");
};
Ok((self.pointer_expr(&p)?, structure))
}
fn float3(&mut self, args: &[ValueId], first: usize) -> Result<Val> {
let parts = [
self.float(self.arg(args, first)?)?,
self.float(self.arg(args, first.saturating_add(1))?)?,
self.float(self.arg(args, first.saturating_add(2))?)?,
];
Ok(self.b.compose(&parts))
}
fn call_operation(
&mut self,
function: Handle<naga::Function>,
arguments: Vec<Handle<E>>,
result: Option<Handle<E>>,
) {
self.b.push(Statement::Call {
function,
arguments,
result,
});
}
pub(super) fn ray_op(&mut self, inst: &Instruction, code: u64, args: &[ValueId]) -> Result<()> {
let result = inst.result;
match code {
151 | 152 => {
let (Some(row), Some(column)) = (
const_int(self.m, self.f, self.arg(args, 1)?),
const_int(self.m, self.f, self.arg(args, 2)?),
) else {
return self.refuse("a transform read at a computed row or column");
};
if !(0..3).contains(&row) || !(0..4).contains(&column) {
return self.refuse("a transform read outside its 3 by 4");
}
let system = if code == 151 {
SystemValue::ObjectToWorld
} else {
SystemValue::WorldToObject
};
let rows = self.system_input_row(system, super::low32(row as u64))?;
let lane = self.b.lane(rows, super::lane_u8(column));
self.define_result(result, Lv::Scalar(lane))
}
155 | 156 => {
let name = if code == 155 {
iridia::rt::IGNORE_HIT
} else {
iridia::rt::ACCEPT_HIT_AND_END_SEARCH
};
let f = self.operation(name, None, |_| Vec::new(), false);
self.call_operation(f, Vec::new(), None);
self.b.push(Statement::Return { value: None });
Ok(())
}
157 => {
let (_, structure) = self.handle_expr(self.arg(args, 1)?, 0)?;
let acceleration = structure;
let (payload, payload_type) = self.memory_pointer(self.arg(args, 15)?)?;
let flags = self.uint(self.arg(args, 2)?)?;
let mask = self.uint(self.arg(args, 3)?)?;
let offset = self.uint(self.arg(args, 4)?)?;
let stride = self.uint(self.arg(args, 5)?)?;
let miss = self.uint(self.arg(args, 6)?)?;
let origin = self.float3(args, 7)?;
let tmin = self.float(self.arg(args, 10)?)?;
let direction = self.float3(args, 11)?;
let tmax = self.float(self.arg(args, 14)?)?;
let f = self.operation(
iridia::rt::TRACE_RAY,
Some(payload_type),
|lift| {
let accel = lift
.b
.ty(TypeInner::AccelerationStructure { vertex_return: false });
let u = lift.b.ty_of(Kind::U, Width::One);
let f = lift.b.ty_of(Kind::F, Width::One);
let v3 = lift.b.ty_of(Kind::F, Width::Three);
let pointer = lift.pointer_type(payload_type);
vec![
("acceleration_structure", accel),
("ray_flags", u),
("instance_mask", u),
("hit_group_offset", u),
("hit_group_stride", u),
("miss_index", u),
("origin", v3),
("t_min", f),
("direction", v3),
("t_max", f),
("payload", pointer),
]
},
false,
);
self.call_operation(
f,
vec![
acceleration,
flags.expr,
mask.expr,
offset.expr,
stride.expr,
miss.expr,
origin.expr,
tmin.expr,
direction.expr,
tmax.expr,
payload,
],
None,
);
self.notes.insert(
"a ray tracing program calls the pipeline's operations (trace_ray, report_hit, call_shader, \
ignore_hit, accept_hit_and_end_search) as functions without a body: it is read, not run"
.into(),
);
Ok(())
}
158 => {
let (attributes, attribute_type) = self.memory_pointer(self.arg(args, 3)?)?;
let t = self.float(self.arg(args, 1)?)?;
let kind = self.uint(self.arg(args, 2)?)?;
let f = self.operation(
iridia::rt::REPORT_HIT,
Some(attribute_type),
|lift| {
let f = lift.b.ty_of(Kind::F, Width::One);
let u = lift.b.ty_of(Kind::U, Width::One);
let pointer = lift.pointer_type(attribute_type);
vec![("t", f), ("hit_kind", u), ("attributes", pointer)]
},
true,
);
let accepted = self.b.result_expr(E::CallResult(f));
self.call_operation(f, vec![t.expr, kind.expr, attributes], Some(accepted));
let accepted = Val {
expr: accepted,
kind: Kind::B,
width: Width::One,
};
self.define_result(result, Lv::Scalar(accepted))
}
159 => {
let (parameter, parameter_type) = self.memory_pointer(self.arg(args, 2)?)?;
let index = self.uint(self.arg(args, 1)?)?;
let f = self.operation(
iridia::rt::CALL_SHADER,
Some(parameter_type),
|lift| {
let u = lift.b.ty_of(Kind::U, Width::One);
let pointer = lift.pointer_type(parameter_type);
vec![("shader_index", u), ("parameter", pointer)]
},
false,
);
self.call_operation(f, vec![index.expr, parameter], None);
Ok(())
}
_ => {
let Some(system) = ray_system_value(code) else {
return self.refuse(format!("ray tracing operation {code}"));
};
let value = self.system_input(system)?;
let value = if value.width > Width::One {
let Some(component) =
const_int(self.m, self.f, self.arg(args, 1)?).filter(|c| (0..3).contains(c))
else {
return self.refuse("a ray value read at a computed component");
};
self.b.lane(value, super::lane_u8(component))
} else {
value
};
let ty = self.result_type(result)?;
let kind = self.kind_of_type(ty)?;
let r = self.b.bitcast_to(value, kind);
self.define_result(result, Lv::Scalar(r))
}
}
}
}