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}