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}