Skip to main content

rsx_reader/fragment/
flow.rs

1//! Conservative texture-unit dependencies, not shader evaluation or role names.
2//! Component writes and half/full register overlap are tracked; arithmetic
3//! conservatively unions all referenced source components. Branch and predicate
4//! dependencies persist after a join, so results are possible influences.
5use super::{Control, Error, Instruction, Opcode, Program, Register, SourceKind, error};
6use std::collections::BTreeSet;
7
8/// The texture units and interpolators a value may depend on, one bit per unit.
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10pub struct Dependencies {
11    /// Bit `n` is texture image unit `n`.
12    pub textures: u16,
13    /// Encoded interpolator indices, not texture image units.
14    pub inputs: u16,
15}
16impl Dependencies {
17    fn join(self, other: Self) -> Self {
18        Self {
19            textures: self.textures | other.textures,
20            inputs: self.inputs | other.inputs,
21        }
22    }
23}
24
25/// One texture sample.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct Sample {
28    /// The byte offset of the sampling instruction.
29    pub offset: usize,
30    /// The texture unit sampled.
31    pub unit: u8,
32    /// What its first source (the coordinates) depends on.
33    pub coordinates: Dependencies,
34    /// All sampling arguments, including separate LOD, bias or gradients.
35    pub arguments: Dependencies,
36}
37
38/// What one requested output register depends on.
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct Output {
41    /// The register.
42    pub register: Register,
43    /// The dependencies of each component (x to w).
44    pub components: [Dependencies; 4],
45}
46
47/// The result of [`analyze`].
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct Analysis {
50    /// The texture samples, in program order.
51    pub samples: Vec<Sample>,
52    /// The requested outputs, in the order they were requested.
53    pub outputs: Vec<Output>,
54    /// What may kill the pixel.
55    pub kill: Dependencies,
56    /// Operations which may consume each texture unit, directly or transitively.
57    pub consumers: [Vec<Opcode>; 16],
58}
59
60#[derive(Clone)]
61struct State {
62    // A full register contains eight 16-bit lanes; H(2n) and H(2n+1)
63    // overlap R(n). Each full component depends on both of its half lanes.
64    lanes: [Dependencies; 512],
65    cc: [[Dependencies; 4]; 2],
66    control: Dependencies,
67}
68impl Default for State {
69    fn default() -> Self {
70        Self {
71            lanes: [Dependencies::default(); 512],
72            cc: [[Dependencies::default(); 4]; 2],
73            control: Dependencies::default(),
74        }
75    }
76}
77/// The bit of texture unit or interpolator `n` (0 to 15) in a [`Dependencies`] mask.
78fn unit_bit(n: usize) -> u16 {
79    u32::try_from(n)
80        .ok()
81        .and_then(|n| 1u16.checked_shl(n))
82        .unwrap_or(0)
83}
84/// The 16-bit lanes component `c` of `r` occupies: one for a half register, two for a full one.
85fn positions(r: Register, c: usize) -> std::ops::Range<usize> {
86    if r.half {
87        let at = usize::from(r.index).saturating_mul(4).saturating_add(c);
88        at..at.saturating_add(1)
89    } else {
90        let at = usize::from(r.index)
91            .saturating_mul(8)
92            .saturating_add(c.saturating_mul(2));
93        at..at.saturating_add(2)
94    }
95}
96impl State {
97    fn read(&self, r: Register, c: usize) -> Dependencies {
98        positions(r, c)
99            .map(|n| self.lanes.get(n).copied().unwrap_or_default())
100            .fold(Dependencies::default(), Dependencies::join)
101    }
102    fn merge(&mut self, other: &Self) {
103        for (a, b) in self.lanes.iter_mut().zip(other.lanes) {
104            *a = a.join(b);
105        }
106        for (a, b) in self.cc.iter_mut().flatten().zip(other.cc.into_iter().flatten()) {
107            *a = a.join(b);
108        }
109        self.control = self.control.join(other.control);
110    }
111    fn predicate(&self, i: &Instruction) -> Dependencies {
112        if matches!(i.predicate.test, 0 | 7) {
113            Dependencies::default()
114        } else {
115            i.predicate
116                .swizzle
117                .iter()
118                .map(|&c| {
119                    self.cc
120                        .get(usize::from(i.predicate.register))
121                        .and_then(|row| row.get(usize::from(c)))
122                        .copied()
123                        .unwrap_or_default()
124                })
125                .fold(Dependencies::default(), Dependencies::join)
126        }
127    }
128}
129
130/// Follow possible influences into precisely the registers requested by the
131/// caller (which owns render-target/depth configuration). Inputs and outputs
132/// are not assigned colour/normal/specular meanings. Structured forward IF,
133/// conditional writes, KIL and RET are supported. Calls, loops and BRK refuse
134/// analysis explicitly; the instruction reader can still inspect them.
135pub fn analyze(program: &Program, outputs: &[Register]) -> Result<Analysis, Error> {
136    if outputs.iter().any(|r| r.index > 63) {
137        return Err(error(0, "output register index exceeds encoding"));
138    }
139    let code = &program.instructions;
140    let mut incoming: Vec<Option<State>> = vec![None; code.len().saturating_add(1)];
141    if let Some(first) = incoming.first_mut() {
142        *first = Some(State::default());
143    }
144    let mut exits: Option<State> = None;
145    let mut samples = Vec::new();
146    let mut consumers: [BTreeSet<u8>; 16] = std::array::from_fn(|_| BTreeSet::new());
147    let mut kill = Dependencies::default();
148    let mut branches = Vec::new();
149    let index = |offset| -> usize { code.iter().position(|i| i.offset == offset).unwrap_or(code.len()) };
150    for (n, i) in code.iter().enumerate() {
151        if i.indexed_input && i.sources.iter().any(|s| matches!(s.kind, SourceKind::Input(_))) {
152            return Err(error(
153                i.offset,
154                "texture flow analysis does not support indexed interpolators",
155            ));
156        }
157        match i.control {
158            Some(Control::If {
159                else_offset,
160                end_offset,
161            }) => branches.push((n, index(else_offset), index(end_offset))),
162            Some(Control::Loop { .. } | Control::Repeat { .. } | Control::Call { .. } | Control::Break) => {
163                return Err(error(
164                    i.offset,
165                    "texture flow analysis does not support calls, loops or BRK",
166                ));
167            }
168            _ => {}
169        }
170    }
171    for &(start, _, end) in &branches {
172        if branches.iter().any(|&(s, _, e)| start < s && s < end && e > end) {
173            return Err(error(
174                code.get(start).map_or(0, |i| i.offset),
175                "crossing IF regions in flow analysis",
176            ));
177        }
178    }
179    let merge_exit = |exits: &mut Option<State>, state: &State| {
180        if let Some(old) = exits {
181            old.merge(state);
182        } else {
183            *exits = Some(state.clone());
184        }
185    };
186    for (n, i) in code.iter().enumerate() {
187        let Some(mut state) = incoming.get_mut(n).and_then(Option::take) else {
188            continue;
189        };
190        let predicate = state.predicate(i);
191        let mut source = Dependencies::default();
192        let mut coordinates = Dependencies::default();
193        for (operand, s) in i.sources.iter().enumerate() {
194            let dependency = match s.kind {
195                SourceKind::Register(r) => s
196                    .swizzle
197                    .iter()
198                    .map(|&c| state.read(r, usize::from(c)))
199                    .fold(Dependencies::default(), Dependencies::join),
200                SourceKind::Input(k) => Dependencies {
201                    textures: 0,
202                    inputs: unit_bit(usize::from(k)),
203                },
204                SourceKind::Constant => Dependencies::default(),
205            };
206            if operand == 0 {
207                coordinates = dependency;
208            }
209            source = source.join(dependency);
210        }
211        let influenced = source.join(predicate).join(state.control);
212        if i.predicate.test != 0 {
213            for (unit, set) in consumers.iter_mut().enumerate() {
214                if influenced.textures & unit_bit(unit) != 0 {
215                    set.insert(i.opcode as u8);
216                }
217            }
218            let mut result = influenced;
219            if let Some(unit) = i.texture_unit {
220                samples.push(Sample {
221                    offset: i.offset,
222                    unit,
223                    coordinates,
224                    arguments: source,
225                });
226                result.textures |= unit_bit(usize::from(unit));
227            }
228            if i.opcode.writes_destination() {
229                for c in 0..4 {
230                    if i.write_mask & unit_bit(c).to_le_bytes()[0] == 0 {
231                        continue;
232                    }
233                    if !i.no_destination && i.destination.index != 63 {
234                        for at in positions(i.destination, c) {
235                            if let Some(lane) = state.lanes.get_mut(at) {
236                                *lane = if i.predicate.test == 7 {
237                                    result
238                                } else {
239                                    lane.join(result)
240                                };
241                            }
242                        }
243                    }
244                    if i.update_condition
245                        && let Some(cc) = state
246                            .cc
247                            .get_mut(usize::from(i.condition_destination))
248                            .and_then(|row| row.get_mut(c))
249                    {
250                        *cc = if i.predicate.test == 7 {
251                            result
252                        } else {
253                            cc.join(result)
254                        };
255                    }
256                }
257            }
258            if i.opcode == Opcode::KIL {
259                kill = kill.join(predicate).join(state.control);
260            }
261        }
262        let mut next = Vec::new();
263        let returns = matches!(i.control, Some(Control::Return));
264        let kills = i.opcode == Opcode::KIL;
265        if returns && i.predicate.test != 0 {
266            state.control = state.control.join(predicate);
267        }
268        if (i.end || returns && i.predicate.test != 0) && !(kills && i.predicate.test == 7) {
269            merge_exit(&mut exits, &state);
270        }
271        if !i.end && (!(returns || kills) || i.predicate.test != 7) {
272            if let Some(Control::If {
273                else_offset,
274                end_offset,
275            }) = i.control
276            {
277                state.control = state.control.join(predicate);
278                if i.predicate.test != 0 {
279                    next.push(if n.saturating_add(1) == index(else_offset) {
280                        index(end_offset)
281                    } else {
282                        n.saturating_add(1)
283                    });
284                }
285                if i.predicate.test != 7 {
286                    next.push(index(else_offset));
287                }
288            } else {
289                next.push(n.saturating_add(1));
290            }
291        }
292        for mut successor in next {
293            // The true block falls over its ELSE body to the matching ENDIF.
294            // Process inner boundaries first; several scopes may close together.
295            for &(start, else_at, end_at) in branches.iter().rev() {
296                if start < n && n < else_at && successor == else_at && else_at != end_at {
297                    successor = end_at;
298                }
299            }
300            if let Some(slot) = incoming.get_mut(successor) {
301                match slot {
302                    Some(old) => old.merge(&state),
303                    None => *slot = Some(state.clone()),
304                }
305            }
306        }
307    }
308    if let Some(state) = incoming.last().and_then(Option::as_ref) {
309        merge_exit(&mut exits, state);
310    }
311    let state = exits.unwrap_or_default();
312    Ok(Analysis {
313        samples,
314        outputs: outputs
315            .iter()
316            .map(|&r| Output {
317                register: r,
318                components: std::array::from_fn(|c| state.read(r, c)),
319            })
320            .collect(),
321        kill,
322        consumers: consumers.map(|set| set.into_iter().filter_map(Opcode::read).collect()),
323    })
324}