#![allow(clippy::unwrap_used)]
use iridia::flow::{At, Node, Values};
use iridia::{D3dClass, Program};
use iridia_exec::value::{Evaluated, Evaluator};
use iridia_exec::{
Input, Invocation, Layout, PixelShader, ProgramShader, View, Views, evaluate::Constants, evaluate::Grid,
evaluate_raw,
};
use std::collections::BTreeMap;
fn programs() -> Vec<(&'static str, Program)> {
vec![
(
"DXBC",
iridia_dxbc::lift(include_bytes!("fixtures/value.dxbc")).unwrap(),
),
(
"DXIL",
iridia_dxil::lift(include_bytes!("fixtures/value.dxil")).unwrap(),
),
]
}
const TEXEL: [f32; 4] = [0.25, 0.5, 1.5, 1.];
struct Case {
normal: [f32; 4],
uv: [f32; 2],
buffers: BTreeMap<u32, Vec<u32>>,
}
fn case(normal: [f32; 4], slot: f32) -> Case {
let palette: Vec<u32> = (0..26u32)
.flat_map(|r| {
let r = r as f32;
[r + 0.5, -(r + 0.25), 2. * r, 1.]
})
.map(f32::to_bits)
.collect();
let material: Vec<u32> = [0., 0., 0., slot, 2., -1., 0.5, 0.75]
.into_iter()
.map(f32::to_bits)
.collect();
Case {
normal,
uv: [0.3, 0.6],
buffers: BTreeMap::from([(4, palette), (1, material)]),
}
}
fn cases() -> Vec<Case> {
let mut out = Vec::new();
for w in [0., 0.4, 0.5, 0.6, 1.4, 1.5, 2.5, -1., 1e10, f32::NAN] {
for slot in [0., 2., 3., 4.] {
out.push(case([0.25, 0.75, 1.5, w], slot));
}
}
out.push(case([-0.5, 0.25, -3., 0.], 1.));
out.push(case([0.5, -0.25, 3., 0.], 1.));
out
}
fn run(program: &Program, c: &Case) -> [u32; 4] {
let shader = ProgramShader::new(program.clone()).unwrap();
let layout = Layout::of(&shader).unwrap();
let mut constants = Constants::new(&layout);
for (slot, words) in &c.buffers {
let size = layout.buffers.iter().find(|b| b.0 == *slot).map_or(0, |b| b.1) as usize;
constants
.set_words(*slot, 0, &words[..size.min(words.len())])
.unwrap();
}
let textures = Views::refusing(BTreeMap::from([(0, View::Constant(TEXEL))]));
let inputs = BTreeMap::from([
(("NORMAL".into(), 0), Input::Constant(c.normal)),
(
("TEXCOORD".into(), 0),
Input::Constant([c.uv[0], c.uv[1], 0., 0.]),
),
]);
let target = shader
.interface()
.outputs
.iter()
.find(|o| o.label.name == "SV_Target")
.map(|o| o.provenance.d3d.unwrap().register)
.unwrap();
let raw = evaluate_raw(
&shader,
&constants,
&textures,
&inputs,
Grid {
size: 1,
period: [1., 1.],
},
&[(target, 15)],
Invocation::Scalar,
)
.unwrap();
raw.pixels[0][0].0
}
fn leaf<'a>(
program: &'a Program,
c: &'a Case,
texels: bool,
) -> impl FnMut(iridia::flow::NodeId, &Node, &[u32]) -> Option<u32> + 'a {
move |_, node, at| match node {
Node::Input { input, lane } => {
let label = &program.interface.inputs[*input].label;
let v = match (label.name.to_ascii_uppercase().as_str(), label.index) {
("NORMAL", 0) => c.normal,
("TEXCOORD", 0) => [c.uv[0], c.uv[1], 0., 0.],
_ => return None,
};
Some(v[*lane as usize].to_bits())
}
Node::Global { resource, at: place } => {
let r = program.interface.resource((*resource)?)?;
let d3d = r.provenance.d3d.as_ref()?;
if d3d.class != D3dClass::ConstantBuffer {
return None;
}
let words = c.buffers.get(&d3d.register)?;
let word = match place {
At::Component(w) => *w as usize,
At::Indexed { lane, .. } => match at {
[row] => (*row as usize).checked_mul(4)?.checked_add(*lane as usize)?,
_ => return None,
},
};
Some(words.get(word).copied().unwrap_or(0))
}
Node::Texel { lane, .. } if texels => Some(TEXEL[*lane as usize].to_bits()),
_ => None,
}
}
#[test]
fn each_lane_evaluated_is_the_runs_word_bit_for_bit() {
for (name, program) in programs() {
let values = Values::of_program(&program).unwrap();
let target = program
.interface
.outputs
.iter()
.position(|o| o.label.name == "SV_Target")
.unwrap();
for (i, c) in cases().iter().enumerate() {
let ran = run(&program, c);
let mut e = Evaluator::new(&values, program.modes.flush_denormals, leaf(&program, c, true));
for lane in 0..4u8 {
let node = values.output(target, lane).unwrap();
assert_eq!(
e.evaluate(node).unwrap(),
Evaluated::Word(ran[lane as usize]),
"{name} case {i} lane {lane}: normal {:?}",
c.normal
);
}
}
}
}
#[test]
fn a_lane_that_needs_an_unanswered_texel_is_unknown_and_names_it() {
for (name, program) in programs() {
let values = Values::of_program(&program).unwrap();
let target = program
.interface
.outputs
.iter()
.position(|o| o.label.name == "SV_Target")
.unwrap();
let c = case([0.25, 0.75, 1.5, 0.], 0.);
let ran = run(&program, &c);
let mut e = Evaluator::new(&values, program.modes.flush_denormals, leaf(&program, &c, false));
for lane in 0..3u8 {
match e.evaluate(values.output(target, lane).unwrap()).unwrap() {
Evaluated::Unknown(id) => {
assert!(
matches!(values.node(id), Node::Texel { .. }),
"{name} lane {lane}"
)
}
other => panic!("{name} lane {lane}: {other:?}"),
}
}
assert_eq!(
e.evaluate(values.output(target, 3).unwrap()).unwrap(),
Evaluated::Word(ran[3]),
"{name}"
);
}
}