1use std::collections::HashMap;
5
6use crate::Result;
7use crate::module::{Constant, Function, Module, Op, Value, ValueId};
8use crate::reflection::ranges;
9
10const CREATE_HANDLE: i64 = 57;
11const ANNOTATE_HANDLE: i64 = 216;
12const CREATE_HANDLE_FROM_BINDING: i64 = 217;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct Handle {
17 pub class: u8,
19 pub space: u32,
21 pub register: Option<u32>,
24 pub range: Option<u32>,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct TextureRead {
32 pub function: ValueId,
34 pub instruction: usize,
36 pub opcode: u16,
40 pub texture: Option<Handle>,
44 pub sampled: bool,
46 pub sampler: Option<Handle>,
48 pub comparison: bool,
51}
52
53fn kind(opcode: i64) -> Option<(bool, bool)> {
55 Some(match opcode {
56 60..=63 | 73 | 81 => (true, false),
57 64 | 65 | 74 => (true, true),
58 66 | 68 | 139 => (false, false),
59 _ => return None,
60 })
61}
62
63fn value<'a>(m: &'a Module, f: &'a Function, id: ValueId) -> Option<&'a Value> {
65 match id.checked_sub(f.first) {
66 None => m.values.get(id),
67 Some(local) => f.locals.get(local),
68 }
69}
70
71fn integer(m: &Module, f: &Function, id: ValueId) -> Option<i64> {
73 match value(m, f, id)? {
74 Value::Constant(Constant::Integer(i)) => Some(*i),
75 Value::Constant(Constant::Null) => Some(0),
76 _ => None,
77 }
78}
79
80fn number(m: &Module, f: &Function, id: ValueId) -> Option<u32> {
81 integer(m, f, id).and_then(|i| u32::try_from(i).ok())
82}
83
84fn handle(
87 m: &Module,
88 f: &Function,
89 calls: &HashMap<ValueId, usize>,
90 spaces: &HashMap<(u8, u32), u32>,
91 id: ValueId,
92) -> Option<Handle> {
93 let mut id = id;
94 for _ in 0..8 {
96 let Op::Call { args, .. } = &f.instructions.get(*calls.get(&id)?)?.op else {
97 return None;
98 };
99 match integer(m, f, *args.first()?)? {
100 ANNOTATE_HANDLE => id = *args.get(1)?,
101 CREATE_HANDLE => {
102 let class = u8::try_from(integer(m, f, *args.get(1)?)?).ok()?;
103 let range = number(m, f, *args.get(2)?)?;
104 return Some(Handle {
105 class,
106 space: *spaces.get(&(class, range))?,
107 register: number(m, f, *args.get(3)?),
108 range: Some(range),
109 });
110 }
111 CREATE_HANDLE_FROM_BINDING => {
112 let Value::Constant(Constant::Aggregate(fields)) = value(m, f, *args.get(1)?)? else {
114 return None;
115 };
116 return Some(Handle {
117 class: u8::try_from(integer(m, f, *fields.get(3)?)?).ok()?,
118 space: number(m, f, *fields.get(2)?)?,
119 register: number(m, f, *args.get(2)?),
120 range: None,
121 });
122 }
123 _ => return None,
124 }
125 }
126 None
127}
128
129pub fn texture_reads(m: &Module) -> Result<Vec<TextureRead>> {
132 let spaces: HashMap<(u8, u32), u32> = ranges(m)?
133 .into_iter()
134 .map(|(range, _, _)| ((range.class, range.id), range.space))
135 .collect();
136 let mut operations: HashMap<ValueId, bool> = HashMap::new();
138 let mut reads = Vec::new();
139 for f in &m.functions {
140 let calls: HashMap<ValueId, usize> = f
142 .instructions
143 .iter()
144 .enumerate()
145 .filter_map(|(at, i)| i.result.map(|(id, _)| (id, at)))
146 .collect();
147 for (at, instruction) in f.instructions.iter().enumerate() {
148 let Op::Call { function, args, .. } = &instruction.op else {
149 continue;
150 };
151 let is_operation = *operations
152 .entry(*function)
153 .or_insert_with(|| m.name(*function).is_some_and(|n| n.starts_with("dx.op.")));
154 if !is_operation {
155 continue;
156 }
157 let Some(opcode) = args.first().and_then(|a| integer(m, f, *a)) else {
158 continue;
159 };
160 let Some((sampled, comparison)) = kind(opcode) else {
161 continue;
162 };
163 let Ok(opcode) = u16::try_from(opcode) else {
164 continue;
165 };
166 let resolve =
167 |position: usize| args.get(position).and_then(|a| handle(m, f, &calls, &spaces, *a));
168 reads.push(TextureRead {
169 function: f.value,
170 instruction: at,
171 opcode,
172 texture: resolve(1),
173 sampled,
174 sampler: if sampled { resolve(2) } else { None },
175 comparison,
176 });
177 }
178 }
179 Ok(reads)
180}