iridia_ir/interface/state.rs
1//! Fixed-function state as Direct3D states it: the sampler state a texture is read through and the blend, depth,
2//! stencil, cull, fill and alpha test a program is drawn with.
3#[allow(unused_imports)]
4use alloc::{
5 boxed::Box,
6 format,
7 string::{String, ToString},
8 vec,
9 vec::Vec,
10};
11
12use serde::Serialize;
13
14/// How texels are filtered when a texture is magnified or minified (glTF's sampler filters).
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
16pub enum Filter {
17 /// The nearest texel: crisp texels.
18 Nearest,
19 /// Bilinear between the four nearest texels.
20 Linear,
21}
22/// How mip levels are used when a texture is minified.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
24pub enum MipFilter {
25 /// No mip levels: the base level only (glTF's `NEAREST` or `LINEAR` minification filter).
26 None,
27 /// The nearest mip level.
28 Nearest,
29 /// Between the two nearest mip levels (trilinear with a linear `min`).
30 Linear,
31}
32/// A sampler's filters as the source states them: each `None` where it states none (the renderer's default).
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
34pub struct Filters {
35 /// Magnification.
36 pub mag: Option<Filter>,
37 /// Minification within a level.
38 pub min: Option<Filter>,
39 /// Minification across mip levels.
40 pub mip: Option<MipFilter>,
41}
42
43/// What a texture coordinate outside 0 to 1 reads, along one axis.
44#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
45#[serde(rename_all = "snake_case")]
46pub enum AddressMode {
47 /// The texture repeats.
48 Repeat,
49 /// The texture repeats, every other copy mirrored.
50 Mirror,
51 /// The edge texel.
52 Clamp,
53 /// The sampler's border colour.
54 Border,
55 /// Mirrored once about the origin, then the edge texel.
56 MirrorOnce,
57}
58/// A sampler's state as the source stores it, for a texture slot or a program's sampler: each part `None` where the
59/// source states none.
60#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
61pub struct SamplerState {
62 /// Addressing along u, v and w.
63 pub wrap: [Option<AddressMode>; 3],
64 /// The sampler's filters.
65 pub filters: Filters,
66 /// The most texels along the axis of anisotropy an anisotropic filter takes (1: none beyond the filters'), where
67 /// the source states an anisotropic filter.
68 pub max_anisotropy: Option<u8>,
69 /// The comparison a depth-comparison sampler makes of the reference against the texel, where the sampler is one.
70 pub compare: Option<Compare>,
71 /// Added to the mip level the hardware computes, where the source states a non-zero bias.
72 pub mip_lod_bias: Option<f32>,
73 /// The colour (red, green, blue, alpha) a coordinate outside the texture reads along an axis addressed
74 /// [`AddressMode::Border`], where the source states it.
75 pub border: Option<[f32; 4]>,
76 /// The least and the greatest mip level the sampler reads (the level of detail is clamped to them), where the
77 /// source states a range; `None` is every level the texture has.
78 pub lod: Option<[f32; 2]>,
79}
80
81impl SamplerState {
82 /// The state agrees with itself: a stated border colour and level range are finite (the range's top may be
83 /// `f32::MAX`, every level), the range is not reversed, and a maximum anisotropy is at least 1.
84 pub fn verify(&self) -> Result<(), String> {
85 if let Some(b) = self.border
86 && b.iter().any(|c| !c.is_finite())
87 {
88 return Err(format!("border colour {b:?} is not finite"));
89 }
90 if let Some([low, high]) = self.lod
91 && (!low.is_finite() || !high.is_finite() || low > high)
92 {
93 return Err(format!(
94 "level range {low}..{high} is not a finite, ordered range"
95 ));
96 }
97 if let Some(b) = self.mip_lod_bias
98 && !b.is_finite()
99 {
100 return Err(format!("mip level bias {b} is not finite"));
101 }
102 if self.max_anisotropy == Some(0) {
103 return Err("a maximum anisotropy of 0".into());
104 }
105 Ok(())
106 }
107}
108/// A blend factor.
109#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
110#[serde(rename_all = "snake_case")]
111pub enum BlendFactor {
112 /// Zero.
113 Zero,
114 /// One.
115 One,
116 /// The source colour.
117 SrcColour,
118 /// One minus the source colour.
119 OneMinusSrcColour,
120 /// The source alpha.
121 SrcAlpha,
122 /// One minus the source alpha.
123 OneMinusSrcAlpha,
124 /// The colour already in the target.
125 DstColour,
126 /// One minus the target colour.
127 OneMinusDstColour,
128 /// The alpha already in the target.
129 DstAlpha,
130 /// One minus the target alpha.
131 OneMinusDstAlpha,
132}
133/// How the two weighted terms of a blend are combined.
134#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
135#[serde(rename_all = "snake_case")]
136pub enum BlendOp {
137 /// `source + destination`.
138 Add,
139 /// `source - destination`.
140 Subtract,
141 /// `destination - source`.
142 ReverseSubtract,
143 /// The smaller of the two unweighted values.
144 Min,
145 /// The larger of the two unweighted values.
146 Max,
147}
148/// `op(src * source, dst * destination)`.
149#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
150pub struct BlendEquation {
151 /// The factor the incoming (source) value is multiplied by.
152 pub source: BlendFactor,
153 /// The factor the value already in the target is multiplied by.
154 pub destination: BlendFactor,
155 /// How the two products are combined.
156 pub op: BlendOp,
157}
158/// Blending of the colour and the alpha channel.
159#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
160pub struct Blend {
161 /// The equation for the red, green and blue channels.
162 pub colour: BlendEquation,
163 /// The equation for the alpha channel.
164 pub alpha: BlendEquation,
165}
166/// How two values are compared; the left one is the fragment's own (its depth or alpha, or the stencil reference), the
167/// right one what it is tested against.
168#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
169#[serde(rename_all = "snake_case")]
170pub enum Compare {
171 /// Never passes.
172 Never,
173 /// Passes where less.
174 Less,
175 /// Passes where equal.
176 Equal,
177 /// Passes where less than or equal.
178 LessEqual,
179 /// Passes where greater.
180 Greater,
181 /// Passes where not equal.
182 NotEqual,
183 /// Passes where greater than or equal.
184 GreaterEqual,
185 /// Always passes.
186 Always,
187}
188/// Which facing of triangle is not drawn.
189#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
190#[serde(rename_all = "snake_case")]
191pub enum Cull {
192 /// Neither: both facings are drawn.
193 None,
194 /// Front-facing triangles are culled.
195 Front,
196 /// Back-facing triangles are culled.
197 Back,
198}
199/// Depth test and write.
200#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
201pub struct DepthState {
202 /// The depth comparison a fragment must pass.
203 pub test: Compare,
204 /// Whether a passing fragment writes its depth.
205 pub write: bool,
206}
207/// Alpha test: a pixel is kept where `alpha compare reference`.
208#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
209pub struct AlphaTest {
210 /// The comparison of the fragment's alpha with `reference`.
211 pub compare: Compare,
212 /// The reference alpha, 0 to 1.
213 pub reference: f32,
214}
215/// A stencil operation (D3D12 and Vulkan have the same eight).
216#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
217#[serde(rename_all = "snake_case")]
218pub enum StencilOp {
219 /// Keep the stored value.
220 Keep,
221 /// Set it to zero.
222 Zero,
223 /// Set it to the reference value.
224 Replace,
225 /// Increment, clamped at the maximum.
226 IncrementClamp,
227 /// Decrement, clamped at zero.
228 DecrementClamp,
229 /// Invert its bits.
230 Invert,
231 /// Increment, wrapping to zero.
232 IncrementWrap,
233 /// Decrement, wrapping to the maximum.
234 DecrementWrap,
235}
236/// What the stencil test does for one facing.
237#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
238pub struct StencilFace {
239 /// On a failed stencil test.
240 pub fail: StencilOp,
241 /// On a passed stencil test and a failed depth test.
242 pub depth_fail: StencilOp,
243 /// On both passed.
244 pub pass: StencilOp,
245 /// The comparison of the reference with the stored value.
246 pub compare: Compare,
247}
248/// An enabled stencil test (a disabled one is `None` in [`RenderState::stencil`]).
249#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
250pub struct Stencil {
251 /// Mask applied to the reference and the stored value before they are compared.
252 pub read_mask: u8,
253 /// Mask of the stencil bits a write changes.
254 pub write_mask: u8,
255 /// The reference value the test compares against and `Replace` writes, where the source states one (D3D12 sets it
256 /// per draw: a source that stores it per draw states none).
257 pub reference: Option<u8>,
258 /// The operations for front-facing triangles.
259 pub front: StencilFace,
260 /// The operations for back-facing triangles.
261 pub back: StencilFace,
262}
263/// What one render target does with a fragment: its blend and which channels it writes.
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
265pub struct TargetState {
266 /// `None` where the target is not blended.
267 pub blend: Option<Blend>,
268 /// The channels written: bit 0 red, 1 green, 2 blue, 3 alpha (`0xf` all).
269 pub write_mask: u8,
270}
271/// A depth bias, D3D12's three terms: `bias = constant * r + slope_scaled * max_slope`, limited to `clamp` when that
272/// is not zero (r the smallest resolvable depth difference).
273#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
274pub struct DepthBias {
275 /// The constant term, in units of the smallest resolvable depth difference.
276 pub constant: f32,
277 /// The factor of the polygon's maximum depth slope.
278 pub slope_scaled: f32,
279 /// The most the bias may reach in magnitude; 0 for no limit.
280 pub clamp: f32,
281}
282/// How a triangle is filled.
283#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
284#[serde(rename_all = "snake_case")]
285pub enum Fill {
286 /// Filled triangles.
287 Solid,
288 /// Triangle outlines.
289 Wireframe,
290}
291/// Fixed-function state as the source stores it; each `None` where it states none. D3D12-shaped: `blend` is render
292/// target 0's blend (what a consumer with one render target reads), and `targets` has every render target's blend
293/// and write mask where the source states them, with `targets[0]`'s blend equal to `blend` (a consumer's
294/// verification checks it).
295#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
296pub struct RenderState {
297 /// Blending of render target 0; `None` where it is not blended.
298 pub blend: Option<Blend>,
299 /// The depth test and write.
300 pub depth: Option<DepthState>,
301 /// The face culling.
302 pub cull: Option<Cull>,
303 /// The alpha test.
304 pub alpha_test: Option<AlphaTest>,
305 /// Per render target, in order.
306 pub targets: [Option<TargetState>; 8],
307 /// The stencil test, where one is enabled.
308 pub stencil: Option<Stencil>,
309 /// The depth bias.
310 pub depth_bias: Option<DepthBias>,
311 /// How triangles are filled.
312 pub fill: Option<Fill>,
313 /// Whether counter-clockwise triangles are front faces.
314 pub front_counter_clockwise: Option<bool>,
315}
316impl RenderState {
317 /// The state is consistent: the write masks fit four channels, the bias is finite, and `blend` is the first
318 /// target's blend (a state with a blend has a first target, and the other way round).
319 pub fn verify(&self) -> Result<(), String> {
320 if let Some(a) = self.alpha_test
321 && !a.reference.is_finite()
322 {
323 return Err("the alpha test's reference is not finite".into());
324 }
325 for (i, t) in self.targets.iter().enumerate() {
326 if let Some(t) = t
327 && t.write_mask > 0xf
328 {
329 return Err(format!(
330 "render target {i}'s write mask {:#x} is wider than four channels",
331 t.write_mask
332 ));
333 }
334 }
335 match self.targets[0] {
336 Some(first) if first.blend != self.blend => {
337 return Err("render target 0's blend is not the state's blend".into());
338 }
339 None if self.blend.is_some() => {
340 return Err("the state has a blend and render target 0 has no state to hold it".into());
341 }
342 _ => {}
343 }
344 if let Some(b) = self.depth_bias
345 && !(b.constant.is_finite() && b.slope_scaled.is_finite() && b.clamp.is_finite())
346 {
347 return Err("the depth bias is not finite".into());
348 }
349 Ok(())
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn a_render_state_is_consistent() {
359 let eq = BlendEquation {
360 source: BlendFactor::SrcAlpha,
361 destination: BlendFactor::OneMinusSrcAlpha,
362 op: BlendOp::Add,
363 };
364 let blend = Blend {
365 colour: eq,
366 alpha: eq,
367 };
368 let mut state = RenderState::default();
369 assert_eq!(state.verify(), Ok(()));
370 // the first target's blend is the state's
371 state.targets[0] = Some(TargetState {
372 blend: Some(blend),
373 write_mask: 0xf,
374 });
375 assert!(state.verify().unwrap_err().contains("target 0's blend"));
376 state.blend = Some(blend);
377 assert_eq!(state.verify(), Ok(()));
378 // a blend with no state for the first target to hold it is not the same as no blend
379 let mut lone = RenderState {
380 blend: Some(blend),
381 ..RenderState::default()
382 };
383 assert!(lone.verify().unwrap_err().contains("no state to hold it"));
384 lone.targets[0] = Some(TargetState {
385 blend: Some(blend),
386 write_mask: 0xf,
387 });
388 assert_eq!(lone.verify(), Ok(()));
389 // other targets are free; a mask is four channels
390 state.targets[3] = Some(TargetState {
391 blend: None,
392 write_mask: 0x1f,
393 });
394 assert!(state.verify().unwrap_err().contains("target 3's write mask"));
395 state.targets[3] = Some(TargetState {
396 blend: None,
397 write_mask: 0x8,
398 });
399 state.depth_bias = Some(DepthBias {
400 constant: 0.0,
401 slope_scaled: f32::NAN,
402 clamp: 0.0,
403 });
404 assert!(state.verify().unwrap_err().contains("depth bias"));
405 }
406
407 #[test]
408 fn a_sampler_state_is_consistent() {
409 let mut state = SamplerState {
410 wrap: [Some(AddressMode::Border); 3],
411 border: Some([0.0, 0.0, 0.0, 0.0]),
412 lod: Some([0.0, 0.0]),
413 ..SamplerState::default()
414 };
415 assert_eq!(state.verify(), Ok(()));
416 assert_eq!(SamplerState::default().verify(), Ok(()));
417 // Direct3D's greatest level, f32::MAX, is every level and finite
418 state.lod = Some([0.0, f32::MAX]);
419 assert_eq!(state.verify(), Ok(()));
420 state.lod = Some([2.0, 1.0]);
421 assert!(state.verify().unwrap_err().contains("level range"));
422 state.lod = Some([0.0, f32::INFINITY]);
423 assert!(state.verify().unwrap_err().contains("level range"));
424 state.lod = None;
425 state.border = Some([0.0, f32::NAN, 0.0, 1.0]);
426 assert!(state.verify().unwrap_err().contains("border colour"));
427 state.border = Some([1.0; 4]);
428 state.mip_lod_bias = Some(f32::NAN);
429 assert!(state.verify().unwrap_err().contains("bias"));
430 state.mip_lod_bias = Some(-0.75);
431 state.max_anisotropy = Some(0);
432 assert!(state.verify().unwrap_err().contains("anisotropy"));
433 state.max_anisotropy = Some(16);
434 assert_eq!(state.verify(), Ok(()));
435 }
436}