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