Skip to main content

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}