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}