Skip to main content

iridia_interface/
resource.rs

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