primase_core/shader/roles.rs
1//! What each thing a shader program reads and writes stands for: input, constant, texture, buffer and output roles. It
2//! states roles, never frozen values: whoever evaluates or renders the program (an offline baker, a renderer) decides
3//! what a role is worth (a camera position, the time, the frame's exposure) and says so. The program's own vocabulary
4//! (its interface, resources and render state) is `iridia_interface`'s; what its code shows of a constant or an output
5//! (`MatrixLines`, `AlphaComparison`, `OutputUse`) is read by `iridia`'s analyses, and turned into roles here
6//! ([`state_lines`], [`bind_output_uses`]).
7
8use serde::Serialize;
9
10use iridia_interface::{AlphaComparison, Label, MatrixLines, OutputUse, ResourceId};
11
12use super::encoding::{LaneAffine, NormalEncoding, NormalSpace, ScalarEncoding};
13
14/// A vertex attribute a program's input reads: what the drawn mesh's stream holds.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
16pub enum Attribute {
17 /// The vertex position.
18 Position,
19 /// The vertex normal.
20 Normal,
21 /// Tangent set: xyz tangent, w the bitangent sign. Set 0 is glTF's standard TANGENT.
22 Tangent(u8),
23 /// u right, v down: (0, 0) is the image's top-left corner (glTF).
24 TexCoord(u8),
25 /// Vertex colour set `n`.
26 Color(u8),
27 /// Joint indices set `n` of a skinned mesh.
28 Joints(u8),
29 /// Joint weights set `n` of a skinned mesh.
30 Weights(u8),
31 /// Opaque per-vertex shader data at the game's input index, copied through geometry passes untransformed.
32 /// A consumer that cannot preserve it must refuse ShaderInput by name.
33 ShaderInput(u8),
34 /// A point per vertex in the mesh's own space at the game's input index (a billboard's pivot), moved with the
35 /// positions ([`crate::Semantic::Point`]).
36 Point(u8),
37}
38
39/// What a pixel or vertex stage's input is.
40#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
41#[serde(rename_all = "snake_case")]
42pub enum InputRole {
43 /// A vertex attribute of the drawn mesh.
44 Stream(Attribute),
45 /// An input whose lanes are different vertex attributes the source packed into one register (two texture
46 /// coordinate sets in one four-lane input), or one component of an attribute the source feeds as an input of its own
47 /// (a tangent's sign as a byte): each entry is the attribute, the components it takes and the lanes it fills.
48 Lanes(Vec<LaneStream>),
49 /// The value the previous stage wrote under the same label.
50 Previous,
51 /// The previous stage's value when it executes; otherwise the source-stated replacement input. The reason
52 /// belongs to the binding pass and is reported by a consumer only when it uses the replacement.
53 PreviousOr {
54 fallback: super::InputFallback,
55 why: String,
56 },
57 /// The pipeline supplies it (the varying's system value says which).
58 System,
59 /// Lanes of vertex attributes as [`InputRole::Lanes`] states them, each component stored through `map` (`v * scale +
60 /// bias` of the attribute's own value `v`) and read by the program as a float: a signed unit vector the source stores
61 /// in unsigned normalized bytes is [`LaneAffine::UNORM`], which the program unpacks as `x * 2 - 1`. A lane no entry
62 /// fills is not stated.
63 Mapped {
64 /// The attributes' components and the lanes they fill.
65 lanes: Vec<LaneStream>,
66 /// The map each component is stored through.
67 map: LaneAffine,
68 },
69 /// Values of the drawn instance the source feeds through a vertex input, the same for every vertex of the instance:
70 /// an instance stream the engine fills at draw time, not a stream of the mesh. Each entry is a value and the lanes it
71 /// fills; a lane no entry fills is not stated. A consumer whose vertices already hold the instance's transform (a
72 /// scene in world space) feeds the identity instance.
73 Instance(Vec<InstanceLanes>),
74 /// A corner of the triangle a full-screen step of a render graph draws, which covers the view: the value of the
75 /// corner it holds. The three corners, in vertex order, are at the clip-space positions (-1, 1, 1, 1), (3, 1, 1, 1)
76 /// and (-1, -3, 1, 1), with the texture coordinates (0, 0), (2, 0) and (0, 2): (0, 0) at the view's top-left
77 /// corner, 1 a whole view to the right and down.
78 ScreenTriangle(ScreenTriangleValue),
79 /// Nothing known: the source's own meaning is not resolved, and why.
80 Unknown {
81 /// What was examined and why no role follows.
82 why: String,
83 },
84}
85/// What an input of a full-screen triangle holds of each corner ([`InputRole::ScreenTriangle`]).
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
87#[serde(rename_all = "snake_case")]
88pub enum ScreenTriangleValue {
89 /// The corner's clip-space position (four lanes).
90 Position,
91 /// The corner's texture coordinate (two lanes).
92 TextureCoordinate,
93}
94/// The lanes `first..first + count` of an input: `count` components of the vertex attribute `semantic`, from component
95/// `component` (the attribute's own components: x 0, y 1, z 2, w 3), each in the encoding the input's scalar type has, as
96/// the other packed inputs take theirs.
97#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
98pub struct LaneStream {
99 /// The vertex attribute the lanes come from.
100 pub semantic: Attribute,
101 /// The first lane (0 for x).
102 pub first: u8,
103 /// How many lanes, starting at `first`, it fills.
104 pub count: u8,
105 /// The first component of the attribute lane `first` takes (0 for all but the programs that take one component of an
106 /// attribute apart: a source game's cloth programs read the tangent's w, the bitangent sign, as the one
107 /// byte of an input named BINORMAL0, as `(byte - 127) / 127` where the other permutations read TANGENT0.w; inferred
108 /// from the program's decode of it, not captured from the game).
109 pub component: u8,
110}
111/// The lanes `first..first + count` of an input that hold one value of the drawn instance ([`InputRole::Instance`]).
112#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
113pub struct InstanceLanes {
114 /// The value.
115 pub value: InstanceValue,
116 /// The first lane (0 for x).
117 pub first: u8,
118 /// How many lanes, from `first`, it fills: the value's own components, from its first.
119 pub count: u8,
120}
121/// A value of the drawn instance an instance stream carries ([`InputRole::Instance`]). The program places a vertex at
122/// `translation + scale * (axis 0, axis 1, axis 2) * p` for the position `p` it takes from the mesh.
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
124#[serde(rename_all = "snake_case")]
125#[non_exhaustive]
126pub enum InstanceValue {
127 /// The instance's translation in world space (three components).
128 Translation,
129 /// The instance's uniform scale (one component).
130 Scale,
131 /// Axis `n` of the instance's rotation (0 x, 1 y, 2 z) in world space, a unit vector (three components).
132 Axis(u8),
133 /// How far the instance's geometry is blended from the coarser level of detail the mesh also stores toward its own
134 /// position (one component): 1 draws the mesh's own position, 0 the coarser one.
135 LodBlend,
136}
137/// A camera or view-wide value.
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
139#[serde(rename_all = "snake_case")]
140#[non_exhaustive]
141pub enum ViewParameter {
142 /// The camera's position in world space (xyz).
143 CameraPosition,
144 /// The world-to-view matrix.
145 View,
146 /// The view-to-clip projection matrix.
147 Projection,
148 /// The world-to-clip matrix (view times projection).
149 ViewProjection,
150 /// The view-projection of a world with the camera at its origin: a program multiplies a world position minus the
151 /// camera's ([`ViewParameter::CameraPosition`]) by it, so it is [`ViewParameter::ViewProjection`] without the camera's
152 /// translation. The program takes the camera position from its own constant, so the two roles are bound together; a
153 /// consumer that feeds the plain matrix here puts the geometry in the wrong place.
154 CameraRelativeViewProjection,
155 /// The view-to-world matrix.
156 InverseView,
157 /// The clip-to-view matrix.
158 InverseProjection,
159 /// The clip-to-world matrix.
160 InverseViewProjection,
161 /// The render target's size in pixels as two values: the width, then the height. A source that packs more into
162 /// the register (the reciprocals beside the size, in either order) binds each pair by its own role over its own
163 /// lanes.
164 ViewportSize,
165 /// The reciprocals of the render target's size in pixels as two values: `1 / width`, then `1 / height`, the
166 /// uv-space step of a whole texel.
167 InverseViewportSize,
168 /// The distance from the camera to the near clip plane, along the view axis, in world units (one value).
169 NearPlane,
170 /// The distance from the camera to the far clip plane, along the view axis, in world units (one value).
171 FarPlane,
172 /// `1 / far plane`: the reciprocal of [`ViewParameter::FarPlane`] (one value).
173 InverseFarPlane,
174 /// The sign the projection puts on up (one value): +1 when a point above the view axis, in view space, has a positive
175 /// clip-space y, -1 when the projection negates y (a render into a texture, on a source whose texture origin is not
176 /// the screen's). A source that stores the opposite convention maps its value onto this one.
177 ProjectionFlip,
178 /// The previous frame's view-projection matrix (what a motion vector is measured against).
179 PreviousViewProjection,
180 /// The part of the render target the view draws into, per axis: its drawn size over the target's size, two values. A
181 /// renderer that lowers its resolution from frame to frame draws into that part of a target of the full size, so a
182 /// program finds its texture coordinate on the screen as the pixel position times
183 /// [`ViewParameter::InverseViewportSize`] over this; a view that fills its target has 1, 1.
184 ResolutionScale,
185 /// Whether the view's projection is a perspective one, as an unsigned 32-bit integer word: 1 for a perspective
186 /// projection (clip w is the depth along the view axis), 0 for an orthographic one.
187 Perspective,
188 /// The far clip plane's distance minus the near clip plane's ([`ViewParameter::FarPlane`] minus
189 /// [`ViewParameter::NearPlane`]), in world units (one value).
190 DepthRange,
191 /// The device depth a perspective projection gives a point infinitely far away: a point at depth `d` along the view
192 /// axis (world units) gets the device depth `offset + scale / d` ([`ViewParameter::DeviceDepthScale`] is `scale`),
193 /// so a program takes the depth back as `scale / (device depth - offset)`. One value.
194 DeviceDepthOffset,
195 /// The scale of a perspective projection's device depth over the depth along the view axis: `scale` in
196 /// [`ViewParameter::DeviceDepthOffset`]'s `offset + scale / d`. One value.
197 DeviceDepthScale,
198}
199/// A transform built from the drawn object's object-to-world matrix.
200#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
201#[serde(rename_all = "snake_case")]
202pub enum ObjectMatrix {
203 /// Object space to world space.
204 World,
205 /// Object space to view space.
206 WorldView,
207 /// Object space to clip space.
208 WorldViewProjection,
209}
210/// A value of the drawn object or instance.
211#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
212#[serde(rename_all = "snake_case")]
213#[non_exhaustive]
214pub enum ObjectParameter {
215 /// The object-to-world matrix.
216 World,
217 /// The world-to-object matrix.
218 InverseWorld,
219 /// The object-to-clip matrix.
220 WorldViewProjection,
221 /// The object-to-view matrix.
222 WorldView,
223 /// The view-to-object matrix: inverse(object-to-world) * inverse(world-to-view).
224 ViewToObject,
225 /// The transform as the previous frame had it: the object's previous pose, the previous view and projection (what a
226 /// motion vector is measured from).
227 PreviousFrame(ObjectMatrix),
228 /// The transform in the engine's camera-relative form (the world with the camera at its origin): a program computes
229 /// its clip position, and its previous-frame position, from this or from the plain transform, the one the draw
230 /// supplies (by the engine's own name for it; no capture checks the translation).
231 CameraRelative(ObjectMatrix),
232 /// The previous frame's transform, with the previous frame's camera at the origin of world space.
233 CameraRelativePreviousFrame(ObjectMatrix),
234 /// A colour the instance tints the surface with.
235 InstanceColour,
236 /// The scale (`xy`) and offset (`zw`) a model's texture coordinate set `n` is drawn with: the engine feeds one
237 /// value per model for each set, a scroll that animates some models (water, flags).
238 TextureScroll(u8),
239 /// The matrices of a skinned draw's joints, each a 3 x 4 transform (the object-to-world transform of the joint
240 /// the pose gives, in the engine's own palette order), its lines as the binding states ([`MatrixLines`]): what a
241 /// skinning program blends by the vertex's joint indices and weights. Where the program also reads
242 /// [`ObjectParameter::ClothCancelPalette`], a joint's transform is the product of the two; a consumer that has
243 /// each joint's whole object-to-world transform states it here and the identity there.
244 BonePalette,
245 /// The matrices of a second palette a vertex program takes the vertex through before
246 /// [`ObjectParameter::BonePalette`] (the engine's name for it is the cloth-cancel matrices): each a 3 x 4
247 /// transform held as the binding states ([`MatrixLines`]), chosen by the same index as the bone palette's (the
248 /// vertex's joint index plus the instance's matrix base, [`ObjectParameter::MatrixBase`]), so as long as it. The
249 /// vertex's transform is `BonePalette[j] * ClothCancelPalette[j]`: the position, and the normal and tangent by
250 /// the 3 x 3 part, go through this matrix first (blended over the vertex's joints by their weights where the
251 /// program blends joints, as Elden Ring's character programs that skin do; those that take one joint per vertex
252 /// do the same with that joint). How an engine splits a joint's transform between the two palettes is not
253 /// established: in a captured Elden Ring frame, 3,746 of the 12,230 recorded entries of skinned draws' palettes
254 /// were the identity, and 7,940 were not. A consumer that states each joint's whole transform in the bone palette
255 /// feeds the identity here, which leaves the first step without effect.
256 ClothCancelPalette,
257 /// The instance's environment data word (an unsigned integer the engine writes per instance; the layered programs
258 /// scale it by 1/255 into the alpha lane of a G-buffer target, and a program that reads a buffer of light sets takes
259 /// the set it names).
260 EnvironmentData,
261 /// The index of the instance's first matrix in the object-matrix palette, which the vertex program adds to the
262 /// index it takes (the joint indices of a skinned vertex, the instance's own for a rigid one).
263 MatrixBase,
264 /// The index of the draw's first record in the instance table ([`BufferTable::Instances`]), an unsigned integer word:
265 /// the vertex program adds the instance number to it and reads that record. It counts records of the instance table,
266 /// where [`ObjectParameter::MatrixBase`] counts matrices of the palette. A draw of one instance that starts the table
267 /// has 0.
268 InstanceBase,
269 /// Whether the draw simulates cloth: a nonzero word where the vertex program takes the cloth's matrix instead of the
270 /// palette's. A consumer that draws a static or rest pose states 0, which runs the palette arm.
271 ClothSimulation,
272 /// The instance's user clip plane (the vertex program's clip distance against it).
273 ClipPlane,
274 /// Row `n` (0 or 1) of an animated affine transform of the first texture coordinate set, three values each:
275 /// `u' = dot(row 0, (u, v, 1))`, `v' = dot(row 1, (u, v, 1))`. Some models animate their textures this way.
276 TextureTransformRow(u8),
277 /// Where along the rows of a palette texture the instance's tint selects (x): the v the program samples the
278 /// palette at, the u being a vertex colour.
279 PaletteCoordinate,
280 /// Whether the draw fades by the camera's distance, dithered (a flag the engine sets per draw): a program that reads it
281 /// keeps a fragment where `distance * CameraFadeInverseRange - CameraFadeEnd` is not below the dither noise.
282 CameraFadeEnabled,
283 /// The camera fade's end term, subtracted from the distance scaled by [`ObjectParameter::CameraFadeInverseRange`].
284 CameraFadeEnd,
285 /// The camera fade's inverse range: the factor the fragment's distance from the camera is scaled by.
286 CameraFadeInverseRange,
287 /// Whether the instance is drawn with dithered transparency (a per-draw flag): a program that reads it, with
288 /// [`ObjectParameter::DitherEnabled`] set, discards a fragment by the dither noise against the amount the instance record
289 /// carries ([`ObjectParameter::Fade`]).
290 DitherAlphaEnabled,
291 /// The second per-model flag of the dithered transparency, set together with [`ObjectParameter::DitherAlphaEnabled`]
292 /// (a program reads both, and fades only where both are on).
293 DitherEnabled,
294 /// The factor the engine fades an instance's visibility by (one value): 1 where it is fully shown, 0 where it is gone. A
295 /// program applies it where its pass keeps the instance's visibility: multiplied into the alpha it writes or tests (an
296 /// alpha test, also one steered through the depth output), stored as the alpha of the image a pass writes, as the weight
297 /// of the surface against the scene behind it where the program blends itself, and, in a pass that has no alpha, into the
298 /// emission it writes, or, under the dithered transparency's flags ([`ObjectParameter::DitherAlphaEnabled`]), as the
299 /// share of fragments a dithered test keeps (a fragment survives where the dither noise less one half plus this factor
300 /// reaches one half). A consumer that draws the instance as fully shown feeds 1. The camera fade and the dither flags
301 /// are other mechanisms: they switch the discard by noise on, they do not carry the factor.
302 Fade,
303 /// Whether the wind leaves the instance's vertices still (one value): above zero, the program places each vertex where
304 /// its rest position and the instance put it; at zero or below, it moves it by the wind state the program's other
305 /// wind constants carry. A consumer that draws the vegetation still states this and says so; one that animates it
306 /// needs those constants as well.
307 WindOff,
308}
309/// A value of the frame being drawn.
310#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
311#[serde(rename_all = "snake_case")]
312#[non_exhaustive]
313pub enum FrameParameter {
314 /// Seconds since a start the game defines.
315 Time,
316 /// The hour of the day the scene shows, one value, 0 to 24: the world's [`crate::TimeOfDay`]. A program of a source
317 /// that keys values by the hour reads it (Elden Ring's `FC_TimeOfDay`, which its sky programs take modulo 24 to blend
318 /// their material's colours by the hour). A world that states no hour gives such a program no value: a consumer
319 /// refuses the draw rather than show one hour as another.
320 TimeOfDay,
321 /// The pre-exposure: the factor the renderer multiplies the radiance it writes into the frame's light targets by (a
322 /// light's contribution, the ambient, a material's emission), so that the targets hold radiance times this factor; 1
323 /// where a renderer does not pre-expose. What the frame's exposure and tone stages do with it is theirs: they are passes
324 /// of their own, with their own bindings. A program that multiplies its emission by this value writes pre-exposed
325 /// radiance; a consumer that lights in unscaled radiance and exposes once at the end gives it 1.
326 PreExposure,
327 /// One over [`FrameParameter::PreExposure`]: the factor a program multiplies a value it reads back from the frame's
328 /// light targets by to take the pre-exposure off it. One value; 1 where the pre-exposure is 1.
329 InversePreExposure,
330 /// The frame's exposure: the factor its exposure stage multiplies the scene's radiance by before the tone curve (a
331 /// renderer's adapted exposure). One value; 1 where the frame is not exposed.
332 Exposure,
333 /// One over [`FrameParameter::Exposure`]: a program that multiplies a colour by it puts that colour on the screen at
334 /// the same brightness whatever the exposure. One value.
335 InverseExposure,
336 /// An index into a palette of colours a draw selects from.
337 ColourSlot,
338 /// The direction the dominant directional light travels (xyz): a surface is lit by `saturate(dot(normal, -xyz))`.
339 LightDirection,
340 /// The unit direction from the scene towards the dominant directional light (xyz): the opposite of
341 /// [`FrameParameter::LightDirection`], so a surface is lit by `saturate(dot(normal, xyz))`.
342 DirectionToLight,
343 /// The colour (rgb) of that light as the source's programs read it: the scene's sun's (`core.environment`'s)
344 /// `color` times its `intensity` over the world's [`crate::DirectionalLightUnit::lux_per_unit`], the value the
345 /// source stored. A world with no sun or no such unit gives a consumer no value of it.
346 LightColour,
347 /// The colour (rgb) of that light's highlights as the source's programs read it: as [`FrameParameter::LightColour`],
348 /// of the sun's `specular` colour and intensity where it states them ([`crate::LightSpecular`]), of its own
349 /// otherwise.
350 LightSpecularColour,
351 /// The directional ambient light as four values: its colour (xyz: a lighting program adds each channel to the same
352 /// channel of the lit colour, scaled by how much the surface faces an ambient direction it also reads), and a w that
353 /// scales the ambient terms a vertex carries, growing from 1 (no change) with distance.
354 DirectionalAmbientColour,
355 /// How many records the frame's light table of this kind holds ([`BufferSource::Lights`]): one unsigned 32-bit
356 /// integer, the number of lights the consumer packed ([`super::PackedLights::lights`]).
357 LightCount(super::LightTableKind),
358 /// The side of a light-list tile in pixels ([`BufferSource::LightLists`]): one unsigned 32-bit integer, the size the
359 /// consumer packed its lists for.
360 LightTileSize,
361 /// How many light-list tiles a row of the screen holds: one unsigned 32-bit integer, the render target's width over
362 /// [`FrameParameter::LightTileSize`], rounded up.
363 LightTilesPerRow,
364 /// The size of the frame's local-light shadow atlas in texels as two values, its width then its height
365 /// ([`crate::render_graph::RenderGraph::local_shadow_atlas`]).
366 LocalShadowAtlasSize,
367 /// The reciprocals of [`FrameParameter::LocalShadowAtlasSize`]: `1 / width`, then `1 / height`, the step of one of
368 /// its texels in its texture coordinates.
369 InverseLocalShadowAtlasSize,
370}
371/// A value of the item one run of a repeated step is for ([`crate::render_graph::Items`]).
372#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
373#[serde(rename_all = "snake_case")]
374pub enum ItemParameter {
375 /// The record of the shadowed light the run is for ([`crate::render_graph::Items::ShadowedLights`]): the bytes the
376 /// frame's light table of the step's kind packs for it with its shadow, the whole record, so the constant's size is
377 /// the table's stride.
378 LightRecord,
379}
380/// What a constant of a buffer stands for.
381#[derive(Clone, Debug, PartialEq, Serialize)]
382#[serde(rename_all = "snake_case")]
383pub enum ConstantRole {
384 /// The material's own parameter, as stored: the 32-bit words the constant takes (bit patterns, so an integer
385 /// parameter and a float one are both exact).
386 Material(Vec<u32>),
387 /// A camera or view-wide value.
388 View(ViewParameter),
389 /// A value of the drawn object.
390 Object(ObjectParameter),
391 /// A value of the frame.
392 Frame(FrameParameter),
393 /// A value of the item the run of a repeated step is for ([`crate::render_graph::Items`]).
394 Item(ItemParameter),
395 /// A reference confirmed by the program's discard graph. `compared` states whether a portable cutoff on output
396 /// alpha is the same test or an approximation. A baking consumer freezes the reference below the alpha's range, and must
397 /// also neutralize any stated dither perturbation; the draw uses the material's original `value`.
398 AlphaReference {
399 /// The reference the discard compares against.
400 value: f32,
401 /// What the reference is compared with.
402 compared: AlphaComparison,
403 },
404 /// Not resolved to a role, and why.
405 Unknown {
406 /// What was examined and why no role follows.
407 why: String,
408 },
409 /// A value of the engine's that no role of core states, set by the source's own policy: the 32-bit words the
410 /// constant takes, and why (as [`super::TextureSource::Default`] sets a texture slot the engine fills). Not the
411 /// material's: a consumer writes the words as they are.
412 Default {
413 /// The words, as [`ConstantRole::Material`]'s.
414 words: Vec<u32>,
415 /// The policy and the evidence for its words.
416 why: String,
417 },
418}
419/// One constant: the bytes `offset..offset + size` of a constant buffer resource of the shader, named when the
420/// source names it (a stripped program has fields by offset only), and what it stands for.
421#[derive(Clone, Debug, PartialEq, Serialize)]
422pub struct ConstantBinding {
423 /// The constant buffer resource holding the constant.
424 pub resource: ResourceId,
425 /// Byte offset in the buffer.
426 pub offset: u32,
427 /// Bytes.
428 pub size: u32,
429 /// The constant's name, `None` for a stripped program.
430 pub name: Option<String>,
431 /// What the constant stands for.
432 pub role: ConstantRole,
433 /// For a matrix role ([`ConstantRole::is_matrix`]), how its 16-byte lines hold the role's matrix
434 /// ([`MatrixLines`]); `None` for every other role.
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub lines: Option<MatrixLines>,
437}
438impl ConstantRole {
439 /// Whether the role is a matrix, whose binding states its lines ([`ConstantBinding::lines`]): the view's and the
440 /// object's transforms, their inverses and previous-frame and camera-relative forms, and the palettes.
441 pub fn is_matrix(&self) -> bool {
442 match self {
443 ConstantRole::View(v) => matches!(
444 v,
445 ViewParameter::View
446 | ViewParameter::Projection
447 | ViewParameter::ViewProjection
448 | ViewParameter::CameraRelativeViewProjection
449 | ViewParameter::InverseView
450 | ViewParameter::InverseProjection
451 | ViewParameter::InverseViewProjection
452 | ViewParameter::PreviousViewProjection
453 ),
454 ConstantRole::Object(o) => matches!(
455 o,
456 ObjectParameter::World
457 | ObjectParameter::InverseWorld
458 | ObjectParameter::WorldViewProjection
459 | ObjectParameter::WorldView
460 | ObjectParameter::ViewToObject
461 | ObjectParameter::PreviousFrame(_)
462 | ObjectParameter::CameraRelative(_)
463 | ObjectParameter::CameraRelativePreviousFrame(_)
464 | ObjectParameter::BonePalette
465 | ObjectParameter::ClothCancelPalette
466 ),
467 ConstantRole::Material(_)
468 | ConstantRole::Frame(_)
469 | ConstantRole::Item(_)
470 | ConstantRole::AlphaReference { .. }
471 | ConstantRole::Unknown { .. }
472 | ConstantRole::Default { .. } => false,
473 }
474 }
475}
476/// An image of the frame being drawn that a program reads: the consumer supplies it from its own frame, as it supplies a view or
477/// frame constant.
478#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
479#[serde(rename_all = "snake_case")]
480#[non_exhaustive]
481pub enum FrameTexture {
482 /// The depth the frame's depth pass wrote, the hardware depth in `x`: a program that reads it linearises it with the scene
483 /// constants' depth parameters (soft edges of fog, foam and water against the geometry behind).
484 SceneDepth,
485 /// The colour image of what the frame has drawn so far behind the surface (the opaque scene, or the screen as it
486 /// stands): what a refracting or see-through surface samples.
487 SceneColour,
488 /// The depth image rendered from a shadow-casting light (the main light's, its cascades side by side where it has
489 /// them): a program compares a surface's depth in that light's space with it.
490 ShadowMap,
491 /// The shadow term of the main light gathered per screen pixel: a program samples it at its own screen position, 1
492 /// where the light reaches and 0 where it is shadowed.
493 ScreenSpaceShadows,
494 /// The ambient occlusion the frame computed per screen pixel (1 unoccluded).
495 AmbientOcclusion,
496 /// The cube image of the environment around the surface as the nearest reflection probe, or the sky where there is
497 /// none, captured: what a reflection samples along the reflected view direction.
498 ReflectionProbe,
499 /// The volume image of the light probes' lighting around the surface (a 3D image of spherical-harmonic
500 /// coefficients the surface samples at its position).
501 LightProbeVolume,
502 /// Image `n` of the decal buffer the frame's decal step draws before the surfaces are lit (Unity's DBuffer: 0 the
503 /// decals' albedo, 1 their normal, 2 their metallic, occlusion and smoothness, each with the decals' coverage in
504 /// alpha): a lit draw reads it at its own screen position and blends the decals into its surface.
505 DecalBuffer(u8),
506}
507impl FrameTexture {
508 /// The dimension of the image: a probe's is a cube, the light probes' a volume, every other a plane.
509 pub fn dim(self) -> iridia_interface::TextureDim {
510 match self {
511 Self::ReflectionProbe => iridia_interface::TextureDim::Cube,
512 Self::LightProbeVolume => iridia_interface::TextureDim::D3,
513 Self::SceneDepth
514 | Self::SceneColour
515 | Self::ShadowMap
516 | Self::ScreenSpaceShadows
517 | Self::AmbientOcclusion
518 | Self::DecalBuffer(_) => iridia_interface::TextureDim::D2,
519 }
520 }
521}
522/// One buffer slot: a buffer resource of the shader (a structured, raw or typed buffer a program indexes at run time) and
523/// what the source binds to it.
524#[derive(Clone, Debug, PartialEq, Serialize)]
525pub struct BufferBinding {
526 /// The buffer resource of the shader.
527 pub resource: ResourceId,
528 /// What the source binds to it.
529 pub source: BufferSource,
530}
531/// What a buffer slot holds.
532#[derive(Clone, Debug, PartialEq, Serialize)]
533#[serde(rename_all = "snake_case")]
534pub enum BufferSource {
535 /// A table of the draw's own data, by what it holds: the consumer fills it from the draw it makes.
536 Table(BufferTable),
537 /// Nothing is known of the contents and the source says why: a consumer states the stand-in it uses, and never
538 /// reads the buffer as zeros without saying so (as a texture with no known source, or an unknown
539 /// constant).
540 Unknown {
541 /// Why nothing is known of the contents.
542 why: String,
543 },
544 /// One of the frame's own buffers: only in the binding of a step of the frame's `core.render_graph`
545 /// ([`crate::render_graph::RenderGraph::buffers`]), whose buffers these are. A material's binding never states one.
546 Graph(crate::render_graph::BufferId),
547 /// The scene's lights of one kind, one record each in the table's layout, which the consumer packs from the world's
548 /// `core.light`s for the frame's view ([`super::LightTable::pack`]).
549 Lights(super::LightTable),
550 /// One of the two buffers of the frame's per-tile light lists, which say which lights a program visits at a pixel;
551 /// the consumer packs both with [`super::pack_light_lists`] from the lights it packed into the stage's light tables.
552 LightLists(super::LightListPart),
553}
554/// A table of the draw's data a program reads from a buffer.
555#[derive(Clone, Debug, PartialEq, Serialize)]
556#[serde(rename_all = "snake_case")]
557#[non_exhaustive]
558pub enum BufferTable {
559 /// The matrices of the draw: the same content as [`ObjectParameter::BonePalette`] (the object-to-world transforms,
560 /// in the engine's own order, each `rows` rows of four floats: three for an affine transform without its last row,
561 /// four for a full matrix) in a buffer instead of constants, and with that role's row convention. They lie one
562 /// matrix after another, so element `i` of the buffer is row `i % rows` of matrix `i / rows`; the elements are
563 /// four floats, 16 bytes.
564 ObjectMatrices {
565 /// Rows of four floats per matrix: 3 or 4.
566 rows: u8,
567 },
568 /// [`BufferTable::ObjectMatrices`] with one header element before each matrix: the same rows and row convention, `rows`
569 /// rows of four floats after a header element of four words, so element `i` of the buffer is the header of matrix
570 /// `i / (rows + 1)` when `i % (rows + 1)` is 0 and row `i % (rows + 1) - 1` of it otherwise.
571 ///
572 /// The program reads flags from the header's first word: its bit 0 with a 16-bit offset in bits 1 to 16, and its bit 18,
573 /// each put the rows later by that many elements. A consumer writes every header as zero, which puts the rows directly
574 /// after it. That the zero header is that layout is what a program run against such a table gave (identity rows after
575 /// a zero header gave the position back), not something the engine documents.
576 HeaderedMatrices {
577 /// Rows of four floats after each header element: 3 or 4.
578 rows: u8,
579 },
580 /// The block of an instance whose program skins, in a table of elements of four floats each (the shape every one of
581 /// the five skinned vertex programs of one game's hat materials reads, measured by running each against tables filled to
582 /// tell the parts apart):
583 ///
584 /// - a header element, as [`BufferTable::HeaderedMatrices`] (the instance record names it);
585 /// - the first prefix element, `X`, `1 + offset` elements after the header, where `offset` is the header's bit 0 and
586 /// 16-bit offset in bits 1 to 16 (zero when the bit is clear): its meaning is `first`;
587 /// - the other prefix elements, `X + 1` to `X + prefix - 1`: none of the five programs reads them. A program that
588 /// reads one is not described by this role (a consumer fills them with zeros);
589 /// - then the joints' matrices, `joint_stride` elements apart, joint `j`'s `rows` rows starting at element
590 /// `X + prefix + bit18 + joint_stride * j`, where `bit18` is the header's bit 18 (the rows are one element later
591 /// when it is set; `X` is not).
592 ///
593 /// The position of a vertex is the sum, over its influences, of the weight times the joint's matrix applied to the
594 /// position (the rows as [`ObjectParameter::BonePalette`] has them), then `first`. A consumer writes a zero header
595 /// (no offset, no bit 18), which puts `X` directly after the header and the first joint's rows `prefix` elements
596 /// after `X`.
597 SkinnedMatrices {
598 /// Rows of one joint's matrix: three (an affine transform without its last row) or four.
599 rows: u8,
600 /// Elements from one joint's first row to the next joint's.
601 joint_stride: u8,
602 /// Elements from `X` to the first joint's first row when the header's bit 18 is clear.
603 prefix: u8,
604 /// What `X` holds.
605 first: SkinPrefix,
606 },
607 /// One record per instance drawn, `stride` 32-bit words each, in instance order: word `matrix_start` of a record is
608 /// the index, in elements of the table `matrices` (a [`BufferTable::ObjectMatrices`] or a
609 /// [`BufferTable::HeaderedMatrices`] or [`BufferTable::SkinnedMatrices`] of the same stage), of the instance's first element:
610 /// its first row, or its header for a headered table. The program adds the indices of the matrices it takes (a joint's, a model's own) to it.
611 Instances {
612 /// 32-bit words in each record.
613 stride: u8,
614 /// Index of the word of a record that holds the index of the instance's first matrix element.
615 matrix_start: u8,
616 /// The matrix table (`ObjectMatrices`, `HeaderedMatrices` or `SkinnedMatrices`) the indices point into.
617 matrices: ResourceId,
618 },
619}
620/// What the first prefix element of a [`BufferTable::SkinnedMatrices`] holds.
621#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
622#[serde(rename_all = "snake_case")]
623#[non_exhaustive]
624pub enum SkinPrefix {
625 /// The xyz lanes are added, once and unweighted, to the position the weighted sum of the joints' transforms gives
626 /// (so a vertex at `p` with one influence of weight `w` ends at `w * (M p) + xyz`); the w lane is not read.
627 Translation,
628}
629/// What an [`OutputRole::Occlusion`] occludes.
630#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
631#[serde(rename_all = "snake_case")]
632#[non_exhaustive]
633pub enum Occluded {
634 /// Every light that reaches the surface from outside it: the one occlusion a source has.
635 AllLight,
636 /// The ambient light of the environment itself (the sky and what it scatters): the term a lighting pass scales that
637 /// ambient by, where the source keeps another occlusion for the light of placed artificial sources.
638 NaturalAmbient,
639 /// The ambient light of placed artificial sources (interior and exterior ambient volumes), apart from the
640 /// environment's own ([`Occluded::NaturalAmbient`]).
641 ArtificialAmbient,
642}
643/// What lanes of an output stand for.
644#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
645#[serde(rename_all = "snake_case")]
646#[non_exhaustive]
647pub enum OutputRole {
648 /// The surface's albedo (base colour), linear.
649 Albedo,
650 /// The surface normal, decoded by `encoding` into `space`.
651 Normal {
652 /// How the normal is stored in the lanes.
653 encoding: NormalEncoding,
654 /// The space the normal is in.
655 space: NormalSpace,
656 },
657 /// Smoothness, 1 - perceptual roughness: the value decoded by `encoding`.
658 Gloss {
659 /// How the gloss is stored.
660 encoding: ScalarEncoding,
661 },
662 /// The perceptual roughness, stored as is.
663 Roughness,
664 /// The metalness (0 to 1).
665 Metallic,
666 /// The factor the light `of` is scaled by where the surface is shadowed from it (1 unoccluded, 0 fully): the value
667 /// decoded by `encoding`, as the lighting pass multiplies the light by it.
668 Occlusion {
669 /// Which light the occlusion applies to.
670 of: Occluded,
671 /// How the factor is stored.
672 encoding: ScalarEncoding,
673 },
674 /// How much of the light a surface reflects as a highlight is blocked where the surface faces away from the
675 /// environment (1 unblocked): the term a lighting pass multiplies its specular by, apart from the diffuse
676 /// [`OutputRole::Occlusion`].
677 SpecularOcclusion,
678 /// How much light passes through the surface (a leaf, a petal): a lighting pass tints the albedo by it and adds it
679 /// to the lit colour, 0 for an opaque surface.
680 Translucency,
681 /// Specular reflectance at normal incidence (F0), the value decoded by `encoding`.
682 Reflectance {
683 /// How the reflectance is stored.
684 encoding: ScalarEncoding,
685 },
686 /// The strength of the specular term, how much light the surface reflects as a highlight (0 to 1 once decoded).
687 SpecularIntensity {
688 /// How the strength is stored.
689 encoding: ScalarEncoding,
690 },
691 /// How tight the specular highlight is: the material's falloff or exponent as the source's lighting uses it
692 /// (what lighting derives from it, a Blinn-Phong exponent for instance, is the source's).
693 SpecularExponent {
694 /// How the exponent is stored.
695 encoding: ScalarEncoding,
696 },
697 /// The source's Fresnel term for the surface, as the source stores it (how the lighting turns it into a
698 /// reflectance at grazing angles is the source's).
699 Fresnel,
700 /// The screen-space displacement, in pixels, from the fragment's position in the frame being drawn to its
701 /// position in the previous frame (x right, y down).
702 MotionVector,
703 /// The light the surface gives off: a scalar in one lane that scales the albedo (the lighting pass adds the albedo
704 /// times it to the lit colour) or a colour in three, the value decoded by `encoding`.
705 Emission {
706 /// How the emission is stored.
707 encoding: ScalarEncoding,
708 },
709 /// The finished colour of a forward pass.
710 Colour,
711 /// Whether the fragment is kept: a value a discard of the program tests (the lane's written value is among what the
712 /// discard's conditions are made of), so the surface is cut where the test fails. Not a fraction.
713 Coverage,
714 /// How opaque the surface is (1 opaque) as the target's blend takes it: the alpha lane of a target whose stated
715 /// blend weighs the colour by the source alpha.
716 Opacity,
717 /// Lanes the program writes that no role is established for, with why: what the source's evidence shows of them
718 /// (what reads them, or that nothing examined does). Never empty.
719 Unknown {
720 /// What the source's evidence shows of the lanes.
721 why: String,
722 },
723}
724impl OutputRole {
725 /// [`OutputRole::Unknown`] with `why`.
726 pub fn unknown(why: impl Into<String>) -> OutputRole {
727 OutputRole::Unknown { why: why.into() }
728 }
729
730 /// The encoding a role's value is stored in, for the roles that state one.
731 pub fn scalar_encoding(&self) -> Option<ScalarEncoding> {
732 match *self {
733 OutputRole::Gloss { encoding }
734 | OutputRole::Occlusion { encoding, .. }
735 | OutputRole::Reflectance { encoding }
736 | OutputRole::SpecularIntensity { encoding }
737 | OutputRole::SpecularExponent { encoding }
738 | OutputRole::Emission { encoding } => Some(encoding),
739 _ => None,
740 }
741 }
742}
743/// Lanes of one output varying and their role: an output's lanes may carry different things (albedo in rgb and
744/// occlusion in a), as several bindings of one label.
745#[derive(Clone, Debug, PartialEq, Serialize)]
746pub struct OutputBinding {
747 /// The output varying the lanes belong to.
748 pub label: Label,
749 /// Lane mask: bit k is lane k of the output's value.
750 pub lanes: u8,
751 /// What the lanes stand for.
752 pub role: OutputRole,
753}
754/// A stage's input binding: the role of an input varying the shader declares.
755#[derive(Clone, Debug, PartialEq, Serialize)]
756pub struct InputBinding {
757 /// The input varying.
758 pub label: Label,
759 /// What the input stands for.
760 pub role: InputRole,
761}
762/// What picks one of several stages of one kind in a binding: the mesh-side field a consumer reads, named by the
763/// dialect's component and field that holds it (`<component>.<field>`), and the value that picks this stage. A
764/// consumer draws a mesh with the stage whose key names a field it has and whose value that field equals; a mesh whose
765/// value matches no stage is refused by name, not drawn with a first or default stage.
766#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
767pub struct VariantKey {
768 /// The component and field the mesh carries the key in, as `component.field` (`<dialect>.<component>.<field>`), or
769 /// the name the source gives its keys where no mesh field holds them (a name in the source's own terms).
770 pub field: String,
771 /// The value of the stage's key, compared for equality with the mesh's field.
772 pub value: u32,
773 /// What the key stands for, in the source's terms. A description only: it never identifies a variant (a binding's
774 /// check, and a consumer picking a stage, go by `field` and `value`) and may be empty.
775 #[serde(skip_serializing_if = "Vec::is_empty")]
776 pub parts: Vec<KeyPart>,
777}
778/// One named number of what a [`VariantKey`] stands for (a program's place in its row, a pixel output count, a
779/// permutation id). It describes and never identifies.
780#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
781pub struct KeyPart {
782 /// The part's name.
783 pub name: String,
784 /// The part's value.
785 pub value: u64,
786}
787
788/// States the lines ([`ConstantBinding::lines`]) of each matrix role among `constants`, as `lines_at` reads them from the
789/// program (`iridia::matrix::lines_at` over the program's value graph); every other role is left alone. Returns how many
790/// are [`MatrixLines::Unshown`].
791pub fn state_lines(
792 constants: &mut [ConstantBinding],
793 mut lines_at: impl FnMut(&ConstantBinding) -> MatrixLines,
794) -> usize {
795 let mut unshown = 0usize;
796 for c in constants.iter_mut().filter(|c| c.role.is_matrix()) {
797 let lines = lines_at(c);
798 if matches!(lines, MatrixLines::Unshown(_)) {
799 unshown = unshown.saturating_add(1);
800 }
801 c.lines = Some(lines);
802 }
803 unshown
804}
805
806/// The lanes [`bind_output_uses`] bound, for a log.
807#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
808pub struct OutputUses {
809 /// Lanes bound as `OutputRole::Coverage`.
810 pub coverage: u32,
811 /// Lanes bound as `OutputRole::Opacity`.
812 pub opacity: u32,
813}
814
815/// Bind the Unknown lanes of a pixel stage's `outputs` that what its program shows of them (`uses`, from
816/// `iridia::output_uses::output_uses`) gives a use for, by the output's label:
817///
818/// - [`OutputRole::Opacity`], a lane the target's stated blend weighs the colour by ([`OutputUse::blend_weight`]: the
819/// alpha lane of a target blended by the source alpha);
820/// - else [`OutputRole::Coverage`], a lane a discard tests ([`OutputUse::discard_tested`]).
821///
822/// A lane with a role keeps it, and an Unknown binding keeps its why on the lanes not bound. A caller that states roles
823/// for the outputs of a program calls it after stating its own; nothing is read from a program's or a target's name.
824pub fn bind_output_uses(outputs: &mut Vec<OutputBinding>, uses: &[OutputUse]) -> OutputUses {
825 let mut counts = OutputUses::default();
826 let mut bound = Vec::with_capacity(outputs.len());
827 for o in outputs.drain(..) {
828 let OutputRole::Unknown { why } = &o.role else {
829 bound.push(o);
830 continue;
831 };
832 let Some(seen) = uses.iter().find(|u| u.label == o.label) else {
833 bound.push(o);
834 continue;
835 };
836 let opacity = o.lanes & seen.blend_weight;
837 let coverage = o.lanes & seen.discard_tested & !opacity;
838 let rest = o.lanes & !(opacity | coverage);
839 if rest != 0 {
840 bound.push(OutputBinding {
841 label: o.label.clone(),
842 lanes: rest,
843 role: OutputRole::unknown(why.clone()),
844 });
845 }
846 for (lanes, role) in [(coverage, OutputRole::Coverage), (opacity, OutputRole::Opacity)] {
847 if lanes != 0 {
848 bound.push(OutputBinding {
849 label: o.label.clone(),
850 lanes,
851 role,
852 });
853 }
854 }
855 counts.coverage = counts.coverage.saturating_add(coverage.count_ones());
856 counts.opacity = counts.opacity.saturating_add(opacity.count_ones());
857 }
858 *outputs = bound;
859 counts
860}
861
862#[cfg(test)]
863#[allow(clippy::unwrap_used, reason = "hand-worked values")]
864mod tests {
865 use super::*;
866
867 /// The camera-relative view-projection is a role of its own: a source that states it is not read as the plain matrix.
868 #[test]
869 fn a_camera_relative_view_projection_is_not_the_plain_one() {
870 assert_ne!(
871 ConstantRole::View(ViewParameter::CameraRelativeViewProjection),
872 ConstantRole::View(ViewParameter::ViewProjection)
873 );
874 assert_eq!(
875 serde_json::to_value(ViewParameter::CameraRelativeViewProjection).unwrap(),
876 serde_json::json!("camera_relative_view_projection")
877 );
878 }
879
880 fn target(n: u32) -> Label {
881 Label {
882 name: "SV_Target".into(),
883 index: n,
884 }
885 }
886
887 fn seen(n: u32, blend_weight: u8, discard_tested: u8) -> OutputUse {
888 OutputUse {
889 label: target(n),
890 target: n,
891 blend_weight,
892 discard_tested,
893 }
894 }
895
896 /// A lane the blend weighs by is Opacity, over a discard's test of it; a tested lane is Coverage; a lane with a
897 /// role keeps it; an Unknown binding keeps its why on the lanes not bound; an output nothing is seen of is left.
898 #[test]
899 fn the_uses_seen_bind_the_unknown_lanes() {
900 let unknown = |n: u32, lanes: u8| OutputBinding {
901 label: target(n),
902 lanes,
903 role: OutputRole::unknown(format!("target {n}: no role")),
904 };
905 let mut outputs = vec![
906 OutputBinding {
907 label: target(0),
908 lanes: 0b0111,
909 role: OutputRole::Albedo,
910 },
911 unknown(0, 0b1000),
912 unknown(1, 0b1111),
913 unknown(2, 0b1000),
914 unknown(3, 0b1111),
915 ];
916 let uses = [
917 // the albedo's lanes are tested too: it keeps its role
918 seen(0, 0, 0b1111),
919 seen(1, 0b1000, 0b1001),
920 seen(2, 0, 0),
921 ];
922 let counts = bind_output_uses(&mut outputs, &uses);
923 let roles: Vec<_> = outputs
924 .iter()
925 .map(|o| (o.label.index, o.lanes, o.role.clone()))
926 .collect();
927 assert_eq!(
928 roles,
929 [
930 (0, 0b0111, OutputRole::Albedo),
931 (0, 0b1000, OutputRole::Coverage),
932 (1, 0b0110, OutputRole::unknown("target 1: no role")),
933 (1, 0b0001, OutputRole::Coverage),
934 (1, 0b1000, OutputRole::Opacity),
935 (2, 0b1000, OutputRole::unknown("target 2: no role")),
936 (3, 0b1111, OutputRole::unknown("target 3: no role")),
937 ]
938 );
939 assert_eq!(
940 counts,
941 OutputUses {
942 coverage: 2,
943 opacity: 1
944 }
945 );
946 }
947
948 /// Each matrix role is given the lines read for it, the others none; the unshown are counted.
949 #[test]
950 fn the_lines_of_each_matrix_role_are_stated() {
951 let constant = |offset: u32, role: ConstantRole| ConstantBinding {
952 resource: ResourceId(0),
953 offset,
954 size: 64,
955 name: None,
956 role,
957 lines: None,
958 };
959 let mut constants = vec![
960 constant(0, ConstantRole::View(ViewParameter::ViewProjection)),
961 constant(64, ConstantRole::Object(ObjectParameter::BonePalette)),
962 constant(128, ConstantRole::View(ViewParameter::CameraPosition)),
963 constant(144, ConstantRole::View(ViewParameter::InverseView)),
964 ];
965 let unshown = state_lines(&mut constants, |c| match c.offset {
966 0 => MatrixLines::Columns,
967 64 => MatrixLines::Rows,
968 _ => MatrixLines::Unshown(iridia_interface::Unshown::NotApplied),
969 });
970 assert_eq!(unshown, 1);
971 let lines: Vec<_> = constants.iter().map(|c| c.lines.clone()).collect();
972 assert_eq!(
973 lines,
974 [
975 Some(MatrixLines::Columns),
976 Some(MatrixLines::Rows),
977 None,
978 Some(MatrixLines::Unshown(iridia_interface::Unshown::NotApplied)),
979 ]
980 );
981 }
982}