Skip to main content

iridia_ir/interface/
varying.rs

1//! A program's interface: the varyings it reads and writes, and the system values among them.
2#[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/// The scalar type of a value or constant.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
17#[serde(rename_all = "snake_case")]
18pub enum ScalarType {
19    /// A boolean, 32 bits wide wherever it is stored (constant buffers, signatures).
20    Bool,
21    /// A signed 32-bit integer.
22    Sint,
23    /// An unsigned 32-bit integer.
24    Uint,
25    /// A 32-bit float.
26    Float,
27    /// A 64-bit float.
28    Double,
29}
30impl ScalarType {
31    /// Bytes one scalar takes in a constant buffer.
32    pub fn bytes(self) -> u32 {
33        match self {
34            Self::Double => 8,
35            _ => 4,
36        }
37    }
38}
39
40/// A scalar type and a width of 1 to 4 lanes.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
42pub struct Value {
43    /// The type of every lane.
44    pub scalar: ScalarType,
45    /// Number of lanes, 1 to 4.
46    pub width: u8,
47}
48
49/// A varying's name in its source: the semantic name and its index (DXBC `TEXCOORD` and 5, SPIR-V `Location` and 3,
50/// GLSL `vUv` and 0). Labels are not interpreted: games put anything in `TEXCOORDn`, and what a label means is
51/// the consumer's business. Stages match by label.
52#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
53pub struct Label {
54    /// The semantic or location name as the source writes it (`TEXCOORD`, `SV_Target`, `vUv`).
55    pub name: String,
56    /// The semantic index (the 5 of `TEXCOORD5`; the location of a SPIR-V variable).
57    pub index: u32,
58}
59
60/// The system values, one vocabulary for every API: what the pipeline itself supplies to or takes from a stage.
61#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
62#[serde(rename_all = "snake_case")]
63pub enum SystemValue {
64    /// A vertex stage's clip-space position output.
65    ClipPosition,
66    /// A pixel stage's window-space position input (x, y, depth, 1/w).
67    FragCoord,
68    /// A pixel stage's input: whether the primitive is front facing.
69    FrontFacing,
70    /// A vertex stage's input: the index of the vertex being processed.
71    VertexIndex,
72    /// A vertex stage's instance number; in a ray tracing hit program, the instance's index in the acceleration
73    /// structure.
74    InstanceIndex,
75    /// A pixel stage's primitive number; in a ray tracing hit program, the hit primitive's index in its geometry.
76    PrimitiveIndex,
77    /// A pixel stage's input: the index of the sample being shaded when it runs per sample.
78    SampleIndex,
79    /// The index of the render target array slice a primitive goes to (a geometry or mesh output, a pixel input).
80    RenderTargetArrayIndex,
81    /// The index of the viewport a primitive is drawn in (a geometry or mesh output, a pixel input).
82    ViewportArrayIndex,
83    /// Clip distances; the label's index says which array element.
84    ClipDistance,
85    /// Cull distances; the label's index says which array element.
86    CullDistance,
87    /// Colour output `n`.
88    Target(u32),
89    /// A pixel stage's depth output, written freely.
90    Depth,
91    /// Depth output the program promises is not less than the rasterized depth.
92    DepthGreaterEqual,
93    /// Depth output the program promises is not greater than the rasterized depth.
94    DepthLessEqual,
95    /// A pixel stage's stencil reference output.
96    StencilRef,
97    /// Coverage mask, as an input or an output.
98    Coverage,
99    /// Coverage of the pixel by the primitive alone (conservative rasterization).
100    InnerCoverage,
101    /// A compute stage's thread group (workgroup) number, three components.
102    GroupId,
103    /// A compute invocation's position inside its thread group, three components.
104    GroupThreadId,
105    /// A compute invocation's position in the whole dispatch (group number times the group size plus its position in
106    /// the group), three components.
107    DispatchThreadId,
108    /// A compute invocation's index in its thread group, row-major over the group's three axes.
109    GroupIndex,
110    /// The invocation's lane in its wave (subgroup).
111    LaneIndex,
112    /// How many lanes the wave (subgroup) has.
113    LaneCount,
114    /// A ray tracing program's position in the dispatch, three components.
115    DispatchRaysIndex,
116    /// The size of the dispatch a ray tracing program runs in, three components.
117    DispatchRaysDimensions,
118    /// The traced ray's origin and direction in world space, three components each.
119    WorldRayOrigin,
120    /// The ray's direction in world space.
121    WorldRayDirection,
122    /// The same in the space of the instance the ray hit (an intersection, any-hit or closest-hit program).
123    ObjectRayOrigin,
124    /// The ray's direction in the instance's space.
125    ObjectRayDirection,
126    /// The ray's start and the distance of the closest accepted hit so far (or the candidate being tested, in an
127    /// any-hit program), along the ray.
128    RayTMin,
129    /// The distance along the ray of the closest accepted hit so far, or of the candidate being tested in an any-hit
130    /// program.
131    RayTCurrent,
132    /// The traced ray's flags.
133    RayFlags,
134    /// The hit instance's own identifier (the 24 bits the acceleration structure was built with).
135    InstanceId,
136    /// The geometry's index within the hit instance's bottom-level acceleration structure.
137    GeometryIndex,
138    /// What the intersection program reported the hit as (`ReportHit`'s kind).
139    HitKind,
140    /// The hit instance's 3 by 4 transform to world space and back. One varying for each row used, the label's index
141    /// naming the row, its four lanes the row's four columns.
142    ObjectToWorld,
143    /// The inverse of [`SystemValue::ObjectToWorld`], in the same row layout.
144    WorldToObject,
145}
146
147/// How a varying is interpolated across a primitive.
148#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
149#[serde(rename_all = "snake_case")]
150pub enum InterpMode {
151    /// Perspective-correct (the default for an interpolant).
152    Perspective,
153    /// Linear in screen space (HLSL `noperspective`).
154    Linear,
155    /// The provoking vertex's value, not interpolated.
156    Flat,
157}
158
159/// Where in a pixel a varying is sampled.
160#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
161#[serde(rename_all = "snake_case")]
162pub enum InterpSampling {
163    /// Sampled at the pixel's centre.
164    Center,
165    /// Sampled inside the primitive when the pixel centre is not covered (HLSL `centroid`).
166    Centroid,
167    /// Per sample.
168    Sample,
169}
170
171/// A pixel input's interpolation: the mode and the sampling location.
172#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
173pub struct Interpolation {
174    /// How the value is interpolated across the primitive.
175    pub mode: InterpMode,
176    /// Where in the pixel the value is taken.
177    pub sampling: InterpSampling,
178}
179
180/// Where a D3D signature element sits: its register (four 32-bit lanes each) and the lanes of it the element
181/// occupies, as a mask (bit k is lane k, x first), and the signature's read/write mask as stored.
182#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
183pub struct D3dSignature {
184    /// The signature register the element is in, counted from 0; each register holds four 32-bit lanes.
185    pub register: u32,
186    /// The lanes of the register the element occupies: bit 0 is x, bit 3 is w.
187    pub mask: u8,
188    /// The signature's second mask as stored: for an input, the lanes the program always reads; for an output, the
189    /// lanes it never writes.
190    pub read_write_mask: u8,
191}
192
193/// The source's own address of a varying; one field per API that has a notion of it.
194#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
195pub struct VaryingProvenance {
196    /// The D3D signature address of the varying; `None` for a source that has none.
197    pub d3d: Option<D3dSignature>,
198}
199
200/// One interface variable: a value passed between stages (or between a stage and the pipeline), one per label. D3D
201/// packs several into one register; they are separate here, with the register and lanes kept in provenance.
202#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
203pub struct Varying {
204    /// The source's name for it, kept for system values too (`SV_Target` and 1).
205    pub label: Label,
206    /// What the pipeline itself supplies or takes here, when it is a system value.
207    pub system: Option<SystemValue>,
208    /// The type and lane count of the varying.
209    pub value: Value,
210    /// How it is interpolated, where the source states it for this varying: a pixel input the program declares.
211    /// `None` for outputs, for stages that do not interpolate, and for an input the program never declares (a source
212    /// that carries interpolation in the program body leaves it out of a signature read alone).
213    pub interp: Option<Interpolation>,
214    /// Whether the program reads (an input) or writes (an output) at least one lane, as the source's signature
215    /// states.
216    pub used: bool,
217    /// The source's address of the varying.
218    pub provenance: VaryingProvenance,
219}
220
221/// The shape a tessellator gives a patch.
222#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
223#[serde(rename_all = "snake_case")]
224pub enum TessDomain {
225    /// Lines: a patch is a set of isolines.
226    Isoline,
227    /// A triangle patch.
228    Triangle,
229    /// A quad patch.
230    Quad,
231}
232
233/// How the tessellator spaces the vertices it adds along an edge.
234#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
235#[serde(rename_all = "snake_case")]
236pub enum Partitioning {
237    /// Each edge is divided into a whole number of segments.
238    Integer,
239    /// The factor is rounded up to a power of two.
240    Pow2,
241    /// Fractional, with an odd number of segments between the ends.
242    FractionalOdd,
243    /// Fractional, with an even number of segments.
244    FractionalEven,
245}
246
247/// What the tessellator emits.
248#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
249#[serde(rename_all = "snake_case")]
250pub enum TessTopology {
251    /// Points.
252    Point,
253    /// Lines.
254    Line,
255    /// Triangles wound clockwise.
256    TriangleCw,
257    /// Triangles wound counter-clockwise.
258    TriangleCcw,
259}
260
261/// What a hull program states about its patch beyond its varyings. The patch-constant varyings are its outputs per
262/// patch (`SV_TessFactor`, `SV_InsideTessFactor`, whatever the domain program reads); `outputs` of the interface are
263/// its control points'.
264#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
265pub struct HullFacts {
266    /// The domain the hull program declares.
267    pub domain: TessDomain,
268    /// How the tessellator divides the edges.
269    pub partitioning: Partitioning,
270    /// What the tessellator outputs.
271    pub topology: TessTopology,
272    /// Control points the program reads per patch (1 to 32).
273    pub input_control_points: u32,
274    /// Control points the program writes per patch (1 to 32).
275    pub output_control_points: u32,
276    /// The largest tessellation factor, as the bits of an `f32` (the interface is hashed).
277    pub max_tess_factor_bits: u32,
278    /// The patch-constant outputs, per patch, in the order the signature lists them.
279    pub patch_constants: Vec<Varying>,
280}
281
282/// What a domain program states about its patch: the same domain as the hull program that feeds it, how many control
283/// points it reads, and the patch-constant varyings it reads (`inputs` of the interface are its control points').
284#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
285pub struct DomainFacts {
286    /// The domain the program was written for (the hull program that feeds it states the same one).
287    pub domain: TessDomain,
288    /// Control points per patch the program reads (1 to 32).
289    pub input_control_points: u32,
290    /// The patch-constant inputs the program reads.
291    pub patch_constants: Vec<Varying>,
292}
293
294/// The primitive a geometry program reads.
295#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
296#[serde(rename_all = "snake_case")]
297pub enum GsInput {
298    /// One point (1 vertex).
299    Point,
300    /// One line (2 vertices).
301    Line,
302    /// One triangle (3 vertices).
303    Triangle,
304    /// A line with its two neighbouring vertices (4 vertices).
305    LineAdjacency,
306    /// A triangle with its three neighbouring vertices (6 vertices).
307    TriangleAdjacency,
308}
309impl GsInput {
310    /// How many vertices the primitive has.
311    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/// What a geometry program's emitted vertices make.
323#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
324#[serde(rename_all = "snake_case")]
325pub enum GsOutput {
326    /// The emitted vertices are separate points.
327    PointList,
328    /// The emitted vertices make a line strip.
329    LineStrip,
330    /// The emitted vertices make a triangle strip.
331    TriangleStrip,
332}
333
334/// What a geometry program states: the primitive it reads, what its emitted vertices make, the most vertices an
335/// invocation emits and how many invocations run for a primitive.
336#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
337pub struct GeometryFacts {
338    /// The primitive the program reads.
339    pub input: GsInput,
340    /// What the emitted vertices make.
341    pub output: GsOutput,
342    /// The most vertices one invocation emits (`maxvertexcount`; `Interface::verify` accepts 1 to 1024).
343    pub max_vertices: u32,
344    /// How many invocations run for each input primitive (`instance`; `Interface::verify` accepts 1 to 32).
345    pub instances: u32,
346}
347
348/// What a mesh program's primitives are.
349#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
350#[serde(rename_all = "snake_case")]
351pub enum MeshTopology {
352    /// Each primitive is a line of two vertex indices.
353    Line,
354    /// Each primitive is a triangle of three vertex indices.
355    Triangle,
356}
357impl MeshTopology {
358    /// How many vertex indices a primitive has.
359    pub fn indices(self) -> u32 {
360        match self {
361            Self::Line => 2,
362            Self::Triangle => 3,
363        }
364    }
365}
366
367/// What a mesh program states: what its primitives are and the most vertices and primitives it writes. Its thread group
368/// size is the entry point's, as a compute program's is.
369#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
370pub struct MeshFacts {
371    /// What the primitives are.
372    pub topology: MeshTopology,
373    /// The most vertices the program writes (`Interface::verify` accepts 1 to 256).
374    pub max_vertices: u32,
375    /// The most primitives the program writes (`Interface::verify` accepts 1 to 256).
376    pub max_primitives: u32,
377}
378
379/// What a hit group runs for the geometry it is bound to.
380#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
381#[serde(rename_all = "snake_case")]
382pub enum HitGroupKind {
383    /// Triangles: the built-in intersection test, an optional any-hit and closest-hit program.
384    Triangles,
385    /// Procedural primitives: an intersection program of the library, an optional any-hit and closest-hit program.
386    ProceduralPrimitives,
387}
388
389/// A hit group of a ray tracing library: the programs it runs, named by the library's exports (none where the group has
390/// no such program).
391#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
392pub struct HitGroup {
393    /// The hit group's name in the library.
394    pub name: String,
395    /// Whether it runs on triangles or procedural primitives.
396    pub kind: HitGroupKind,
397    /// Export name of the any-hit program, if any.
398    pub any_hit: Option<String>,
399    /// Export name of the closest-hit program, if any.
400    pub closest_hit: Option<String>,
401    /// Export name of the intersection program; `None` for a triangle group.
402    pub intersection: Option<String>,
403}
404
405/// A state object subobject of a ray tracing library, as the library declares it.
406#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
407#[serde(rename_all = "snake_case")]
408pub enum Subobject {
409    /// The state object's flags (D3D12's `D3D12_STATE_OBJECT_FLAGS`).
410    StateObjectConfig {
411        /// The subobject's name in the library.
412        name: String,
413        /// The flags value, as stored.
414        flags: u32,
415    },
416    /// A root signature as D3D serialised it, for every program of the state object.
417    GlobalRootSignature {
418        /// The subobject's name.
419        name: String,
420        /// The serialised root signature bytes.
421        signature: Vec<u8>,
422    },
423    /// A root signature as D3D serialised it, for the programs an association names.
424    LocalRootSignature {
425        /// The subobject's name.
426        name: String,
427        /// The serialised root signature bytes.
428        signature: Vec<u8>,
429    },
430    /// The exports (programs and hit groups, by name) another subobject applies to.
431    ExportsAssociation {
432        /// The association's own name.
433        name: String,
434        /// The name of the subobject whose exports these are.
435        subobject: String,
436        /// The export names the association covers.
437        exports: Vec<String>,
438    },
439    /// The largest payload and hit attributes the state object's programs use.
440    ShaderConfig {
441        /// The subobject's name.
442        name: String,
443        /// The largest payload the programs use, in bytes.
444        max_payload_bytes: u32,
445        /// The largest hit attribute structure the programs use, in bytes.
446        max_attribute_bytes: u32,
447    },
448    /// The deepest `TraceRay` recursion, and the pipeline flags where the library states them.
449    PipelineConfig {
450        /// The subobject's name.
451        name: String,
452        /// The deepest recursion of `TraceRay`.
453        max_recursion_depth: u32,
454        /// The pipeline flags where the library states them.
455        flags: Option<u32>,
456    },
457    /// A hit group.
458    HitGroup(HitGroup),
459}
460
461/// What a ray tracing program's library states: the program's own export name (as the subobjects name it) and every
462/// subobject of the library, in the order it stores them. A pipeline is built from a library's programs and these.
463#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
464pub struct RayTracingFacts {
465    /// The export name of this program, as the subobjects refer to it.
466    pub export: String,
467    /// Every subobject of the library, in the order stored.
468    pub subobjects: Vec<Subobject>,
469}
470
471/// Facts about a program a stage's varyings and resources do not carry.
472#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
473#[serde(rename_all = "snake_case")]
474#[non_exhaustive]
475pub enum StageFacts {
476    /// The program states nothing beyond its varyings and resources.
477    #[default]
478    None,
479    /// A vertex program that writes no clip-space position: its outputs feed a hull program (or a stream-output
480    /// stage), so the program is a function (`main`) and not an entry point.
481    VertexWithoutPosition,
482    /// A hull program's patch.
483    Hull(HullFacts),
484    /// A domain program's patch.
485    Domain(DomainFacts),
486    /// A geometry program's primitives.
487    Geometry(GeometryFacts),
488    /// A ray tracing program of a library (see [`RayTracingFacts`]).
489    RayTracing(RayTracingFacts),
490    /// A mesh program.
491    Mesh(MeshFacts),
492}
493
494/// What a program exposes: its stage, the varyings it takes and gives, and the resources it binds.
495#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
496pub struct Interface {
497    /// The stage the program runs in.
498    pub stage: Stage,
499    /// The varyings the program reads, one per label.
500    pub inputs: Vec<Varying>,
501    /// The varyings the program writes, one per label.
502    pub outputs: Vec<Varying>,
503    /// The resources the program binds, each with a different id.
504    pub resources: Vec<Resource>,
505    /// Facts the stage adds; `Interface::verify` checks that they fit the stage.
506    pub facts: StageFacts,
507}
508impl Interface {
509    /// The facts agree with the stage: a hull or domain program states its patch, nothing else does, and a vertex
510    /// program that feeds a tessellator is a vertex program.
511    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    /// The resource with this id.
580    pub fn resource(&self, id: ResourceId) -> Option<&Resource> {
581        self.resources.iter().find(|r| r.id == id)
582    }
583
584    /// The interface's own invariants: one varying per label in each direction, values of 1 to 4 lanes, a colour
585    /// output `Target(n)` of a pixel stage with `n` of 0 to 7 and no such input, resource ids unique, every resource
586    /// spanning at least one binding with a buffer stride (a multiple of 4, up to 2048) or element width that means
587    /// something, every constant of a buffer (or of a `tbuffer` view's layout) inside it with a layout that does not
588    /// overflow, a hidden counter that belongs to a storage resource and has no binding of its own, a D3D register
589    /// class that fits the resource's kind, and no two resources on one register of one class and space.
590    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        // two resources may not share a register: each spans `count` registers (an unbounded array, all the rest)
697        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
729/// Every constant of `layout` lies inside it, and none has a layout that overflows.
730fn 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        // not an input, and not an output of another stage
829        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        // the owner is a texture
908        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        // a counter with a binding of its own
914        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            // every other class is refused
939            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        // the same register in another space, or another class, or the next register, is free
955        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        // the same register twice, an array over a register, an unbounded array over a later one
966        i.resources = vec![t(0, 2, 0, 1), t(1, 2, 0, 1)];
967        broken(&i, "share a ShaderResource register of space 0");
968        // an array of 4 from register 2 holds registers 2 to 5: register 6 is free, register 5 is not
969        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}