use std::collections::HashMap;
use crate::Result;
use crate::module::{Constant, Function, Module, Op, Value, ValueId};
use crate::reflection::ranges;
const CREATE_HANDLE: i64 = 57;
const ANNOTATE_HANDLE: i64 = 216;
const CREATE_HANDLE_FROM_BINDING: i64 = 217;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Handle {
pub class: u8,
pub space: u32,
pub register: Option<u32>,
pub range: Option<u32>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TextureRead {
pub function: ValueId,
pub instruction: usize,
pub opcode: u16,
pub texture: Option<Handle>,
pub sampled: bool,
pub sampler: Option<Handle>,
pub comparison: bool,
}
fn kind(opcode: i64) -> Option<(bool, bool)> {
Some(match opcode {
60..=63 | 73 | 81 => (true, false),
64 | 65 | 74 => (true, true),
66 | 68 | 139 => (false, false),
_ => return None,
})
}
fn value<'a>(m: &'a Module, f: &'a Function, id: ValueId) -> Option<&'a Value> {
match id.checked_sub(f.first) {
None => m.values.get(id),
Some(local) => f.locals.get(local),
}
}
fn integer(m: &Module, f: &Function, id: ValueId) -> Option<i64> {
match value(m, f, id)? {
Value::Constant(Constant::Integer(i)) => Some(*i),
Value::Constant(Constant::Null) => Some(0),
_ => None,
}
}
fn number(m: &Module, f: &Function, id: ValueId) -> Option<u32> {
integer(m, f, id).and_then(|i| u32::try_from(i).ok())
}
fn handle(
m: &Module,
f: &Function,
calls: &HashMap<ValueId, usize>,
spaces: &HashMap<(u8, u32), u32>,
id: ValueId,
) -> Option<Handle> {
let mut id = id;
for _ in 0..8 {
let Op::Call { args, .. } = &f.instructions.get(*calls.get(&id)?)?.op else {
return None;
};
match integer(m, f, *args.first()?)? {
ANNOTATE_HANDLE => id = *args.get(1)?,
CREATE_HANDLE => {
let class = u8::try_from(integer(m, f, *args.get(1)?)?).ok()?;
let range = number(m, f, *args.get(2)?)?;
return Some(Handle {
class,
space: *spaces.get(&(class, range))?,
register: number(m, f, *args.get(3)?),
range: Some(range),
});
}
CREATE_HANDLE_FROM_BINDING => {
let Value::Constant(Constant::Aggregate(fields)) = value(m, f, *args.get(1)?)? else {
return None;
};
return Some(Handle {
class: u8::try_from(integer(m, f, *fields.get(3)?)?).ok()?,
space: number(m, f, *fields.get(2)?)?,
register: number(m, f, *args.get(2)?),
range: None,
});
}
_ => return None,
}
}
None
}
pub fn texture_reads(m: &Module) -> Result<Vec<TextureRead>> {
let spaces: HashMap<(u8, u32), u32> = ranges(m)?
.into_iter()
.map(|(range, _, _)| ((range.class, range.id), range.space))
.collect();
let mut operations: HashMap<ValueId, bool> = HashMap::new();
let mut reads = Vec::new();
for f in &m.functions {
let calls: HashMap<ValueId, usize> = f
.instructions
.iter()
.enumerate()
.filter_map(|(at, i)| i.result.map(|(id, _)| (id, at)))
.collect();
for (at, instruction) in f.instructions.iter().enumerate() {
let Op::Call { function, args, .. } = &instruction.op else {
continue;
};
let is_operation = *operations
.entry(*function)
.or_insert_with(|| m.name(*function).is_some_and(|n| n.starts_with("dx.op.")));
if !is_operation {
continue;
}
let Some(opcode) = args.first().and_then(|a| integer(m, f, *a)) else {
continue;
};
let Some((sampled, comparison)) = kind(opcode) else {
continue;
};
let Ok(opcode) = u16::try_from(opcode) else {
continue;
};
let resolve =
|position: usize| args.get(position).and_then(|a| handle(m, f, &calls, &spaces, *a));
reads.push(TextureRead {
function: f.value,
instruction: at,
opcode,
texture: resolve(1),
sampled,
sampler: if sampled { resolve(2) } else { None },
comparison,
});
}
}
Ok(reads)
}