Skip to main content

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}