1use serde::Serialize;
4
5use super::{BufferKind, BufferLayout, D3dClass, Resource, ResourceId, ResourceKind, Stage, StorageTarget};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ScalarType {
11 Bool,
13 Sint,
15 Uint,
17 Float,
19 Double,
21}
22impl ScalarType {
23 pub fn bytes(self) -> u32 {
25 match self {
26 Self::Double => 8,
27 _ => 4,
28 }
29 }
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
34pub struct Value {
35 pub scalar: ScalarType,
37 pub width: u8,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
45pub struct Label {
46 pub name: String,
48 pub index: u32,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
54#[serde(rename_all = "snake_case")]
55pub enum SystemValue {
56 ClipPosition,
58 FragCoord,
60 FrontFacing,
62 VertexIndex,
64 InstanceIndex,
67 PrimitiveIndex,
69 SampleIndex,
71 RenderTargetArrayIndex,
73 ViewportArrayIndex,
75 ClipDistance,
77 CullDistance,
79 Target(u32),
81 Depth,
83 DepthGreaterEqual,
85 DepthLessEqual,
87 StencilRef,
89 Coverage,
91 InnerCoverage,
93 GroupId,
95 GroupThreadId,
97 DispatchThreadId,
100 GroupIndex,
102 LaneIndex,
104 LaneCount,
106 DispatchRaysIndex,
108 DispatchRaysDimensions,
110 WorldRayOrigin,
112 WorldRayDirection,
114 ObjectRayOrigin,
116 ObjectRayDirection,
118 RayTMin,
121 RayTCurrent,
124 RayFlags,
126 InstanceId,
128 GeometryIndex,
130 HitKind,
132 ObjectToWorld,
135 WorldToObject,
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
141#[serde(rename_all = "snake_case")]
142pub enum InterpMode {
143 Perspective,
145 Linear,
147 Flat,
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
153#[serde(rename_all = "snake_case")]
154pub enum InterpSampling {
155 Center,
157 Centroid,
159 Sample,
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
165pub struct Interpolation {
166 pub mode: InterpMode,
168 pub sampling: InterpSampling,
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
175pub struct D3dSignature {
176 pub register: u32,
178 pub mask: u8,
180 pub read_write_mask: u8,
183}
184
185#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
187pub struct VaryingProvenance {
188 pub d3d: Option<D3dSignature>,
190}
191
192#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
195pub struct Varying {
196 pub label: Label,
198 pub system: Option<SystemValue>,
200 pub value: Value,
202 pub interp: Option<Interpolation>,
206 pub used: bool,
209 pub provenance: VaryingProvenance,
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
215#[serde(rename_all = "snake_case")]
216pub enum TessDomain {
217 Isoline,
219 Triangle,
221 Quad,
223}
224
225#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
227#[serde(rename_all = "snake_case")]
228pub enum Partitioning {
229 Integer,
231 Pow2,
233 FractionalOdd,
235 FractionalEven,
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
241#[serde(rename_all = "snake_case")]
242pub enum TessTopology {
243 Point,
245 Line,
247 TriangleCw,
249 TriangleCcw,
251}
252
253#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
257pub struct HullFacts {
258 pub domain: TessDomain,
260 pub partitioning: Partitioning,
262 pub topology: TessTopology,
264 pub input_control_points: u32,
266 pub output_control_points: u32,
268 pub max_tess_factor_bits: u32,
270 pub patch_constants: Vec<Varying>,
272}
273
274#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
277pub struct DomainFacts {
278 pub domain: TessDomain,
280 pub input_control_points: u32,
282 pub patch_constants: Vec<Varying>,
284}
285
286#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
288#[serde(rename_all = "snake_case")]
289pub enum GsInput {
290 Point,
292 Line,
294 Triangle,
296 LineAdjacency,
298 TriangleAdjacency,
300}
301impl GsInput {
302 pub fn vertices(self) -> u32 {
304 match self {
305 Self::Point => 1,
306 Self::Line => 2,
307 Self::Triangle => 3,
308 Self::LineAdjacency => 4,
309 Self::TriangleAdjacency => 6,
310 }
311 }
312}
313
314#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
316#[serde(rename_all = "snake_case")]
317pub enum GsOutput {
318 PointList,
320 LineStrip,
322 TriangleStrip,
324}
325
326#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
329pub struct GeometryFacts {
330 pub input: GsInput,
332 pub output: GsOutput,
334 pub max_vertices: u32,
336 pub instances: u32,
338}
339
340#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
342#[serde(rename_all = "snake_case")]
343pub enum MeshTopology {
344 Line,
346 Triangle,
348}
349impl MeshTopology {
350 pub fn indices(self) -> u32 {
352 match self {
353 Self::Line => 2,
354 Self::Triangle => 3,
355 }
356 }
357}
358
359#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
362pub struct MeshFacts {
363 pub topology: MeshTopology,
365 pub max_vertices: u32,
367 pub max_primitives: u32,
369}
370
371#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
373#[serde(rename_all = "snake_case")]
374pub enum HitGroupKind {
375 Triangles,
377 ProceduralPrimitives,
379}
380
381#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
384pub struct HitGroup {
385 pub name: String,
387 pub kind: HitGroupKind,
389 pub any_hit: Option<String>,
391 pub closest_hit: Option<String>,
393 pub intersection: Option<String>,
395}
396
397#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
399#[serde(rename_all = "snake_case")]
400pub enum Subobject {
401 StateObjectConfig {
403 name: String,
405 flags: u32,
407 },
408 GlobalRootSignature {
410 name: String,
412 signature: Vec<u8>,
414 },
415 LocalRootSignature {
417 name: String,
419 signature: Vec<u8>,
421 },
422 ExportsAssociation {
424 name: String,
426 subobject: String,
428 exports: Vec<String>,
430 },
431 ShaderConfig {
433 name: String,
435 max_payload_bytes: u32,
437 max_attribute_bytes: u32,
439 },
440 PipelineConfig {
442 name: String,
444 max_recursion_depth: u32,
446 flags: Option<u32>,
448 },
449 HitGroup(HitGroup),
451}
452
453#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
456pub struct RayTracingFacts {
457 pub export: String,
459 pub subobjects: Vec<Subobject>,
461}
462
463#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
465#[serde(rename_all = "snake_case")]
466#[non_exhaustive]
467pub enum StageFacts {
468 #[default]
470 None,
471 VertexWithoutPosition,
474 Hull(HullFacts),
476 Domain(DomainFacts),
478 Geometry(GeometryFacts),
480 RayTracing(RayTracingFacts),
482 Mesh(MeshFacts),
484}
485
486#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
488pub struct Interface {
489 pub stage: Stage,
491 pub inputs: Vec<Varying>,
493 pub outputs: Vec<Varying>,
495 pub resources: Vec<Resource>,
497 pub facts: StageFacts,
499}
500impl Interface {
501 fn verify_facts(&self) -> Result<(), String> {
504 let patch = |what: &str, count: u32, constants: &[Varying]| -> Result<(), String> {
505 if !(1..=32).contains(&count) {
506 return Err(format!("{what}: {count} control points, not 1 to 32"));
507 }
508 for (i, v) in constants.iter().enumerate() {
509 if !(1..=4).contains(&v.value.width) {
510 return Err(format!(
511 "patch constant {}{} has a width of {}, not 1 to 4",
512 v.label.name, v.label.index, v.value.width
513 ));
514 }
515 if constants.iter().take(i).any(|other| other.label == v.label) {
516 return Err(format!(
517 "patch constant {}{} is listed twice",
518 v.label.name, v.label.index
519 ));
520 }
521 }
522 Ok(())
523 };
524 match (&self.stage, &self.facts) {
525 (Stage::Hull, StageFacts::Hull(h)) => {
526 patch("hull input", h.input_control_points, &h.patch_constants)?;
527 patch("hull output", h.output_control_points, &[])?;
528 let factor = f32::from_bits(h.max_tess_factor_bits);
529 if !(1.0..=64.0).contains(&factor) {
530 return Err(format!("a maximum tessellation factor of {factor}"));
531 }
532 Ok(())
533 }
534 (Stage::Domain, StageFacts::Domain(d)) => {
535 patch("domain", d.input_control_points, &d.patch_constants)
536 }
537 (Stage::Geometry, StageFacts::Geometry(g)) => {
538 if !(1..=1024).contains(&g.max_vertices) || !(1..=32).contains(&g.instances) {
539 return Err(format!(
540 "a geometry program of {} vertices and {} instances",
541 g.max_vertices, g.instances
542 ));
543 }
544 Ok(())
545 }
546 (Stage::Vertex, StageFacts::VertexWithoutPosition) => Ok(()),
547 (Stage::Hull | Stage::Domain, _) => {
548 Err(format!("a {:?} program without its patch facts", self.stage))
549 }
550 (Stage::Mesh, StageFacts::Mesh(m)) => {
551 if !(1..=256).contains(&m.max_vertices) || !(1..=256).contains(&m.max_primitives) {
552 return Err(format!(
553 "a mesh program of {} vertices and {} primitives",
554 m.max_vertices, m.max_primitives
555 ));
556 }
557 Ok(())
558 }
559 (Stage::Mesh, _) => Err("a mesh program without its facts".into()),
560 (stage, StageFacts::RayTracing(r)) if stage.is_ray_tracing() => {
561 if r.export.is_empty() {
562 return Err(format!("a {stage:?} program that names no export"));
563 }
564 Ok(())
565 }
566 (_, StageFacts::None) => Ok(()),
567 (stage, facts) => Err(format!("{stage:?} program with {facts:?}")),
568 }
569 }
570
571 pub fn resource(&self, id: ResourceId) -> Option<&Resource> {
573 self.resources.iter().find(|r| r.id == id)
574 }
575
576 pub fn verify(&self) -> Result<(), String> {
583 self.verify_facts()?;
584 for (direction, varyings) in [("input", &self.inputs), ("output", &self.outputs)] {
585 for (i, v) in varyings.iter().enumerate() {
586 if let Some(SystemValue::Target(n)) = v.system {
587 if direction == "input" || self.stage != Stage::Pixel {
588 return Err(format!(
589 "{direction} {}{} is colour output {n} of a {:?} stage: only a pixel stage's outputs are",
590 v.label.name, v.label.index, self.stage
591 ));
592 }
593 if n > 7 {
594 return Err(format!(
595 "output {}{} is colour output {n}, not 0 to 7",
596 v.label.name, v.label.index
597 ));
598 }
599 }
600 if !(1..=4).contains(&v.value.width) {
601 return Err(format!(
602 "{direction} {}{} has a width of {}, not 1 to 4",
603 v.label.name, v.label.index, v.value.width
604 ));
605 }
606 if varyings.iter().take(i).any(|other| other.label == v.label) {
607 return Err(format!(
608 "{direction} {}{} is listed twice",
609 v.label.name, v.label.index
610 ));
611 }
612 }
613 }
614 let width_ok = |w: u8| (1..=4).contains(&w);
615 for (i, r) in self.resources.iter().enumerate() {
616 let name = &r.provenance.name;
617 if self.resources.iter().take(i).any(|other| other.id == r.id) {
618 return Err(format!("resource id {} ({name}) is used twice", r.id.0));
619 }
620 if r.count == 0 {
621 return Err(format!("resource {name} spans no bindings"));
622 }
623 let buffer = |kind: &BufferKind| match kind {
624 BufferKind::Typed(v) if !width_ok(v.width) => {
625 Err(format!("buffer {name} has elements of {} lanes", v.width))
626 }
627 BufferKind::Structured { stride: 0 } => Err(format!("buffer {name} has a stride of 0")),
628 BufferKind::Structured { stride } if !stride.is_multiple_of(4) || *stride > 2048 => Err(
629 format!("buffer {name} has a stride of {stride}, not a multiple of 4 up to 2048"),
630 ),
631 _ => Ok(()),
632 };
633 match &r.kind {
634 ResourceKind::Buffer(kind)
635 | ResourceKind::Storage {
636 target: StorageTarget::Buffer(kind),
637 ..
638 } => {
639 buffer(kind)?;
640 }
641 ResourceKind::ConstantBuffer { layout } => layout_fits(name, layout)?,
642 _ => {}
643 }
644 if let Some(layout) = &r.provenance.layout {
645 layout_fits(name, layout)?;
646 }
647 if let Some(target) = r.provenance.counter_of {
648 let owner = self.resource(target).ok_or_else(|| {
649 format!(
650 "resource {name} is the counter of resource {}, which is not there",
651 target.0
652 )
653 })?;
654 if target == r.id || !matches!(owner.kind, ResourceKind::Storage { .. }) {
655 return Err(format!(
656 "resource {name} is the counter of resource {} ({}), which is not another storage resource",
657 target.0, owner.provenance.name
658 ));
659 }
660 if r.provenance.d3d.is_some() {
661 return Err(format!(
662 "resource {name} is a hidden counter and has a binding of its own"
663 ));
664 }
665 }
666 if let Some(d3d) = r.provenance.d3d {
667 let class = match &r.kind {
668 ResourceKind::ConstantBuffer { .. } => D3dClass::ConstantBuffer,
669 ResourceKind::Texture { .. }
670 | ResourceKind::Buffer(_)
671 | ResourceKind::AccelerationStructure => D3dClass::ShaderResource,
672 ResourceKind::Sampler { .. } => D3dClass::Sampler,
673 ResourceKind::Storage { .. } => D3dClass::UnorderedAccess,
674 };
675 if d3d.class != class {
676 return Err(format!(
677 "resource {name} is bound as {:?} but its kind is read through {class:?}",
678 d3d.class
679 ));
680 }
681 }
682 }
683 let span = |r: &Resource| {
685 r.provenance.d3d.map(|d| {
686 let first = u64::from(d.register);
687 let end = if r.is_unbounded() {
688 u64::MAX
689 } else {
690 first.saturating_add(u64::from(r.count))
691 };
692 (d.class, d.space, first, end)
693 })
694 };
695 for (i, a) in self.resources.iter().enumerate() {
696 let Some((class, space, first, end)) = span(a) else {
697 continue;
698 };
699 for b in self.resources.iter().take(i) {
700 if let Some((c, s, f, e)) = span(b)
701 && (c, s) == (class, space)
702 && first < e
703 && f < end
704 {
705 return Err(format!(
706 "resources {} and {} share a {class:?} register of space {space}",
707 b.provenance.name, a.provenance.name
708 ));
709 }
710 }
711 }
712 Ok(())
713 }
714}
715
716fn layout_fits(name: &str, layout: &BufferLayout) -> Result<(), String> {
718 for c in &layout.constants {
719 let size =
720 c.ty.size()
721 .ok_or_else(|| format!("constant {name}.{} overflows", c.name))?;
722 if c.offset.checked_add(size).is_none_or(|end| end > layout.size) {
723 return Err(format!(
724 "constant {name}.{} ({size} bytes at {}) reaches past the end of the {}-byte buffer",
725 c.name, c.offset, layout.size
726 ));
727 }
728 }
729 Ok(())
730}
731
732#[cfg(test)]
733mod tests {
734 use super::*;
735 use crate::{
736 BufferLayout, Constant, ConstantType, D3dBinding, ResourceProvenance, SampleType, TextureDim,
737 };
738
739 fn pixel() -> Interface {
740 Interface {
741 stage: Stage::Pixel,
742 inputs: Vec::new(),
743 outputs: Vec::new(),
744 resources: Vec::new(),
745 facts: Default::default(),
746 }
747 }
748 fn target(index: u32, system: Option<SystemValue>) -> Varying {
749 Varying {
750 label: Label {
751 name: "SV_Target".into(),
752 index,
753 },
754 system,
755 value: Value {
756 scalar: ScalarType::Float,
757 width: 4,
758 },
759 interp: None,
760 used: true,
761 provenance: Default::default(),
762 }
763 }
764 fn texture() -> ResourceKind {
765 ResourceKind::Texture {
766 dim: TextureDim::D2,
767 arrayed: false,
768 multisampled: false,
769 sample: SampleType::Float,
770 }
771 }
772 fn res(
773 id: u32,
774 name: &str,
775 kind: ResourceKind,
776 d3d: Option<(D3dClass, u32, u32)>,
777 count: u32,
778 ) -> Resource {
779 Resource {
780 id: ResourceId(id),
781 kind,
782 count,
783 provenance: ResourceProvenance {
784 name: name.into(),
785 d3d: d3d.map(|(class, register, space)| D3dBinding {
786 class,
787 register,
788 space,
789 }),
790 ..ResourceProvenance::default()
791 },
792 }
793 }
794 fn storage() -> ResourceKind {
795 ResourceKind::Storage {
796 target: StorageTarget::Buffer(BufferKind::Raw),
797 access: crate::Access::ReadWrite,
798 }
799 }
800 fn broken(i: &Interface, expect: &str) {
801 let message = i.verify().unwrap_err();
802 assert!(message.contains(expect), "{expect}: {message}");
803 }
804
805 #[test]
806 fn a_colour_output_is_a_pixel_outputs_target_of_0_to_7() {
807 let mut i = pixel();
808 i.outputs = vec![
809 target(0, Some(SystemValue::Target(0))),
810 target(7, Some(SystemValue::Target(7))),
811 ];
812 assert_eq!(i.verify(), Ok(()));
813 i.outputs.push(target(8, Some(SystemValue::Target(8))));
814 broken(&i, "colour output 8, not 0 to 7");
815 let mut i = pixel();
817 i.inputs = vec![target(0, Some(SystemValue::Target(0)))];
818 broken(&i, "only a pixel stage's outputs are");
819 let mut i = pixel();
820 i.stage = Stage::Vertex;
821 i.outputs = vec![target(0, Some(SystemValue::Target(0)))];
822 broken(&i, "only a pixel stage's outputs are");
823 }
824
825 #[test]
826 fn a_structured_buffers_stride_is_a_multiple_of_4_up_to_2048() {
827 for (stride, ok) in [
828 (4, true),
829 (16, true),
830 (2048, true),
831 (0, false),
832 (6, false),
833 (2052, false),
834 ] {
835 let mut i = pixel();
836 i.resources = vec![res(
837 0,
838 "b",
839 ResourceKind::Buffer(BufferKind::Structured { stride }),
840 Some((D3dClass::ShaderResource, 0, 0)),
841 1,
842 )];
843 assert_eq!(i.verify().is_ok(), ok, "stride {stride}");
844 }
845 }
846
847 #[test]
848 fn a_tbuffers_members_lie_inside_its_layout() {
849 let layout = |offset| BufferLayout {
850 size: 16,
851 constants: vec![Constant {
852 name: "m".into(),
853 offset,
854 ty: ConstantType::Vector(Value {
855 scalar: ScalarType::Float,
856 width: 4,
857 }),
858 default: None,
859 }],
860 };
861 let mut view = res(
862 0,
863 "tb",
864 ResourceKind::Buffer(BufferKind::Typed(Value {
865 scalar: ScalarType::Float,
866 width: 4,
867 })),
868 Some((D3dClass::ShaderResource, 0, 0)),
869 1,
870 );
871 view.provenance.layout = Some(layout(0));
872 let mut i = pixel();
873 i.resources = vec![view.clone()];
874 assert_eq!(i.verify(), Ok(()));
875 i.resources[0].provenance.layout = Some(layout(4));
876 broken(&i, "reaches past the end");
877 }
878
879 #[test]
880 fn a_hidden_counter_belongs_to_a_storage_resource_and_has_no_binding() {
881 let counter = |of: u32, d3d| {
882 let mut c = res(1, "c", ResourceKind::Buffer(BufferKind::Raw), d3d, 1);
883 c.provenance.counter_of = Some(ResourceId(of));
884 c
885 };
886 let uav = res(0, "u", storage(), Some((D3dClass::UnorderedAccess, 0, 0)), 1);
887 let mut i = pixel();
888 i.resources = vec![uav.clone(), counter(0, None)];
889 assert_eq!(i.verify(), Ok(()));
890 i.resources = vec![uav.clone(), counter(5, None)];
891 broken(&i, "which is not there");
892 i.resources = vec![uav.clone(), counter(1, None)];
893 broken(&i, "not another storage resource");
894 i.resources = vec![
896 res(0, "t", texture(), Some((D3dClass::ShaderResource, 0, 0)), 1),
897 counter(0, None),
898 ];
899 broken(&i, "not another storage resource");
900 i.resources = vec![uav, counter(0, Some((D3dClass::ShaderResource, 3, 0)))];
902 broken(&i, "binding of its own");
903 }
904
905 #[test]
906 fn a_register_class_fits_the_kind_and_a_register_holds_one_resource() {
907 let cb = ResourceKind::ConstantBuffer {
908 layout: BufferLayout {
909 size: 16,
910 constants: Vec::new(),
911 },
912 };
913 let kinds = [
914 (cb.clone(), D3dClass::ConstantBuffer),
915 (texture(), D3dClass::ShaderResource),
916 (ResourceKind::Buffer(BufferKind::Raw), D3dClass::ShaderResource),
917 (ResourceKind::AccelerationStructure, D3dClass::ShaderResource),
918 (ResourceKind::Sampler { comparison: false }, D3dClass::Sampler),
919 (storage(), D3dClass::UnorderedAccess),
920 ];
921 for (kind, class) in &kinds {
922 let mut i = pixel();
923 i.resources = vec![res(0, "r", kind.clone(), Some((*class, 0, 0)), 1)];
924 assert_eq!(i.verify(), Ok(()), "{kind:?}");
925 for (_, other) in kinds.iter().filter(|(_, c)| c != class) {
927 i.resources = vec![res(0, "r", kind.clone(), Some((*other, 0, 0)), 1)];
928 broken(&i, "its kind is read through");
929 }
930 }
931 let t = |id, register, space, count| {
932 res(
933 id,
934 &format!("t{id}"),
935 texture(),
936 Some((D3dClass::ShaderResource, register, space)),
937 count,
938 )
939 };
940 let mut i = pixel();
941 i.resources = vec![t(0, 2, 0, 1), t(1, 2, 1, 1), t(2, 3, 0, 1)];
943 assert_eq!(i.verify(), Ok(()));
944 i.resources.push(res(
945 3,
946 "s",
947 ResourceKind::Sampler { comparison: false },
948 Some((D3dClass::Sampler, 2, 0)),
949 1,
950 ));
951 assert_eq!(i.verify(), Ok(()));
952 i.resources = vec![t(0, 2, 0, 1), t(1, 2, 0, 1)];
954 broken(&i, "share a ShaderResource register of space 0");
955 i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1)];
957 assert_eq!(i.verify(), Ok(()));
958 i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1), t(2, 5, 0, 1)];
959 broken(&i, "t0 and t2 share");
960 i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 90, 0, 1)];
961 broken(&i, "share a ShaderResource");
962 i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 1, 0, 1), t(2, 2, 1, 1)];
963 assert_eq!(i.verify(), Ok(()));
964 }
965
966 #[test]
967 fn a_varying_serializes_with_its_provenance() {
968 let v = Varying {
969 label: Label {
970 name: "TEXCOORD".into(),
971 index: 5,
972 },
973 system: None,
974 value: Value {
975 scalar: ScalarType::Float,
976 width: 2,
977 },
978 interp: Some(Interpolation {
979 mode: InterpMode::Perspective,
980 sampling: InterpSampling::Center,
981 }),
982 used: true,
983 provenance: VaryingProvenance {
984 d3d: Some(D3dSignature {
985 register: 3,
986 mask: 0b1100,
987 read_write_mask: 0b1100,
988 }),
989 },
990 };
991 assert_eq!(
992 serde_json::to_value(&v).unwrap(),
993 serde_json::json!({
994 "label": {"name": "TEXCOORD", "index": 5},
995 "system": null,
996 "value": {"scalar": "float", "width": 2},
997 "interp": {"mode": "perspective", "sampling": "center"},
998 "used": true,
999 "provenance": {"d3d": {"register": 3, "mask": 12, "read_write_mask": 12}},
1000 })
1001 );
1002 assert_eq!(
1003 serde_json::to_value(SystemValue::Target(2)).unwrap(),
1004 serde_json::json!({"target": 2})
1005 );
1006 }
1007}