use serde::Serialize;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub enum Filter {
Nearest,
Linear,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub enum MipFilter {
None,
Nearest,
Linear,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct Filters {
pub mag: Option<Filter>,
pub min: Option<Filter>,
pub mip: Option<MipFilter>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AddressMode {
Repeat,
Mirror,
Clamp,
Border,
MirrorOnce,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
pub struct SamplerState {
pub wrap: [Option<AddressMode>; 3],
pub filters: Filters,
pub max_anisotropy: Option<u8>,
pub compare: Option<Compare>,
pub mip_lod_bias: Option<f32>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BlendFactor {
Zero,
One,
SrcColour,
OneMinusSrcColour,
SrcAlpha,
OneMinusSrcAlpha,
DstColour,
OneMinusDstColour,
DstAlpha,
OneMinusDstAlpha,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BlendOp {
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct BlendEquation {
pub source: BlendFactor,
pub destination: BlendFactor,
pub op: BlendOp,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct Blend {
pub colour: BlendEquation,
pub alpha: BlendEquation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Compare {
Never,
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
Always,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Cull {
None,
Front,
Back,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct DepthState {
pub test: Compare,
pub write: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct AlphaTest {
pub compare: Compare,
pub reference: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StencilOp {
Keep,
Zero,
Replace,
IncrementClamp,
DecrementClamp,
Invert,
IncrementWrap,
DecrementWrap,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct StencilFace {
pub fail: StencilOp,
pub depth_fail: StencilOp,
pub pass: StencilOp,
pub compare: Compare,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct Stencil {
pub read_mask: u8,
pub write_mask: u8,
pub reference: Option<u8>,
pub front: StencilFace,
pub back: StencilFace,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct TargetState {
pub blend: Option<Blend>,
pub write_mask: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct DepthBias {
pub constant: f32,
pub slope_scaled: f32,
pub clamp: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Fill {
Solid,
Wireframe,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
pub struct RenderState {
pub blend: Option<Blend>,
pub depth: Option<DepthState>,
pub cull: Option<Cull>,
pub alpha_test: Option<AlphaTest>,
pub targets: [Option<TargetState>; 8],
pub stencil: Option<Stencil>,
pub depth_bias: Option<DepthBias>,
pub fill: Option<Fill>,
pub front_counter_clockwise: Option<bool>,
}
impl RenderState {
pub fn verify(&self) -> Result<(), String> {
if let Some(a) = self.alpha_test
&& !a.reference.is_finite()
{
return Err("the alpha test's reference is not finite".into());
}
for (i, t) in self.targets.iter().enumerate() {
if let Some(t) = t
&& t.write_mask > 0xf
{
return Err(format!(
"render target {i}'s write mask {:#x} is wider than four channels",
t.write_mask
));
}
}
match self.targets[0] {
Some(first) if first.blend != self.blend => {
return Err("render target 0's blend is not the state's blend".into());
}
None if self.blend.is_some() => {
return Err("the state has a blend and render target 0 has no state to hold it".into());
}
_ => {}
}
if let Some(b) = self.depth_bias
&& !(b.constant.is_finite() && b.slope_scaled.is_finite() && b.clamp.is_finite())
{
return Err("the depth bias is not finite".into());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_render_state_is_consistent() {
let eq = BlendEquation {
source: BlendFactor::SrcAlpha,
destination: BlendFactor::OneMinusSrcAlpha,
op: BlendOp::Add,
};
let blend = Blend {
colour: eq,
alpha: eq,
};
let mut state = RenderState::default();
assert_eq!(state.verify(), Ok(()));
state.targets[0] = Some(TargetState {
blend: Some(blend),
write_mask: 0xf,
});
assert!(state.verify().unwrap_err().contains("target 0's blend"));
state.blend = Some(blend);
assert_eq!(state.verify(), Ok(()));
let mut lone = RenderState {
blend: Some(blend),
..RenderState::default()
};
assert!(lone.verify().unwrap_err().contains("no state to hold it"));
lone.targets[0] = Some(TargetState {
blend: Some(blend),
write_mask: 0xf,
});
assert_eq!(lone.verify(), Ok(()));
state.targets[3] = Some(TargetState {
blend: None,
write_mask: 0x1f,
});
assert!(state.verify().unwrap_err().contains("target 3's write mask"));
state.targets[3] = Some(TargetState {
blend: None,
write_mask: 0x8,
});
state.depth_bias = Some(DepthBias {
constant: 0.0,
slope_scaled: f32::NAN,
clamp: 0.0,
});
assert!(state.verify().unwrap_err().contains("depth bias"));
}
}