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}
378
379#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
381#[serde(rename_all = "snake_case")]
382pub enum HitGroupKind {
383 Triangles,
385 ProceduralPrimitives,
387}
388
389#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
392pub struct HitGroup {
393 pub name: String,
395 pub kind: HitGroupKind,
397 pub any_hit: Option<String>,
399 pub closest_hit: Option<String>,
401 pub intersection: Option<String>,
403}
404
405#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
407#[serde(rename_all = "snake_case")]
408pub enum Subobject {
409 StateObjectConfig {
411 name: String,
413 flags: u32,
415 },
416 GlobalRootSignature {
418 name: String,
420 signature: Vec<u8>,
422 },
423 LocalRootSignature {
425 name: String,
427 signature: Vec<u8>,
429 },
430 ExportsAssociation {
432 name: String,
434 subobject: String,
436 exports: Vec<String>,
438 },
439 ShaderConfig {
441 name: String,
443 max_payload_bytes: u32,
445 max_attribute_bytes: u32,
447 },
448 PipelineConfig {
450 name: String,
452 max_recursion_depth: u32,
454 flags: Option<u32>,
456 },
457 HitGroup(HitGroup),
459}
460
461#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
464pub struct RayTracingFacts {
465 pub export: String,
467 pub subobjects: Vec<Subobject>,
469}
470
471#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
473#[serde(rename_all = "snake_case")]
474#[non_exhaustive]
475pub enum StageFacts {
476 #[default]
478 None,
479 VertexWithoutPosition,
482 Hull(HullFacts),
484 Domain(DomainFacts),
486 Geometry(GeometryFacts),
488 RayTracing(RayTracingFacts),
490 Mesh(MeshFacts),
492}
493
494#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
496pub struct Interface {
497 pub stage: Stage,
499 pub inputs: Vec<Varying>,
501 pub outputs: Vec<Varying>,
503 pub resources: Vec<Resource>,
505 pub facts: StageFacts,
507}
508impl Interface {
509 fn verify_facts(&self) -> Result<(), String> {
512 let patch = |what: &str, count: u32, constants: &[Varying]| -> Result<(), String> {
513 if !(1..=32).contains(&count) {
514 return Err(format!("{what}: {count} control points, not 1 to 32"));
515 }
516 for (i, v) in constants.iter().enumerate() {
517 if !(1..=4).contains(&v.value.width) {
518 return Err(format!(
519 "patch constant {}{} has a width of {}, not 1 to 4",
520 v.label.name, v.label.index, v.value.width
521 ));
522 }
523 if constants.iter().take(i).any(|other| other.label == v.label) {
524 return Err(format!(
525 "patch constant {}{} is listed twice",
526 v.label.name, v.label.index
527 ));
528 }
529 }
530 Ok(())
531 };
532 match (&self.stage, &self.facts) {
533 (Stage::Hull, StageFacts::Hull(h)) => {
534 patch("hull input", h.input_control_points, &h.patch_constants)?;
535 patch("hull output", h.output_control_points, &[])?;
536 let factor = f32::from_bits(h.max_tess_factor_bits);
537 if !(1.0..=64.0).contains(&factor) {
538 return Err(format!("a maximum tessellation factor of {factor}"));
539 }
540 Ok(())
541 }
542 (Stage::Domain, StageFacts::Domain(d)) => {
543 patch("domain", d.input_control_points, &d.patch_constants)
544 }
545 (Stage::Geometry, StageFacts::Geometry(g)) => {
546 if !(1..=1024).contains(&g.max_vertices) || !(1..=32).contains(&g.instances) {
547 return Err(format!(
548 "a geometry program of {} vertices and {} instances",
549 g.max_vertices, g.instances
550 ));
551 }
552 Ok(())
553 }
554 (Stage::Vertex, StageFacts::VertexWithoutPosition) => Ok(()),
555 (Stage::Hull | Stage::Domain, _) => {
556 Err(format!("a {:?} program without its patch facts", self.stage))
557 }
558 (Stage::Mesh, StageFacts::Mesh(m)) => {
559 if !(1..=256).contains(&m.max_vertices) || !(1..=256).contains(&m.max_primitives) {
560 return Err(format!(
561 "a mesh program of {} vertices and {} primitives",
562 m.max_vertices, m.max_primitives
563 ));
564 }
565 Ok(())
566 }
567 (Stage::Mesh, _) => Err("a mesh program without its facts".into()),
568 (stage, StageFacts::RayTracing(r)) if stage.is_ray_tracing() => {
569 if r.export.is_empty() {
570 return Err(format!("a {stage:?} program that names no export"));
571 }
572 Ok(())
573 }
574 (_, StageFacts::None) => Ok(()),
575 (stage, facts) => Err(format!("{stage:?} program with {facts:?}")),
576 }
577 }
578
579 pub fn resource(&self, id: ResourceId) -> Option<&Resource> {
581 self.resources.iter().find(|r| r.id == id)
582 }
583
584 pub fn verify(&self) -> Result<(), String> {
591 self.verify_facts()?;
592 for (direction, varyings) in [("input", &self.inputs), ("output", &self.outputs)] {
593 for (i, v) in varyings.iter().enumerate() {
594 if let Some(SystemValue::Target(n)) = v.system {
595 if direction == "input" || self.stage != Stage::Pixel {
596 return Err(format!(
597 "{direction} {}{} is colour output {n} of a {:?} stage: only a pixel stage's outputs are",
598 v.label.name, v.label.index, self.stage
599 ));
600 }
601 if n > 7 {
602 return Err(format!(
603 "output {}{} is colour output {n}, not 0 to 7",
604 v.label.name, v.label.index
605 ));
606 }
607 }
608 if !(1..=4).contains(&v.value.width) {
609 return Err(format!(
610 "{direction} {}{} has a width of {}, not 1 to 4",
611 v.label.name, v.label.index, v.value.width
612 ));
613 }
614 if varyings.iter().take(i).any(|other| other.label == v.label) {
615 return Err(format!(
616 "{direction} {}{} is listed twice",
617 v.label.name, v.label.index
618 ));
619 }
620 }
621 }
622 let width_ok = |w: u8| (1..=4).contains(&w);
623 for (i, r) in self.resources.iter().enumerate() {
624 let name = &r.provenance.name;
625 if self.resources.iter().take(i).any(|other| other.id == r.id) {
626 return Err(format!("resource id {} ({name}) is used twice", r.id.0));
627 }
628 if r.count == 0 {
629 return Err(format!("resource {name} spans no bindings"));
630 }
631 let buffer = |kind: &BufferKind| match kind {
632 BufferKind::Typed(v) if !width_ok(v.width) => {
633 Err(format!("buffer {name} has elements of {} lanes", v.width))
634 }
635 BufferKind::Structured { stride: 0 } => Err(format!("buffer {name} has a stride of 0")),
636 BufferKind::Structured { stride } if !stride.is_multiple_of(4) || *stride > 2048 => Err(
637 format!("buffer {name} has a stride of {stride}, not a multiple of 4 up to 2048"),
638 ),
639 _ => Ok(()),
640 };
641 match &r.kind {
642 ResourceKind::Buffer(kind)
643 | ResourceKind::Storage {
644 target: StorageTarget::Buffer(kind),
645 ..
646 } => {
647 buffer(kind)?;
648 }
649 ResourceKind::ConstantBuffer { layout } => layout_fits(name, layout)?,
650 _ => {}
651 }
652 if let Some(layout) = &r.provenance.layout {
653 layout_fits(name, layout)?;
654 }
655 if let Some(target) = r.provenance.counter_of {
656 let owner = self.resource(target).ok_or_else(|| {
657 format!(
658 "resource {name} is the counter of resource {}, which is not there",
659 target.0
660 )
661 })?;
662 if target == r.id || !matches!(owner.kind, ResourceKind::Storage { .. }) {
663 return Err(format!(
664 "resource {name} is the counter of resource {} ({}), which is not another storage resource",
665 target.0, owner.provenance.name
666 ));
667 }
668 if r.provenance.d3d.is_some() {
669 return Err(format!(
670 "resource {name} is a hidden counter and has a binding of its own"
671 ));
672 }
673 }
674 if let Some(d3d) = r.provenance.d3d {
675 let class = match &r.kind {
676 ResourceKind::ConstantBuffer { .. } => D3dClass::ConstantBuffer,
677 ResourceKind::Texture { .. }
678 | ResourceKind::Buffer(_)
679 | ResourceKind::AccelerationStructure => D3dClass::ShaderResource,
680 ResourceKind::Sampler { .. } => D3dClass::Sampler,
681 ResourceKind::Storage { .. } => D3dClass::UnorderedAccess,
682 ResourceKind::PipelineState(_) => {
683 return Err(format!(
684 "resource {name} is pipeline state, set with the draw, and has a binding"
685 ));
686 }
687 };
688 if d3d.class != class {
689 return Err(format!(
690 "resource {name} is bound as {:?} but its kind is read through {class:?}",
691 d3d.class
692 ));
693 }
694 }
695 }
696 let span = |r: &Resource| {
698 r.provenance.d3d.map(|d| {
699 let first = u64::from(d.register);
700 let end = if r.is_unbounded() {
701 u64::MAX
702 } else {
703 first.saturating_add(u64::from(r.count))
704 };
705 (d.class, d.space, first, end)
706 })
707 };
708 for (i, a) in self.resources.iter().enumerate() {
709 let Some((class, space, first, end)) = span(a) else {
710 continue;
711 };
712 for b in self.resources.iter().take(i) {
713 if let Some((c, s, f, e)) = span(b)
714 && (c, s) == (class, space)
715 && first < e
716 && f < end
717 {
718 return Err(format!(
719 "resources {} and {} share a {class:?} register of space {space}",
720 b.provenance.name, a.provenance.name
721 ));
722 }
723 }
724 }
725 Ok(())
726 }
727}
728
729fn layout_fits(name: &str, layout: &BufferLayout) -> Result<(), String> {
731 for c in &layout.constants {
732 let size =
733 c.ty.size()
734 .ok_or_else(|| format!("constant {name}.{} overflows", c.name))?;
735 if c.offset.checked_add(size).is_none_or(|end| end > layout.size) {
736 return Err(format!(
737 "constant {name}.{} ({size} bytes at {}) reaches past the end of the {}-byte buffer",
738 c.name, c.offset, layout.size
739 ));
740 }
741 }
742 Ok(())
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use crate::interface::{
749 BufferLayout, Constant, ConstantType, D3dBinding, ResourceProvenance, SampleType, TextureDim,
750 };
751
752 fn pixel() -> Interface {
753 Interface {
754 stage: Stage::Pixel,
755 inputs: Vec::new(),
756 outputs: Vec::new(),
757 resources: Vec::new(),
758 facts: Default::default(),
759 }
760 }
761 fn target(index: u32, system: Option<SystemValue>) -> Varying {
762 Varying {
763 label: Label {
764 name: "SV_Target".into(),
765 index,
766 },
767 system,
768 value: Value {
769 scalar: ScalarType::Float,
770 width: 4,
771 },
772 interp: None,
773 used: true,
774 provenance: Default::default(),
775 }
776 }
777 fn texture() -> ResourceKind {
778 ResourceKind::Texture {
779 dim: TextureDim::D2,
780 arrayed: false,
781 multisampled: false,
782 sample: SampleType::Float,
783 }
784 }
785 fn res(
786 id: u32,
787 name: &str,
788 kind: ResourceKind,
789 d3d: Option<(D3dClass, u32, u32)>,
790 count: u32,
791 ) -> Resource {
792 Resource {
793 id: ResourceId(id),
794 kind,
795 count,
796 provenance: ResourceProvenance {
797 name: name.into(),
798 d3d: d3d.map(|(class, register, space)| D3dBinding {
799 class,
800 register,
801 space,
802 }),
803 ..ResourceProvenance::default()
804 },
805 }
806 }
807 fn storage() -> ResourceKind {
808 ResourceKind::Storage {
809 target: StorageTarget::Buffer(BufferKind::Raw),
810 access: crate::interface::Access::ReadWrite,
811 }
812 }
813 fn broken(i: &Interface, expect: &str) {
814 let message = i.verify().unwrap_err();
815 assert!(message.contains(expect), "{expect}: {message}");
816 }
817
818 #[test]
819 fn a_colour_output_is_a_pixel_outputs_target_of_0_to_7() {
820 let mut i = pixel();
821 i.outputs = vec![
822 target(0, Some(SystemValue::Target(0))),
823 target(7, Some(SystemValue::Target(7))),
824 ];
825 assert_eq!(i.verify(), Ok(()));
826 i.outputs.push(target(8, Some(SystemValue::Target(8))));
827 broken(&i, "colour output 8, not 0 to 7");
828 let mut i = pixel();
830 i.inputs = vec![target(0, Some(SystemValue::Target(0)))];
831 broken(&i, "only a pixel stage's outputs are");
832 let mut i = pixel();
833 i.stage = Stage::Vertex;
834 i.outputs = vec![target(0, Some(SystemValue::Target(0)))];
835 broken(&i, "only a pixel stage's outputs are");
836 }
837
838 #[test]
839 fn a_structured_buffers_stride_is_a_multiple_of_4_up_to_2048() {
840 for (stride, ok) in [
841 (4, true),
842 (16, true),
843 (2048, true),
844 (0, false),
845 (6, false),
846 (2052, false),
847 ] {
848 let mut i = pixel();
849 i.resources = vec![res(
850 0,
851 "b",
852 ResourceKind::Buffer(BufferKind::Structured { stride }),
853 Some((D3dClass::ShaderResource, 0, 0)),
854 1,
855 )];
856 assert_eq!(i.verify().is_ok(), ok, "stride {stride}");
857 }
858 }
859
860 #[test]
861 fn a_tbuffers_members_lie_inside_its_layout() {
862 let layout = |offset| BufferLayout {
863 size: 16,
864 constants: vec![Constant {
865 name: "m".into(),
866 offset,
867 ty: ConstantType::Vector(Value {
868 scalar: ScalarType::Float,
869 width: 4,
870 }),
871 default: None,
872 }],
873 };
874 let mut view = res(
875 0,
876 "tb",
877 ResourceKind::Buffer(BufferKind::Typed(Value {
878 scalar: ScalarType::Float,
879 width: 4,
880 })),
881 Some((D3dClass::ShaderResource, 0, 0)),
882 1,
883 );
884 view.provenance.layout = Some(layout(0));
885 let mut i = pixel();
886 i.resources = vec![view.clone()];
887 assert_eq!(i.verify(), Ok(()));
888 i.resources[0].provenance.layout = Some(layout(4));
889 broken(&i, "reaches past the end");
890 }
891
892 #[test]
893 fn a_hidden_counter_belongs_to_a_storage_resource_and_has_no_binding() {
894 let counter = |of: u32, d3d| {
895 let mut c = res(1, "c", ResourceKind::Buffer(BufferKind::Raw), d3d, 1);
896 c.provenance.counter_of = Some(ResourceId(of));
897 c
898 };
899 let uav = res(0, "u", storage(), Some((D3dClass::UnorderedAccess, 0, 0)), 1);
900 let mut i = pixel();
901 i.resources = vec![uav.clone(), counter(0, None)];
902 assert_eq!(i.verify(), Ok(()));
903 i.resources = vec![uav.clone(), counter(5, None)];
904 broken(&i, "which is not there");
905 i.resources = vec![uav.clone(), counter(1, None)];
906 broken(&i, "not another storage resource");
907 i.resources = vec![
909 res(0, "t", texture(), Some((D3dClass::ShaderResource, 0, 0)), 1),
910 counter(0, None),
911 ];
912 broken(&i, "not another storage resource");
913 i.resources = vec![uav, counter(0, Some((D3dClass::ShaderResource, 3, 0)))];
915 broken(&i, "binding of its own");
916 }
917
918 #[test]
919 fn a_register_class_fits_the_kind_and_a_register_holds_one_resource() {
920 let cb = ResourceKind::ConstantBuffer {
921 layout: BufferLayout {
922 size: 16,
923 constants: Vec::new(),
924 },
925 };
926 let kinds = [
927 (cb.clone(), D3dClass::ConstantBuffer),
928 (texture(), D3dClass::ShaderResource),
929 (ResourceKind::Buffer(BufferKind::Raw), D3dClass::ShaderResource),
930 (ResourceKind::AccelerationStructure, D3dClass::ShaderResource),
931 (ResourceKind::Sampler { comparison: false }, D3dClass::Sampler),
932 (storage(), D3dClass::UnorderedAccess),
933 ];
934 for (kind, class) in &kinds {
935 let mut i = pixel();
936 i.resources = vec![res(0, "r", kind.clone(), Some((*class, 0, 0)), 1)];
937 assert_eq!(i.verify(), Ok(()), "{kind:?}");
938 for (_, other) in kinds.iter().filter(|(_, c)| c != class) {
940 i.resources = vec![res(0, "r", kind.clone(), Some((*other, 0, 0)), 1)];
941 broken(&i, "its kind is read through");
942 }
943 }
944 let t = |id, register, space, count| {
945 res(
946 id,
947 &format!("t{id}"),
948 texture(),
949 Some((D3dClass::ShaderResource, register, space)),
950 count,
951 )
952 };
953 let mut i = pixel();
954 i.resources = vec![t(0, 2, 0, 1), t(1, 2, 1, 1), t(2, 3, 0, 1)];
956 assert_eq!(i.verify(), Ok(()));
957 i.resources.push(res(
958 3,
959 "s",
960 ResourceKind::Sampler { comparison: false },
961 Some((D3dClass::Sampler, 2, 0)),
962 1,
963 ));
964 assert_eq!(i.verify(), Ok(()));
965 i.resources = vec![t(0, 2, 0, 1), t(1, 2, 0, 1)];
967 broken(&i, "share a ShaderResource register of space 0");
968 i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1)];
970 assert_eq!(i.verify(), Ok(()));
971 i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1), t(2, 5, 0, 1)];
972 broken(&i, "t0 and t2 share");
973 i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 90, 0, 1)];
974 broken(&i, "share a ShaderResource");
975 i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 1, 0, 1), t(2, 2, 1, 1)];
976 assert_eq!(i.verify(), Ok(()));
977 }
978
979 #[test]
980 fn a_varying_serializes_with_its_provenance() {
981 let v = Varying {
982 label: Label {
983 name: "TEXCOORD".into(),
984 index: 5,
985 },
986 system: None,
987 value: Value {
988 scalar: ScalarType::Float,
989 width: 2,
990 },
991 interp: Some(Interpolation {
992 mode: InterpMode::Perspective,
993 sampling: InterpSampling::Center,
994 }),
995 used: true,
996 provenance: VaryingProvenance {
997 d3d: Some(D3dSignature {
998 register: 3,
999 mask: 0b1100,
1000 read_write_mask: 0b1100,
1001 }),
1002 },
1003 };
1004 assert_eq!(
1005 serde_json::to_value(&v).unwrap(),
1006 serde_json::json!({
1007 "label": {"name": "TEXCOORD", "index": 5},
1008 "system": null,
1009 "value": {"scalar": "float", "width": 2},
1010 "interp": {"mode": "perspective", "sampling": "center"},
1011 "used": true,
1012 "provenance": {"d3d": {"register": 3, "mask": 12, "read_write_mask": 12}},
1013 })
1014 );
1015 assert_eq!(
1016 serde_json::to_value(SystemValue::Target(2)).unwrap(),
1017 serde_json::json!({"target": 2})
1018 );
1019 }
1020}