Skip to main content

dxbc_reader/
sampling.rs

1//! Which sampler each texture read of a program uses: every sample, gather, level-of-detail query and load that names a
2//! texture register, with the texture register, the sampler register and whether the instruction compares.
3use crate::decode::{Declared, Decoded, Instruction, Operand, Register};
4
5/// What a texture or sampler operand names: its register, whether a register adds to it, and in Shader Model 5.1 the
6/// id of the declaration whose range holds it (`T1[r1.x + 4]`: id 1, register 4 plus `r1.x`).
7struct Named {
8    register: u32,
9    indexed: bool,
10    id: Option<u32>,
11}
12
13/// One instruction of a program that reads a texture (a shader resource view, `t<n>`).
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct TextureRead {
16    /// The instruction's first word in the token stream.
17    pub word: usize,
18    /// The instruction's opcode (see [`crate::opcode_name`]).
19    pub opcode: u16,
20    /// The number of the `t` register, or the constant part of its index where [`TextureRead::texture_indexed`]. In
21    /// Shader Model 5.1 the register of the declared range the instruction reads, as fxc's disassembly gives it.
22    pub texture: u32,
23    /// The texture register is indexed by a register (`t[r1.x + 3]`).
24    pub texture_indexed: bool,
25    /// The texture's register space: 0 before Shader Model 5.1; in 5.1 its declaration's, `None` where the program
26    /// declares no texture of the id the instruction names.
27    pub texture_space: Option<u32>,
28    /// The number of the `s` register the instruction samples with; none for a load, which uses no sampler.
29    pub sampler: Option<u32>,
30    /// The sampler register is indexed by a register.
31    pub sampler_indexed: bool,
32    /// The sampler's register space, as [`TextureRead::texture_space`] gives the texture's; none without a sampler.
33    pub sampler_space: Option<u32>,
34    /// The instruction compares the sampled value with a reference (`sample_c`, `sample_c_lz`, `gather4_c`,
35    /// `gather4_po_c` and the feedback forms of them).
36    pub comparison: bool,
37    /// How the program declares the sampler (`dcl_sampler`): 0 default, 1 comparison, 2 mono; none where the program
38    /// declares no sampler of that number (or the instruction has no sampler).
39    pub sampler_mode: Option<u32>,
40}
41
42/// How an opcode reads its texture: whether it takes a sampler and whether it compares.
43fn kind(name: &str) -> Option<(bool, bool)> {
44    Some(match name {
45        "sample"
46        | "sample_l"
47        | "sample_d"
48        | "sample_b"
49        | "lod"
50        | "gather4"
51        | "gather4_po"
52        | "gather4_feedback"
53        | "gather4_po_feedback"
54        | "sample_l_feedback"
55        | "sample_clamp_feedback"
56        | "sample_b_clamp_feedback"
57        | "sample_d_clamp_feedback" => (true, false),
58        "sample_c"
59        | "sample_c_lz"
60        | "gather4_c"
61        | "gather4_po_c"
62        | "gather4_c_feedback"
63        | "gather4_po_c_feedback"
64        | "sample_c_lz_feedback"
65        | "sample_c_clamp_feedback" => (true, true),
66        "ld"
67        | "ld_ms"
68        | "ld_raw"
69        | "ld_structured"
70        | "ld_feedback"
71        | "ld_ms_feedback"
72        | "ld_raw_feedback"
73        | "ld_structured_feedback" => (false, false),
74        _ => return None,
75    })
76}
77
78/// The register an operand names, whether a register adds to it and (Shader Model 5.1) its declaration's id; none for
79/// an operand without the indices its model gives it.
80fn named(operand: &Operand, sm51: bool) -> Option<Named> {
81    let (id, index) = if sm51 {
82        let [id, index] = operand.indices.as_slice() else {
83            return None;
84        };
85        (Some(id.base), index)
86    } else {
87        (None, operand.indices.first()?)
88    };
89    Some(Named {
90        register: index.base,
91        indexed: index.relative.is_some(),
92        id,
93    })
94}
95
96fn first(instruction: &Instruction, wanted: Register) -> Option<&Operand> {
97    instruction.operands.iter().find(|o| o.register == wanted)
98}
99
100/// Every texture read of `program` (an instruction with a `t<n>` operand that samples, gathers, queries the level of
101/// detail of or loads it), in program order. An instruction that reads a raw or structured buffer or an
102/// unordered-access view through a `u` or `g` register names no texture and is not listed.
103pub fn texture_reads(program: &Decoded) -> Vec<TextureRead> {
104    let sm51 = (program.major, program.minor) >= (5, 1);
105    // the declaration a name refers to: by its id in 5.1, by its register before
106    let declared = |name: &Named, sampler: bool| {
107        program.declarations.iter().find(|d| {
108            let slot = match d.kind {
109                Declared::Sampler { slot, .. } if sampler => slot,
110                Declared::Resource { slot, .. }
111                | Declared::ResourceRaw { slot, .. }
112                | Declared::ResourceStructured { slot, .. }
113                    if !sampler =>
114                {
115                    slot
116                }
117                _ => return false,
118            };
119            slot == name.id.unwrap_or(name.register)
120        })
121    };
122    let space = |name: &Named, sampler: bool| {
123        if sm51 {
124            declared(name, sampler)
125                .and_then(|d| d.kind.registers())
126                .map(|r| r.space)
127        } else {
128            Some(0)
129        }
130    };
131    let mut reads = Vec::new();
132    for instruction in &program.instructions {
133        let Some((sampled, comparison)) = kind(instruction.name()) else {
134            continue;
135        };
136        let Some(texture) = first(instruction, Register::Texture).and_then(|o| named(o, sm51)) else {
137            continue;
138        };
139        let sampler = if sampled {
140            first(instruction, Register::Sampler).and_then(|o| named(o, sm51))
141        } else {
142            None
143        };
144        reads.push(TextureRead {
145            word: instruction.word,
146            opcode: instruction.opcode,
147            texture: texture.register,
148            texture_indexed: texture.indexed,
149            texture_space: space(&texture, false),
150            sampler: sampler.as_ref().map(|s| s.register),
151            sampler_indexed: sampler.as_ref().is_some_and(|s| s.indexed),
152            sampler_space: sampler.as_ref().and_then(|s| space(s, true)),
153            comparison,
154            sampler_mode: sampler.as_ref().and_then(|s| {
155                declared(s, true).and_then(|d| match d.kind {
156                    Declared::Sampler { mode, .. } => Some(mode),
157                    _ => None,
158                })
159            }),
160        });
161    }
162    reads
163}