Skip to main content

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}