primase_core/draw_order.rs
1//! The order in which surfaces that overlap are drawn, as the source states it (`core.draw_order`, on material
2//! entities).
3
4use serde::Serialize;
5use serde_json::Value;
6
7use crate::pbr::{AlphaMode, PbrMaterial};
8use crate::{Component, Entity, JsonError, World};
9
10/// What a surface is, for the draw order: it drives depth bias, shadows and a target's material class, not the
11/// sequence (that is [`DrawOrder::queue`]).
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
13#[serde(rename_all = "snake_case")]
14pub enum DrawLayer {
15 Opaque,
16 /// Alpha tested.
17 Cutout,
18 /// Drawn over the surface beneath it, coplanar or nearly so (a game's decal bucket): a consumer biases its depth
19 /// and does not cast its shadow.
20 Decal,
21 /// The sky: a dome, the moon's or the sun's disc, far backdrops and cloud layers. It is drawn after the opaque
22 /// surfaces are lit and before the blended surfaces, depth tested against the scene so that it shows only where
23 /// nothing nearer was drawn, and it casts no shadow. The sky's own programs give its colour: a consumer adds no
24 /// fog or lighting of its own to it. Whether the source's own fog composite (a volumetric fog laid over the lit
25 /// picture) comes before the sky or over it is the source's: its `core.render_graph` states it by the order of its
26 /// passes (Sekiro composites it before the sky, Elden Ring over it, in a recorded frame of each).
27 /// Where it stands is often relative to the view ([`crate::SkyAnchor`]).
28 Sky,
29 /// Blended.
30 Transparent,
31}
32
33/// `core.draw_order`: where a material's surfaces come in the source's draw sequence. `queue` is that sequence across
34/// every layer, lower first, on core's own scale: [`DrawOrder::OPAQUE`] 2000, [`DrawOrder::CUTOUT`] 2450,
35/// [`DrawOrder::SKY`] 2900 and [`DrawOrder::TRANSPARENT`] 3000 are where a layer's surfaces come by default (the sky
36/// has no alpha mode that implies it: a source states it), and a lowering places a source's
37/// own sequence on this scale (a source whose numbers are already on it keeps them). One scale for every layer, so a
38/// source can order a surface of one layer among another's. Within one queue, surfaces draw in entity order, then
39/// primitive order: the source order readers keep.
40///
41/// `depth_write`: whether the source draws the surface writing depth (a blended surface may, where the source draws it
42/// among opaque ones), so a consumer orders and composites it as the source does.
43///
44/// A material without it draws by its alpha mode ([`DrawOrder::of`]), and is never a decal: that is a fact the
45/// source states.
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
47pub struct DrawOrder {
48 pub layer: DrawLayer,
49 pub queue: i32,
50 pub depth_write: bool,
51}
52
53impl DrawOrder {
54 /// Core's default queues of the layers, which a material's alpha mode implies where the source states no order.
55 pub const OPAQUE: i32 = 2000;
56 pub const CUTOUT: i32 = 2450;
57 /// The sky's default queue: after every opaque, alpha-tested and decal surface, before the blended ones.
58 pub const SKY: i32 = 2900;
59 pub const TRANSPARENT: i32 = 3000;
60
61 /// A draw order with the conventional depth write of its layer (glTF's): opaque and alpha-tested surfaces write
62 /// depth, decals and blended ones do not. For a source that states the layer and queue but not the depth write.
63 pub fn conventional(layer: DrawLayer, queue: i32) -> DrawOrder {
64 DrawOrder {
65 layer,
66 queue,
67 depth_write: matches!(layer, DrawLayer::Opaque | DrawLayer::Cutout),
68 }
69 }
70
71 /// Material `e`'s draw order: its `core.draw_order`, else what its `pbr.material`'s alpha mode implies (opaque
72 /// without one), conventionally ([`DrawOrder::conventional`]).
73 pub fn of(world: &World, e: Entity) -> DrawOrder {
74 if let Some(&d) = world.get::<DrawOrder>(e) {
75 return d;
76 }
77 match world.get::<PbrMaterial>(e).map(|m| m.alpha_mode) {
78 Some(AlphaMode::Mask { .. }) => Self::conventional(DrawLayer::Cutout, Self::CUTOUT),
79 Some(AlphaMode::Blend) => Self::conventional(DrawLayer::Transparent, Self::TRANSPARENT),
80 Some(AlphaMode::Opaque) | None => Self::conventional(DrawLayer::Opaque, Self::OPAQUE),
81 }
82 }
83}
84
85impl Component for DrawOrder {
86 const NAME: &'static str = "core.draw_order";
87 const GEOMETRY: crate::Geometry = crate::Geometry::None;
88 /// 2: `depth_write`. 3: the `sky` layer.
89 const VERSION: u32 = 3;
90 fn to_json(&self) -> Result<Option<Value>, JsonError> {
91 crate::json::to_value(self).map(Some)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn a_material_without_one_draws_by_its_alpha_mode_and_is_never_a_decal() {
101 let mut w = World::new();
102 let blend = w.spawn();
103 w.insert(
104 blend,
105 PbrMaterial {
106 alpha_mode: AlphaMode::Blend,
107 ..PbrMaterial::default()
108 },
109 );
110 let mask = w.spawn();
111 w.insert(
112 mask,
113 PbrMaterial {
114 alpha_mode: AlphaMode::Mask { cutoff: 0.5 },
115 ..PbrMaterial::default()
116 },
117 );
118 let bare = w.spawn();
119 let decal = w.spawn();
120 // a decal the source states writes depth (the convention would not)
121 w.insert(
122 decal,
123 DrawOrder {
124 layer: DrawLayer::Decal,
125 queue: 2500,
126 depth_write: true,
127 },
128 );
129 // without one: the alpha mode's layer and queue, and glTF's depth write
130 assert_eq!(
131 DrawOrder::of(&w, blend),
132 DrawOrder::conventional(DrawLayer::Transparent, 3000)
133 );
134 assert!(!DrawOrder::of(&w, blend).depth_write);
135 assert_eq!(DrawOrder::of(&w, mask).queue, 2450);
136 assert!(DrawOrder::of(&w, mask).depth_write);
137 assert_eq!(DrawOrder::of(&w, bare).layer, DrawLayer::Opaque);
138 assert!(DrawOrder::of(&w, bare).depth_write);
139 assert!(!DrawOrder::conventional(DrawLayer::Decal, 2500).depth_write);
140 assert_eq!(
141 DrawOrder::of(&w, decal),
142 DrawOrder {
143 layer: DrawLayer::Decal,
144 queue: 2500,
145 depth_write: true
146 }
147 );
148 assert_eq!(
149 w.get::<DrawOrder>(decal).unwrap().to_json().unwrap(),
150 Some(serde_json::json!({"layer": "decal", "queue": 2500, "depth_write": true}))
151 );
152 }
153}