Skip to main content

iridia_ir/interface/
resource.rs

1//! A program's resources: constant buffers with explicit layouts, textures, samplers, buffers and storage.
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::{ScalarType, Value};
14
15/// Names a resource within one [`super::Interface`]; the reader numbers them in its source's order.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
17pub struct ResourceId(pub u32);
18
19/// The dimensionality of a texture or image. Whether it is an array or multisampled is separate.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
21#[serde(rename_all = "snake_case")]
22pub enum TextureDim {
23    /// A one-dimensional texture or image.
24    D1,
25    /// A two-dimensional texture or image.
26    D2,
27    /// A three-dimensional (volume) texture or image.
28    D3,
29    /// A cube map (six faces, sampled by direction).
30    Cube,
31}
32
33/// What a texture returns when it is read.
34#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
35#[serde(rename_all = "snake_case")]
36pub enum SampleType {
37    /// Floating-point (or normalized) texels, read as floats.
38    Float,
39    /// Signed integer texels.
40    Sint,
41    /// Unsigned integer texels.
42    Uint,
43    /// A depth texture (a source whose API distinguishes it from a float texture; D3D does not).
44    Depth,
45}
46
47/// How a buffer's elements are addressed.
48#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
49#[serde(rename_all = "snake_case")]
50pub enum BufferKind {
51    /// Elements of one value type (a formatted view).
52    Typed(Value),
53    /// Elements of this many bytes.
54    /// Elements of `stride` bytes each (a D3D structured buffer's element size).
55    Structured {
56        /// Bytes per element.
57        stride: u32,
58    },
59    /// Bytes, addressed by offset.
60    Raw,
61}
62
63/// What a storage resource is.
64#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
65#[serde(rename_all = "snake_case")]
66pub enum StorageTarget {
67    /// A storage image.
68    Image {
69        /// Its dimensionality.
70        dim: TextureDim,
71        /// Whether it is an array of layers.
72        arrayed: bool,
73        /// What a read returns.
74        sample: SampleType,
75    },
76    /// A storage buffer.
77    Buffer(BufferKind),
78}
79
80/// What a program may do to a storage resource.
81#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
82#[serde(rename_all = "snake_case")]
83pub enum Access {
84    /// The program only reads it.
85    Read,
86    /// The program only writes it.
87    Write,
88    /// The program reads and writes it.
89    ReadWrite,
90}
91
92/// Whether consecutive lines of a matrix are its rows or its columns.
93#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
94#[serde(rename_all = "snake_case")]
95pub enum Majorness {
96    /// Each row is a line (HLSL `row_major`, GLSL `row_major`).
97    Row,
98    /// Each column is a line (HLSL's default `column_major`, GLSL's default).
99    Column,
100}
101
102/// The type of one constant, with the layout it has in its buffer written out: vectors are tightly packed lanes,
103/// every matrix line and array element sits at a stated stride, whatever packing rules (D3D, std140, std430, DX9
104/// registers) put it there.
105#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
106#[serde(rename_all = "snake_case")]
107pub enum ConstantType {
108    /// A single value.
109    Scalar(ScalarType),
110    /// Consecutive lanes of one scalar type with no padding between them.
111    Vector(Value),
112    /// `rows` by `columns` of `scalar`. The matrix is stored as lines (rows for [`Majorness::Row`], columns for
113    /// [`Majorness::Column`]), `stride` bytes apart; each line's lanes are consecutive.
114    Matrix {
115        /// The scalar type of every lane.
116        scalar: ScalarType,
117        /// Number of rows.
118        rows: u8,
119        /// Number of columns.
120        columns: u8,
121        /// Whether the stored lines are the rows or the columns.
122        major: Majorness,
123        /// Bytes from the start of one stored line to the start of the next.
124        stride: u32,
125    },
126    /// A run of equal elements.
127    Array {
128        /// The element type.
129        element: Box<ConstantType>,
130        /// Number of elements.
131        count: u32,
132        /// Bytes from the start of one element to the start of the next.
133        stride: u32,
134    },
135    /// Named members at explicit offsets.
136    Struct {
137        /// The members, in declaration order.
138        members: Vec<Member>,
139    },
140}
141
142/// A structure's member: its offset is from the start of the structure.
143#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
144pub struct Member {
145    /// The member's name.
146    pub name: String,
147    /// Byte offset from the start of the structure.
148    pub offset: u32,
149    /// The member's type.
150    pub ty: ConstantType,
151}
152
153impl ConstantType {
154    /// Bytes from the start of a value of this type to the end of its last byte of data: no padding after the last
155    /// matrix line, array element or member (a buffer whose rules pad an array's tail states that in the
156    /// buffer's size). A value with no data (a matrix of no lines or lanes, an array of no elements) is 0. `None` if the
157    /// layout overflows 32 bits.
158    pub fn size(&self) -> Option<u32> {
159        match self {
160            Self::Scalar(s) => Some(s.bytes()),
161            Self::Vector(v) => u32::from(v.width).checked_mul(v.scalar.bytes()),
162            Self::Matrix {
163                scalar,
164                rows,
165                columns,
166                major,
167                stride,
168            } => {
169                let (lines, lanes) = match major {
170                    Majorness::Row => (*rows, *columns),
171                    Majorness::Column => (*columns, *rows),
172                };
173                if lines == 0 || lanes == 0 {
174                    return Some(0);
175                }
176                u32::from(lines.saturating_sub(1))
177                    .checked_mul(*stride)?
178                    .checked_add(u32::from(lanes).checked_mul(scalar.bytes())?)
179            }
180            Self::Array {
181                element,
182                count,
183                stride,
184            } => match count.checked_sub(1) {
185                None => Some(0),
186                Some(before) => before.checked_mul(*stride)?.checked_add(element.size()?),
187            },
188            Self::Struct { members } => members
189                .iter()
190                .try_fold(0u32, |end, m| Some(end.max(m.offset.checked_add(m.ty.size()?)?))),
191        }
192    }
193
194    /// The members of a structure, or of the structure an array holds (those of its first element): empty for any
195    /// other type.
196    pub fn members(&self) -> &[Member] {
197        match self {
198            Self::Struct { members } => members,
199            Self::Array { element, .. } => element.members(),
200            _ => &[],
201        }
202    }
203}
204
205/// One named constant of a buffer.
206#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
207pub struct Constant {
208    /// The constant's name in the source.
209    pub name: String,
210    /// Byte offset in the buffer.
211    pub offset: u32,
212    /// The constant's type and layout.
213    pub ty: ConstantType,
214    /// The default value the source states, as bytes of the constant's layout.
215    pub default: Option<Vec<u8>>,
216}
217
218/// A constant buffer's contents.
219#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
220pub struct BufferLayout {
221    /// Bytes.
222    pub size: u32,
223    /// The constants the source names; their offsets can leave gaps.
224    pub constants: Vec<Constant>,
225}
226
227/// What a resource is.
228#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
229#[serde(rename_all = "snake_case")]
230pub enum ResourceKind {
231    /// Constants by explicit byte offset. A program whose source states no names (a stripped one) has none here;
232    /// the consumer names fields by offset.
233    /// The constants and size of one constant buffer.
234    ConstantBuffer {
235        /// The buffer's size and the constants in it.
236        layout: BufferLayout,
237    },
238    /// A texture read by sampling or by load.
239    Texture {
240        /// Its dimensionality.
241        dim: TextureDim,
242        /// Whether it is an array of layers.
243        arrayed: bool,
244        /// Whether it has multiple samples per texel.
245        multisampled: bool,
246        /// What a read returns.
247        sample: SampleType,
248    },
249    /// A sampler state object.
250    Sampler {
251        /// A comparison sampler (shadow lookups).
252        comparison: bool,
253    },
254    /// A buffer the program reads.
255    Buffer(BufferKind),
256    /// An image or buffer the program may write.
257    /// A resource the program may write: an image or a buffer, and what it may do to it.
258    Storage {
259        /// Whether it is an image or a buffer, and which kind.
260        target: StorageTarget,
261        /// What the program does with it.
262        access: Access,
263    },
264    /// A ray tracing acceleration structure the program traces rays into.
265    AccelerationStructure,
266    /// Fixed-function pipeline state the code depends on, set by the host with the draw rather than in a buffer.
267    PipelineState(StateParam),
268}
269
270/// A piece of pipeline state a program reads (`Op::LoadState`), by the stage or unit it belongs to. A known one is
271/// folded into literals by specialisation; an unknown numeric one is carried in a state block the binding plan
272/// places; an unknown one that changes types (a texture's dimension) has no uniform form.
273#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
274#[serde(rename_all = "snake_case")]
275pub enum StateParam {
276    /// Whether texture stage `n` divides its coordinate by its last lane before sampling (D3D9's
277    /// `D3DTTFF_PROJECTED`): a `bool`.
278    Projected(u32),
279    /// The dimension of the texture bound to stage `n` (what D3D9's `ps_1_x` `tex` samples): a `u32`, the
280    /// [`TextureDim::state_code`] of it. It changes the type of the sample, so only specialisation lowers it.
281    TextureDim(u32),
282    /// Stage `n`'s bump environment matrix (D3D9's `D3DTSS_BUMPENVMAT00`, `01`, `10`, `11`, in that order): a
283    /// `vec4<f32>`.
284    BumpEnvMat(u32),
285    /// Stage `n`'s bump luminance scale (D3D9's `D3DTSS_BUMPENVLSCALE`): an `f32`.
286    BumpLScale(u32),
287    /// Stage `n`'s bump luminance offset (D3D9's `D3DTSS_BUMPENVLOFFSET`): an `f32`.
288    BumpLOffset(u32),
289}
290
291impl StateParam {
292    /// The value the program reads.
293    pub fn value(self) -> Value {
294        let (scalar, width) = match self {
295            StateParam::Projected(_) => (ScalarType::Bool, 1),
296            StateParam::TextureDim(_) => (ScalarType::Uint, 1),
297            StateParam::BumpEnvMat(_) => (ScalarType::Float, 4),
298            StateParam::BumpLScale(_) | StateParam::BumpLOffset(_) => (ScalarType::Float, 1),
299        };
300        Value { scalar, width }
301    }
302}
303
304impl TextureDim {
305    /// The value [`StateParam::TextureDim`] reads for this dimension: 1, 2 and 3 for 1D, 2D and volume, 4 for a cube.
306    pub fn state_code(self) -> u32 {
307        match self {
308            TextureDim::D1 => 1,
309            TextureDim::D2 => 2,
310            TextureDim::D3 => 3,
311            TextureDim::Cube => 4,
312        }
313    }
314}
315
316/// The register class of a D3D binding: `b`, `t`, `s` or `u`.
317#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
318#[serde(rename_all = "snake_case")]
319pub enum D3dClass {
320    /// `b<n>`
321    ConstantBuffer,
322    /// `t<n>`
323    ShaderResource,
324    /// `s<n>`
325    Sampler,
326    /// `u<n>`
327    UnorderedAccess,
328}
329
330/// A D3D bind point: class, register index and register space.
331#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
332pub struct D3dBinding {
333    /// Which register class (`b`, `t`, `s` or `u`) the resource is bound in.
334    pub class: D3dClass,
335    /// The register index within the class and space.
336    pub register: u32,
337    /// The register space (`space<n>`; 0 where the source has no spaces).
338    pub space: u32,
339}
340
341/// The source's own name and address for a resource; one field per API that has a notion of it.
342#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
343pub struct ResourceProvenance {
344    /// The name the source gives it (`Texture2D_8`, a constant buffer's name).
345    pub name: String,
346    /// The D3D bind point; `None` when the source has no such address.
347    pub d3d: Option<D3dBinding>,
348    /// The resource this one is the hidden counter of: an unordered-access view's 32-bit counter (D3D's append and
349    /// consume buffers, `IncrementCounter`), a buffer of its own in WebGPU. It has no D3D binding of its own; the
350    /// renderer binds it with the view, starting from the view's initial count.
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub counter_of: Option<ResourceId>,
353    /// The members the source states for a buffer view: a D3D `tbuffer`'s constant-register layout or a structured
354    /// buffer's single element in storage packing. For a structured buffer, size is its element stride, not the
355    /// bound element count. The kind says how it is read; this says what it holds, by name and byte offset.
356    /// Absent when the source does not retain the reflection needed to name its contents.
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub layout: Option<BufferLayout>,
359}
360
361/// One bound resource.
362#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
363pub struct Resource {
364    /// This resource's id within its interface.
365    pub id: ResourceId,
366    /// What the resource is.
367    pub kind: ResourceKind,
368    /// How many consecutive bindings it spans: 1, or the length of a resource array. [`Resource::UNBOUNDED`] is an
369    /// array whose length is the bound data's, not the program's (D3D's `-1`): nothing in the program says how far
370    /// an index may go.
371    pub count: u32,
372    /// The source's own name and address.
373    pub provenance: ResourceProvenance,
374}
375
376impl Resource {
377    /// The `count` of an array the program does not size.
378    pub const UNBOUNDED: u32 = u32::MAX;
379
380    /// Whether the program sizes this array no further than the bound data does.
381    pub fn is_unbounded(&self) -> bool {
382        self.count == Self::UNBOUNDED
383    }
384
385    /// Whether it is an array of bindings, read by an index the program computes, rather than one binding.
386    pub fn is_array(&self) -> bool {
387        self.count != 1
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    fn float(width: u8) -> ConstantType {
396        ConstantType::Vector(Value {
397            scalar: ScalarType::Float,
398            width,
399        })
400    }
401
402    /// Sizes worked out by hand: a line takes only its own lanes at the end, an array's last element only its own.
403    #[test]
404    fn sizes_end_at_the_last_byte_of_data() {
405        assert_eq!(ConstantType::Scalar(ScalarType::Double).size(), Some(8));
406        assert_eq!(float(3).size(), Some(12));
407        let matrix = |rows, columns, major| ConstantType::Matrix {
408            scalar: ScalarType::Float,
409            rows,
410            columns,
411            major,
412            stride: 16,
413        };
414        // 3 rows of 3 lanes: two full 16-byte lines and 12 bytes
415        assert_eq!(matrix(3, 3, Majorness::Row).size(), Some(44));
416        // 3 columns of 4 lanes: two full lines and 16 bytes
417        assert_eq!(matrix(4, 3, Majorness::Column).size(), Some(48));
418        let offsets = ConstantType::Array {
419            element: Box::new(float(2)),
420            count: 3,
421            stride: 16,
422        };
423        assert_eq!(offsets.size(), Some(40));
424        let empty = ConstantType::Array {
425            element: Box::new(float(2)),
426            count: 0,
427            stride: 16,
428        };
429        assert_eq!(empty.size(), Some(0));
430        // no lines or no lanes is no data, as an empty array: 0, not an overflow
431        assert_eq!(matrix(0, 3, Majorness::Row).size(), Some(0));
432        assert_eq!(matrix(3, 0, Majorness::Row).size(), Some(0));
433        let record = ConstantType::Struct {
434            members: vec![
435                Member {
436                    name: "a".into(),
437                    offset: 0,
438                    ty: float(3),
439                },
440                Member {
441                    name: "b".into(),
442                    offset: 12,
443                    ty: ConstantType::Scalar(ScalarType::Float),
444                },
445                Member {
446                    name: "c".into(),
447                    offset: 16,
448                    ty: offsets,
449                },
450            ],
451        };
452        assert_eq!(record.size(), Some(56));
453        let overflow = ConstantType::Array {
454            element: Box::new(float(4)),
455            count: u32::MAX,
456            stride: 16,
457        };
458        assert_eq!(overflow.size(), None);
459    }
460
461    #[test]
462    fn members_are_found_through_arrays_of_structures() {
463        let inner = ConstantType::Struct {
464            members: vec![Member {
465                name: "x".into(),
466                offset: 0,
467                ty: float(1),
468            }],
469        };
470        let array = ConstantType::Array {
471            element: Box::new(inner),
472            count: 2,
473            stride: 16,
474        };
475        assert_eq!(array.members().len(), 1);
476        assert!(float(4).members().is_empty());
477    }
478
479    #[test]
480    fn a_resource_serializes_by_kind_with_its_provenance() {
481        let r = Resource {
482            id: ResourceId(2),
483            kind: ResourceKind::Texture {
484                dim: TextureDim::D2,
485                arrayed: false,
486                multisampled: false,
487                sample: SampleType::Float,
488            },
489            count: 1,
490            provenance: ResourceProvenance {
491                name: "Texture2D_8".into(),
492                d3d: Some(D3dBinding {
493                    class: D3dClass::ShaderResource,
494                    register: 8,
495                    space: 0,
496                }),
497                counter_of: None,
498                layout: None,
499            },
500        };
501        assert_eq!(
502            serde_json::to_value(&r).unwrap(),
503            serde_json::json!({
504                "id": 2,
505                "kind": {"texture": {"dim": "d2", "arrayed": false, "multisampled": false, "sample": "float"}},
506                "count": 1,
507                "provenance": {
508                    "name": "Texture2D_8",
509                    "d3d": {"class": "shader_resource", "register": 8, "space": 0},
510                },
511            })
512        );
513    }
514}