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, the most vertices and primitives it writes and the task payload
368/// it reads. Its thread group 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    /// The bytes of the task payload it reads from the amplification program that launches it; 0 where it reads none
378    /// (`Interface::verify` accepts up to 16,384, a multiple of 4).
379    pub payload: u32,
380}
381
382/// What an amplification program states: the task payload it hands to the mesh thread groups it launches. Its thread
383/// group size is the entry point's, as a compute program's is.
384#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
385pub struct AmplificationFacts {
386    /// The bytes of its task payload (`Interface::verify` accepts 4 to 16,384, a multiple of 4: Direct3D requires a
387    /// payload, even of a program that writes none).
388    pub payload: u32,
389}
390
391/// The most bytes of a task payload, Direct3D's.
392pub const MAX_TASK_PAYLOAD: u32 = 16384;
393
394/// What a hit group runs for the geometry it is bound to.
395#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
396#[serde(rename_all = "snake_case")]
397pub enum HitGroupKind {
398    /// Triangles: the built-in intersection test, an optional any-hit and closest-hit program.
399    Triangles,
400    /// Procedural primitives: an intersection program of the library, an optional any-hit and closest-hit program.
401    ProceduralPrimitives,
402}
403
404/// A hit group of a ray tracing library: the programs it runs, named by the library's exports (none where the group has
405/// no such program).
406#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
407pub struct HitGroup {
408    /// The hit group's name in the library.
409    pub name: String,
410    /// Whether it runs on triangles or procedural primitives.
411    pub kind: HitGroupKind,
412    /// Export name of the any-hit program, if any.
413    pub any_hit: Option<String>,
414    /// Export name of the closest-hit program, if any.
415    pub closest_hit: Option<String>,
416    /// Export name of the intersection program; `None` for a triangle group.
417    pub intersection: Option<String>,
418}
419
420/// A state object subobject of a ray tracing library, as the library declares it.
421#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
422#[serde(rename_all = "snake_case")]
423pub enum Subobject {
424    /// The state object's flags (D3D12's `D3D12_STATE_OBJECT_FLAGS`).
425    StateObjectConfig {
426        /// The subobject's name in the library.
427        name: String,
428        /// The flags value, as stored.
429        flags: u32,
430    },
431    /// A root signature as D3D serialised it, for every program of the state object.
432    GlobalRootSignature {
433        /// The subobject's name.
434        name: String,
435        /// The serialised root signature bytes.
436        signature: Vec<u8>,
437    },
438    /// A root signature as D3D serialised it, for the programs an association names.
439    LocalRootSignature {
440        /// The subobject's name.
441        name: String,
442        /// The serialised root signature bytes.
443        signature: Vec<u8>,
444    },
445    /// The exports (programs and hit groups, by name) another subobject applies to.
446    ExportsAssociation {
447        /// The association's own name.
448        name: String,
449        /// The name of the subobject whose exports these are.
450        subobject: String,
451        /// The export names the association covers.
452        exports: Vec<String>,
453    },
454    /// The largest payload and hit attributes the state object's programs use.
455    ShaderConfig {
456        /// The subobject's name.
457        name: String,
458        /// The largest payload the programs use, in bytes.
459        max_payload_bytes: u32,
460        /// The largest hit attribute structure the programs use, in bytes.
461        max_attribute_bytes: u32,
462    },
463    /// The deepest `TraceRay` recursion, and the pipeline flags where the library states them.
464    PipelineConfig {
465        /// The subobject's name.
466        name: String,
467        /// The deepest recursion of `TraceRay`.
468        max_recursion_depth: u32,
469        /// The pipeline flags where the library states them.
470        flags: Option<u32>,
471    },
472    /// A hit group.
473    HitGroup(HitGroup),
474}
475
476/// What a ray tracing program's library states: the program's own export name (as the subobjects name it) and every
477/// subobject of the library, in the order it stores them. A pipeline is built from a library's programs and these.
478#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
479pub struct RayTracingFacts {
480    /// The export name of this program, as the subobjects refer to it.
481    pub export: String,
482    /// Every subobject of the library, in the order stored.
483    pub subobjects: Vec<Subobject>,
484}
485
486/// Facts about a program a stage's varyings and resources do not carry.
487#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
488#[serde(rename_all = "snake_case")]
489#[non_exhaustive]
490pub enum StageFacts {
491    /// The program states nothing beyond its varyings and resources.
492    #[default]
493    None,
494    /// A vertex program that writes no clip-space position: its outputs feed a hull program (or a stream-output
495    /// stage), so the program is a function (`main`) and not an entry point.
496    VertexWithoutPosition,
497    /// A hull program's patch.
498    Hull(HullFacts),
499    /// A domain program's patch.
500    Domain(DomainFacts),
501    /// A geometry program's primitives.
502    Geometry(GeometryFacts),
503    /// A ray tracing program of a library (see [`RayTracingFacts`]).
504    RayTracing(RayTracingFacts),
505    /// A mesh program.
506    Mesh(MeshFacts),
507    /// An amplification program.
508    Amplification(AmplificationFacts),
509}
510
511/// What a program exposes: its stage, the varyings it takes and gives, and the resources it binds.
512#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
513pub struct Interface {
514    /// The stage the program runs in.
515    pub stage: Stage,
516    /// The varyings the program reads, one per label.
517    pub inputs: Vec<Varying>,
518    /// The varyings the program writes, one per label.
519    pub outputs: Vec<Varying>,
520    /// The resources the program binds, each with a different id.
521    pub resources: Vec<Resource>,
522    /// Facts the stage adds; `Interface::verify` checks that they fit the stage.
523    pub facts: StageFacts,
524}
525impl Interface {
526    /// The facts agree with the stage: a hull or domain program states its patch, nothing else does, and a vertex
527    /// program that feeds a tessellator is a vertex program.
528    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    /// The resource with this id.
613    pub fn resource(&self, id: ResourceId) -> Option<&Resource> {
614        self.resources.iter().find(|r| r.id == id)
615    }
616
617    /// The interface's own invariants: one varying per label in each direction, values of 1 to 4 lanes, a colour
618    /// output `Target(n)` of a pixel stage with `n` of 0 to 7 and no such input, resource ids unique, every resource
619    /// spanning at least one binding with a buffer stride (a multiple of 4, up to 2048) or element width that means
620    /// something, every constant of a buffer (or of a `tbuffer` view's layout) inside it with a layout that does not
621    /// overflow, a hidden counter that belongs to a storage resource and has no binding of its own, a D3D register
622    /// class that fits the resource's kind, and no two resources on one register of one class and space.
623    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        // two resources may not share a register: each spans `count` registers (an unbounded array, all the rest)
730        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
762/// Every constant of `layout` lies inside it, and none has a layout that overflows.
763fn 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        // not an input, and not an output of another stage
862        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        // the owner is a texture
941        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        // a counter with a binding of its own
947        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            // every other class is refused
972            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        // the same register in another space, or another class, or the next register, is free
988        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        // the same register twice, an array over a register, an unbounded array over a later one
999        i.resources = vec![t(0, 2, 0, 1), t(1, 2, 0, 1)];
1000        broken(&i, "share a ShaderResource register of space 0");
1001        // an array of 4 from register 2 holds registers 2 to 5: register 6 is free, register 5 is not
1002        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}