1#[allow(unused_imports)]
3use alloc::{
4 boxed::Box,
5 format,
6 string::{String, ToString},
7 vec,
8 vec::Vec,
9};
10
11use serde::Serialize;
12
13use super::{BufferKind, BufferLayout, D3dClass, Resource, ResourceId, ResourceKind, Stage, StorageTarget};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
17#[serde(rename_all = "snake_case")]
18pub enum ScalarType {
19 Bool,
21 Sint,
23 Uint,
25 Float,
27 Double,
29}
30impl ScalarType {
31 pub fn bytes(self) -> u32 {
33 match self {
34 Self::Double => 8,
35 _ => 4,
36 }
37 }
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
42pub struct Value {
43 pub scalar: ScalarType,
45 pub width: u8,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
53pub struct Label {
54 pub name: String,
56 pub index: u32,
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
62#[serde(rename_all = "snake_case")]
63pub enum SystemValue {
64 ClipPosition,
66 FragCoord,
68 FrontFacing,
70 VertexIndex,
72 InstanceIndex,
75 PrimitiveIndex,
77 SampleIndex,
79 RenderTargetArrayIndex,
81 ViewportArrayIndex,
83 ClipDistance,
85 CullDistance,
87 Target(u32),
89 Depth,
91 DepthGreaterEqual,
93 DepthLessEqual,
95 StencilRef,
97 Coverage,
99 InnerCoverage,
101 GroupId,
103 GroupThreadId,
105 DispatchThreadId,
108 GroupIndex,
110 LaneIndex,
112 LaneCount,
114 DispatchRaysIndex,
116 DispatchRaysDimensions,
118 WorldRayOrigin,
120 WorldRayDirection,
122 ObjectRayOrigin,
124 ObjectRayDirection,
126 RayTMin,
129 RayTCurrent,
132 RayFlags,
134 InstanceId,
136 GeometryIndex,
138 HitKind,
140 ObjectToWorld,
143 WorldToObject,
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
149#[serde(rename_all = "snake_case")]
150pub enum InterpMode {
151 Perspective,
153 Linear,
155 Flat,
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
161#[serde(rename_all = "snake_case")]
162pub enum InterpSampling {
163 Center,
165 Centroid,
167 Sample,
169}
170
171#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
173pub struct Interpolation {
174 pub mode: InterpMode,
176 pub sampling: InterpSampling,
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
183pub struct D3dSignature {
184 pub register: u32,
186 pub mask: u8,
188 pub read_write_mask: u8,
191}
192
193#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
195pub struct VaryingProvenance {
196 pub d3d: Option<D3dSignature>,
198}
199
200#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
203pub struct Varying {
204 pub label: Label,
206 pub system: Option<SystemValue>,
208 pub value: Value,
210 pub interp: Option<Interpolation>,
214 pub used: bool,
217 pub provenance: VaryingProvenance,
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
223#[serde(rename_all = "snake_case")]
224pub enum TessDomain {
225 Isoline,
227 Triangle,
229 Quad,
231}
232
233#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
235#[serde(rename_all = "snake_case")]
236pub enum Partitioning {
237 Integer,
239 Pow2,
241 FractionalOdd,
243 FractionalEven,
245}
246
247#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
249#[serde(rename_all = "snake_case")]
250pub enum TessTopology {
251 Point,
253 Line,
255 TriangleCw,
257 TriangleCcw,
259}
260
261#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
265pub struct HullFacts {
266 pub domain: TessDomain,
268 pub partitioning: Partitioning,
270 pub topology: TessTopology,
272 pub input_control_points: u32,
274 pub output_control_points: u32,
276 pub max_tess_factor_bits: u32,
278 pub patch_constants: Vec<Varying>,
280}
281
282#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
285pub struct DomainFacts {
286 pub domain: TessDomain,
288 pub input_control_points: u32,
290 pub patch_constants: Vec<Varying>,
292}
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
296#[serde(rename_all = "snake_case")]
297pub enum GsInput {
298 Point,
300 Line,
302 Triangle,
304 LineAdjacency,
306 TriangleAdjacency,
308}
309impl GsInput {
310 pub fn vertices(self) -> u32 {
312 match self {
313 Self::Point => 1,
314 Self::Line => 2,
315 Self::Triangle => 3,
316 Self::LineAdjacency => 4,
317 Self::TriangleAdjacency => 6,
318 }
319 }
320}
321
322#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
324#[serde(rename_all = "snake_case")]
325pub enum GsOutput {
326 PointList,
328 LineStrip,
330 TriangleStrip,
332}
333
334#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
337pub struct GeometryFacts {
338 pub input: GsInput,
340 pub output: GsOutput,
342 pub max_vertices: u32,
344 pub instances: u32,
346}
347
348#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
350#[serde(rename_all = "snake_case")]
351pub enum MeshTopology {
352 Line,
354 Triangle,
356}
357impl MeshTopology {
358 pub fn indices(self) -> u32 {
360 match self {
361 Self::Line => 2,
362 Self::Triangle => 3,
363 }
364 }
365}
366
367#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
370pub struct MeshFacts {
371 pub topology: MeshTopology,
373 pub max_vertices: u32,
375 pub max_primitives: u32,
377 pub payload: u32,
380}
381
382#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
385pub struct AmplificationFacts {
386 pub payload: u32,
389}
390
391pub const MAX_TASK_PAYLOAD: u32 = 16384;
393
394#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
396#[serde(rename_all = "snake_case")]
397pub enum HitGroupKind {
398 Triangles,
400 ProceduralPrimitives,
402}
403
404#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
407pub struct HitGroup {
408 pub name: String,
410 pub kind: HitGroupKind,
412 pub any_hit: Option<String>,
414 pub closest_hit: Option<String>,
416 pub intersection: Option<String>,
418}
419
420#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
422#[serde(rename_all = "snake_case")]
423pub enum Subobject {
424 StateObjectConfig {
426 name: String,
428 flags: u32,
430 },
431 GlobalRootSignature {
433 name: String,
435 signature: Vec<u8>,
437 },
438 LocalRootSignature {
440 name: String,
442 signature: Vec<u8>,
444 },
445 ExportsAssociation {
447 name: String,
449 subobject: String,
451 exports: Vec<String>,
453 },
454 ShaderConfig {
456 name: String,
458 max_payload_bytes: u32,
460 max_attribute_bytes: u32,
462 },
463 PipelineConfig {
465 name: String,
467 max_recursion_depth: u32,
469 flags: Option<u32>,
471 },
472 HitGroup(HitGroup),
474}
475
476#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
479pub struct RayTracingFacts {
480 pub export: String,
482 pub subobjects: Vec<Subobject>,
484}
485
486#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
488#[serde(rename_all = "snake_case")]
489#[non_exhaustive]
490pub enum StageFacts {
491 #[default]
493 None,
494 VertexWithoutPosition,
497 Hull(HullFacts),
499 Domain(DomainFacts),
501 Geometry(GeometryFacts),
503 RayTracing(RayTracingFacts),
505 Mesh(MeshFacts),
507 Amplification(AmplificationFacts),
509}
510
511#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
513pub struct Interface {
514 pub stage: Stage,
516 pub inputs: Vec<Varying>,
518 pub outputs: Vec<Varying>,
520 pub resources: Vec<Resource>,
522 pub facts: StageFacts,
524}
525impl Interface {
526 fn verify_facts(&self) -> Result<(), String> {
529 let patch = |what: &str, count: u32, constants: &[Varying]| -> Result<(), String> {
530 if !(1..=32).contains(&count) {
531 return Err(format!("{what}: {count} control points, not 1 to 32"));
532 }
533 for (i, v) in constants.iter().enumerate() {
534 if !(1..=4).contains(&v.value.width) {
535 return Err(format!(
536 "patch constant {}{} has a width of {}, not 1 to 4",
537 v.label.name, v.label.index, v.value.width
538 ));
539 }
540 if constants.iter().take(i).any(|other| other.label == v.label) {
541 return Err(format!(
542 "patch constant {}{} is listed twice",
543 v.label.name, v.label.index
544 ));
545 }
546 }
547 Ok(())
548 };
549 match (&self.stage, &self.facts) {
550 (Stage::Hull, StageFacts::Hull(h)) => {
551 patch("hull input", h.input_control_points, &h.patch_constants)?;
552 patch("hull output", h.output_control_points, &[])?;
553 let factor = f32::from_bits(h.max_tess_factor_bits);
554 if !(1.0..=64.0).contains(&factor) {
555 return Err(format!("a maximum tessellation factor of {factor}"));
556 }
557 Ok(())
558 }
559 (Stage::Domain, StageFacts::Domain(d)) => {
560 patch("domain", d.input_control_points, &d.patch_constants)
561 }
562 (Stage::Geometry, StageFacts::Geometry(g)) => {
563 if !(1..=1024).contains(&g.max_vertices) || !(1..=32).contains(&g.instances) {
564 return Err(format!(
565 "a geometry program of {} vertices and {} instances",
566 g.max_vertices, g.instances
567 ));
568 }
569 Ok(())
570 }
571 (Stage::Vertex, StageFacts::VertexWithoutPosition) => Ok(()),
572 (Stage::Hull | Stage::Domain, _) => {
573 Err(format!("a {:?} program without its patch facts", self.stage))
574 }
575 (Stage::Mesh, StageFacts::Mesh(m)) => {
576 if !(1..=256).contains(&m.max_vertices) || !(1..=256).contains(&m.max_primitives) {
577 return Err(format!(
578 "a mesh program of {} vertices and {} primitives",
579 m.max_vertices, m.max_primitives
580 ));
581 }
582 if m.payload > MAX_TASK_PAYLOAD || m.payload % 4 != 0 {
583 return Err(format!(
584 "a mesh program reading a task payload of {} bytes",
585 m.payload
586 ));
587 }
588 Ok(())
589 }
590 (Stage::Mesh, _) => Err("a mesh program without its facts".into()),
591 (Stage::Amplification, StageFacts::Amplification(a)) => {
592 if !(4..=MAX_TASK_PAYLOAD).contains(&a.payload) || a.payload % 4 != 0 {
593 return Err(format!(
594 "an amplification program of a task payload of {} bytes",
595 a.payload
596 ));
597 }
598 Ok(())
599 }
600 (Stage::Amplification, _) => Err("an amplification program without its facts".into()),
601 (stage, StageFacts::RayTracing(r)) if stage.is_ray_tracing() => {
602 if r.export.is_empty() {
603 return Err(format!("a {stage:?} program that names no export"));
604 }
605 Ok(())
606 }
607 (_, StageFacts::None) => Ok(()),
608 (stage, facts) => Err(format!("{stage:?} program with {facts:?}")),
609 }
610 }
611
612 pub fn resource(&self, id: ResourceId) -> Option<&Resource> {
614 self.resources.iter().find(|r| r.id == id)
615 }
616
617 pub fn verify(&self) -> Result<(), String> {
624 self.verify_facts()?;
625 for (direction, varyings) in [("input", &self.inputs), ("output", &self.outputs)] {
626 for (i, v) in varyings.iter().enumerate() {
627 if let Some(SystemValue::Target(n)) = v.system {
628 if direction == "input" || self.stage != Stage::Pixel {
629 return Err(format!(
630 "{direction} {}{} is colour output {n} of a {:?} stage: only a pixel stage's outputs are",
631 v.label.name, v.label.index, self.stage
632 ));
633 }
634 if n > 7 {
635 return Err(format!(
636 "output {}{} is colour output {n}, not 0 to 7",
637 v.label.name, v.label.index
638 ));
639 }
640 }
641 if !(1..=4).contains(&v.value.width) {
642 return Err(format!(
643 "{direction} {}{} has a width of {}, not 1 to 4",
644 v.label.name, v.label.index, v.value.width
645 ));
646 }
647 if varyings.iter().take(i).any(|other| other.label == v.label) {
648 return Err(format!(
649 "{direction} {}{} is listed twice",
650 v.label.name, v.label.index
651 ));
652 }
653 }
654 }
655 let width_ok = |w: u8| (1..=4).contains(&w);
656 for (i, r) in self.resources.iter().enumerate() {
657 let name = &r.provenance.name;
658 if self.resources.iter().take(i).any(|other| other.id == r.id) {
659 return Err(format!("resource id {} ({name}) is used twice", r.id.0));
660 }
661 if r.count == 0 {
662 return Err(format!("resource {name} spans no bindings"));
663 }
664 let buffer = |kind: &BufferKind| match kind {
665 BufferKind::Typed(v) if !width_ok(v.width) => {
666 Err(format!("buffer {name} has elements of {} lanes", v.width))
667 }
668 BufferKind::Structured { stride: 0 } => Err(format!("buffer {name} has a stride of 0")),
669 BufferKind::Structured { stride } if !stride.is_multiple_of(4) || *stride > 2048 => Err(
670 format!("buffer {name} has a stride of {stride}, not a multiple of 4 up to 2048"),
671 ),
672 _ => Ok(()),
673 };
674 match &r.kind {
675 ResourceKind::Buffer(kind)
676 | ResourceKind::Storage {
677 target: StorageTarget::Buffer(kind),
678 ..
679 } => {
680 buffer(kind)?;
681 }
682 ResourceKind::ConstantBuffer { layout } => layout_fits(name, layout)?,
683 _ => {}
684 }
685 if let Some(layout) = &r.provenance.layout {
686 layout_fits(name, layout)?;
687 }
688 if let Some(target) = r.provenance.counter_of {
689 let owner = self.resource(target).ok_or_else(|| {
690 format!(
691 "resource {name} is the counter of resource {}, which is not there",
692 target.0
693 )
694 })?;
695 if target == r.id || !matches!(owner.kind, ResourceKind::Storage { .. }) {
696 return Err(format!(
697 "resource {name} is the counter of resource {} ({}), which is not another storage resource",
698 target.0, owner.provenance.name
699 ));
700 }
701 if r.provenance.d3d.is_some() {
702 return Err(format!(
703 "resource {name} is a hidden counter and has a binding of its own"
704 ));
705 }
706 }
707 if let Some(d3d) = r.provenance.d3d {
708 let class = match &r.kind {
709 ResourceKind::ConstantBuffer { .. } => D3dClass::ConstantBuffer,
710 ResourceKind::Texture { .. }
711 | ResourceKind::Buffer(_)
712 | ResourceKind::AccelerationStructure => D3dClass::ShaderResource,
713 ResourceKind::Sampler { .. } => D3dClass::Sampler,
714 ResourceKind::Storage { .. } => D3dClass::UnorderedAccess,
715 ResourceKind::PipelineState(_) => {
716 return Err(format!(
717 "resource {name} is pipeline state, set with the draw, and has a binding"
718 ));
719 }
720 };
721 if d3d.class != class {
722 return Err(format!(
723 "resource {name} is bound as {:?} but its kind is read through {class:?}",
724 d3d.class
725 ));
726 }
727 }
728 }
729 let span = |r: &Resource| {
731 r.provenance.d3d.map(|d| {
732 let first = u64::from(d.register);
733 let end = if r.is_unbounded() {
734 u64::MAX
735 } else {
736 first.saturating_add(u64::from(r.count))
737 };
738 (d.class, d.space, first, end)
739 })
740 };
741 for (i, a) in self.resources.iter().enumerate() {
742 let Some((class, space, first, end)) = span(a) else {
743 continue;
744 };
745 for b in self.resources.iter().take(i) {
746 if let Some((c, s, f, e)) = span(b)
747 && (c, s) == (class, space)
748 && first < e
749 && f < end
750 {
751 return Err(format!(
752 "resources {} and {} share a {class:?} register of space {space}",
753 b.provenance.name, a.provenance.name
754 ));
755 }
756 }
757 }
758 Ok(())
759 }
760}
761
762fn layout_fits(name: &str, layout: &BufferLayout) -> Result<(), String> {
764 for c in &layout.constants {
765 let size =
766 c.ty.size()
767 .ok_or_else(|| format!("constant {name}.{} overflows", c.name))?;
768 if c.offset.checked_add(size).is_none_or(|end| end > layout.size) {
769 return Err(format!(
770 "constant {name}.{} ({size} bytes at {}) reaches past the end of the {}-byte buffer",
771 c.name, c.offset, layout.size
772 ));
773 }
774 }
775 Ok(())
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781 use crate::interface::{
782 BufferLayout, Constant, ConstantType, D3dBinding, ResourceProvenance, SampleType, TextureDim,
783 };
784
785 fn pixel() -> Interface {
786 Interface {
787 stage: Stage::Pixel,
788 inputs: Vec::new(),
789 outputs: Vec::new(),
790 resources: Vec::new(),
791 facts: Default::default(),
792 }
793 }
794 fn target(index: u32, system: Option<SystemValue>) -> Varying {
795 Varying {
796 label: Label {
797 name: "SV_Target".into(),
798 index,
799 },
800 system,
801 value: Value {
802 scalar: ScalarType::Float,
803 width: 4,
804 },
805 interp: None,
806 used: true,
807 provenance: Default::default(),
808 }
809 }
810 fn texture() -> ResourceKind {
811 ResourceKind::Texture {
812 dim: TextureDim::D2,
813 arrayed: false,
814 multisampled: false,
815 sample: SampleType::Float,
816 }
817 }
818 fn res(
819 id: u32,
820 name: &str,
821 kind: ResourceKind,
822 d3d: Option<(D3dClass, u32, u32)>,
823 count: u32,
824 ) -> Resource {
825 Resource {
826 id: ResourceId(id),
827 kind,
828 count,
829 provenance: ResourceProvenance {
830 name: name.into(),
831 d3d: d3d.map(|(class, register, space)| D3dBinding {
832 class,
833 register,
834 space,
835 }),
836 ..ResourceProvenance::default()
837 },
838 }
839 }
840 fn storage() -> ResourceKind {
841 ResourceKind::Storage {
842 target: StorageTarget::Buffer(BufferKind::Raw),
843 access: crate::interface::Access::ReadWrite,
844 }
845 }
846 fn broken(i: &Interface, expect: &str) {
847 let message = i.verify().unwrap_err();
848 assert!(message.contains(expect), "{expect}: {message}");
849 }
850
851 #[test]
852 fn a_colour_output_is_a_pixel_outputs_target_of_0_to_7() {
853 let mut i = pixel();
854 i.outputs = vec![
855 target(0, Some(SystemValue::Target(0))),
856 target(7, Some(SystemValue::Target(7))),
857 ];
858 assert_eq!(i.verify(), Ok(()));
859 i.outputs.push(target(8, Some(SystemValue::Target(8))));
860 broken(&i, "colour output 8, not 0 to 7");
861 let mut i = pixel();
863 i.inputs = vec![target(0, Some(SystemValue::Target(0)))];
864 broken(&i, "only a pixel stage's outputs are");
865 let mut i = pixel();
866 i.stage = Stage::Vertex;
867 i.outputs = vec![target(0, Some(SystemValue::Target(0)))];
868 broken(&i, "only a pixel stage's outputs are");
869 }
870
871 #[test]
872 fn a_structured_buffers_stride_is_a_multiple_of_4_up_to_2048() {
873 for (stride, ok) in [
874 (4, true),
875 (16, true),
876 (2048, true),
877 (0, false),
878 (6, false),
879 (2052, false),
880 ] {
881 let mut i = pixel();
882 i.resources = vec![res(
883 0,
884 "b",
885 ResourceKind::Buffer(BufferKind::Structured { stride }),
886 Some((D3dClass::ShaderResource, 0, 0)),
887 1,
888 )];
889 assert_eq!(i.verify().is_ok(), ok, "stride {stride}");
890 }
891 }
892
893 #[test]
894 fn a_tbuffers_members_lie_inside_its_layout() {
895 let layout = |offset| BufferLayout {
896 size: 16,
897 constants: vec![Constant {
898 name: "m".into(),
899 offset,
900 ty: ConstantType::Vector(Value {
901 scalar: ScalarType::Float,
902 width: 4,
903 }),
904 default: None,
905 }],
906 };
907 let mut view = res(
908 0,
909 "tb",
910 ResourceKind::Buffer(BufferKind::Typed(Value {
911 scalar: ScalarType::Float,
912 width: 4,
913 })),
914 Some((D3dClass::ShaderResource, 0, 0)),
915 1,
916 );
917 view.provenance.layout = Some(layout(0));
918 let mut i = pixel();
919 i.resources = vec![view.clone()];
920 assert_eq!(i.verify(), Ok(()));
921 i.resources[0].provenance.layout = Some(layout(4));
922 broken(&i, "reaches past the end");
923 }
924
925 #[test]
926 fn a_hidden_counter_belongs_to_a_storage_resource_and_has_no_binding() {
927 let counter = |of: u32, d3d| {
928 let mut c = res(1, "c", ResourceKind::Buffer(BufferKind::Raw), d3d, 1);
929 c.provenance.counter_of = Some(ResourceId(of));
930 c
931 };
932 let uav = res(0, "u", storage(), Some((D3dClass::UnorderedAccess, 0, 0)), 1);
933 let mut i = pixel();
934 i.resources = vec![uav.clone(), counter(0, None)];
935 assert_eq!(i.verify(), Ok(()));
936 i.resources = vec![uav.clone(), counter(5, None)];
937 broken(&i, "which is not there");
938 i.resources = vec![uav.clone(), counter(1, None)];
939 broken(&i, "not another storage resource");
940 i.resources = vec![
942 res(0, "t", texture(), Some((D3dClass::ShaderResource, 0, 0)), 1),
943 counter(0, None),
944 ];
945 broken(&i, "not another storage resource");
946 i.resources = vec![uav, counter(0, Some((D3dClass::ShaderResource, 3, 0)))];
948 broken(&i, "binding of its own");
949 }
950
951 #[test]
952 fn a_register_class_fits_the_kind_and_a_register_holds_one_resource() {
953 let cb = ResourceKind::ConstantBuffer {
954 layout: BufferLayout {
955 size: 16,
956 constants: Vec::new(),
957 },
958 };
959 let kinds = [
960 (cb.clone(), D3dClass::ConstantBuffer),
961 (texture(), D3dClass::ShaderResource),
962 (ResourceKind::Buffer(BufferKind::Raw), D3dClass::ShaderResource),
963 (ResourceKind::AccelerationStructure, D3dClass::ShaderResource),
964 (ResourceKind::Sampler { comparison: false }, D3dClass::Sampler),
965 (storage(), D3dClass::UnorderedAccess),
966 ];
967 for (kind, class) in &kinds {
968 let mut i = pixel();
969 i.resources = vec![res(0, "r", kind.clone(), Some((*class, 0, 0)), 1)];
970 assert_eq!(i.verify(), Ok(()), "{kind:?}");
971 for (_, other) in kinds.iter().filter(|(_, c)| c != class) {
973 i.resources = vec![res(0, "r", kind.clone(), Some((*other, 0, 0)), 1)];
974 broken(&i, "its kind is read through");
975 }
976 }
977 let t = |id, register, space, count| {
978 res(
979 id,
980 &format!("t{id}"),
981 texture(),
982 Some((D3dClass::ShaderResource, register, space)),
983 count,
984 )
985 };
986 let mut i = pixel();
987 i.resources = vec![t(0, 2, 0, 1), t(1, 2, 1, 1), t(2, 3, 0, 1)];
989 assert_eq!(i.verify(), Ok(()));
990 i.resources.push(res(
991 3,
992 "s",
993 ResourceKind::Sampler { comparison: false },
994 Some((D3dClass::Sampler, 2, 0)),
995 1,
996 ));
997 assert_eq!(i.verify(), Ok(()));
998 i.resources = vec![t(0, 2, 0, 1), t(1, 2, 0, 1)];
1000 broken(&i, "share a ShaderResource register of space 0");
1001 i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1)];
1003 assert_eq!(i.verify(), Ok(()));
1004 i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1), t(2, 5, 0, 1)];
1005 broken(&i, "t0 and t2 share");
1006 i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 90, 0, 1)];
1007 broken(&i, "share a ShaderResource");
1008 i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 1, 0, 1), t(2, 2, 1, 1)];
1009 assert_eq!(i.verify(), Ok(()));
1010 }
1011
1012 #[test]
1013 fn a_varying_serializes_with_its_provenance() {
1014 let v = Varying {
1015 label: Label {
1016 name: "TEXCOORD".into(),
1017 index: 5,
1018 },
1019 system: None,
1020 value: Value {
1021 scalar: ScalarType::Float,
1022 width: 2,
1023 },
1024 interp: Some(Interpolation {
1025 mode: InterpMode::Perspective,
1026 sampling: InterpSampling::Center,
1027 }),
1028 used: true,
1029 provenance: VaryingProvenance {
1030 d3d: Some(D3dSignature {
1031 register: 3,
1032 mask: 0b1100,
1033 read_write_mask: 0b1100,
1034 }),
1035 },
1036 };
1037 assert_eq!(
1038 serde_json::to_value(&v).unwrap(),
1039 serde_json::json!({
1040 "label": {"name": "TEXCOORD", "index": 5},
1041 "system": null,
1042 "value": {"scalar": "float", "width": 2},
1043 "interp": {"mode": "perspective", "sampling": "center"},
1044 "used": true,
1045 "provenance": {"d3d": {"register": 3, "mask": 12, "read_write_mask": 12}},
1046 })
1047 );
1048 assert_eq!(
1049 serde_json::to_value(SystemValue::Target(2)).unwrap(),
1050 serde_json::json!({"target": 2})
1051 );
1052 }
1053}