1use std::collections::BTreeMap;
7use std::path::{Path, PathBuf};
8
9type 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}