Skip to main content

dxil_reader/
psv.rs

1//! The pipeline state validation (`PSV0`) part of a Shader Model 6 program container: the runtime information, resource
2//! records and signature elements a pipeline is validated against, which the validator writes from the program's own
3//! metadata. Little-endian.
4//!
5//! ```text
6//! u32 runtime-info size (36, 48, 52 or 56), then the runtime info
7//! u32 resource count; when it is not zero, u32 record size (16 or 24), then the records
8//! u32 string-table size, then the table
9//! u32 semantic-index count, then the indices (u32 each)
10//! u32 element size (16) when there are any input, output or patch-constant elements, then the input, the output and
11//!   the patch-constant elements
12//! the rest: the input-to-output dependency tables, as words
13//! ```
14//!
15//! The runtime info is 16 bytes whose meaning depends on the stage, the lowest and highest wave lane counts (u32 each),
16//! then from 36 bytes the stage (u8), the uses-view-id flag (u8), a u16 (a geometry program's largest output vertex
17//! count; in the low byte a hull or domain program's patch-constant vector count), the input, output and
18//! patch-constant element counts (u8 each), the input vector count (u8) and four output vector counts (u8 each), from
19//! 48 bytes the thread group size (three u32), at 52 bytes the offset of the entry point's name in the string table,
20//! and at 56 bytes the group-shared memory a compute, mesh or amplification program uses (u32).
21//! The stage-dependent 16 bytes are: vertex, whether an `SV_Position` output exists (word 3); pixel, whether it writes
22//! depth (byte 0) and whether it shades per sample (byte 1); hull, the input and output control point counts, the
23//! domain and the output primitive; domain, the input control point count; geometry, the input primitive, the output
24//! topology, the output stream mask and whether it writes a position. The record sizes follow the validator that wrote
25//! the part: validator 1.5 writes a 36-byte runtime info and 16-byte resource records, 1.6 writes 48 and 24, and 1.10
26//! writes 52 and 24 (the 16-byte records have no kind or flags). The 56-byte runtime info is the fourth version of the
27//! layout in dxc's source (`PSVRuntimeInfo4`). The reader reads these four sizes only.
28//!
29//! Every field above was checked against the metadata of the same program in the stock containers of skate. (19,240
30//! with a `PSV0` part, validator 1.10) and Elden Ring (46,002; 10 with validator 1.5, 45,992 with 1.6): the layout
31//! consumes each part to its last byte, and the stage, the thread group, the resource ranges, the elements' name,
32//! semantic indices, size, kind, interpolation and placement, the vector counts and the stage unions equal what
33//! [`crate::reflection`] reads from the metadata. The dependency tables are kept as stored words; the part's remainder
34//! after the elements is as long as the vectors state (the input vectors' components times the words of the output
35//! vectors' components).
36use primase_foundations::bytes::View;
37
38use crate::{Result, error};
39
40/// Most resource records a part may state.
41const MAX_RECORDS: usize = 1 << 20;
42
43/// The runtime information of a `PSV0` part.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct RuntimeInfo {
46    /// The stage-dependent 16 bytes, as four words (see the module documentation).
47    pub stage_words: [u32; 4],
48    /// The smallest wave lane count the program expects (0 where it states none).
49    pub min_wave_lanes: u32,
50    /// The largest wave lane count the program expects (`u32::MAX` where it states none).
51    pub max_wave_lanes: u32,
52    /// The shader kind (0 pixel, 1 vertex, 2 geometry, 3 hull, 4 domain, 5 compute, 13 mesh, ...), as in the program
53    /// header.
54    pub stage: u8,
55    /// The program uses `SV_ViewID`.
56    pub uses_view_id: bool,
57    /// A geometry program's largest output vertex count; in the low byte a hull or domain program's patch-constant
58    /// vector count.
59    pub stage_data: u16,
60    /// The input, output and patch-constant element counts as stored (the lengths of [`Psv::inputs`] and the others).
61    pub element_counts: [u8; 3],
62    /// How many input vectors (registers) the program reads.
63    pub input_vectors: u8,
64    /// How many output vectors it writes, per stream.
65    pub output_vectors: [u8; 4],
66    /// The thread group size, from 48 bytes of runtime info.
67    pub num_threads: Option<[u32; 3]>,
68    /// The offset of the entry point's name in the string table, from 52 bytes of runtime info.
69    pub entry_name: Option<u32>,
70    /// The bytes of group-shared memory a compute, mesh or amplification program uses, from 56 bytes of runtime info.
71    pub group_shared_bytes: Option<u32>,
72    /// The size of the runtime info in bytes (36, 48, 52 or 56), which says which of its versions the part holds.
73    pub size: u32,
74}
75
76/// A resource record.
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct Resource {
79    /// 1 sampler, 2 constant buffer, 3 typed SRV, 4 raw SRV, 5 structured SRV, 6 typed UAV, 7 raw UAV, 8 structured UAV,
80    /// 9 structured UAV with a counter.
81    pub resource_type: u32,
82    /// The register space.
83    pub space: u32,
84    /// The first register.
85    pub lower: u32,
86    /// The last register (`u32::MAX` for an unbounded range).
87    pub upper: u32,
88    /// The resource kind (the metadata's `DxilResourceKind` for an SRV or UAV, 13 for a constant buffer, 14 for a
89    /// sampler), in the 24-byte record only.
90    pub kind: Option<u32>,
91    /// The record's flags, in the 24-byte record only.
92    pub flags: Option<u32>,
93}
94
95/// A signature element record (16 bytes).
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct Element {
98    /// The offset of the semantic name in the string table (an empty string for a system value).
99    pub name: u32,
100    /// The offset of the element's first semantic index in the semantic-index table.
101    pub indices: u32,
102    /// How many registers the element spans.
103    pub rows: u8,
104    /// The first register.
105    pub start_row: u8,
106    /// How many components of each register it uses.
107    pub columns: u8,
108    /// The first component (0 x to 3 w).
109    pub start_column: u8,
110    /// The packer placed the element in registers.
111    pub allocated: bool,
112    /// DXIL semantic kind (0 arbitrary, 3 `SV_Position`, ...).
113    pub semantic_kind: u8,
114    /// The component type (1 u32, 2 i32, 3 f32: not the metadata's numbers, which are 5, 4 and 9).
115    pub component_type: u8,
116    /// DXIL interpolation mode.
117    pub interpolation: u8,
118    /// The components indexed dynamically, a 4-bit mask.
119    pub dynamic_index_mask: u8,
120    /// The output stream (0 to 3).
121    pub stream: u8,
122    /// The record's last byte, as stored.
123    pub reserved: u8,
124}
125
126/// A `PSV0` part.
127#[derive(Clone, Debug, PartialEq, Eq)]
128pub struct Psv {
129    /// The runtime information.
130    pub runtime: RuntimeInfo,
131    /// The resource records, in the part's order (constant buffers, samplers, SRVs, UAVs).
132    pub resources: Vec<Resource>,
133    /// The string table: names, null-terminated.
134    pub strings: Vec<u8>,
135    /// The semantic-index table.
136    pub semantic_indices: Vec<u32>,
137    /// The input elements.
138    pub inputs: Vec<Element>,
139    /// The output elements.
140    pub outputs: Vec<Element>,
141    /// The patch-constant elements.
142    pub patch_constants: Vec<Element>,
143    /// The part after the elements: the dependency tables, as words.
144    pub dependencies: Vec<u32>,
145}
146
147impl Psv {
148    /// The string at `offset` of the string table (an error where it is outside the table, has no end, or is not UTF-8).
149    pub fn string(&self, offset: u32) -> Result<&str> {
150        let rest = usize::try_from(offset)
151            .ok()
152            .and_then(|at| self.strings.get(at..))
153            .ok_or_else(|| error(0, format!("PSV0: string {offset} past the string table")))?;
154        let end = rest
155            .iter()
156            .position(|b| *b == 0)
157            .ok_or_else(|| error(0, "PSV0: a string without its end"))?;
158        std::str::from_utf8(rest.get(..end).unwrap_or_default())
159            .map_err(|_| error(0, "PSV0: a string that is not UTF-8"))
160    }
161
162    /// The semantic indices of `element`, one for each of its rows.
163    pub fn element_indices(&self, element: &Element) -> Result<&[u32]> {
164        usize::try_from(element.indices)
165            .ok()
166            .and_then(|start| {
167                let end = start.checked_add(usize::from(element.rows))?;
168                self.semantic_indices.get(start..end)
169            })
170            .ok_or_else(|| error(0, "PSV0: an element's semantic indices lie past the index table"))
171    }
172}
173
174/// Reads the `PSV0` part `data`. Anything outside the layout above (a runtime info or record of another size, a table
175/// past the part's end, a remainder that is not whole words) is an error naming the bit.
176pub fn read(data: &[u8]) -> Result<Psv> {
177    let view = View::new(data);
178    let bit = |at: usize| at.saturating_mul(8);
179    let word = |at: usize| -> Result<u32> {
180        view.array::<4>(at)
181            .map(u32::from_le_bytes)
182            .map_err(|e| error(bit(at), format!("PSV0: {e}")))
183    };
184    let count = |at: usize, what: &str| -> Result<usize> {
185        usize::try_from(word(at)?).map_err(|_| error(bit(at), format!("PSV0: {what} past the address space")))
186    };
187    let advance = |at: usize, by: usize| {
188        at.checked_add(by)
189            .ok_or_else(|| error(bit(at), "PSV0: a table past the address space"))
190    };
191
192    let info_size = count(0, "the runtime info size")?;
193    let mut at = 4usize;
194    if !matches!(info_size, 36 | 48 | 52 | 56) {
195        return Err(error(
196            0,
197            format!("PSV0: a runtime info of {info_size} bytes, where only 36, 48, 52 and 56 are read"),
198        ));
199    }
200    let info = view
201        .slice(at, info_size)
202        .map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
203    let info_word = |i: usize| -> Result<u32> {
204        let start = i.saturating_mul(4);
205        info.get(start..start.saturating_add(4))
206            .and_then(|w| <[u8; 4]>::try_from(w).ok())
207            .map(u32::from_le_bytes)
208            .ok_or_else(|| error(bit(at), "PSV0: the runtime info is cut short"))
209    };
210    let byte = |i: usize| -> Result<u8> {
211        info.get(i)
212            .copied()
213            .ok_or_else(|| error(bit(at), "PSV0: the runtime info is cut short"))
214    };
215    let runtime = RuntimeInfo {
216        stage_words: [info_word(0)?, info_word(1)?, info_word(2)?, info_word(3)?],
217        min_wave_lanes: info_word(4)?,
218        max_wave_lanes: info_word(5)?,
219        stage: byte(24)?,
220        uses_view_id: byte(25)? != 0,
221        stage_data: u16::from_le_bytes([byte(26)?, byte(27)?]),
222        element_counts: [byte(28)?, byte(29)?, byte(30)?],
223        input_vectors: byte(31)?,
224        output_vectors: [byte(32)?, byte(33)?, byte(34)?, byte(35)?],
225        num_threads: if info_size >= 48 {
226            Some([info_word(9)?, info_word(10)?, info_word(11)?])
227        } else {
228            None
229        },
230        entry_name: if info_size >= 52 {
231            Some(info_word(12)?)
232        } else {
233            None
234        },
235        group_shared_bytes: if info_size >= 56 {
236            Some(info_word(13)?)
237        } else {
238            None
239        },
240        size: u32::try_from(info_size).unwrap_or(u32::MAX),
241    };
242    at = advance(at, info_size)?;
243
244    let resource_count = count(at, "the resource count")?;
245    at = advance(at, 4)?;
246    let mut resources = Vec::new();
247    if resource_count > 0 {
248        if resource_count > MAX_RECORDS {
249            return Err(error(bit(at), format!("PSV0: {resource_count} resource records")));
250        }
251        let size = count(at, "the resource record size")?;
252        at = advance(at, 4)?;
253        if !matches!(size, 16 | 24) {
254            return Err(error(
255                bit(at),
256                format!("PSV0: resource records of {size} bytes, where only 16 and 24 are read"),
257            ));
258        }
259        for _ in 0..resource_count {
260            let long = size == 24;
261            resources.push(Resource {
262                resource_type: word(at)?,
263                space: word(advance(at, 4)?)?,
264                lower: word(advance(at, 8)?)?,
265                upper: word(advance(at, 12)?)?,
266                kind: if long { Some(word(advance(at, 16)?)?) } else { None },
267                flags: if long { Some(word(advance(at, 20)?)?) } else { None },
268            });
269            at = advance(at, size)?;
270        }
271    }
272
273    let strings_size = count(at, "the string table size")?;
274    at = advance(at, 4)?;
275    let strings = view
276        .slice(at, strings_size)
277        .map_err(|e| error(bit(at), format!("PSV0: {e}")))?
278        .to_vec();
279    at = advance(at, strings_size)?;
280
281    let index_count = count(at, "the semantic index count")?;
282    at = advance(at, 4)?;
283    let index_bytes = view
284        .slice(
285            at,
286            index_count
287                .checked_mul(4)
288                .ok_or_else(|| error(bit(at), "PSV0: the semantic index count"))?,
289        )
290        .map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
291    let semantic_indices = index_bytes
292        .as_chunks::<4>()
293        .0
294        .iter()
295        .map(|w| u32::from_le_bytes(*w))
296        .collect();
297    at = advance(at, index_bytes.len())?;
298
299    let mut elements = [Vec::new(), Vec::new(), Vec::new()];
300    if runtime.element_counts.iter().any(|n| *n > 0) {
301        let size = count(at, "the element size")?;
302        at = advance(at, 4)?;
303        if size != 16 {
304            return Err(error(
305                bit(at),
306                format!("PSV0: signature elements of {size} bytes, where only 16 are read"),
307            ));
308        }
309        for (list, n) in elements.iter_mut().zip(runtime.element_counts) {
310            for _ in 0..n {
311                let raw = view
312                    .array::<16>(at)
313                    .map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
314                let [
315                    n0,
316                    n1,
317                    n2,
318                    n3,
319                    i0,
320                    i1,
321                    i2,
322                    i3,
323                    rows,
324                    start_row,
325                    packed,
326                    kind,
327                    component,
328                    interpolation,
329                    masks,
330                    reserved,
331                ] = raw;
332                list.push(Element {
333                    name: u32::from_le_bytes([n0, n1, n2, n3]),
334                    indices: u32::from_le_bytes([i0, i1, i2, i3]),
335                    rows,
336                    start_row,
337                    columns: packed & 15,
338                    start_column: (packed >> 4) & 3,
339                    allocated: (packed >> 6) & 1 == 1,
340                    semantic_kind: kind,
341                    component_type: component,
342                    interpolation,
343                    dynamic_index_mask: masks & 15,
344                    stream: (masks >> 4) & 3,
345                    reserved,
346                });
347                at = advance(at, 16)?;
348            }
349        }
350    }
351    let [inputs, outputs, patch_constants] = elements;
352
353    let rest = data.get(at..).unwrap_or_default();
354    let (words, ragged) = rest.as_chunks::<4>();
355    if !ragged.is_empty() {
356        return Err(error(
357            bit(at),
358            format!(
359                "PSV0: {} bytes after the elements, not a whole number of words",
360                rest.len()
361            ),
362        ));
363    }
364    Ok(Psv {
365        runtime,
366        resources,
367        strings,
368        semantic_indices,
369        inputs,
370        outputs,
371        patch_constants,
372        dependencies: words.iter().map(|w| u32::from_le_bytes(*w)).collect(),
373    })
374}
375
376/// The `PSV0` part of a container's directory, if it has one (a library has none; it has runtime data, [`crate::rdat`]).
377pub fn part(chunks: &[dxcontainer::Chunk<'_>]) -> Result<Option<Psv>> {
378    chunks
379        .iter()
380        .find(|c| &c.kind == b"PSV0")
381        .map(|c| read(c.data))
382        .transpose()
383}