ggez/graphics/
canvas.rs

1use crevice::std140::AsStd140;
2
3use crate::{
4    context::{Has, HasMut},
5    GameError, GameResult,
6};
7
8use super::{
9    gpu::arc::{ArcBindGroup, ArcBindGroupLayout},
10    internal_canvas::{screen_to_mat, InstanceArrayView, InternalCanvas},
11    BlendMode, Color, DrawParam, Drawable, GraphicsContext, Image, InstanceArray, Mesh, Rect,
12    Sampler, ScreenImage, Shader, ShaderParams, Text, WgpuContext, ZIndex,
13};
14use std::{collections::BTreeMap, sync::Arc};
15
16/// Canvases are the main method of drawing meshes and text to images in ggez.
17///
18/// They can draw to any image that is capable of being drawn to (i.e. has been created with [`Image::new_canvas_image()`] or [`ScreenImage`]),
19/// or they can draw directly to the screen.
20///
21/// Canvases are also where you can bind your own custom shaders and samplers to use while drawing.
22/// Canvases *do not* automatically batch draws. To used batched (instanced) drawing, refer to [`InstanceArray`].
23// note:
24//   Canvas does not draw anything itself. It is merely a state-tracking and draw-reordering wrapper around InternalCanvas, which does the actual
25// drawing.
26#[derive(Debug)]
27pub struct Canvas {
28    pub(crate) wgpu: Arc<WgpuContext>,
29    draws: BTreeMap<ZIndex, Vec<DrawCommand>>,
30    state: DrawState,
31    original_state: DrawState,
32    screen: Option<Rect>,
33    defaults: DefaultResources,
34
35    target: Image,
36    resolve: Option<Image>,
37    clear: Option<Color>,
38
39    // This will be removed after queue_text and draw_queued_text have been removed.
40    pub(crate) queued_texts: Vec<(Text, mint::Point2<f32>, Option<Color>)>,
41}
42
43impl Canvas {
44    /// Create a new [Canvas] from an image. This will allow for drawing to a single color image.
45    ///
46    /// `clear` will set the image initially to the given color, if a color is provided, or keep it as is, if it's `None`.
47    ///
48    /// The image must be created for Canvas usage, i.e. [`Image::new_canvas_image`()], or [`ScreenImage`], and must only have a sample count of 1.
49    #[inline]
50    pub fn from_image(
51        gfx: &impl Has<GraphicsContext>,
52        image: Image,
53        clear: impl Into<Option<Color>>,
54    ) -> Self {
55        Canvas::new(gfx, image, None, clear.into())
56    }
57
58    /// Helper for [`Canvas::from_image`] for construction of a [`Canvas`] from a [`ScreenImage`].
59    #[inline]
60    pub fn from_screen_image(
61        gfx: &impl Has<GraphicsContext>,
62        image: &mut ScreenImage,
63        clear: impl Into<Option<Color>>,
64    ) -> Self {
65        let gfx = gfx.retrieve();
66        let image = image.image(gfx);
67        Canvas::from_image(gfx, image, clear)
68    }
69
70    /// Create a new [Canvas] from an MSAA image and a resolve target. This will allow for drawing with MSAA to a color image, then resolving the samples into a secondary target.
71    ///
72    /// Both images must be created for Canvas usage (see [`Canvas::from_image`]). `msaa_image` must have a sample count > 1 and `resolve_image` must strictly have a sample count of 1.
73    #[inline]
74    pub fn from_msaa(
75        gfx: &impl Has<GraphicsContext>,
76        msaa_image: Image,
77        resolve: Image,
78        clear: impl Into<Option<Color>>,
79    ) -> Self {
80        Canvas::new(gfx, msaa_image, Some(resolve), clear.into())
81    }
82
83    /// Helper for [`Canvas::from_msaa`] for construction of an MSAA [`Canvas`] from a [`ScreenImage`].
84    #[inline]
85    pub fn from_screen_msaa(
86        gfx: &impl Has<GraphicsContext>,
87        msaa_image: &mut ScreenImage,
88        resolve: &mut ScreenImage,
89        clear: impl Into<Option<Color>>,
90    ) -> Self {
91        let msaa = msaa_image.image(gfx);
92        let resolve = resolve.image(gfx);
93        Canvas::from_msaa(gfx, msaa, resolve, clear)
94    }
95
96    /// Create a new [Canvas] that renders directly to the window surface.
97    ///
98    /// `clear` will set the image initially to the given color, if a color is provided, or keep it as is, if it's `None`.
99    pub fn from_frame(gfx: &impl Has<GraphicsContext>, clear: impl Into<Option<Color>>) -> Self {
100        let gfx = gfx.retrieve();
101        // these unwraps will never fail
102        let samples = gfx.frame_msaa_image.as_ref().unwrap().samples();
103        let (target, resolve) = if samples > 1 {
104            (
105                gfx.frame_msaa_image.clone().unwrap(),
106                Some(gfx.frame_image.clone().unwrap()),
107            )
108        } else {
109            (gfx.frame_image.clone().unwrap(), None)
110        };
111        Canvas::new(gfx, target, resolve, clear.into())
112    }
113
114    fn new(
115        gfx: &impl Has<GraphicsContext>,
116        target: Image,
117        resolve: Option<Image>,
118        clear: Option<Color>,
119    ) -> Self {
120        let gfx = gfx.retrieve();
121
122        let defaults = DefaultResources::new(gfx);
123
124        let state = DrawState {
125            shader: default_shader(),
126            params: None,
127            text_shader: default_text_shader(),
128            text_params: None,
129            sampler: Sampler::default(),
130            blend_mode: BlendMode::ALPHA,
131            premul_text: true,
132            projection: glam::Mat4::IDENTITY.into(),
133            scissor_rect: (0, 0, target.width(), target.height()),
134        };
135
136        let screen = Rect {
137            x: 0.,
138            y: 0.,
139            w: target.width() as _,
140            h: target.height() as _,
141        };
142
143        let mut this = Canvas {
144            wgpu: gfx.wgpu.clone(),
145            draws: BTreeMap::new(),
146            state: state.clone(),
147            original_state: state,
148            screen: Some(screen),
149            defaults,
150
151            target,
152            resolve,
153            clear,
154
155            queued_texts: Vec::new(),
156        };
157
158        this.set_screen_coordinates(screen);
159
160        this
161    }
162
163    /// Sets the shader to use when drawing meshes.
164    #[inline]
165    pub fn set_shader(&mut self, shader: &Shader) {
166        self.state.shader = shader.clone();
167    }
168
169    /// Returns the current shader being used when drawing meshes.
170    #[inline]
171    pub fn shader(&self) -> Shader {
172        self.state.shader.clone()
173    }
174
175    /// Sets the shader parameters to use when drawing meshes.
176    ///
177    /// **Bound to bind group 3.**
178    #[inline]
179    pub fn set_shader_params<Uniforms: AsStd140>(&mut self, params: &ShaderParams<Uniforms>) {
180        self.state.params = Some((
181            params.bind_group.clone().unwrap(/* always Some */),
182            params.layout.clone().unwrap(/* always Some */),
183            params.buffer_offset,
184        ));
185    }
186
187    /// Sets the shader to use when drawing text.
188    #[inline]
189    pub fn set_text_shader(&mut self, shader: Shader) {
190        self.state.text_shader = shader;
191    }
192
193    /// Returns the current text shader being used when drawing text.
194    #[inline]
195    pub fn text_shader(&self) -> Shader {
196        self.state.text_shader.clone()
197    }
198
199    /// Sets the shader parameters to use when drawing text.
200    ///
201    /// **Bound to bind group 3.**
202    #[inline]
203    pub fn set_text_shader_params<Uniforms: AsStd140>(
204        &mut self,
205        params: &ShaderParams<Uniforms>,
206    ) -> GameResult {
207        self.state.text_params = Some((
208            params.bind_group.clone().unwrap(/* always Some */),
209            params.layout.clone().unwrap(/* always Some */),
210            params.buffer_offset,
211        ));
212        Ok(())
213    }
214
215    /// Resets the active mesh shader to the default.
216    #[inline]
217    pub fn set_default_shader(&mut self) {
218        self.state.shader = default_shader();
219    }
220
221    /// Resets the active text shader to the default.
222    #[inline]
223    pub fn set_default_text_shader(&mut self) {
224        self.state.text_shader = default_text_shader();
225    }
226
227    /// Sets the active sampler used to sample images.
228    ///
229    /// Use `set_sampler(Sampler::nearest_clamp())` for drawing pixel art graphics without blurring them.
230    #[inline]
231    pub fn set_sampler(&mut self, sampler: impl Into<Sampler>) {
232        self.state.sampler = sampler.into();
233    }
234
235    /// Returns the currently active sampler used to sample images.
236    #[inline]
237    pub fn sampler(&self) -> Sampler {
238        self.state.sampler
239    }
240
241    /// Resets the active sampler to the default.
242    ///
243    /// This is equivalent to `set_sampler(Sampler::linear_clamp())`.
244    #[inline]
245    pub fn set_default_sampler(&mut self) {
246        self.set_sampler(Sampler::default());
247    }
248
249    /// Sets the active blend mode used when drawing images.
250    #[inline]
251    pub fn set_blend_mode(&mut self, blend_mode: BlendMode) {
252        self.state.blend_mode = blend_mode;
253    }
254
255    /// Returns the currently active blend mode used when drawing images.
256    #[inline]
257    pub fn blend_mode(&self) -> BlendMode {
258        self.state.blend_mode
259    }
260
261    /// Selects whether text will be drawn with [`BlendMode::PREMULTIPLIED`] when the current blend
262    /// mode is [`BlendMode::ALPHA`]. This is `true` by default.
263    #[inline]
264    pub fn set_premultiplied_text(&mut self, premultiplied_text: bool) {
265        self.state.premul_text = premultiplied_text;
266    }
267
268    /// Sets the raw projection matrix to the given homogeneous
269    /// transformation matrix.  For an introduction to graphics matrices,
270    /// a good source is this: <http://ncase.me/matrix/>
271    #[inline]
272    pub fn set_projection(&mut self, proj: impl Into<mint::ColumnMatrix4<f32>>) {
273        self.state.projection = proj.into();
274        self.screen = None;
275    }
276
277    /// Gets a copy of the canvas's raw projection matrix.
278    #[inline]
279    pub fn projection(&self) -> mint::ColumnMatrix4<f32> {
280        self.state.projection
281    }
282
283    /// Premultiplies the given transformation matrix with the current projection matrix.
284    pub fn mul_projection(&mut self, transform: impl Into<mint::ColumnMatrix4<f32>>) {
285        self.set_projection(
286            glam::Mat4::from(transform.into()) * glam::Mat4::from(self.state.projection),
287        );
288        self.screen = None;
289    }
290
291    /// Sets the bounds of the screen viewport. This is a shortcut for `set_projection`
292    /// and thus will override any previous projection matrix set.
293    ///
294    /// The default coordinate system has \[0.0, 0.0\] at the top-left corner
295    /// with X increasing to the right and Y increasing down, with the
296    /// viewport scaled such that one coordinate unit is one pixel on the
297    /// screen.  This function lets you change this coordinate system to
298    /// be whatever you prefer.
299    ///
300    /// The `Rect`'s x and y will define the top-left corner of the screen,
301    /// and that plus its w and h will define the bottom-right corner.
302    #[inline]
303    pub fn set_screen_coordinates(&mut self, rect: Rect) {
304        self.set_projection(screen_to_mat(rect));
305        self.screen = Some(rect);
306    }
307
308    /// Returns the boudns of the screen viewport, iff the projection was last set with
309    /// `set_screen_coordinates`. If the last projection was set with `set_projection` or
310    /// `mul_projection`, `None` will be returned.
311    #[inline]
312    pub fn screen_coordinates(&self) -> Option<Rect> {
313        self.screen
314    }
315
316    /// Sets the scissor rectangle used when drawing. Nothing will be drawn to the canvas
317    /// that falls outside of this region.
318    ///
319    /// Note: The rectangle is in pixel coordinates, and therefore the values will be rounded towards zero.
320    #[inline]
321    pub fn set_scissor_rect(&mut self, rect: Rect) -> GameResult {
322        if rect.w as u32 == 0 || rect.h as u32 == 0 {
323            return Err(GameError::RenderError(String::from(
324                "the scissor rectangle size must be larger than zero.",
325            )));
326        }
327
328        let image_size = (self.target.width(), self.target.height());
329        if rect.x as u32 >= image_size.0 || rect.y as u32 >= image_size.1 {
330            return Err(GameError::RenderError(String::from(
331                "the scissor rectangle cannot start outside the canvas image.",
332            )));
333        }
334
335        // clamp the scissor rectangle to the target image size
336        let rect_width = u32::min(image_size.0 - rect.x as u32, rect.w as u32);
337        let rect_height = u32::min(image_size.1 - rect.y as u32, rect.h as u32);
338
339        self.state.scissor_rect = (rect.x as u32, rect.y as u32, rect_width, rect_height);
340
341        Ok(())
342    }
343
344    /// Returns the scissor rectangle as set by [`Canvas::set_scissor_rect`].
345    #[inline]
346    pub fn scissor_rect(&self) -> Rect {
347        Rect::new(
348            self.state.scissor_rect.0 as f32,
349            self.state.scissor_rect.1 as f32,
350            self.state.scissor_rect.2 as f32,
351            self.state.scissor_rect.3 as f32,
352        )
353    }
354
355    /// Resets the scissorr rectangle back to the original value. This will effectively disable any
356    /// scissoring.
357    #[inline]
358    pub fn set_default_scissor_rect(&mut self) {
359        self.state.scissor_rect = self.original_state.scissor_rect;
360    }
361
362    /// Draws the given `Drawable` to the canvas with a given `DrawParam`.
363    #[inline]
364    pub fn draw(&mut self, drawable: &impl Drawable, param: impl Into<DrawParam>) {
365        drawable.draw(self, param)
366    }
367
368    /// Draws a `Mesh` textured with an `Image`.
369    ///
370    /// This differs from `canvas.draw(mesh, param)` as in that case, the mesh is untextured.
371    pub fn draw_textured_mesh(&mut self, mesh: Mesh, image: Image, param: impl Into<DrawParam>) {
372        self.push_draw(
373            Draw::Mesh {
374                mesh,
375                image,
376                scale: false,
377            },
378            param.into(),
379        );
380    }
381
382    /// Draws an `InstanceArray` textured with a `Mesh`.
383    ///
384    /// This differs from `canvas.draw(instances, param)` as in that case, the instances are
385    /// drawn as quads.
386    pub fn draw_instanced_mesh(
387        &mut self,
388        mesh: Mesh,
389        instances: &InstanceArray,
390        param: impl Into<DrawParam>,
391    ) {
392        instances.flush_wgpu(&self.wgpu).unwrap(); // Will only fail if you can't lock the buffers shouldn't happen
393        self.push_draw(
394            Draw::MeshInstances {
395                mesh,
396                instances: InstanceArrayView::from_instances(instances).unwrap(),
397                scale: false,
398            },
399            param.into(),
400        );
401    }
402
403    /// Finish drawing with this canvas and submit all the draw calls.
404    #[inline]
405    pub fn finish(mut self, gfx: &mut impl HasMut<GraphicsContext>) -> GameResult {
406        let gfx = gfx.retrieve_mut();
407        self.finalize(gfx)
408    }
409
410    #[inline]
411    pub(crate) fn default_resources(&self) -> &DefaultResources {
412        &self.defaults
413    }
414
415    #[inline]
416    pub(crate) fn push_draw(&mut self, draw: Draw, param: DrawParam) {
417        self.draws.entry(param.z).or_default().push(DrawCommand {
418            state: self.state.clone(),
419            draw,
420            param,
421        });
422    }
423
424    fn finalize(&mut self, gfx: &mut GraphicsContext) -> GameResult {
425        let mut canvas = if let Some(resolve) = &self.resolve {
426            InternalCanvas::from_msaa(gfx, self.clear, &self.target, resolve)?
427        } else {
428            InternalCanvas::from_image(gfx, self.clear, &self.target)?
429        };
430
431        let mut state = self.state.clone();
432
433        // apply initial state
434        canvas.set_shader(state.shader.clone());
435        if let Some((bind_group, layout, offset)) = &state.params {
436            canvas.set_shader_params(bind_group.clone(), layout.clone(), *offset);
437        }
438
439        canvas.set_text_shader(state.text_shader.clone());
440        if let Some((bind_group, layout, offset)) = &state.text_params {
441            canvas.set_text_shader_params(bind_group.clone(), layout.clone(), *offset);
442        }
443
444        canvas.set_sampler(state.sampler);
445        canvas.set_blend_mode(state.blend_mode);
446        canvas.set_projection(state.projection);
447
448        if state.scissor_rect.2 > 0 && state.scissor_rect.3 > 0 {
449            canvas.set_scissor_rect(state.scissor_rect);
450        }
451
452        for draws in self.draws.values() {
453            for draw in draws {
454                // track state and apply to InternalCanvas if changed
455
456                if draw.state.shader != state.shader {
457                    canvas.set_shader(draw.state.shader.clone());
458                }
459
460                if draw.state.params != state.params {
461                    if let Some((bind_group, layout, offset)) = &draw.state.params {
462                        canvas.set_shader_params(bind_group.clone(), layout.clone(), *offset);
463                    }
464                }
465
466                if draw.state.text_shader != state.text_shader {
467                    canvas.set_text_shader(draw.state.text_shader.clone());
468                }
469
470                if draw.state.text_params != state.text_params {
471                    if let Some((bind_group, layout, offset)) = &draw.state.text_params {
472                        canvas.set_text_shader_params(bind_group.clone(), layout.clone(), *offset);
473                    }
474                }
475
476                if draw.state.sampler != state.sampler {
477                    canvas.set_sampler(draw.state.sampler);
478                }
479
480                if draw.state.blend_mode != state.blend_mode {
481                    canvas.set_blend_mode(draw.state.blend_mode);
482                }
483
484                if draw.state.premul_text != state.premul_text {
485                    canvas.set_premultiplied_text(draw.state.premul_text);
486                }
487
488                if draw.state.projection != state.projection {
489                    canvas.set_projection(draw.state.projection);
490                }
491
492                if draw.state.scissor_rect != state.scissor_rect {
493                    canvas.set_scissor_rect(draw.state.scissor_rect);
494                }
495
496                state = draw.state.clone();
497
498                match &draw.draw {
499                    Draw::Mesh { mesh, image, scale } => {
500                        canvas.draw_mesh(mesh, image, draw.param, *scale)
501                    }
502                    Draw::MeshInstances {
503                        mesh,
504                        instances,
505                        scale,
506                    } => canvas.draw_mesh_instances(mesh, instances, draw.param, *scale)?,
507                    Draw::BoundedText { text } => canvas.draw_bounded_text(text, draw.param)?,
508                }
509            }
510        }
511
512        canvas.finish();
513
514        Ok(())
515    }
516}
517
518#[derive(Debug, Clone)]
519struct DrawState {
520    shader: Shader,
521    params: Option<(ArcBindGroup, ArcBindGroupLayout, u32)>,
522    text_shader: Shader,
523    text_params: Option<(ArcBindGroup, ArcBindGroupLayout, u32)>,
524    sampler: Sampler,
525    blend_mode: BlendMode,
526    premul_text: bool,
527    projection: mint::ColumnMatrix4<f32>,
528    scissor_rect: (u32, u32, u32, u32),
529}
530
531#[derive(Debug)]
532pub(crate) enum Draw {
533    Mesh {
534        mesh: Mesh,
535        image: Image,
536        scale: bool,
537    },
538    MeshInstances {
539        mesh: Mesh,
540        instances: InstanceArrayView,
541        scale: bool,
542    },
543    BoundedText {
544        text: Text,
545    },
546}
547
548// Stores *everything* you need to know to draw something.
549#[derive(Debug)]
550struct DrawCommand {
551    state: DrawState,
552    param: DrawParam,
553    draw: Draw,
554}
555
556#[derive(Debug)]
557pub(crate) struct DefaultResources {
558    pub mesh: Mesh,
559    pub image: Image,
560}
561
562impl DefaultResources {
563    fn new(gfx: &GraphicsContext) -> Self {
564        let mesh = gfx.rect_mesh.clone();
565        let image = gfx.white_image.clone();
566
567        DefaultResources { mesh, image }
568    }
569}
570
571/// The default shader.
572pub fn default_shader() -> Shader {
573    Shader {
574        fs_module: None,
575        vs_module: None,
576    }
577}
578
579/// The default text shader.
580pub fn default_text_shader() -> Shader {
581    Shader {
582        fs_module: None,
583        vs_module: None,
584    }
585}