1use crate::module::{Constant, Metadata, MetadataId, Module, Type, TypeId, Value, ValueId};
7use crate::{Result, error};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Element {
13 pub id: u32,
15 pub name: String,
17 pub index: u32,
19 pub component_type: u8,
21 pub system_value: u8,
23 pub interpolation: u8,
25 pub row: u32,
27 pub column: u8,
29 pub rows: u32,
31 pub columns: u8,
33 pub placed: bool,
35}
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct Range {
39 pub class: u8,
41 pub id: u32,
43 pub lower: u32,
45 pub count: u32,
47 pub extra: u32,
50 pub space: u32,
52 pub element: Option<u32>,
54 pub stride: Option<u32>,
56 pub counter: bool,
58 pub variable: Option<ValueId>,
61}
62
63#[derive(Clone, Copy, Debug)]
65struct Node<'a> {
66 m: &'a Module,
67 id: MetadataId,
68 ops: &'a [Option<MetadataId>],
69 what: &'static str,
71}
72
73impl<'a> Node<'a> {
74 fn new(m: &'a Module, at: usize, id: MetadataId, what: &'static str) -> Result<Self> {
76 match m.metadata.get(id) {
77 Some(Metadata::Node(ops)) => Ok(Node { m, id, ops, what }),
78 _ => Err(error(at, format!("{what}: expected a metadata node"))),
79 }
80 }
81 fn bit(&self) -> usize {
82 self.m.metadata_at.get(self.id).copied().unwrap_or(self.m.at)
83 }
84 fn fail(&self, detail: impl std::fmt::Display) -> crate::Error {
86 error(self.bit(), format!("{}: {detail}", self.what))
87 }
88 fn node(&self, id: Option<MetadataId>, what: &'static str) -> Result<Node<'a>> {
90 let id = id.ok_or_else(|| self.fail(format!("{what} is null")))?;
91 Node::new(self.m, self.bit(), id, what)
92 }
93 fn op(&self, i: usize) -> Result<Option<MetadataId>> {
95 self.ops
96 .get(i)
97 .copied()
98 .ok_or_else(|| self.fail(format!("node has no operand {i}")))
99 }
100 fn maybe(&self, i: usize) -> Option<MetadataId> {
102 self.ops.get(i).copied().flatten()
103 }
104 fn int_of(&self, id: Option<MetadataId>, what: &str) -> Result<i64> {
106 match id.and_then(|i| self.m.metadata.get(i)) {
107 Some(Metadata::Value { value, .. }) => match self.m.values.get(*value) {
108 Some(Value::Constant(Constant::Integer(v))) => Ok(*v),
109 Some(Value::Constant(Constant::Null)) => Ok(0),
111 _ => Err(self.fail(format!("{what}: expected an integer constant"))),
112 },
113 _ => Err(self.fail(format!("{what}: expected an integer"))),
114 }
115 }
116 fn int(&self, i: usize, what: &str) -> Result<i64> {
117 self.int_of(self.op(i)?, what)
118 }
119 fn number_of<T: TryFrom<i64>>(&self, id: Option<MetadataId>, what: &str) -> Result<T> {
121 let v = self.int_of(id, what)?;
122 T::try_from(v).map_err(|_| self.fail(format!("{what}: {v} is out of range")))
123 }
124 fn number<T: TryFrom<i64>>(&self, i: usize, what: &str) -> Result<T> {
125 self.number_of(self.op(i)?, what)
126 }
127 fn string_of(&self, id: Option<MetadataId>, what: &str) -> Result<String> {
129 match id.and_then(|i| self.m.metadata.get(i)) {
130 Some(Metadata::String(s)) => std::str::from_utf8(s).map(str::to_owned).map_err(|e| {
131 self.fail(format!(
132 "{what}: not UTF-8 (valid up to byte {})",
133 e.valid_up_to()
134 ))
135 }),
136 _ => Ok(String::new()),
137 }
138 }
139}
140
141fn named<'a>(m: &'a Module, name: &str) -> Option<(usize, &'a [MetadataId])> {
143 let i = m.named_metadata.iter().position(|(n, _)| n == name)?;
144 let (_, ids) = m.named_metadata.get(i)?;
145 Some((
146 m.named_metadata_at.get(i).copied().unwrap_or(m.at),
147 ids.as_slice(),
148 ))
149}
150
151pub fn library_kind(m: &Module) -> Result<u32> {
154 let entry = the_entry(m)?;
155 let properties = entry
156 .node(entry.maybe(4), "entry point properties")
157 .map_err(|_| entry.fail("a library entry point without properties"))?;
158 for pair in properties.ops.chunks(2) {
159 let [tag, value] = pair else {
160 return Err(properties.fail("a tag without a value"));
161 };
162 if properties.int_of(*tag, "property tag")? == 8 {
163 return properties.number_of(*value, "shader kind");
164 }
165 }
166 Err(properties.fail("a library entry point that states no shader kind"))
167}
168
169fn the_entry(m: &Module) -> Result<Node<'_>> {
171 let (at, entries) =
172 named(m, "dx.entryPoints").ok_or_else(|| error(m.at, "the module has no dx.entryPoints metadata"))?;
173 let mut with_function = Vec::new();
174 for &id in entries {
175 let entry = Node::new(m, at, id, "entry point")?;
176 if entry.op(0)?.is_some() {
177 with_function.push(entry);
178 }
179 }
180 match with_function.as_slice() {
181 [entry] => Ok(*entry),
182 _ => Err(error(
183 at,
184 format!(
185 "dx.entryPoints: expected exactly one entry point with a function, found {}",
186 with_function.len()
187 ),
188 )),
189 }
190}
191
192pub fn entry(m: &Module) -> Result<ValueId> {
194 let entry = the_entry(m)?;
195 match entry.op(0)?.and_then(|i| m.metadata.get(i)) {
196 Some(Metadata::Value { value, .. }) => Ok(*value),
197 _ => Err(entry.fail("operand 0 names no function")),
198 }
199}
200
201pub fn num_threads(m: &Module) -> Result<Option<[u32; 3]>> {
204 let entry = the_entry(m)?;
205 let Some(properties) = entry.maybe(4) else {
206 return Ok(None);
207 };
208 let properties = entry.node(Some(properties), "entry point properties")?;
209 for pair in properties.ops.chunks(2) {
210 let [tag, value] = pair else {
211 return Err(properties.fail("a tag without a value"));
212 };
213 let tag = properties.int_of(*tag, "property tag")?;
214 if tag != 4 && tag != 9 {
215 continue;
216 }
217 let mut size = properties.node(*value, "numthreads")?;
218 if tag == 9 {
220 size = size.node(size.op(0)?, "numthreads")?;
221 }
222 return Ok(Some([
223 size.number(0, "numthreads x")?,
224 size.number(1, "numthreads y")?,
225 size.number(2, "numthreads z")?,
226 ]));
227 }
228 Ok(None)
229}
230
231pub fn elements(m: &Module) -> Result<(Vec<Element>, Vec<Element>)> {
233 let [inputs, outputs, _] = signature_lists(m)?;
234 Ok((inputs, outputs))
235}
236
237pub fn patch_elements(m: &Module) -> Result<Vec<Element>> {
239 let [_, _, patch] = signature_lists(m)?;
240 Ok(patch)
241}
242
243#[derive(Clone, Debug, PartialEq)]
246pub struct HullState {
247 pub patch_constant: ValueId,
249 pub input_control_points: u32,
251 pub output_control_points: u32,
253 pub domain: u32,
255 pub partitioning: u32,
257 pub output_primitive: u32,
259 pub max_tess_factor: f32,
261}
262
263fn entry_property(m: &Module, wanted: i64) -> Result<Option<Node<'_>>> {
265 let entry = the_entry(m)?;
266 let Some(properties) = entry.maybe(4) else {
267 return Ok(None);
268 };
269 let properties = entry.node(Some(properties), "entry point properties")?;
270 for pair in properties.ops.chunks(2) {
271 let [tag, value] = pair else {
272 return Err(properties.fail("a tag without a value"));
273 };
274 if properties.int_of(*tag, "property tag")? == wanted {
275 return properties.node(*value, "property").map(Some);
276 }
277 }
278 Ok(None)
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Eq)]
286pub struct GeometryState {
287 pub input: u32,
289 pub max_vertices: u32,
291 pub streams: u32,
293 pub topology: u32,
295 pub instances: u32,
297}
298
299pub fn geometry_state(m: &Module) -> Result<GeometryState> {
301 let state = entry_property(m, 1)?.ok_or_else(|| {
302 the_entry(m).map_or_else(
303 |e| e,
304 |e| e.fail("a geometry program without its state (properties tag 1)"),
305 )
306 })?;
307 Ok(GeometryState {
308 input: state.number(0, "geometry input primitive")?,
309 max_vertices: state.number(1, "geometry maximum vertices")?,
310 streams: state.number(2, "geometry streams")?,
311 topology: state.number(3, "geometry topology")?,
312 instances: state.number(4, "geometry instances")?,
313 })
314}
315
316#[derive(Clone, Copy, Debug, PartialEq, Eq)]
320pub struct MeshState {
321 pub max_vertices: u32,
323 pub max_primitives: u32,
325 pub topology: u32,
327 pub payload: u32,
329}
330
331pub fn mesh_state(m: &Module) -> Result<MeshState> {
333 let state = entry_property(m, 9)?.ok_or_else(|| {
334 the_entry(m).map_or_else(
335 |e| e,
336 |e| e.fail("a mesh program without its state (properties tag 9)"),
337 )
338 })?;
339 let field = |k: usize, what: &str| -> Result<u32> {
341 match state.op(k)? {
342 None => Ok(0),
343 some => state.number_of(some, what),
344 }
345 };
346 Ok(MeshState {
347 max_vertices: field(1, "mesh maximum vertices")?,
348 max_primitives: field(2, "mesh maximum primitives")?,
349 topology: field(3, "mesh topology")?,
350 payload: field(4, "mesh payload size")?,
351 })
352}
353
354pub fn hull_state(m: &Module) -> Result<HullState> {
356 let state = entry_property(m, 3)?.ok_or_else(|| {
357 the_entry(m).map_or_else(
358 |e| e,
359 |e| e.fail("a hull program without its patch state (properties tag 3)"),
360 )
361 })?;
362 let small = |k: usize, what: &str| -> Result<u32> { state.number_of(state.maybe(k), what) };
363 let patch_constant = match state.maybe(0).and_then(|i| m.metadata.get(i)) {
364 Some(Metadata::Value { value, .. }) => *value,
365 _ => return Err(state.fail("a hull program without its patch-constant function")),
366 };
367 let max = match state.maybe(6).and_then(|i| m.metadata.get(i)) {
368 Some(Metadata::Value { value, .. }) => match m.values.get(*value) {
369 Some(Value::Constant(Constant::Float(bits))) => {
370 let bits = u32::try_from(*bits)
371 .map_err(|_| state.fail("the maximum tessellation factor is wider than a float"))?;
372 f32::from_bits(bits)
373 }
374 _ => return Err(state.fail("the maximum tessellation factor is not a float")),
375 },
376 _ => return Err(state.fail("hull state without a maximum tessellation factor")),
377 };
378 Ok(HullState {
379 patch_constant,
380 input_control_points: small(1, "hull input control points")?,
381 output_control_points: small(2, "hull output control points")?,
382 domain: small(3, "hull domain")?,
383 partitioning: small(4, "hull partitioning")?,
384 output_primitive: small(5, "hull output primitive")?,
385 max_tess_factor: max,
386 })
387}
388
389pub fn domain_state(m: &Module) -> Result<(u32, u32)> {
391 let state = entry_property(m, 2)?.ok_or_else(|| {
392 the_entry(m).map_or_else(
393 |e| e,
394 |e| e.fail("a domain program without its patch state (properties tag 2)"),
395 )
396 })?;
397 Ok((
398 state.number_of(state.maybe(0), "domain")?,
399 state.number_of(state.maybe(1), "domain input control points")?,
400 ))
401}
402
403fn signature_lists(m: &Module) -> Result<[Vec<Element>; 3]> {
404 let entry = the_entry(m)?;
405 let Some(signatures) = entry.op(2)? else {
406 return Ok([Vec::new(), Vec::new(), Vec::new()]);
407 };
408 let signatures = entry.node(Some(signatures), "signatures")?;
409 let list = |i: usize| -> Result<Vec<Element>> {
410 let Some(list) = signatures.op(i)? else {
411 return Ok(Vec::new());
412 };
413 let list = signatures.node(Some(list), "signature")?;
414 list.ops
415 .iter()
416 .map(|e| {
417 let e = list.node(*e, "signature element")?;
418 let row = e.int(8, "row")?;
419 let element = Element {
420 id: e.number(0, "element id")?,
421 name: e.string_of(e.op(1)?, "element name")?,
422 index: match e.op(4)? {
423 Some(list) => match e.node(Some(list), "semantic indices")?.ops.first() {
424 Some(first) => e.number_of(*first, "semantic index")?,
425 None => 0,
426 },
427 None => 0,
428 },
429 component_type: e.number(2, "component type")?,
430 system_value: e.number(3, "system value")?,
431 interpolation: e.number(5, "interpolation")?,
432 rows: e.number(6, "rows")?,
433 columns: e.number(7, "columns")?,
434 placed: row >= 0,
435 row: u32::try_from(row.max(0))
436 .map_err(|_| e.fail(format!("row {row} is out of range")))?,
437 column: u8::try_from(e.int(9, "column")?.max(0))
438 .map_err(|_| e.fail("column is out of range"))?,
439 };
440 if element.rows == 0
442 || element.columns == 0
443 || u32::from(element.column)
444 .checked_add(u32::from(element.columns))
445 .is_none_or(|end| end > 4)
446 || element.row.checked_add(element.rows).is_none_or(|end| end > 32)
447 {
448 return Err(e.fail("the element lies outside the 32 four-lane registers"));
449 }
450 Ok(element)
451 })
452 .collect()
453 };
454 Ok([list(0)?, list(1)?, list(2)?])
455}
456
457fn tags(resource: Node<'_>, list: Option<MetadataId>) -> Result<(Option<u32>, Option<u32>)> {
459 let Some(list) = list else {
460 return Ok((None, None));
461 };
462 let ops = resource.node(Some(list), "resource tags")?;
463 let (pairs, rest) = ops.ops.as_chunks::<2>();
464 if !rest.is_empty() {
465 return Err(ops.fail("a tag without its value"));
466 }
467 let (mut element, mut stride) = (None, None);
468 for [tag, value] in pairs {
469 let value = ops.number_of(*value, "tag value")?;
470 match ops.int_of(*tag, "tag")? {
471 0 => element = Some(value),
472 1 => stride = Some(value),
473 _ => {}
474 }
475 }
476 Ok((element, stride))
477}
478
479fn variable_of(m: &Module, id: Option<MetadataId>) -> Option<ValueId> {
481 let Some(Metadata::Value { value, .. }) = id.and_then(|i| m.metadata.get(i)) else {
482 return None;
483 };
484 match m.values.get(*value)? {
485 Value::Constant(Constant::Cast { value, .. }) => Some(*value),
486 Value::Global { .. } => Some(*value),
487 _ => None,
488 }
489}
490
491pub fn ranges(m: &Module) -> Result<Vec<(Range, String, Option<TypeId>)>> {
494 let Some((at, [resources])) = named(m, "dx.resources") else {
495 return Ok(Vec::new());
496 };
497 let lists = Node::new(m, at, *resources, "dx.resources")?;
498 let mut out = Vec::new();
499 for (class, list) in lists.ops.iter().enumerate() {
500 let Some(list) = list else { continue };
501 let class =
502 u8::try_from(class).map_err(|_| lists.fail(format!("resource class {class} is out of range")))?;
503 let list = lists.node(Some(*list), "resource list")?;
504 for r in list.ops {
505 let r = list.node(*r, "resource")?;
506 let ty = match r.op(1)?.and_then(|i| m.metadata.get(i)) {
508 Some(Metadata::Value { ty, .. }) => match m.types.get(*ty) {
509 Some(Type::Pointer(t)) => Some(*t),
510 _ => None,
511 },
512 _ => None,
513 };
514 let extra = r.number(6, "resource field")?;
515 let (element, stride) = match class {
516 0 => tags(r, r.maybe(8))?,
517 1 => tags(r, r.maybe(10))?,
518 _ => (None, None),
519 };
520 out.push((
521 Range {
522 class,
523 id: r.number(0, "resource id")?,
524 lower: r.number(4, "lower bound")?,
525 count: {
527 let v = r.int(5, "range size")?;
528 u32::try_from(v)
529 .or_else(|_| i32::try_from(v).map(|v| u32::from_ne_bytes(v.to_ne_bytes())))
530 .map_err(|_| r.fail(format!("range size {v} is out of range")))?
531 },
532 extra,
533 space: r.number(3, "register space")?,
534 element,
535 stride,
536 counter: class == 1 && r.maybe(8).is_some() && r.int(8, "counter")? != 0,
538 variable: variable_of(m, r.op(1)?),
539 },
540 r.string_of(r.op(2)?, "resource name")?,
541 ty,
542 ));
543 }
544 }
545 Ok(out)
546}
547
548#[derive(Clone, Debug, PartialEq, Eq)]
550pub struct FieldAnnotation {
551 pub name: String,
553 pub offset: u32,
555 pub component: Option<u32>,
557 pub matrix: Option<(u32, u32, u32)>,
560}
561
562pub fn fields(m: &Module, t: TypeId) -> Result<Vec<FieldAnnotation>> {
565 let Some((at, annotations)) = named(m, "dx.typeAnnotations") else {
566 return Ok(Vec::new());
567 };
568 for &a in annotations {
569 let a = Node::new(m, at, a, "type annotations")?;
570 if a.int(0, "annotation kind")? != 0 {
572 continue;
573 }
574 let (pairs, rest) = a.ops.get(1..).unwrap_or_default().as_chunks::<2>();
575 if !rest.is_empty() {
576 return Err(a.fail("a struct type without its annotation"));
577 }
578 for [struct_type, annotation] in pairs {
579 let ty = match struct_type.and_then(|i| m.metadata.get(i)) {
580 Some(Metadata::Value { ty, .. }) => *ty,
581 _ => continue,
582 };
583 if ty != t {
584 continue;
585 }
586 let annotation = a.node(*annotation, "struct annotation")?;
587 let fields = annotation
589 .ops
590 .get(1..)
591 .ok_or_else(|| annotation.fail("a struct annotation without its first operand"))?;
592 return fields
593 .iter()
594 .map(|f| {
595 let f = annotation.node(*f, "field annotation")?;
596 let (mut name, mut offset, mut component, mut matrix) = (String::new(), 0, None, None);
597 let (tags, rest) = f.ops.as_chunks::<2>();
598 if !rest.is_empty() {
599 return Err(f.fail("a tag without its value"));
600 }
601 for [tag, value] in tags {
602 match f.int_of(*tag, "field tag")? {
603 6 => name = f.string_of(*value, "field name")?,
604 3 => offset = f.number_of(*value, "field offset")?,
605 2 => {
606 let ops = f.node(*value, "matrix annotation")?;
607 matrix = Some((
608 ops.number(0, "matrix rows")?,
609 ops.number(1, "matrix columns")?,
610 ops.number(2, "matrix orientation")?,
611 ));
612 }
613 7 => component = Some(f.number_of(*value, "field component type")?),
614 _ => {}
615 }
616 }
617 Ok(FieldAnnotation {
618 name,
619 offset,
620 component,
621 matrix,
622 })
623 })
624 .collect();
625 }
626 }
627 Ok(Vec::new())
628}
629
630#[derive(Clone, Debug, PartialEq, Eq)]
632pub struct SignatureElement {
633 pub name: String,
635 pub index: u32,
637 pub system_value: u32,
639 pub component_type: u32,
641 pub register: u32,
643 pub mask: u8,
645 pub read_write_mask: u8,
647 pub bit: usize,
649}
650
651pub fn signature(part: &[u8]) -> Result<Vec<SignatureElement>> {
655 let bit = |byte: usize| byte.saturating_mul(8);
656 let v = primase_foundations::bytes::View::new(part);
657 let word = |at: usize| -> Result<u32> {
658 v.array::<4>(at)
659 .map(u32::from_le_bytes)
660 .map_err(|e| error(bit(at), e.to_string()))
661 };
662 let field = |record: usize, delta: usize| -> Result<u32> {
664 word(
665 record
666 .checked_add(delta)
667 .ok_or_else(|| error(bit(record), "signature element offset"))?,
668 )
669 };
670 let n = word(0)? as usize;
671 let first = word(4)? as usize;
672 let table = n
674 .checked_mul(32)
675 .and_then(|size| first.checked_add(size))
676 .filter(|&end| end <= part.len())
677 .ok_or_else(|| {
678 error(
679 0,
680 format!(
681 "{n} records of 32 bytes from byte {first} do not fit the part's {} bytes",
682 part.len()
683 ),
684 )
685 })?;
686 let mut out = Vec::with_capacity(n);
687 for i in 0..n {
688 let e = i
689 .checked_mul(32)
690 .and_then(|o| o.checked_add(first))
691 .filter(|&e| e < table)
692 .ok_or_else(|| error(32, "signature element offset"))?;
693 let name_at = field(e, 4)? as usize;
694 let tail = part
695 .get(name_at..)
696 .ok_or_else(|| error(bit(name_at), "signature name offset"))?;
697 let end = tail
698 .iter()
699 .position(|&b| b == 0)
700 .filter(|&n| n <= 256)
701 .ok_or_else(|| error(bit(name_at), "signature name"))?;
702 let name = tail
703 .get(..end)
704 .and_then(|name| std::str::from_utf8(name).ok())
705 .ok_or_else(|| error(bit(name_at), "signature name is not UTF-8"))?;
706 let [mask, read_write_mask] = v
707 .array::<2>(
708 e.checked_add(24)
709 .ok_or_else(|| error(bit(e), "signature element offset"))?,
710 )
711 .map_err(|x| error(bit(e), x.to_string()))?;
712 out.push(SignatureElement {
713 name: name.to_owned(),
714 index: field(e, 8)?,
715 system_value: field(e, 12)?,
716 component_type: field(e, 16)?,
717 register: field(e, 20)?,
718 mask,
719 read_write_mask,
720 bit: bit(e),
721 });
722 }
723 Ok(out)
724}