Skip to main content

iridia_interface/
interface.rs

1//! A program's interface: the varyings it reads and writes, and the system values among them.
2
3use serde::Serialize;
4
5use super::{BufferKind, BufferLayout, D3dClass, Resource, ResourceId, ResourceKind, Stage, StorageTarget};
6
7/// The scalar type of a value or constant.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ScalarType {
11    /// A boolean, 32 bits wide wherever it is stored (constant buffers, signatures).
12    Bool,
13    /// A signed 32-bit integer.
14    Sint,
15    /// An unsigned 32-bit integer.
16    Uint,
17    /// A 32-bit float.
18    Float,
19    /// A 64-bit float.
20    Double,
21}
22impl ScalarType {
23    /// Bytes one scalar takes in a constant buffer.
24    pub fn bytes(self) -> u32 {
25        match self {
26            Self::Double => 8,
27            _ => 4,
28        }
29    }
30}
31
32/// A scalar type and a width of 1 to 4 lanes.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
34pub struct Value {
35    /// The type of every lane.
36    pub scalar: ScalarType,
37    /// Number of lanes, 1 to 4.
38    pub width: u8,
39}
40
41/// A varying's name in its source: the semantic name and its index (DXBC `TEXCOORD` and 5, SPIR-V `Location` and 3,
42/// GLSL `vUv` and 0). Labels are not interpreted: games put anything in `TEXCOORDn`, and what a label means is
43/// the consumer's business. Stages match by label.
44#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
45pub struct Label {
46    /// The semantic or location name as the source writes it (`TEXCOORD`, `SV_Target`, `vUv`).
47    pub name: String,
48    /// The semantic index (the 5 of `TEXCOORD5`; the location of a SPIR-V variable).
49    pub index: u32,
50}
51
52/// The system values, one vocabulary for every API: what the pipeline itself supplies to or takes from a stage.
53#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
54#[serde(rename_all = "snake_case")]
55pub enum SystemValue {
56    /// A vertex stage's clip-space position output.
57    ClipPosition,
58    /// A pixel stage's window-space position input (x, y, depth, 1/w).
59    FragCoord,
60    /// A pixel stage's input: whether the primitive is front facing.
61    FrontFacing,
62    /// A vertex stage's input: the index of the vertex being processed.
63    VertexIndex,
64    /// A vertex stage's instance number; in a ray tracing hit program, the instance's index in the acceleration
65    /// structure.
66    InstanceIndex,
67    /// A pixel stage's primitive number; in a ray tracing hit program, the hit primitive's index in its geometry.
68    PrimitiveIndex,
69    /// A pixel stage's input: the index of the sample being shaded when it runs per sample.
70    SampleIndex,
71    /// The index of the render target array slice a primitive goes to (a geometry or mesh output, a pixel input).
72    RenderTargetArrayIndex,
73    /// The index of the viewport a primitive is drawn in (a geometry or mesh output, a pixel input).
74    ViewportArrayIndex,
75    /// Clip distances; the label's index says which array element.
76    ClipDistance,
77    /// Cull distances; the label's index says which array element.
78    CullDistance,
79    /// Colour output `n`.
80    Target(u32),
81    /// A pixel stage's depth output, written freely.
82    Depth,
83    /// Depth output the program promises is not less than the rasterized depth.
84    DepthGreaterEqual,
85    /// Depth output the program promises is not greater than the rasterized depth.
86    DepthLessEqual,
87    /// A pixel stage's stencil reference output.
88    StencilRef,
89    /// Coverage mask, as an input or an output.
90    Coverage,
91    /// Coverage of the pixel by the primitive alone (conservative rasterization).
92    InnerCoverage,
93    /// A compute stage's thread group (workgroup) number, three components.
94    GroupId,
95    /// A compute invocation's position inside its thread group, three components.
96    GroupThreadId,
97    /// A compute invocation's position in the whole dispatch (group number times the group size plus its position in
98    /// the group), three components.
99    DispatchThreadId,
100    /// A compute invocation's index in its thread group, row-major over the group's three axes.
101    GroupIndex,
102    /// The invocation's lane in its wave (subgroup).
103    LaneIndex,
104    /// How many lanes the wave (subgroup) has.
105    LaneCount,
106    /// A ray tracing program's position in the dispatch, three components.
107    DispatchRaysIndex,
108    /// The size of the dispatch a ray tracing program runs in, three components.
109    DispatchRaysDimensions,
110    /// The traced ray's origin and direction in world space, three components each.
111    WorldRayOrigin,
112    /// The ray's direction in world space.
113    WorldRayDirection,
114    /// The same in the space of the instance the ray hit (an intersection, any-hit or closest-hit program).
115    ObjectRayOrigin,
116    /// The ray's direction in the instance's space.
117    ObjectRayDirection,
118    /// The ray's start and the distance of the closest accepted hit so far (or the candidate being tested, in an
119    /// any-hit program), along the ray.
120    RayTMin,
121    /// The distance along the ray of the closest accepted hit so far, or of the candidate being tested in an any-hit
122    /// program.
123    RayTCurrent,
124    /// The traced ray's flags.
125    RayFlags,
126    /// The hit instance's own identifier (the 24 bits the acceleration structure was built with).
127    InstanceId,
128    /// The geometry's index within the hit instance's bottom-level acceleration structure.
129    GeometryIndex,
130    /// What the intersection program reported the hit as (`ReportHit`'s kind).
131    HitKind,
132    /// The hit instance's 3 by 4 transform to world space and back. One varying for each row used, the label's index
133    /// naming the row, its four lanes the row's four columns.
134    ObjectToWorld,
135    /// The inverse of [`SystemValue::ObjectToWorld`], in the same row layout.
136    WorldToObject,
137}
138
139/// How a varying is interpolated across a primitive.
140#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
141#[serde(rename_all = "snake_case")]
142pub enum InterpMode {
143    /// Perspective-correct (the default for an interpolant).
144    Perspective,
145    /// Linear in screen space (HLSL `noperspective`).
146    Linear,
147    /// The provoking vertex's value, not interpolated.
148    Flat,
149}
150
151/// Where in a pixel a varying is sampled.
152#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
153#[serde(rename_all = "snake_case")]
154pub enum InterpSampling {
155    /// Sampled at the pixel's centre.
156    Center,
157    /// Sampled inside the primitive when the pixel centre is not covered (HLSL `centroid`).
158    Centroid,
159    /// Per sample.
160    Sample,
161}
162
163/// A pixel input's interpolation: the mode and the sampling location.
164#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
165pub struct Interpolation {
166    /// How the value is interpolated across the primitive.
167    pub mode: InterpMode,
168    /// Where in the pixel the value is taken.
169    pub sampling: InterpSampling,
170}
171
172/// Where a D3D signature element sits: its register (four 32-bit lanes each) and the lanes of it the element
173/// occupies, as a mask (bit k is lane k, x first), and the signature's read/write mask as stored.
174#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
175pub struct D3dSignature {
176    /// The signature register the element is in, counted from 0; each register holds four 32-bit lanes.
177    pub register: u32,
178    /// The lanes of the register the element occupies: bit 0 is x, bit 3 is w.
179    pub mask: u8,
180    /// The signature's second mask as stored: for an input, the lanes the program always reads; for an output, the
181    /// lanes it never writes.
182    pub read_write_mask: u8,
183}
184
185/// The source's own address of a varying; one field per API that has a notion of it.
186#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
187pub struct VaryingProvenance {
188    /// The D3D signature address of the varying; `None` for a source that has none.
189    pub d3d: Option<D3dSignature>,
190}
191
192/// One interface variable: a value passed between stages (or between a stage and the pipeline), one per label. D3D
193/// packs several into one register; they are separate here, with the register and lanes kept in provenance.
194#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
195pub struct Varying {
196    /// The source's name for it, kept for system values too (`SV_Target` and 1).
197    pub label: Label,
198    /// What the pipeline itself supplies or takes here, when it is a system value.
199    pub system: Option<SystemValue>,
200    /// The type and lane count of the varying.
201    pub value: Value,
202    /// How it is interpolated, where the source states it for this varying: a pixel input the program declares.
203    /// `None` for outputs, for stages that do not interpolate, and for an input the program never declares (a source
204    /// that carries interpolation in the program body leaves it out of a signature read alone).
205    pub interp: Option<Interpolation>,
206    /// Whether the program reads (an input) or writes (an output) at least one lane, as the source's signature
207    /// states.
208    pub used: bool,
209    /// The source's address of the varying.
210    pub provenance: VaryingProvenance,
211}
212
213/// The shape a tessellator gives a patch.
214#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
215#[serde(rename_all = "snake_case")]
216pub enum TessDomain {
217    /// Lines: a patch is a set of isolines.
218    Isoline,
219    /// A triangle patch.
220    Triangle,
221    /// A quad patch.
222    Quad,
223}
224
225/// How the tessellator spaces the vertices it adds along an edge.
226#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
227#[serde(rename_all = "snake_case")]
228pub enum Partitioning {
229    /// Each edge is divided into a whole number of segments.
230    Integer,
231    /// The factor is rounded up to a power of two.
232    Pow2,
233    /// Fractional, with an odd number of segments between the ends.
234    FractionalOdd,
235    /// Fractional, with an even number of segments.
236    FractionalEven,
237}
238
239/// What the tessellator emits.
240#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
241#[serde(rename_all = "snake_case")]
242pub enum TessTopology {
243    /// Points.
244    Point,
245    /// Lines.
246    Line,
247    /// Triangles wound clockwise.
248    TriangleCw,
249    /// Triangles wound counter-clockwise.
250    TriangleCcw,
251}
252
253/// What a hull program states about its patch beyond its varyings. The patch-constant varyings are its outputs per
254/// patch (`SV_TessFactor`, `SV_InsideTessFactor`, whatever the domain program reads); `outputs` of the interface are
255/// its control points'.
256#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
257pub struct HullFacts {
258    /// The domain the hull program declares.
259    pub domain: TessDomain,
260    /// How the tessellator divides the edges.
261    pub partitioning: Partitioning,
262    /// What the tessellator outputs.
263    pub topology: TessTopology,
264    /// Control points the program reads per patch (1 to 32).
265    pub input_control_points: u32,
266    /// Control points the program writes per patch (1 to 32).
267    pub output_control_points: u32,
268    /// The largest tessellation factor, as the bits of an `f32` (the interface is hashed).
269    pub max_tess_factor_bits: u32,
270    /// The patch-constant outputs, per patch, in the order the signature lists them.
271    pub patch_constants: Vec<Varying>,
272}
273
274/// What a domain program states about its patch: the same domain as the hull program that feeds it, how many control
275/// points it reads, and the patch-constant varyings it reads (`inputs` of the interface are its control points').
276#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
277pub struct DomainFacts {
278    /// The domain the program was written for (the hull program that feeds it states the same one).
279    pub domain: TessDomain,
280    /// Control points per patch the program reads (1 to 32).
281    pub input_control_points: u32,
282    /// The patch-constant inputs the program reads.
283    pub patch_constants: Vec<Varying>,
284}
285
286/// The primitive a geometry program reads.
287#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
288#[serde(rename_all = "snake_case")]
289pub enum GsInput {
290    /// One point (1 vertex).
291    Point,
292    /// One line (2 vertices).
293    Line,
294    /// One triangle (3 vertices).
295    Triangle,
296    /// A line with its two neighbouring vertices (4 vertices).
297    LineAdjacency,
298    /// A triangle with its three neighbouring vertices (6 vertices).
299    TriangleAdjacency,
300}
301impl GsInput {
302    /// How many vertices the primitive has.
303    pub fn vertices(self) -> u32 {
304        match self {
305            Self::Point => 1,
306            Self::Line => 2,
307            Self::Triangle => 3,
308            Self::LineAdjacency => 4,
309            Self::TriangleAdjacency => 6,
310        }
311    }
312}
313
314/// What a geometry program's emitted vertices make.
315#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
316#[serde(rename_all = "snake_case")]
317pub enum GsOutput {
318    /// The emitted vertices are separate points.
319    PointList,
320    /// The emitted vertices make a line strip.
321    LineStrip,
322    /// The emitted vertices make a triangle strip.
323    TriangleStrip,
324}
325
326/// What a geometry program states: the primitive it reads, what its emitted vertices make, the most vertices an
327/// invocation emits and how many invocations run for a primitive.
328#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
329pub struct GeometryFacts {
330    /// The primitive the program reads.
331    pub input: GsInput,
332    /// What the emitted vertices make.
333    pub output: GsOutput,
334    /// The most vertices one invocation emits (`maxvertexcount`; `Interface::verify` accepts 1 to 1024).
335    pub max_vertices: u32,
336    /// How many invocations run for each input primitive (`instance`; `Interface::verify` accepts 1 to 32).
337    pub instances: u32,
338}
339
340/// What a mesh program's primitives are.
341#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
342#[serde(rename_all = "snake_case")]
343pub enum MeshTopology {
344    /// Each primitive is a line of two vertex indices.
345    Line,
346    /// Each primitive is a triangle of three vertex indices.
347    Triangle,
348}
349impl MeshTopology {
350    /// How many vertex indices a primitive has.
351    pub fn indices(self) -> u32 {
352        match self {
353            Self::Line => 2,
354            Self::Triangle => 3,
355        }
356    }
357}
358
359/// What a mesh program states: what its primitives are and the most vertices and primitives it writes. Its thread group
360/// size is the entry point's, as a compute program's is.
361#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
362pub struct MeshFacts {
363    /// What the primitives are.
364    pub topology: MeshTopology,
365    /// The most vertices the program writes (`Interface::verify` accepts 1 to 256).
366    pub max_vertices: u32,
367    /// The most primitives the program writes (`Interface::verify` accepts 1 to 256).
368    pub max_primitives: u32,
369}
370
371/// What a hit group runs for the geometry it is bound to.
372#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
373#[serde(rename_all = "snake_case")]
374pub enum HitGroupKind {
375    /// Triangles: the built-in intersection test, an optional any-hit and closest-hit program.
376    Triangles,
377    /// Procedural primitives: an intersection program of the library, an optional any-hit and closest-hit program.
378    ProceduralPrimitives,
379}
380
381/// A hit group of a ray tracing library: the programs it runs, named by the library's exports (none where the group has
382/// no such program).
383#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
384pub struct HitGroup {
385    /// The hit group's name in the library.
386    pub name: String,
387    /// Whether it runs on triangles or procedural primitives.
388    pub kind: HitGroupKind,
389    /// Export name of the any-hit program, if any.
390    pub any_hit: Option<String>,
391    /// Export name of the closest-hit program, if any.
392    pub closest_hit: Option<String>,
393    /// Export name of the intersection program; `None` for a triangle group.
394    pub intersection: Option<String>,
395}
396
397/// A state object subobject of a ray tracing library, as the library declares it.
398#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
399#[serde(rename_all = "snake_case")]
400pub enum Subobject {
401    /// The state object's flags (D3D12's `D3D12_STATE_OBJECT_FLAGS`).
402    StateObjectConfig {
403        /// The subobject's name in the library.
404        name: String,
405        /// The flags value, as stored.
406        flags: u32,
407    },
408    /// A root signature as D3D serialised it, for every program of the state object.
409    GlobalRootSignature {
410        /// The subobject's name.
411        name: String,
412        /// The serialised root signature bytes.
413        signature: Vec<u8>,
414    },
415    /// A root signature as D3D serialised it, for the programs an association names.
416    LocalRootSignature {
417        /// The subobject's name.
418        name: String,
419        /// The serialised root signature bytes.
420        signature: Vec<u8>,
421    },
422    /// The exports (programs and hit groups, by name) another subobject applies to.
423    ExportsAssociation {
424        /// The association's own name.
425        name: String,
426        /// The name of the subobject whose exports these are.
427        subobject: String,
428        /// The export names the association covers.
429        exports: Vec<String>,
430    },
431    /// The largest payload and hit attributes the state object's programs use.
432    ShaderConfig {
433        /// The subobject's name.
434        name: String,
435        /// The largest payload the programs use, in bytes.
436        max_payload_bytes: u32,
437        /// The largest hit attribute structure the programs use, in bytes.
438        max_attribute_bytes: u32,
439    },
440    /// The deepest `TraceRay` recursion, and the pipeline flags where the library states them.
441    PipelineConfig {
442        /// The subobject's name.
443        name: String,
444        /// The deepest recursion of `TraceRay`.
445        max_recursion_depth: u32,
446        /// The pipeline flags where the library states them.
447        flags: Option<u32>,
448    },
449    /// A hit group.
450    HitGroup(HitGroup),
451}
452
453/// What a ray tracing program's library states: the program's own export name (as the subobjects name it) and every
454/// subobject of the library, in the order it stores them. A pipeline is built from a library's programs and these.
455#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
456pub struct RayTracingFacts {
457    /// The export name of this program, as the subobjects refer to it.
458    pub export: String,
459    /// Every subobject of the library, in the order stored.
460    pub subobjects: Vec<Subobject>,
461}
462
463/// Facts about a program a stage's varyings and resources do not carry.
464#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
465#[serde(rename_all = "snake_case")]
466#[non_exhaustive]
467pub enum StageFacts {
468    /// The program states nothing beyond its varyings and resources.
469    #[default]
470    None,
471    /// A vertex program that writes no clip-space position: its outputs feed a hull program (or a stream-output
472    /// stage), so the program is a function (`main`) and not an entry point.
473    VertexWithoutPosition,
474    /// A hull program's patch.
475    Hull(HullFacts),
476    /// A domain program's patch.
477    Domain(DomainFacts),
478    /// A geometry program's primitives.
479    Geometry(GeometryFacts),
480    /// A ray tracing program of a library (see [`RayTracingFacts`]).
481    RayTracing(RayTracingFacts),
482    /// A mesh program.
483    Mesh(MeshFacts),
484}
485
486/// What a program exposes: its stage, the varyings it takes and gives, and the resources it binds.
487#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
488pub struct Interface {
489    /// The stage the program runs in.
490    pub stage: Stage,
491    /// The varyings the program reads, one per label.
492    pub inputs: Vec<Varying>,
493    /// The varyings the program writes, one per label.
494    pub outputs: Vec<Varying>,
495    /// The resources the program binds, each with a different id.
496    pub resources: Vec<Resource>,
497    /// Facts the stage adds; `Interface::verify` checks that they fit the stage.
498    pub facts: StageFacts,
499}
500impl Interface {
501    /// The facts agree with the stage: a hull or domain program states its patch, nothing else does, and a vertex
502    /// program that feeds a tessellator is a vertex program.
503    fn verify_facts(&self) -> Result<(), String> {
504        let patch = |what: &str, count: u32, constants: &[Varying]| -> Result<(), String> {
505            if !(1..=32).contains(&count) {
506                return Err(format!("{what}: {count} control points, not 1 to 32"));
507            }
508            for (i, v) in constants.iter().enumerate() {
509                if !(1..=4).contains(&v.value.width) {
510                    return Err(format!(
511                        "patch constant {}{} has a width of {}, not 1 to 4",
512                        v.label.name, v.label.index, v.value.width
513                    ));
514                }
515                if constants.iter().take(i).any(|other| other.label == v.label) {
516                    return Err(format!(
517                        "patch constant {}{} is listed twice",
518                        v.label.name, v.label.index
519                    ));
520                }
521            }
522            Ok(())
523        };
524        match (&self.stage, &self.facts) {
525            (Stage::Hull, StageFacts::Hull(h)) => {
526                patch("hull input", h.input_control_points, &h.patch_constants)?;
527                patch("hull output", h.output_control_points, &[])?;
528                let factor = f32::from_bits(h.max_tess_factor_bits);
529                if !(1.0..=64.0).contains(&factor) {
530                    return Err(format!("a maximum tessellation factor of {factor}"));
531                }
532                Ok(())
533            }
534            (Stage::Domain, StageFacts::Domain(d)) => {
535                patch("domain", d.input_control_points, &d.patch_constants)
536            }
537            (Stage::Geometry, StageFacts::Geometry(g)) => {
538                if !(1..=1024).contains(&g.max_vertices) || !(1..=32).contains(&g.instances) {
539                    return Err(format!(
540                        "a geometry program of {} vertices and {} instances",
541                        g.max_vertices, g.instances
542                    ));
543                }
544                Ok(())
545            }
546            (Stage::Vertex, StageFacts::VertexWithoutPosition) => Ok(()),
547            (Stage::Hull | Stage::Domain, _) => {
548                Err(format!("a {:?} program without its patch facts", self.stage))
549            }
550            (Stage::Mesh, StageFacts::Mesh(m)) => {
551                if !(1..=256).contains(&m.max_vertices) || !(1..=256).contains(&m.max_primitives) {
552                    return Err(format!(
553                        "a mesh program of {} vertices and {} primitives",
554                        m.max_vertices, m.max_primitives
555                    ));
556                }
557                Ok(())
558            }
559            (Stage::Mesh, _) => Err("a mesh program without its facts".into()),
560            (stage, StageFacts::RayTracing(r)) if stage.is_ray_tracing() => {
561                if r.export.is_empty() {
562                    return Err(format!("a {stage:?} program that names no export"));
563                }
564                Ok(())
565            }
566            (_, StageFacts::None) => Ok(()),
567            (stage, facts) => Err(format!("{stage:?} program with {facts:?}")),
568        }
569    }
570
571    /// The resource with this id.
572    pub fn resource(&self, id: ResourceId) -> Option<&Resource> {
573        self.resources.iter().find(|r| r.id == id)
574    }
575
576    /// The interface's own invariants: one varying per label in each direction, values of 1 to 4 lanes, a colour
577    /// output `Target(n)` of a pixel stage with `n` of 0 to 7 and no such input, resource ids unique, every resource
578    /// spanning at least one binding with a buffer stride (a multiple of 4, up to 2048) or element width that means
579    /// something, every constant of a buffer (or of a `tbuffer` view's layout) inside it with a layout that does not
580    /// overflow, a hidden counter that belongs to a storage resource and has no binding of its own, a D3D register
581    /// class that fits the resource's kind, and no two resources on one register of one class and space.
582    pub fn verify(&self) -> Result<(), String> {
583        self.verify_facts()?;
584        for (direction, varyings) in [("input", &self.inputs), ("output", &self.outputs)] {
585            for (i, v) in varyings.iter().enumerate() {
586                if let Some(SystemValue::Target(n)) = v.system {
587                    if direction == "input" || self.stage != Stage::Pixel {
588                        return Err(format!(
589                            "{direction} {}{} is colour output {n} of a {:?} stage: only a pixel stage's outputs are",
590                            v.label.name, v.label.index, self.stage
591                        ));
592                    }
593                    if n > 7 {
594                        return Err(format!(
595                            "output {}{} is colour output {n}, not 0 to 7",
596                            v.label.name, v.label.index
597                        ));
598                    }
599                }
600                if !(1..=4).contains(&v.value.width) {
601                    return Err(format!(
602                        "{direction} {}{} has a width of {}, not 1 to 4",
603                        v.label.name, v.label.index, v.value.width
604                    ));
605                }
606                if varyings.iter().take(i).any(|other| other.label == v.label) {
607                    return Err(format!(
608                        "{direction} {}{} is listed twice",
609                        v.label.name, v.label.index
610                    ));
611                }
612            }
613        }
614        let width_ok = |w: u8| (1..=4).contains(&w);
615        for (i, r) in self.resources.iter().enumerate() {
616            let name = &r.provenance.name;
617            if self.resources.iter().take(i).any(|other| other.id == r.id) {
618                return Err(format!("resource id {} ({name}) is used twice", r.id.0));
619            }
620            if r.count == 0 {
621                return Err(format!("resource {name} spans no bindings"));
622            }
623            let buffer = |kind: &BufferKind| match kind {
624                BufferKind::Typed(v) if !width_ok(v.width) => {
625                    Err(format!("buffer {name} has elements of {} lanes", v.width))
626                }
627                BufferKind::Structured { stride: 0 } => Err(format!("buffer {name} has a stride of 0")),
628                BufferKind::Structured { stride } if !stride.is_multiple_of(4) || *stride > 2048 => Err(
629                    format!("buffer {name} has a stride of {stride}, not a multiple of 4 up to 2048"),
630                ),
631                _ => Ok(()),
632            };
633            match &r.kind {
634                ResourceKind::Buffer(kind)
635                | ResourceKind::Storage {
636                    target: StorageTarget::Buffer(kind),
637                    ..
638                } => {
639                    buffer(kind)?;
640                }
641                ResourceKind::ConstantBuffer { layout } => layout_fits(name, layout)?,
642                _ => {}
643            }
644            if let Some(layout) = &r.provenance.layout {
645                layout_fits(name, layout)?;
646            }
647            if let Some(target) = r.provenance.counter_of {
648                let owner = self.resource(target).ok_or_else(|| {
649                    format!(
650                        "resource {name} is the counter of resource {}, which is not there",
651                        target.0
652                    )
653                })?;
654                if target == r.id || !matches!(owner.kind, ResourceKind::Storage { .. }) {
655                    return Err(format!(
656                        "resource {name} is the counter of resource {} ({}), which is not another storage resource",
657                        target.0, owner.provenance.name
658                    ));
659                }
660                if r.provenance.d3d.is_some() {
661                    return Err(format!(
662                        "resource {name} is a hidden counter and has a binding of its own"
663                    ));
664                }
665            }
666            if let Some(d3d) = r.provenance.d3d {
667                let class = match &r.kind {
668                    ResourceKind::ConstantBuffer { .. } => D3dClass::ConstantBuffer,
669                    ResourceKind::Texture { .. }
670                    | ResourceKind::Buffer(_)
671                    | ResourceKind::AccelerationStructure => D3dClass::ShaderResource,
672                    ResourceKind::Sampler { .. } => D3dClass::Sampler,
673                    ResourceKind::Storage { .. } => D3dClass::UnorderedAccess,
674                };
675                if d3d.class != class {
676                    return Err(format!(
677                        "resource {name} is bound as {:?} but its kind is read through {class:?}",
678                        d3d.class
679                    ));
680                }
681            }
682        }
683        // two resources may not share a register: each spans `count` registers (an unbounded array, all the rest)
684        let span = |r: &Resource| {
685            r.provenance.d3d.map(|d| {
686                let first = u64::from(d.register);
687                let end = if r.is_unbounded() {
688                    u64::MAX
689                } else {
690                    first.saturating_add(u64::from(r.count))
691                };
692                (d.class, d.space, first, end)
693            })
694        };
695        for (i, a) in self.resources.iter().enumerate() {
696            let Some((class, space, first, end)) = span(a) else {
697                continue;
698            };
699            for b in self.resources.iter().take(i) {
700                if let Some((c, s, f, e)) = span(b)
701                    && (c, s) == (class, space)
702                    && first < e
703                    && f < end
704                {
705                    return Err(format!(
706                        "resources {} and {} share a {class:?} register of space {space}",
707                        b.provenance.name, a.provenance.name
708                    ));
709                }
710            }
711        }
712        Ok(())
713    }
714}
715
716/// Every constant of `layout` lies inside it, and none has a layout that overflows.
717fn layout_fits(name: &str, layout: &BufferLayout) -> Result<(), String> {
718    for c in &layout.constants {
719        let size =
720            c.ty.size()
721                .ok_or_else(|| format!("constant {name}.{} overflows", c.name))?;
722        if c.offset.checked_add(size).is_none_or(|end| end > layout.size) {
723            return Err(format!(
724                "constant {name}.{} ({size} bytes at {}) reaches past the end of the {}-byte buffer",
725                c.name, c.offset, layout.size
726            ));
727        }
728    }
729    Ok(())
730}
731
732#[cfg(test)]
733mod tests {
734    use super::*;
735    use crate::{
736        BufferLayout, Constant, ConstantType, D3dBinding, ResourceProvenance, SampleType, TextureDim,
737    };
738
739    fn pixel() -> Interface {
740        Interface {
741            stage: Stage::Pixel,
742            inputs: Vec::new(),
743            outputs: Vec::new(),
744            resources: Vec::new(),
745            facts: Default::default(),
746        }
747    }
748    fn target(index: u32, system: Option<SystemValue>) -> Varying {
749        Varying {
750            label: Label {
751                name: "SV_Target".into(),
752                index,
753            },
754            system,
755            value: Value {
756                scalar: ScalarType::Float,
757                width: 4,
758            },
759            interp: None,
760            used: true,
761            provenance: Default::default(),
762        }
763    }
764    fn texture() -> ResourceKind {
765        ResourceKind::Texture {
766            dim: TextureDim::D2,
767            arrayed: false,
768            multisampled: false,
769            sample: SampleType::Float,
770        }
771    }
772    fn res(
773        id: u32,
774        name: &str,
775        kind: ResourceKind,
776        d3d: Option<(D3dClass, u32, u32)>,
777        count: u32,
778    ) -> Resource {
779        Resource {
780            id: ResourceId(id),
781            kind,
782            count,
783            provenance: ResourceProvenance {
784                name: name.into(),
785                d3d: d3d.map(|(class, register, space)| D3dBinding {
786                    class,
787                    register,
788                    space,
789                }),
790                ..ResourceProvenance::default()
791            },
792        }
793    }
794    fn storage() -> ResourceKind {
795        ResourceKind::Storage {
796            target: StorageTarget::Buffer(BufferKind::Raw),
797            access: crate::Access::ReadWrite,
798        }
799    }
800    fn broken(i: &Interface, expect: &str) {
801        let message = i.verify().unwrap_err();
802        assert!(message.contains(expect), "{expect}: {message}");
803    }
804
805    #[test]
806    fn a_colour_output_is_a_pixel_outputs_target_of_0_to_7() {
807        let mut i = pixel();
808        i.outputs = vec![
809            target(0, Some(SystemValue::Target(0))),
810            target(7, Some(SystemValue::Target(7))),
811        ];
812        assert_eq!(i.verify(), Ok(()));
813        i.outputs.push(target(8, Some(SystemValue::Target(8))));
814        broken(&i, "colour output 8, not 0 to 7");
815        // not an input, and not an output of another stage
816        let mut i = pixel();
817        i.inputs = vec![target(0, Some(SystemValue::Target(0)))];
818        broken(&i, "only a pixel stage's outputs are");
819        let mut i = pixel();
820        i.stage = Stage::Vertex;
821        i.outputs = vec![target(0, Some(SystemValue::Target(0)))];
822        broken(&i, "only a pixel stage's outputs are");
823    }
824
825    #[test]
826    fn a_structured_buffers_stride_is_a_multiple_of_4_up_to_2048() {
827        for (stride, ok) in [
828            (4, true),
829            (16, true),
830            (2048, true),
831            (0, false),
832            (6, false),
833            (2052, false),
834        ] {
835            let mut i = pixel();
836            i.resources = vec![res(
837                0,
838                "b",
839                ResourceKind::Buffer(BufferKind::Structured { stride }),
840                Some((D3dClass::ShaderResource, 0, 0)),
841                1,
842            )];
843            assert_eq!(i.verify().is_ok(), ok, "stride {stride}");
844        }
845    }
846
847    #[test]
848    fn a_tbuffers_members_lie_inside_its_layout() {
849        let layout = |offset| BufferLayout {
850            size: 16,
851            constants: vec![Constant {
852                name: "m".into(),
853                offset,
854                ty: ConstantType::Vector(Value {
855                    scalar: ScalarType::Float,
856                    width: 4,
857                }),
858                default: None,
859            }],
860        };
861        let mut view = res(
862            0,
863            "tb",
864            ResourceKind::Buffer(BufferKind::Typed(Value {
865                scalar: ScalarType::Float,
866                width: 4,
867            })),
868            Some((D3dClass::ShaderResource, 0, 0)),
869            1,
870        );
871        view.provenance.layout = Some(layout(0));
872        let mut i = pixel();
873        i.resources = vec![view.clone()];
874        assert_eq!(i.verify(), Ok(()));
875        i.resources[0].provenance.layout = Some(layout(4));
876        broken(&i, "reaches past the end");
877    }
878
879    #[test]
880    fn a_hidden_counter_belongs_to_a_storage_resource_and_has_no_binding() {
881        let counter = |of: u32, d3d| {
882            let mut c = res(1, "c", ResourceKind::Buffer(BufferKind::Raw), d3d, 1);
883            c.provenance.counter_of = Some(ResourceId(of));
884            c
885        };
886        let uav = res(0, "u", storage(), Some((D3dClass::UnorderedAccess, 0, 0)), 1);
887        let mut i = pixel();
888        i.resources = vec![uav.clone(), counter(0, None)];
889        assert_eq!(i.verify(), Ok(()));
890        i.resources = vec![uav.clone(), counter(5, None)];
891        broken(&i, "which is not there");
892        i.resources = vec![uav.clone(), counter(1, None)];
893        broken(&i, "not another storage resource");
894        // the owner is a texture
895        i.resources = vec![
896            res(0, "t", texture(), Some((D3dClass::ShaderResource, 0, 0)), 1),
897            counter(0, None),
898        ];
899        broken(&i, "not another storage resource");
900        // a counter with a binding of its own
901        i.resources = vec![uav, counter(0, Some((D3dClass::ShaderResource, 3, 0)))];
902        broken(&i, "binding of its own");
903    }
904
905    #[test]
906    fn a_register_class_fits_the_kind_and_a_register_holds_one_resource() {
907        let cb = ResourceKind::ConstantBuffer {
908            layout: BufferLayout {
909                size: 16,
910                constants: Vec::new(),
911            },
912        };
913        let kinds = [
914            (cb.clone(), D3dClass::ConstantBuffer),
915            (texture(), D3dClass::ShaderResource),
916            (ResourceKind::Buffer(BufferKind::Raw), D3dClass::ShaderResource),
917            (ResourceKind::AccelerationStructure, D3dClass::ShaderResource),
918            (ResourceKind::Sampler { comparison: false }, D3dClass::Sampler),
919            (storage(), D3dClass::UnorderedAccess),
920        ];
921        for (kind, class) in &kinds {
922            let mut i = pixel();
923            i.resources = vec![res(0, "r", kind.clone(), Some((*class, 0, 0)), 1)];
924            assert_eq!(i.verify(), Ok(()), "{kind:?}");
925            // every other class is refused
926            for (_, other) in kinds.iter().filter(|(_, c)| c != class) {
927                i.resources = vec![res(0, "r", kind.clone(), Some((*other, 0, 0)), 1)];
928                broken(&i, "its kind is read through");
929            }
930        }
931        let t = |id, register, space, count| {
932            res(
933                id,
934                &format!("t{id}"),
935                texture(),
936                Some((D3dClass::ShaderResource, register, space)),
937                count,
938            )
939        };
940        let mut i = pixel();
941        // the same register in another space, or another class, or the next register, is free
942        i.resources = vec![t(0, 2, 0, 1), t(1, 2, 1, 1), t(2, 3, 0, 1)];
943        assert_eq!(i.verify(), Ok(()));
944        i.resources.push(res(
945            3,
946            "s",
947            ResourceKind::Sampler { comparison: false },
948            Some((D3dClass::Sampler, 2, 0)),
949            1,
950        ));
951        assert_eq!(i.verify(), Ok(()));
952        // the same register twice, an array over a register, an unbounded array over a later one
953        i.resources = vec![t(0, 2, 0, 1), t(1, 2, 0, 1)];
954        broken(&i, "share a ShaderResource register of space 0");
955        // an array of 4 from register 2 holds registers 2 to 5: register 6 is free, register 5 is not
956        i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1)];
957        assert_eq!(i.verify(), Ok(()));
958        i.resources = vec![t(0, 2, 0, 4), t(1, 6, 0, 1), t(2, 5, 0, 1)];
959        broken(&i, "t0 and t2 share");
960        i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 90, 0, 1)];
961        broken(&i, "share a ShaderResource");
962        i.resources = vec![t(0, 2, 0, Resource::UNBOUNDED), t(1, 1, 0, 1), t(2, 2, 1, 1)];
963        assert_eq!(i.verify(), Ok(()));
964    }
965
966    #[test]
967    fn a_varying_serializes_with_its_provenance() {
968        let v = Varying {
969            label: Label {
970                name: "TEXCOORD".into(),
971                index: 5,
972            },
973            system: None,
974            value: Value {
975                scalar: ScalarType::Float,
976                width: 2,
977            },
978            interp: Some(Interpolation {
979                mode: InterpMode::Perspective,
980                sampling: InterpSampling::Center,
981            }),
982            used: true,
983            provenance: VaryingProvenance {
984                d3d: Some(D3dSignature {
985                    register: 3,
986                    mask: 0b1100,
987                    read_write_mask: 0b1100,
988                }),
989            },
990        };
991        assert_eq!(
992            serde_json::to_value(&v).unwrap(),
993            serde_json::json!({
994                "label": {"name": "TEXCOORD", "index": 5},
995                "system": null,
996                "value": {"scalar": "float", "width": 2},
997                "interp": {"mode": "perspective", "sampling": "center"},
998                "used": true,
999                "provenance": {"d3d": {"register": 3, "mask": 12, "read_write_mask": 12}},
1000            })
1001        );
1002        assert_eq!(
1003            serde_json::to_value(SystemValue::Target(2)).unwrap(),
1004            serde_json::json!({"target": 2})
1005        );
1006    }
1007}