use std::collections::{BTreeMap, BTreeSet};
use iridia::{
EntryIo, Interface, Resource, ResourceId, ResourceKind, Stage, StageFacts, SystemValue, entry_io,
};
use crate::reflection;
use crate::{Result, error, read};
use dxcontainer::Chunk;
use dxil_reader::module::{Constant, Function, Module, Op, Value, ValueId};
use dxil_reader::reflection::{self as records, Element, Range};
pub(super) fn const_int(m: &Module, f: &Function, v: ValueId) -> Option<i64> {
let value = match v.checked_sub(f.first) {
None => m.values.get(v)?,
Some(local) => f.locals.get(local)?,
};
match value {
Value::Constant(Constant::Integer(i)) => Some(*i),
Value::Constant(Constant::Null) => Some(0),
_ => None,
}
}
pub(super) fn binding_of(m: &Module, f: &Function, bind: ValueId) -> Option<(u8, u32, u32)> {
let value = match bind.checked_sub(f.first) {
None => m.values.get(bind)?,
Some(local) => f.locals.get(local)?,
};
let field = |k: usize| match value {
Value::Constant(Constant::Null) => Some(0),
Value::Constant(Constant::Aggregate(items)) => const_int(m, f, *items.get(k)?),
_ => None,
};
Some((
u8::try_from(field(3)?).ok()?,
u32::try_from(field(0)?).ok()?,
u32::try_from(field(2)?).ok()?,
))
}
pub(super) fn ray_system_value(code: u64) -> Option<SystemValue> {
Some(match code {
141 => SystemValue::InstanceId,
142 => SystemValue::InstanceIndex,
143 => SystemValue::HitKind,
144 => SystemValue::RayFlags,
145 => SystemValue::DispatchRaysIndex,
146 => SystemValue::DispatchRaysDimensions,
147 => SystemValue::WorldRayOrigin,
148 => SystemValue::WorldRayDirection,
149 => SystemValue::ObjectRayOrigin,
150 => SystemValue::ObjectRayDirection,
153 => SystemValue::RayTMin,
154 => SystemValue::RayTCurrent,
161 => SystemValue::PrimitiveIndex,
213 => SystemValue::GeometryIndex,
_ => return None,
})
}
pub(super) fn variable_loaded(m: &Module, f: &Function, v: ValueId) -> Option<ValueId> {
let load = f
.instructions
.iter()
.find(|i| i.result.is_some_and(|(r, _)| r == v))?;
let Op::Load { pointer, .. } = &load.op else {
return None;
};
let mut at = *pointer;
loop {
let value = match at.checked_sub(f.first) {
None => m.values.get(at)?,
Some(local) => f.locals.get(local)?,
};
match value {
Value::Global { .. } if at < f.first => return Some(at),
Value::Constant(Constant::Cast { value, .. }) => at = *value,
_ => return None,
}
}
}
pub(super) fn dx_opcode(m: &Module, f: &Function, function: ValueId, args: &[ValueId]) -> Option<u64> {
if !m.name(function)?.starts_with("dx.op.") {
return None;
}
u64::try_from(const_int(m, f, *args.first()?)?).ok()
}
pub(crate) struct Setup {
pub interface: Interface,
pub io: EntryIo,
pub input_elements: Vec<Element>,
pub output_elements: Vec<Element>,
pub patch_elements: Vec<Element>,
pub sigs: Option<Vec<iridia::FunctionSig>>,
pub ranges: Vec<Range>,
pub notes: BTreeSet<String>,
pub lane_by_rank: bool,
pub heap: BTreeMap<ValueId, usize>,
}
impl Setup {
pub(super) fn new(chunks: &[Chunk<'_>], m: &Module, functions: &[&Function]) -> Result<Self> {
let named = dxil_reader::module(chunks, b"STAT").map_err(read)?;
let mut interface = reflection::reflection(chunks, m, named.as_ref())?;
if !matches!(
interface.stage,
Stage::Pixel
| Stage::Vertex
| Stage::Compute
| Stage::Hull
| Stage::Domain
| Stage::Geometry
| Stage::Mesh
) && !interface.stage.is_ray_tracing()
{
return Err(error(0, format!("{:?} programs are not lifted", interface.stage)));
}
let (input_elements, output_elements) = records::elements(m).map_err(read)?;
let patch_elements = records::patch_elements(m).map_err(read)?;
for elements in [&input_elements, &output_elements, &patch_elements] {
crate::reflection::check_elements(elements)?;
}
let program_ranges: Vec<Range> = records::ranges(m)
.map_err(read)?
.into_iter()
.map(|r| r.0)
.collect();
if program_ranges.len() != interface.resources.len() {
return Err(error(
0,
"the reflection module's resources differ from the program's",
));
}
let mut cells_in: BTreeSet<(u32, u8)> = BTreeSet::new();
let mut cells_out: BTreeSet<(u32, u8)> = BTreeSet::new();
let mut cells_patch: BTreeSet<(u32, u8)> = BTreeSet::new();
let mut system_out: BTreeSet<u8> = BTreeSet::new();
let mut used_ranges: BTreeSet<(u8, u32)> = BTreeSet::new();
let mut used_bindings: BTreeSet<(u8, u32, u32)> = BTreeSet::new();
let mut used_variables: BTreeSet<ValueId> = BTreeSet::new();
let mut system_inputs: BTreeSet<SystemValue> = BTreeSet::new();
let mut matrix_rows: BTreeSet<(SystemValue, u32)> = BTreeSet::new();
for entry in functions.iter().copied() {
for inst in &entry.instructions {
let Op::Call { function, args, .. } = &inst.op else {
continue;
};
let Some(code) = dx_opcode(m, entry, *function, args) else {
continue;
};
let konst = |n: usize| args.get(n).and_then(|&a| const_int(m, entry, a));
match code {
4 | 5 | 103 | 104 | 106 | 171 => {
let list = match code {
4 => &input_elements,
5 | 103 | 171 => &output_elements,
_ => &patch_elements,
};
let Some(id) = konst(1) else {
return Err(error(inst.at, "a signature element named by a computed id"));
};
let e = list
.iter()
.find(|e| i64::from(e.id) == id)
.ok_or_else(|| error(inst.at, format!("signature element {id}")))?;
if !e.placed {
if code == 5 || code == 171 {
system_out.insert(e.system_value);
continue;
}
return Err(error(
inst.at,
format!("input element {} is not placed in a register", e.name),
));
}
let (rows, columns) = match (konst(2), konst(3)) {
(Some(r), Some(c)) => (r..r.saturating_add(1), c..c.saturating_add(1)),
(Some(r), None) => (r..r.saturating_add(1), 0..i64::from(e.columns)),
(None, Some(c)) => (0..i64::from(e.rows), c..c.saturating_add(1)),
(None, None) => (0..i64::from(e.rows), 0..i64::from(e.columns)),
};
for r in rows {
for c in columns.clone() {
let cell = u32::try_from(r)
.ok()
.and_then(|r| e.row.checked_add(r))
.zip(u8::try_from(c).ok().and_then(|c| e.column.checked_add(c)))
.ok_or_else(|| {
error(
inst.at,
format!(
"row {r}, column {c} of element {}, which starts at row {}, column {}",
e.name, e.row, e.column
),
)
})?;
match code {
4 => cells_in.insert(cell),
5 | 103 | 171 => cells_out.insert(cell),
_ => cells_patch.insert(cell),
};
}
}
}
57 => {
if let (Some(class), Some(id)) = (konst(1), konst(2)) {
used_ranges.insert((
u8::try_from(class)
.map_err(|_| error(inst.at, format!("resource class {class}")))?,
u32::try_from(id)
.map_err(|_| error(inst.at, format!("resource range {id}")))?,
));
} else {
return Err(error(inst.at, "a handle of a computed range"));
}
}
90 => {
system_inputs.insert(SystemValue::SampleIndex);
}
93 => {
system_inputs.insert(SystemValue::DispatchThreadId);
}
94 => {
system_inputs.insert(SystemValue::GroupId);
}
95 => {
system_inputs.insert(SystemValue::GroupThreadId);
}
96 => {
system_inputs.insert(SystemValue::GroupIndex);
}
110 | 111 | 136 => {
system_inputs.insert(SystemValue::LaneIndex);
}
112 => {
system_inputs.insert(SystemValue::LaneCount);
}
151 | 152 => {
let Some(row) = konst(1).filter(|r| (0..3).contains(r)) else {
return Err(error(inst.at, "a transform read at a computed row"));
};
let system = if code == 151 {
SystemValue::ObjectToWorld
} else {
SystemValue::WorldToObject
};
matrix_rows.insert((system, super::low32(row as u64)));
}
141..=150 | 153 | 154 | 161 | 213 => {
if let Some(system) = ray_system_value(code) {
system_inputs.insert(system);
}
}
217 => {
let Some((class, lower, space)) =
binding_of(m, entry, super::argument(args, 1, inst.at)?)
else {
return Err(error(inst.at, "a handle of a binding that is not constant"));
};
used_bindings.insert((class, lower, space));
}
160 => match variable_loaded(m, entry, super::argument(args, 1, inst.at)?) {
Some(global) => {
used_variables.insert(global);
}
None => {
return Err(error(
inst.at,
"a handle of a resource that is not a global's value",
));
}
},
_ => {}
}
}
}
let settle = |v: &mut iridia::Varying, cells: &BTreeSet<(u32, u8)>, system: &BTreeSet<u8>| {
match v.provenance.d3d {
Some(d3d) if d3d.register != u32::MAX => {
v.used = (0..4u8)
.any(|lane| d3d.mask >> lane & 1 != 0 && cells.contains(&(d3d.register, lane)));
true
}
_ => match v.system {
Some(SystemValue::Depth) => system.contains(&17),
Some(SystemValue::DepthLessEqual) => system.contains(&18),
Some(SystemValue::DepthGreaterEqual) => system.contains(&19),
Some(SystemValue::Coverage) => system.contains(&14),
_ => false,
},
}
};
let none = BTreeSet::new();
interface.inputs.retain_mut(|v| settle(v, &cells_in, &none));
interface
.outputs
.retain_mut(|v| settle(v, &cells_out, &system_out));
match &mut interface.facts {
StageFacts::Hull(h) => h.patch_constants.retain_mut(|v| settle(v, &cells_patch, &none)),
StageFacts::Domain(d) => d.patch_constants.retain_mut(|v| settle(v, &cells_patch, &none)),
_ => {}
}
if interface.stage == Stage::Vertex
&& !interface
.outputs
.iter()
.any(|v| v.used && v.system == Some(SystemValue::ClipPosition))
{
interface.facts = StageFacts::VertexWithoutPosition;
}
let multi_dimensional = interface.stage == Stage::Compute
&& records::num_threads(m)
.map_err(read)?
.is_some_and(|[_, y, z]| y != 1 || z != 1);
let lane_by_rank = multi_dimensional && system_inputs.remove(&SystemValue::LaneIndex);
for system in system_inputs {
if interface.inputs.iter().any(|v| v.system == Some(system)) {
continue;
}
let (name, width) = match system {
SystemValue::SampleIndex => ("SV_SampleIndex", 1),
SystemValue::DispatchThreadId => ("SV_DispatchThreadID", 3),
SystemValue::GroupId => ("SV_GroupID", 3),
SystemValue::GroupThreadId => ("SV_GroupThreadID", 3),
SystemValue::LaneIndex => ("SV_LaneIndex", 1),
SystemValue::LaneCount => ("SV_LaneCount", 1),
SystemValue::DispatchRaysIndex => ("SV_DispatchRaysIndex", 3),
SystemValue::DispatchRaysDimensions => ("SV_DispatchRaysDimensions", 3),
SystemValue::WorldRayOrigin => ("SV_WorldRayOrigin", 3),
SystemValue::WorldRayDirection => ("SV_WorldRayDirection", 3),
SystemValue::ObjectRayOrigin => ("SV_ObjectRayOrigin", 3),
SystemValue::ObjectRayDirection => ("SV_ObjectRayDirection", 3),
SystemValue::RayTMin => ("SV_RayTMin", 1),
SystemValue::RayTCurrent => ("SV_RayTCurrent", 1),
SystemValue::RayFlags => ("SV_RayFlags", 1),
SystemValue::InstanceId => ("SV_InstanceID", 1),
SystemValue::InstanceIndex => ("SV_InstanceIndex", 1),
SystemValue::PrimitiveIndex => ("SV_PrimitiveIndex", 1),
SystemValue::GeometryIndex => ("SV_GeometryIndex", 1),
SystemValue::HitKind => ("SV_HitKind", 1),
_ => ("SV_GroupIndex", 1),
};
let float = matches!(
system,
SystemValue::WorldRayOrigin
| SystemValue::WorldRayDirection
| SystemValue::ObjectRayOrigin
| SystemValue::ObjectRayDirection
| SystemValue::RayTMin
| SystemValue::RayTCurrent
);
interface.inputs.push(iridia::Varying {
label: iridia::Label {
name: name.into(),
index: 0,
},
system: Some(system),
value: iridia::Value {
scalar: if float {
iridia::ScalarType::Float
} else {
iridia::ScalarType::Uint
},
width,
},
interp: None,
used: true,
provenance: iridia::VaryingProvenance::default(),
});
}
for (system, row) in matrix_rows {
interface.inputs.push(iridia::Varying {
label: iridia::Label {
name: if system == SystemValue::ObjectToWorld {
"SV_ObjectToWorld"
} else {
"SV_WorldToObject"
}
.into(),
index: row,
},
system: Some(system),
value: iridia::Value {
scalar: iridia::ScalarType::Float,
width: 4,
},
interp: None,
used: true,
provenance: iridia::VaryingProvenance::default(),
});
}
let mut resources = Vec::new();
let mut ranges = Vec::new();
for (original, range) in interface.resources.drain(..).zip(program_ranges) {
if !used_ranges.contains(&(range.class, range.id))
&& !used_bindings.contains(&(range.class, range.lower, range.space))
&& !range.variable.is_some_and(|g| used_variables.contains(&g))
{
continue;
}
if original.is_array()
&& !matches!(
original.kind,
ResourceKind::Texture { .. }
| ResourceKind::ConstantBuffer { .. }
| ResourceKind::Sampler { .. }
| ResourceKind::Buffer(_)
| ResourceKind::Storage { .. }
)
{
return Err(error(
0,
format!(
"{}: arrays of resources other than textures, samplers, buffers and storage ({}, a range of {}) are not lifted",
original.provenance.name,
match &original.kind {
ResourceKind::ConstantBuffer { .. } => "constant buffers",
ResourceKind::AccelerationStructure => "acceleration structures",
_ => "resources",
},
original.count
),
));
}
resources.push(Resource {
id: ResourceId(
u32::try_from(resources.len()).map_err(|_| error(0, "more than 4294967295 resources"))?,
),
..original
});
ranges.push(range);
}
let heap = heap_resources(m, functions, &mut resources, &mut ranges)?;
interface.resources = resources;
let sigs = iridia::stage_functions(&interface).map_err(|detail| error(0, detail))?;
let io = match sigs {
Some(_) => EntryIo {
arguments: Vec::new(),
results: Vec::new(),
},
None => entry_io(&interface).map_err(|detail| error(0, detail))?,
};
Ok(Setup {
interface,
io,
sigs,
input_elements,
output_elements,
patch_elements,
ranges,
notes: BTreeSet::new(),
lane_by_rank,
heap,
})
}
pub(super) fn input_cell(&self, register: u32, lane: u8) -> Option<(usize, u8)> {
cell(&self.interface.inputs, register, lane)
}
pub(super) fn output_cell(&self, register: u32, lane: u8) -> Option<(usize, u8)> {
cell(&self.interface.outputs, register, lane)
}
pub(super) fn resource_by_binding(&self, class: u8, lower: u32, space: u32) -> Option<usize> {
self.ranges
.iter()
.position(|r| r.class == class && r.lower == lower && r.space == space)
}
pub(super) fn resource(&self, class: u8, id: u32) -> Option<usize> {
self.ranges.iter().position(|r| r.class == class && r.id == id)
}
pub(super) fn resource_of_variable(&self, global: ValueId) -> Option<usize> {
self.ranges.iter().position(|r| r.variable == Some(global))
}
pub(super) fn kind(&self, resource: usize) -> &ResourceKind {
self.interface
.resources
.get(resource)
.map_or(&ResourceKind::AccelerationStructure, |r| &r.kind)
}
}
const HEAP_SPACE: u32 = 0x8000;
fn heap_resources(
m: &Module,
functions: &[&Function],
resources: &mut Vec<Resource>,
ranges: &mut Vec<Range>,
) -> Result<BTreeMap<ValueId, usize>> {
let mut made: BTreeMap<(u8, i64, i64), usize> = BTreeMap::new();
let mut handles = BTreeMap::new();
for f in functions.iter().copied() {
for inst in &f.instructions {
let (Op::Call { function, args, .. }, Some((handle, _))) = (&inst.op, inst.result) else {
continue;
};
if dx_opcode(m, f, *function, args) != Some(218) {
continue;
}
let sampler = const_int(m, f, super::argument(args, 2, inst.at)?) == Some(1);
let properties = functions.iter().copied().find_map(|g| {
g.instructions.iter().find_map(|i| {
let Op::Call { function, args, .. } = &i.op else {
return None;
};
if dx_opcode(m, g, *function, args) != Some(216) || args.get(1) != Some(&handle) {
return None;
}
let annotation = *args.get(2)?;
let value = match annotation.checked_sub(g.first) {
None => m.values.get(annotation)?,
Some(local) => g.locals.get(local)?,
};
match value {
Value::Constant(Constant::Aggregate(items)) => Some((
const_int(m, g, *items.first()?)?,
const_int(m, g, *items.get(1)?)?,
)),
Value::Constant(Constant::Null) => Some((0, 0)),
_ => None,
}
})
});
let Some((first, second)) = properties else {
return Err(error(
inst.at,
"a descriptor from a heap that the program does not annotate with its properties",
));
};
let kind = first & 0xff;
let class = match (sampler || kind == 14, kind) {
(true, _) => 3,
(_, 13) => 2,
_ if first & 0x1000 != 0 => 1,
_ => 0,
};
let index = match made.get(&(class, first, second)) {
Some(&i) => i,
None => {
let n = u32::try_from(made.len())
.map_err(|_| error(inst.at, "more than 4294967295 heap descriptors"))?;
let range = Range {
class,
id: u32::MAX.saturating_sub(n),
lower: n,
count: Resource::UNBOUNDED,
extra: match class {
3 => u32::from(first & 0x8000 != 0),
2 => {
u32::try_from(second).map_err(|_| error(inst.at, "a constant buffer size"))?
}
_ => super::low32(kind as u64),
},
space: HEAP_SPACE,
element: (class < 2 && kind != 11 && kind != 12)
.then_some(super::low32(second as u64 & 0xff)),
stride: (kind == 12).then_some(super::low32(second as u64)),
counter: false,
variable: None,
};
let id = ResourceId(
u32::try_from(resources.len())
.map_err(|_| error(inst.at, "more than 4294967295 resources"))?,
);
let name = if class == 3 {
"SamplerDescriptorHeap"
} else {
"ResourceDescriptorHeap"
};
let mut resource = reflection::resource_of(id, &range, name.into(), None, m)
.map_err(|e| error(inst.at, e.to_string()))?;
resource.provenance.d3d = None;
resources.push(resource);
ranges.push(range);
let index = resources.len().saturating_sub(1);
made.insert((class, first, second), index);
index
}
};
handles.insert(handle, index);
}
}
Ok(handles)
}
fn cell(list: &[iridia::Varying], register: u32, lane: u8) -> Option<(usize, u8)> {
list.iter().enumerate().find_map(|(i, v)| {
let d3d = v.provenance.d3d?;
(d3d.register == register)
.then(|| super::stages::lanes_below(d3d.mask, lane))
.flatten()
.map(|position| (i, position))
})
}