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}