engine/
renderer.rs

1// SPDX-FileCopyrightText: 2024 Jens Pitkänen <jens.pitkanen@helsinki.fi>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5pub mod sprite;
6
7use platform::{BlendMode, DrawSettings2D, Platform, SpriteRef, TextureFilter, Vertex2D};
8
9use crate::{allocators::LinearAllocator, collections::FixedVec};
10
11/// Parameters for rendering a sprite.
12///
13/// Generally created by the engine in e.g.
14/// [`SpriteAsset::draw`](crate::resources::sprite::SpriteAsset::draw).
15#[derive(Debug)]
16pub struct SpriteQuad {
17    /// The top-left coordinate of the quad in the same coordinate system as
18    /// what [`Platform::draw_area`] returns.
19    pub position_top_left: (f32, f32),
20    /// The bottom-right coordinate of the quad in the same coordinate system as
21    /// what [`Platform::draw_area`] returns.
22    pub position_bottom_right: (f32, f32),
23    /// The top-left texture coordinate of the quad, each axis between 0..1,
24    /// with (0, 0) describing the top-left corner of the texture.
25    pub texcoord_top_left: (f32, f32),
26    /// The bottom-right texture coordinate of the quad, each axis between 0..1,
27    /// with (0, 0) describing the top-left corner of the texture.
28    pub texcoord_bottom_right: (f32, f32),
29    /// The drawing order of this particular sprite. Sprites with a lower draw
30    /// order are rendered below others with a higher one.
31    pub draw_order: u8,
32    /// The blending mode (if any) to use to draw this sprite above the other
33    /// sprites drawn below this one.
34    pub blend_mode: BlendMode,
35    /// The sprite used to draw this quad with. The region of the sprite used is
36    /// controlled with the `texcoord_*` fields.
37    pub sprite: SpriteRef,
38}
39
40impl SpriteQuad {
41    fn draw_call_identifier(&self) -> (SpriteRef, BlendMode, u8) {
42        (self.sprite, self.blend_mode, self.draw_order)
43    }
44}
45
46/// Queue of draw commands to be sorted and shipped off to the platform for
47/// rendering and some related rendering state.
48///
49/// Intended to be recreated every frame, but can be reused between frames to
50/// avoid having to queue up the draws again.
51pub struct DrawQueue<'frm> {
52    /// Sprites to draw.
53    pub sprites: FixedVec<'frm, SpriteQuad>,
54    /// [`Platform::draw_scale_factor`], stored here because all sprite
55    /// rendering needs it, and also has access to the draw queue.
56    pub scale_factor: f32,
57}
58
59impl<'frm> DrawQueue<'frm> {
60    /// Creates a new queue of draws.
61    pub fn new(
62        allocator: &'frm LinearAllocator,
63        max_quads: usize,
64        scale_factor: f32,
65    ) -> Option<DrawQueue<'frm>> {
66        Some(DrawQueue {
67            sprites: FixedVec::new(allocator, max_quads)?,
68            scale_factor,
69        })
70    }
71
72    /// Calls the platform draw functions to draw everything queued up until
73    /// this point.
74    pub fn dispatch_draw(&mut self, allocator: &LinearAllocator, platform: &dyn Platform) {
75        'draw_quads: {
76            if self.sprites.is_empty() {
77                break 'draw_quads;
78            }
79
80            self.sprites.sort_unstable_by(|a, b| {
81                a.draw_order
82                    .cmp(&b.draw_order)
83                    .then_with(|| a.sprite.cmp(&b.sprite))
84                    .then_with(|| a.blend_mode.cmp(&b.blend_mode))
85            });
86
87            let mut max_draw_call_length = 0;
88            {
89                let mut prev_draw_call_id = None;
90                let mut current_draw_call_length = 0;
91                for quad in self.sprites.iter() {
92                    let current_draw_call_id = Some(quad.draw_call_identifier());
93                    if current_draw_call_id == prev_draw_call_id {
94                        current_draw_call_length += 1;
95                    } else {
96                        max_draw_call_length = max_draw_call_length.max(current_draw_call_length);
97                        prev_draw_call_id = current_draw_call_id;
98                        current_draw_call_length = 1;
99                    }
100                }
101                max_draw_call_length = max_draw_call_length.max(current_draw_call_length);
102            }
103
104            let Some(mut vertices) = FixedVec::new(allocator, max_draw_call_length * 4) else {
105                break 'draw_quads;
106            };
107            let Some(mut indices) = FixedVec::new(allocator, max_draw_call_length * 6) else {
108                break 'draw_quads;
109            };
110
111            let mut quad_i = 0;
112            while quad_i < self.sprites.len() {
113                // Gather vertices for this draw call
114                let current_draw_call_id = self.sprites[quad_i].draw_call_identifier();
115                while quad_i < self.sprites.len() {
116                    let quad = &self.sprites[quad_i];
117                    if quad.draw_call_identifier() != current_draw_call_id {
118                        break;
119                    }
120
121                    let (x0, y0) = quad.position_top_left;
122                    let (x1, y1) = quad.position_bottom_right;
123                    let (u0, v0) = quad.texcoord_top_left;
124                    let (u1, v1) = quad.texcoord_bottom_right;
125                    let vert_offset = vertices.len() as u32;
126                    let _ = vertices.push(Vertex2D::new(x0, y0, u0, v0));
127                    let _ = vertices.push(Vertex2D::new(x0, y1, u0, v1));
128                    let _ = vertices.push(Vertex2D::new(x1, y1, u1, v1));
129                    let _ = vertices.push(Vertex2D::new(x1, y0, u1, v0));
130                    let _ = indices.push(vert_offset);
131                    let _ = indices.push(vert_offset + 1);
132                    let _ = indices.push(vert_offset + 2);
133                    let _ = indices.push(vert_offset);
134                    let _ = indices.push(vert_offset + 2);
135                    let _ = indices.push(vert_offset + 3);
136
137                    quad_i += 1;
138                }
139
140                // Draw this one
141                let (sprite, blend_mode, _) = current_draw_call_id;
142                platform.draw_2d(
143                    &vertices,
144                    &indices,
145                    DrawSettings2D {
146                        sprite: Some(sprite),
147                        blend_mode,
148                        texture_filter: TextureFilter::Linear,
149                        clip_area: None,
150                    },
151                );
152                vertices.clear();
153                indices.clear();
154            }
155        }
156    }
157}