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