Skip to main content

mirage_engine/
surface_style.rs

1//! Surface styles: WGSL of a game's own, compiled into the engine's
2//! forward shader.
3//!
4//! A surface style is a type: its fields are the values its WGSL reads, its
5//! [`PASS`](SurfaceStyle::PASS) is where its draws land in the frame, and
6//! its [`SURFACE`](SurfaceStyle::SURFACE) and
7//! [`DISPLACE`](SurfaceStyle::DISPLACE) are the WGSL it runs. Name it in
8//! [`Game::SurfaceStyles`](crate::Game::SurfaceStyles) through
9//! [`surface_styles!`](crate::surface_styles), draw with it through
10//! [`Instance::surface_style`](crate::mesh::Instance::surface_style), and
11//! pass it its values with
12//! [`set_surface_style`](crate::FrameContext::set_surface_style).
13//!
14//! # What the engine declares
15//!
16//! A style's WGSL is compiled into the forward shader, which declares
17//! what each of the two is passed:
18//!
19//! ```wgsl
20//! struct Surface {
21//!     color: vec4<f32>,     // the base color, tint and texel together
22//!     normal: vec3<f32>,    // world space, unit length
23//!     emissive: vec3<f32>,  // the light this surface adds of its own
24//!     world: vec3<f32>,     // where the fragment is, in world space
25//!     uv: vec2<f32>,        // held within the draw's own frame window
26//! }
27//!
28//! struct Placed {
29//!     world: vec3<f32>,     // where the transform placed this vertex
30//!     normal: vec3<f32>,    // world space, unit length
31//!     uv: vec2<f32>,        // the raw window, held within none
32//!     local: vec3<f32>,     // where the mesh was built, in object space
33//! }
34//! ```
35//!
36//! The engine reads `color`, `normal` and `emissive` back from a
37//! `Surface`, and nothing else: `world` and `uv` are there to read. It
38//! lights by the `normal` as it is returned, so a style that turns one
39//! keeps it unit length.
40//!
41//! # What a style declares
42//!
43//! Either of the two, or neither:
44//!
45//! ```wgsl
46//! fn surface(surface: Surface) -> Surface;   // SURFACE, fragment stage
47//! fn displace(placed: Placed) -> vec3<f32>;  // DISPLACE, vertex stage
48//! ```
49//!
50//! `None` on either leaves the engine's own code there: a style with
51//! neither compiles and draws exactly as the built-in look does, which is
52//! what a style declared for its [`DrawPass`] alone is for. `displace`
53//! returns how far to move the vertex in world space.
54//!
55//! A style's values are bound as `style`, in a WGSL struct named after the
56//! Rust type. A style is [`Default`] because a frame that never passes it
57//! values draws with the default value of every field — where a
58//! [`PostEffect`](crate::PostEffect) is not, since an effect no frame
59//! submits never runs at all.
60//!
61//! A styled draw lands in its style's own pass whatever its material
62//! holds. In [`DrawPass::Translucent`] the `color.a` a style returns is
63//! what the draw is blended over the frame by; in [`DrawPass::Cutout`] a
64//! returned `color.a` under `0.5` drops the texel, whatever the draw's own
65//! material declares; in [`DrawPass::Additive`] the draw is added and that
66//! alpha is dropped.
67//!
68//! A style may declare whatever else it needs beside the two: its own
69//! code, its own constant values, its own struct types. The forward shader
70//! it is compiled into declares over `100` names of its own, and declaring
71//! any of them again stops startup under the style's name; the ones a
72//! style would reach for first are `Surface`, `Placed`,
73//! `Fragment`, `surface_of`, `received`, `shading_of`, `scaled_by`,
74//! `held_inside`, `placed`, `columns`, `cofactor`, `unit`, `lit`,
75//! `shaded`, `dropped`, `styled`, `displaced`, `THRESHOLD`, `HALF`,
76//! `frame`, `lights`, `base_color`, `shading`, `relief` and
77//! `emissive_map`. Every name the frame's own sky declares starts with
78//! `sky`, `sky_light` and `sky_reflection` among them.
79//!
80//! A game may name any number of styles, each one pipeline built at
81//! startup.
82
83use crate::holds::Sealed;
84use crate::shader_values::ShaderValues;
85
86/// The engine's own shader, with [`SEAM`] where a style's code goes.
87const FORWARD: &str = include_str!("renderer/forward.wgsl");
88
89/// The line every stitch replaces.
90const SEAM: &str = "// mirage-engine:style";
91
92/// The bind group a style's values are read through.
93pub(crate) const GROUP: u32 = 3;
94
95/// The vertex seam: the vertex where the engine placed it, and where a
96/// style's own code moves it to.
97const PLACED: &str = "fn displaced(placed: Placed) -> vec3<f32> {\n    return placed.world;\n}\n";
98const DISPLACED: &str =
99    "fn displaced(placed: Placed) -> vec3<f32> {\n    return placed.world + displace(placed);\n}\n";
100
101/// The fragment seam: the surface as the engine read it, and as a style's
102/// own code paints it.
103const READ: &str = "fn styled(base: Surface) -> Surface {\n    return base;\n}\n";
104const SURFACED: &str = "fn styled(base: Surface) -> Surface {\n    return surface(base);\n}\n";
105
106/// The pass a style's draws land in, and the way it draws them.
107///
108/// A styled draw follows its style's pass whatever its material declares; a
109/// draw with no style is drawn in the pass its tint alpha and its cutout
110/// resolve to. A
111/// flagged light's depth maps take the opaque pass and no other.
112#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113pub enum DrawPass {
114    /// Drawn first, in batch order, written to depth, and cast by a
115    /// flagged light.
116    Opaque,
117    /// Drawn after those and into the same depth, dropping the texels its
118    /// alpha leaves out.
119    Cutout,
120    /// Blended over both of those, back to front, leaving depth alone.
121    Translucent,
122    /// Added over all of them in the order they were submitted, leaving
123    /// depth alone.
124    Additive,
125}
126
127/// A look of a game's own, written in WGSL and stitched into the engine's
128/// shader at startup.
129///
130/// Required if you want a surface the materials cannot draw: implement it
131/// on a type whose fields are the values its WGSL reads, and name that type
132/// in [`Game::SurfaceStyles`](crate::Game::SurfaceStyles). The engine keeps the instance
133/// data, the lighting, the shadows and the curves that take the frame to
134/// the screen; the two hooks below are where a style's own code runs.
135pub trait SurfaceStyle: ShaderValues + Default {
136    /// The pass this style's draws land in, whatever their material
137    /// holds; see the module's own docs.
138    const PASS: DrawPass;
139
140    /// WGSL declaring `fn surface(surface: Surface) -> Surface`, run in
141    /// the fragment stage before the surface is lit; `None` leaves the
142    /// engine's own code there. `Surface` is the engine's, and the module's
143    /// own docs state what each of its fields holds.
144    ///
145    /// `Surface` holds the base color as `color`, the world-space
146    /// `normal`, the light the surface adds of its own as `emissive`, and
147    /// the `world` position and `uv` it is read at. The engine draws with
148    /// `color`, `normal` and `emissive`; `world` and `uv` are there only
149    /// to read. A draw with a `Frame` of its own reads its texels at this
150    /// `uv`, held within that window. The engine lights with the `normal`
151    /// as it is returned, so
152    /// a style that turns it keeps it unit length.
153    const SURFACE: Option<&'static str> = None;
154
155    /// WGSL declaring `fn displace(placed: Placed) -> vec3<f32>`, run in
156    /// the vertex stage, which returns how far to move the vertex in world
157    /// space; `None` leaves the vertex where the transform placed it.
158    ///
159    /// `Placed` holds the `world` position the transform placed the
160    /// vertex at, its `normal`, its `uv`, and the `local` position the mesh
161    /// was built with. A draw with a `Frame` of its own holds the raw
162    /// `uv` of that window here; only `surface` reads one held within it.
163    /// A light's depth maps take the vertex unmoved, so a
164    /// displaced surface casts the shadow of where it was placed, and so
165    /// does the sphere the camera holds the draw by: what this moves is
166    /// the look, not the draw.
167    const DISPLACE: Option<&'static str> = None;
168}
169
170/// The styles one game draws with, named together as
171/// [`Game::SurfaceStyles`](crate::Game::SurfaceStyles).
172///
173/// Written by [`surface_styles!`](crate::surface_styles) and never
174/// implemented by hand; [`NoSurfaceStyles`] for a game that draws with the
175/// built-in look alone.
176pub trait SurfaceStyles: Sealed + 'static {
177    /// The styles startup compiles, in the order the set lists them.
178    #[doc(hidden)]
179    fn declared(into: &mut Declarations);
180
181    /// Seat of the style this set value holds: `0` for the first style the
182    /// set names, one more for each after it.
183    #[doc(hidden)]
184    fn seat(&self) -> u32;
185
186    /// Lays out the values of the style this set value holds.
187    #[doc(hidden)]
188    fn write(&self, into: &mut Vec<u8>);
189}
190
191/// The set of a game that draws with the built-in look alone.
192///
193/// No value of it exists, so no styled draw compiles for such a game. `()` is
194/// not a style set:
195///
196/// ```compile_fail
197/// use mirage_engine::prelude::*;
198///
199/// fn set<S: SurfaceStyles>() {}
200///
201/// set::<()>();
202/// ```
203#[derive(Clone, Debug, Eq, Hash, PartialEq)]
204pub enum NoSurfaceStyles {}
205
206impl Sealed for NoSurfaceStyles {}
207
208impl SurfaceStyles for NoSurfaceStyles {
209    fn declared(_into: &mut Declarations) {}
210
211    fn seat(&self) -> u32 {
212        match *self {}
213    }
214
215    fn write(&self, _into: &mut Vec<u8>) {
216        match *self {}
217    }
218}
219
220/// The styles a set declares, in the order it lists them; the set macro
221/// fills it and startup compiles what it holds.
222#[doc(hidden)]
223#[derive(Default)]
224pub struct Declarations(Vec<Declaration>);
225
226impl Declarations {
227    /// Declares the style at the next seat.
228    #[doc(hidden)]
229    pub fn declare<S: SurfaceStyle>(&mut self) {
230        self.0.push(Declaration::of::<S>());
231    }
232
233    /// Everything `S` declares, in seat order.
234    pub(crate) fn of<S: SurfaceStyles>() -> Vec<Declaration> {
235        let mut declared = Self::default();
236        S::declared(&mut declared);
237        declared.0
238    }
239}
240
241/// One style as startup takes it: its own name, the pass its draws land
242/// in, the shader it is drawn with, and the values it reads before a frame
243/// writes any.
244#[derive(Debug)]
245pub(crate) struct Declaration {
246    pub(crate) name: &'static str,
247    pub(crate) pass: DrawPass,
248    pub(crate) source: String,
249    pub(crate) defaults: Vec<u8>,
250}
251
252impl Declaration {
253    fn of<S: SurfaceStyle>() -> Self {
254        let mut defaults = Vec::new();
255        S::default().write(&mut defaults);
256        Self {
257            name: core::any::type_name::<S>(),
258            pass: S::PASS,
259            source: stitched(&S::bound(GROUP, "style"), S::SURFACE, S::DISPLACE),
260            defaults,
261        }
262    }
263}
264
265/// Which of a game's styles this is, counted in the order its set lists
266/// them.
267#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
268pub(crate) struct SurfaceStyleId(pub(crate) u32);
269
270/// The style of a draw: which seat of the set it is, and where it
271/// draws.
272#[derive(Clone, Copy, Debug, Eq, PartialEq)]
273pub(crate) struct Styled {
274    pub(crate) id: SurfaceStyleId,
275    pub(crate) pass: DrawPass,
276    /// Whether the style moves the corners it is drawn with, which takes the
277    /// draw off the plane its mesh was built in.
278    pub(crate) displaces: bool,
279}
280
281impl Styled {
282    /// The style `T`, at the seat its set holds it at.
283    pub(crate) fn at<T: SurfaceStyle>(at: SurfaceStyleId) -> Self {
284        Self {
285            id: at,
286            pass: T::PASS,
287            displaces: T::DISPLACE.is_some(),
288        }
289    }
290}
291
292/// Writes the set of every style a game draws with: an enum with one
293/// variant wrapping each named type, which
294/// [`Game::SurfaceStyles`](crate::Game::SurfaceStyles) names.
295///
296/// Each type is spelled by its own name and must be in scope; a set that
297/// holds a type twice does not compile. Seats run in the order the set
298/// lists them, and a draw's style is its seat. A `pub` before `enum` makes
299/// the enum public, and `///` lines before that are the enum's.
300///
301/// ```
302/// use mirage_engine::prelude::*;
303///
304/// #[derive(Default, ShaderValues)]
305/// struct Water {
306///     wave: f32,
307/// }
308///
309/// impl SurfaceStyle for Water {
310///     const PASS: DrawPass = DrawPass::Translucent;
311/// }
312///
313/// surface_styles! { enum Looks { Water } }
314/// ```
315#[macro_export]
316macro_rules! surface_styles {
317    ($(#[$attribute:meta])* $vis:vis enum $set:ident { $($style:ident),+ $(,)? }) => {
318        $(#[$attribute])*
319        $vis enum $set {
320            $($style($style)),+
321        }
322
323        $(
324            impl ::core::convert::From<$style> for $set {
325                fn from(style: $style) -> Self {
326                    Self::$style(style)
327                }
328            }
329
330            impl $crate::Holds<$style> for $set {}
331        )+
332
333        impl $crate::Sealed for $set {}
334
335        impl $crate::SurfaceStyles for $set {
336            fn declared(into: &mut $crate::SurfaceStyleDeclarations) {
337                $(into.declare::<$style>();)+
338            }
339
340            fn seat(&self) -> u32 {
341                let mut at = 0;
342                $(
343                    if ::core::matches!(self, Self::$style(_)) {
344                        return at;
345                    }
346                    at += 1;
347                )+
348                at
349            }
350
351            fn write(&self, into: &mut ::std::vec::Vec<u8>) {
352                match self {
353                    $(Self::$style(values) => $crate::ShaderValues::write(values, into)),+
354                }
355            }
356        }
357    };
358}
359
360/// The shader every draw with no style of its own is drawn with: the
361/// engine's own, with both seams left where they were.
362pub(crate) fn built_in() -> String {
363    stitched("", None, None)
364}
365
366/// The shader one style is drawn with: the engine's own, with the style's
367/// values declared and its code called where each seam marks.
368fn stitched(values: &str, surface: Option<&str>, displace: Option<&str>) -> String {
369    let mut code = String::from(values);
370    for hook in [displace, surface].into_iter().flatten() {
371        code.push_str(hook);
372        code.push('\n');
373    }
374    code.push_str(match displace {
375        Some(_) => DISPLACED,
376        None => PLACED,
377    });
378    code.push_str(match surface {
379        Some(_) => SURFACED,
380        None => READ,
381    });
382
383    FORWARD.replace(SEAM, &code)
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use crate::Color;
390
391    /// A style with both hooks and values of its own, written the way a
392    /// game writes one.
393    #[derive(Default, crate::ShaderValues)]
394    struct Water {
395        height: f32,
396        tint: Color,
397    }
398
399    impl SurfaceStyle for Water {
400        const PASS: DrawPass = DrawPass::Translucent;
401        const SURFACE: Option<&'static str> =
402            Some("fn surface(s: Surface) -> Surface { return s; }");
403        const DISPLACE: Option<&'static str> =
404            Some("fn displace(p: Placed) -> vec3<f32> { return vec3<f32>(0.0); }");
405    }
406
407    /// A style that reads no values, which is what a unit struct is for.
408    #[derive(Default, crate::ShaderValues)]
409    struct Toon;
410
411    impl SurfaceStyle for Toon {
412        const PASS: DrawPass = DrawPass::Opaque;
413        const SURFACE: Option<&'static str> =
414            Some("fn surface(s: Surface) -> Surface { return s; }");
415    }
416
417    surface_styles! { enum Looks { Water, Toon } }
418
419    #[test]
420    fn the_shader_carries_one_seam_for_a_style_to_be_stitched_into() {
421        assert_eq!(FORWARD.matches(SEAM).count(), 1);
422    }
423
424    #[test]
425    fn a_frame_with_no_style_is_drawn_with_the_seams_left_where_they_were() {
426        let source = built_in();
427
428        assert!(!source.contains(SEAM), "the seam itself is replaced");
429        assert!(source.contains(PLACED) && source.contains(READ));
430        assert!(
431            !source.contains("@group(3)"),
432            "and nothing of a style is bound"
433        );
434    }
435
436    #[test]
437    fn a_styles_own_code_is_called_from_the_seams_and_its_values_are_bound() {
438        let source = Declaration::of::<Water>().source;
439
440        assert!(source.contains(DISPLACED) && source.contains(SURFACED));
441        assert!(
442            source.contains("fn surface(s: Surface)") && source.contains("fn displace(p: Placed)"),
443            "the style's own code is stitched in whole"
444        );
445        assert!(source.contains("struct Water"));
446        assert!(source.contains("@group(3) @binding(0) var<uniform> style: Water;"));
447        assert!(
448            source.find("struct Water") < source.find("fn surface(s: Surface)"),
449            "and the values are declared before the code that reads them"
450        );
451    }
452
453    #[test]
454    fn a_style_with_no_fields_reads_no_values_and_binds_none() {
455        let declared = Declaration::of::<Toon>();
456
457        assert!(!declared.source.contains("@group(3)"));
458        assert!(declared.source.contains(PLACED), "and it moves no vertex");
459        assert!(declared.defaults.is_empty());
460    }
461
462    #[test]
463    fn a_set_declares_its_styles_in_the_order_it_names_them() {
464        let declared = Declarations::of::<Looks>();
465
466        assert_eq!(
467            declared.iter().map(|style| style.pass).collect::<Vec<_>>(),
468            vec![DrawPass::Translucent, DrawPass::Opaque]
469        );
470        assert_eq!(
471            (
472                Looks::from(Water::default()).seat(),
473                Looks::from(Toon).seat()
474            ),
475            (0, 1)
476        );
477        assert!(Declarations::of::<NoSurfaceStyles>().is_empty());
478    }
479
480    #[test]
481    fn a_set_value_lays_out_the_values_of_the_style_it_holds() {
482        let mut written = Vec::new();
483        Looks::from(Water {
484            height: 1.5,
485            tint: Color::WHITE,
486        })
487        .write(&mut written);
488
489        assert_eq!(
490            f32::from_le_bytes(written[..4].try_into().expect("four bytes")),
491            1.5
492        );
493        assert_eq!(written.len(), 32, "and pads to the block the shader reads");
494
495        let mut none = Vec::new();
496        Looks::from(Toon).write(&mut none);
497        assert!(none.is_empty(), "where a style reads nothing");
498    }
499
500    #[test]
501    fn a_styles_defaults_are_its_own_default_value_laid_out() {
502        #[derive(crate::ShaderValues)]
503        struct Deep {
504            height: f32,
505        }
506
507        impl Default for Deep {
508            fn default() -> Self {
509                Self { height: 3.0 }
510            }
511        }
512
513        impl SurfaceStyle for Deep {
514            const PASS: DrawPass = DrawPass::Opaque;
515        }
516
517        let mut written = Vec::new();
518        Deep::default().write(&mut written);
519
520        assert_eq!(Declaration::of::<Deep>().defaults, written);
521        assert_eq!(
522            f32::from_le_bytes(written[..4].try_into().expect("four bytes")),
523            3.0,
524            "which is not the zero a blank buffer would read"
525        );
526    }
527}