Skip to main content

pairs_dxbc/
pairs_dxbc.rs

1//! `cargo run -p dxbc-reader --example pairs_dxbc -- <file or folder>...`: for every Shader Model 4 or 5 program under
2//! the paths, print one tab-separated line per distinct texture read: the FNV-1a 64 hash of the file, its path, the stage,
3//! the texture register, the sampler register (`-` for a load, `?` where the instruction's sampler is indexed), whether the
4//! instruction compares, the sampler's declared mode (`-` where it declares none), how many such instructions the program
5//! has and their opcode names. DXIL containers and other files are skipped (see the `pairs_dxil` example of `dxil-reader`).
6use std::collections::BTreeMap;
7use std::path::{Path, PathBuf};
8
9/// Per (texture, sampler, comparison, declared mode): the number of instructions, and of each opcode name among them.
10type Pairs = BTreeMap<(String, String, bool, String), (usize, BTreeMap<&'static str, usize>)>;
11
12fn fnv1a64(bytes: &[u8]) -> u64 {
13    bytes.iter().fold(0xcbf2_9ce4_8422_2325, |h, &b| {
14        (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3)
15    })
16}
17
18fn files(path: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
19    if path.is_dir() {
20        let mut entries: Vec<PathBuf> = std::fs::read_dir(path)?
21            .map(|e| e.map(|e| e.path()))
22            .collect::<Result<_, _>>()?;
23        entries.sort();
24        for entry in entries {
25            files(&entry, out)?;
26        }
27    } else {
28        out.push(path.to_owned());
29    }
30    Ok(())
31}
32
33fn main() -> Result<(), Box<dyn std::error::Error>> {
34    let mut paths = Vec::new();
35    for argument in std::env::args().skip(1) {
36        files(Path::new(&argument), &mut paths)?;
37    }
38    let (mut programs, mut skipped) = (0_usize, 0_usize);
39    for path in &paths {
40        let bytes = std::fs::read(path)?;
41        let has_code = dxbc_reader::parts(&bytes)
42            .is_ok_and(|parts| parts.iter().any(|c| matches!(&c.kind, b"SHDR" | b"SHEX")));
43        if !has_code {
44            skipped += 1;
45            continue;
46        }
47        let program = match dxbc_reader::code(&bytes).and_then(|code| dxbc_reader::decode(&code)) {
48            Ok(program) => program,
49            Err(e) => {
50                eprintln!("{}: {e}", path.display());
51                continue;
52            }
53        };
54        programs += 1;
55        let hash = fnv1a64(&bytes);
56        let mut pairs = Pairs::new();
57        for read in dxbc_reader::texture_reads(&program) {
58            let texture = if read.texture_indexed {
59                format!("t[{}+]", read.texture)
60            } else {
61                format!("t{}", read.texture)
62            };
63            let sampler = match (read.sampler, read.sampler_indexed) {
64                (None, _) => "-".to_owned(),
65                (Some(slot), true) => format!("s[{slot}+]"),
66                (Some(slot), false) => format!("s{slot}"),
67            };
68            let mode = read
69                .sampler_mode
70                .map_or_else(|| "-".to_owned(), |m| m.to_string());
71            let entry = pairs
72                .entry((texture, sampler, read.comparison, mode))
73                .or_default();
74            entry.0 += 1;
75            *entry.1.entry(dxbc_reader::opcode_name(read.opcode)).or_default() += 1;
76        }
77        for ((texture, sampler, comparison, mode), (count, names)) in pairs {
78            let names: Vec<String> = names.iter().map(|(n, c)| format!("{n}x{c}")).collect();
79            println!(
80                "{hash:016x}\t{}\t{}\t{texture}\t{sampler}\t{}\t{mode}\t{count}\t{}",
81                path.display(),
82                program.kind.name(),
83                u8::from(comparison),
84                names.join(",")
85            );
86        }
87    }
88    eprintln!("{programs} programs, {skipped} files skipped");
89    Ok(())
90}