dxbc_reader/statistics.rs
1//! The `STAT` part of a Shader Model 4 or 5 container: the compiler's counts of what the program does, as 32-bit words.
2//!
3//! `fxc` writes 37 words for a Shader Model 5 program and 29 for Shader Model 4.0 and 4.1 ones (148 and 116 bytes).
4//! Most words this reader names are fields of `D3D11_SHADER_DESC` that the compiler's own reflection reports for the
5//! same container, checked by calling `D3DReflect` (`d3dcompiler_47`) on every container that has the part: the 432
6//! Shader Model 5 programs of Sekiro (37 words), the 16 Shader Model 4.0 programs of a Unity player (29 words) and 21
7//! programs compiled with `fxc` from small sources (6 with 29 words and 15 with 37). A name is a word that equalled
8//! the field in every one of those containers and was not zero in all of them; word 20 is not a field of that
9//! structure and is named from the decoder's count (below). The other words are kept as stored
10//! ([`Statistics::words`]): 2, 9 to 13, 17 and 22 to 36 are zero in all 448 of Sekiro's and the Unity player's, and
11//! words 19 and 21 are nonzero but no count of the decoder's instructions that was tried (`mov`; the conversions)
12//! equals them.
13//!
14//! In a `DXIL` container the `STAT` part is a bitcode module, not counters; [`statistics`] refuses it.
15use crate::{Result, error, parts};
16
17/// The counters of a `STAT` part, as stored.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct Statistics {
20 /// Every word of the part.
21 pub words: Vec<u32>,
22}
23
24macro_rules! counters {
25 ($($(#[$doc:meta])* $name:ident = $at:literal;)+) => {
26 impl Statistics {
27 $(
28 $(#[$doc])*
29 pub fn $name(&self) -> Option<u32> {
30 self.words.get($at).copied()
31 }
32 )+
33 }
34 };
35}
36
37counters! {
38 /// Word 0: the program's instructions, declarations not counted (equal to the decoder's instruction count in all
39 /// 466 containers that have the part and decode).
40 instructions = 0;
41 /// Word 1: the temporary registers: the largest `dcl_temps` of the program (a hull program declares them once a
42 /// phase), the only one in the other stages; equal to the decoder's in the same 466 containers and the fixtures.
43 temp_registers = 1;
44 /// Word 3: the declarations (`D3D11_SHADER_DESC::DclCount`). It is not a simple sum of the declaration opcodes.
45 declarations = 3;
46 /// Word 4: the floating-point instructions.
47 float_instructions = 4;
48 /// Word 5: the signed-integer instructions.
49 int_instructions = 5;
50 /// Word 6: the unsigned-integer instructions.
51 uint_instructions = 6;
52 /// Word 7: the static flow control instructions.
53 static_flow_control = 7;
54 /// Word 8: the dynamic flow control instructions.
55 dynamic_flow_control = 8;
56 /// Word 14: the texture instructions that sample normally (`sample`, `gather4`, ...).
57 texture_normal = 14;
58 /// Word 15: the texture load instructions.
59 texture_load = 15;
60 /// Word 16: the comparison texture instructions.
61 texture_comparison = 16;
62 /// Word 18: the instructions that sample with explicit gradients (`sample_d`; equal to the decoder's count in the
63 /// same 466 containers).
64 texture_gradient = 18;
65 /// Word 20: the `movc` instructions (equal to the decoder's count of them in all 466 containers that have the part
66 /// and decode, the 21 small programs among them).
67 movc_instructions = 20;
68 /// Word 28: 1 for a pixel program that runs once per sample (an input interpolated per sample, or the sample
69 /// index), 0 otherwise; the disassembler's "runs at sample frequency" note follows it.
70 sample_frequency = 28;
71 /// Word 30: a hull program's output control points, a domain program's input control points.
72 control_points = 30;
73 /// Word 31: a hull program's tessellator output primitive (`D3D_TESSELLATOR_OUTPUT_PRIMITIVE`: 1 point, 2 line, 3
74 /// clockwise triangles, 4 counter-clockwise).
75 tessellator_output_primitive = 31;
76 /// Word 32: a hull program's tessellator partitioning (`D3D_TESSELLATOR_PARTITIONING`: 1 integer, 2 power of two,
77 /// 3 odd fractional, 4 even fractional).
78 tessellator_partitioning = 32;
79 /// Word 33: the tessellator domain of a hull or domain program (`D3D_TESSELLATOR_DOMAIN`: 1 isoline, 2 triangle,
80 /// 3 quad).
81 tessellator_domain = 33;
82}
83
84/// The `STAT` part of a Shader Model 4 or 5 container: `None` for a container without one (Riders Republic's have none; Sekiro's
85/// have it in all 432, 16 of the Unity player's 4,854 do). Refuses a container [`parts`] refuses, a `DXIL` container
86/// (its `STAT` part is a bitcode module) and a part whose size is not a multiple of four bytes.
87pub fn statistics(bytes: &[u8]) -> Result<Option<Statistics>> {
88 let directory = parts(bytes)?;
89 let Some(stat) = directory.iter().find(|c| &c.kind == b"STAT") else {
90 return Ok(None);
91 };
92 if directory.iter().any(|c| &c.kind == b"DXIL") {
93 return Err(error(
94 0,
95 "the STAT part of a DXIL container is a bitcode module, not counters",
96 ));
97 }
98 let (words, rest) = stat.data.as_chunks::<4>();
99 if !rest.is_empty() {
100 return Err(error(
101 stat.data.len() / 4,
102 format!("the STAT part has {} bytes, not a multiple of 4", stat.data.len()),
103 ));
104 }
105 Ok(Some(Statistics {
106 words: words.iter().map(|w| u32::from_le_bytes(*w)).collect(),
107 }))
108}