use crate::shader_values::{Sealed, ShaderValues};
const FORWARD: &str = include_str!("renderer/forward.wgsl");
const SEAM: &str = "// mirage-engine:style";
pub(crate) const GROUP: u32 = 3;
const PLACED: &str = "fn displaced(placed: Placed) -> vec3<f32> {\n return placed.world;\n}\n";
const DISPLACED: &str =
"fn displaced(placed: Placed) -> vec3<f32> {\n return placed.world + displace(placed);\n}\n";
const READ: &str = "fn styled(base: Surface) -> Surface {\n return base;\n}\n";
const SURFACED: &str = "fn styled(base: Surface) -> Surface {\n return surface(base);\n}\n";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DrawPass {
Opaque,
Cutout,
Translucent,
Additive,
}
pub trait SurfaceStyle: ShaderValues + Default {
const PASS: DrawPass;
const SURFACE: Option<&'static str> = None;
const DISPLACE: Option<&'static str> = None;
}
pub trait SurfaceStyles: Sealed + 'static {
#[doc(hidden)]
fn declared(into: &mut Declarations);
#[doc(hidden)]
fn seat(&self) -> u32;
#[doc(hidden)]
fn write(&self, into: &mut Vec<u8>);
}
#[doc(hidden)]
#[derive(Default)]
pub struct Declarations(Vec<Declaration>);
impl Declarations {
#[doc(hidden)]
pub fn declare<S: SurfaceStyle>(&mut self) {
self.0.push(Declaration::of::<S>());
}
pub(crate) fn of<S: SurfaceStyles>() -> Vec<Declaration> {
let mut declared = Self::default();
S::declared(&mut declared);
declared.0
}
}
#[derive(Debug)]
pub(crate) struct Declaration {
pub(crate) name: &'static str,
pub(crate) pass: DrawPass,
pub(crate) source: String,
pub(crate) defaults: Vec<u8>,
}
impl Declaration {
fn of<S: SurfaceStyle>() -> Self {
let mut defaults = Vec::new();
S::default().write(&mut defaults);
Self {
name: core::any::type_name::<S>(),
pass: S::PASS,
source: stitched(&S::bound(GROUP, "style"), S::SURFACE, S::DISPLACE),
defaults,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) struct SurfaceStyleId(pub(crate) u32);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Styled {
pub(crate) id: SurfaceStyleId,
pub(crate) pass: DrawPass,
pub(crate) displaces: bool,
}
impl Styled {
pub(crate) fn at<T: SurfaceStyle>(at: SurfaceStyleId) -> Self {
Self {
id: at,
pass: T::PASS,
displaces: T::DISPLACE.is_some(),
}
}
}
impl SurfaceStyles for () {
fn declared(_into: &mut Declarations) {}
fn seat(&self) -> u32 {
0
}
fn write(&self, _into: &mut Vec<u8>) {}
}
#[macro_export]
macro_rules! surface_styles {
($(#[$attribute:meta])* $vis:vis enum $set:ident { $($style:ident),+ $(,)? }) => {
$(#[$attribute])*
$vis enum $set {
$($style($style)),+
}
$(
impl ::core::convert::From<$style> for $set {
fn from(style: $style) -> Self {
Self::$style(style)
}
}
impl $crate::Holds<$style> for $set {}
)+
impl $crate::Sealed for $set {}
impl $crate::SurfaceStyles for $set {
fn declared(into: &mut $crate::SurfaceStyleDeclarations) {
$(into.declare::<$style>();)+
}
fn seat(&self) -> u32 {
let mut at = 0;
$(
if ::core::matches!(self, Self::$style(_)) {
return at;
}
at += 1;
)+
at
}
fn write(&self, into: &mut ::std::vec::Vec<u8>) {
match self {
$(Self::$style(values) => $crate::ShaderValues::write(values, into)),+
}
}
}
};
}
pub(crate) fn built_in() -> String {
stitched("", None, None)
}
fn stitched(values: &str, surface: Option<&str>, displace: Option<&str>) -> String {
let mut code = String::from(values);
for hook in [displace, surface].into_iter().flatten() {
code.push_str(hook);
code.push('\n');
}
code.push_str(match displace {
Some(_) => DISPLACED,
None => PLACED,
});
code.push_str(match surface {
Some(_) => SURFACED,
None => READ,
});
FORWARD.replace(SEAM, &code)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Color;
#[derive(Default, crate::ShaderValues)]
struct Water {
height: f32,
tint: Color,
}
impl SurfaceStyle for Water {
const PASS: DrawPass = DrawPass::Translucent;
const SURFACE: Option<&'static str> =
Some("fn surface(s: Surface) -> Surface { return s; }");
const DISPLACE: Option<&'static str> =
Some("fn displace(p: Placed) -> vec3<f32> { return vec3<f32>(0.0); }");
}
#[derive(Default, crate::ShaderValues)]
struct Toon;
impl SurfaceStyle for Toon {
const PASS: DrawPass = DrawPass::Opaque;
const SURFACE: Option<&'static str> =
Some("fn surface(s: Surface) -> Surface { return s; }");
}
surface_styles! { enum Looks { Water, Toon } }
#[test]
fn the_shader_carries_one_seam_for_a_style_to_be_stitched_into() {
assert_eq!(FORWARD.matches(SEAM).count(), 1);
}
#[test]
fn a_frame_with_no_style_is_drawn_with_the_seams_left_where_they_were() {
let source = built_in();
assert!(!source.contains(SEAM), "the seam itself is replaced");
assert!(source.contains(PLACED) && source.contains(READ));
assert!(
!source.contains("@group(3)"),
"and nothing of a style is bound"
);
}
#[test]
fn a_styles_own_code_is_called_from_the_seams_and_its_values_are_bound() {
let source = Declaration::of::<Water>().source;
assert!(source.contains(DISPLACED) && source.contains(SURFACED));
assert!(
source.contains("fn surface(s: Surface)") && source.contains("fn displace(p: Placed)"),
"the style's own code is stitched in whole"
);
assert!(source.contains("struct Water"));
assert!(source.contains("@group(3) @binding(0) var<uniform> style: Water;"));
assert!(
source.find("struct Water") < source.find("fn surface(s: Surface)"),
"and the values are declared before the code that reads them"
);
}
#[test]
fn a_style_with_no_fields_reads_no_values_and_binds_none() {
let declared = Declaration::of::<Toon>();
assert!(!declared.source.contains("@group(3)"));
assert!(declared.source.contains(PLACED), "and it moves no vertex");
assert!(declared.defaults.is_empty());
}
#[test]
fn a_set_declares_its_styles_in_the_order_it_names_them() {
let declared = Declarations::of::<Looks>();
assert_eq!(
declared.iter().map(|style| style.pass).collect::<Vec<_>>(),
vec![DrawPass::Translucent, DrawPass::Opaque]
);
assert_eq!(
(
Looks::from(Water::default()).seat(),
Looks::from(Toon).seat()
),
(0, 1)
);
assert!(Declarations::of::<()>().is_empty());
}
#[test]
fn a_set_value_lays_out_the_values_of_the_style_it_holds() {
let mut written = Vec::new();
Looks::from(Water {
height: 1.5,
tint: Color::WHITE,
})
.write(&mut written);
assert_eq!(
f32::from_le_bytes(written[..4].try_into().expect("four bytes")),
1.5
);
assert_eq!(written.len(), 32, "and pads to the block the shader reads");
let mut none = Vec::new();
Looks::from(Toon).write(&mut none);
assert!(none.is_empty(), "where a style reads nothing");
}
#[test]
fn a_styles_defaults_are_its_own_default_value_laid_out() {
#[derive(crate::ShaderValues)]
struct Deep {
height: f32,
}
impl Default for Deep {
fn default() -> Self {
Self { height: 3.0 }
}
}
impl SurfaceStyle for Deep {
const PASS: DrawPass = DrawPass::Opaque;
}
let mut written = Vec::new();
Deep::default().write(&mut written);
assert_eq!(Declaration::of::<Deep>().defaults, written);
assert_eq!(
f32::from_le_bytes(written[..4].try_into().expect("four bytes")),
3.0,
"which is not the zero a blank buffer would read"
);
}
}