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