bevy_keith/
canvas.rs

1//! A canvas represents the drawing surface storing draw commands.
2//!
3//! To prepare to draw with 🐕 Bevy Keith, add a [`Canvas`] component to the
4//! same [`Entity`] as a [`Camera`]. Currently only 2D orthographic cameras are
5//! supported.
6//!
7//! In general, you don't need to interact directly with a [`Canvas`] to draw.
8//! Instead, the [`RenderContext`] exposes a more convenient interface on top of
9//! a specific [`Canvas`]. Simply retrieve the render context for an existing
10//! canvas and use it to enqueue draw commands.
11//!
12//! ```
13//! # use bevy_keith::*;
14//! # use bevy::{prelude::*, color::palettes::css::*};
15//! fn draw(mut query: Query<&mut Canvas>) {
16//!     let mut canvas = query.single_mut();
17//!     canvas.clear();
18//!     let mut ctx = canvas.render_context();
19//!     let brush = ctx.solid_brush(RED.into());
20//!     ctx.fill(Rect::from_center_size(Vec2::ZERO, Vec2::ONE), &brush);
21//! }
22//! ```
23//!
24//! At the end of each frame, the render commands stored in the [`Canvas`] are
25//! extracted into the render app and drawn. Then the command list is flushed.
26//! Commands are not reused from one frame to the other; you need to redraw each
27//! frame ("immediate-mode" style rendering).
28
29use std::mem::MaybeUninit;
30
31use bevy::{
32    asset::{AssetId, Assets, Handle},
33    color::Color,
34    ecs::{
35        component::Component,
36        entity::Entity,
37        query::{With, Without},
38        system::{Commands, Query, ResMut},
39    },
40    log::trace,
41    math::{bounding::Aabb2d, Rect, UVec2, Vec2, Vec3},
42    prelude::*,
43    render::{camera::Camera, texture::Image},
44    sprite::TextureAtlasLayout,
45    utils::default,
46    window::PrimaryWindow,
47};
48use bytemuck::{Pod, Zeroable};
49
50use crate::{
51    render::{ExtractedCanvas, ExtractedText, PreparedPrimitive},
52    render_context::{ImageScaling, RenderContext, TextLayout},
53    ShapeRef,
54};
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub(crate) struct PrimitiveInfo {
58    /// Row count per sub-primitive.
59    pub row_count: u32,
60    /// Number of sub-primitives.
61    pub sub_prim_count: u32,
62}
63
64/// Kind of primitives understood by the GPU shader.
65///
66/// Determines the shader path and the SDF function to use to render a
67/// primitive. Each primitive has a different shader encoding and
68/// functionalities.
69///
70/// # Note
71///
72/// The enum values must be kept in sync with the values inside the primitive
73/// shader.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[repr(u8)]
76pub enum GpuPrimitiveKind {
77    /// Axis-aligned rectangle, possibly textured.
78    Rect = 0,
79    /// Text glyph. Same as `Rect`, but samples from texture's alpha instead of
80    /// RGB, and is always textured.
81    Glyph = 1,
82    /// Line segment.
83    Line = 2,
84    /// Quarter pie.
85    QuarterPie = 3,
86}
87
88/// Drawing primitives.
89///
90/// The drawing primitives are the lowest-level concepts mapping directly to
91/// shader instructions. For the higher level shapes to draw on a [`Canvas`],
92/// see the [`shapes`] module instead.
93///
94/// [`shapes`]: crate::shapes
95#[derive(Debug, Clone, Copy)]
96pub enum Primitive {
97    /// A line between two points, with a color and thickness.
98    Line(LinePrimitive),
99    /// An axis-aligned rectangle with a color, optional rounded corners, and
100    /// optional texture.
101    Rect(RectPrimitive),
102    /// A text with a color.
103    Text(TextPrimitive),
104    QuarterPie(QuarterPiePrimitive),
105}
106
107impl Primitive {
108    /// Get the [`GpuPrimitiveKind`] of a primitive.
109    pub fn gpu_kind(&self) -> GpuPrimitiveKind {
110        match self {
111            Primitive::Line(_) => GpuPrimitiveKind::Line,
112            Primitive::Rect(_) => GpuPrimitiveKind::Rect,
113            Primitive::Text(_) => GpuPrimitiveKind::Glyph,
114            Primitive::QuarterPie(_) => GpuPrimitiveKind::QuarterPie,
115        }
116    }
117
118    /// Get the AABB of a primitive.
119    ///
120    /// This is mainly used internally for tiling. There's no guarantee that the
121    /// AABB is tightly fitting; instead it only needs to be conservative and
122    /// enclose all the primitive.
123    pub fn aabb(&self) -> Aabb2d {
124        match self {
125            Primitive::Line(l) => l.aabb(),
126            Primitive::Rect(r) => r.aabb(),
127            Primitive::Text(_) => panic!("Cannot compute text AABB intrinsically."),
128            Primitive::QuarterPie(q) => q.aabb(),
129        }
130    }
131
132    /// Is the primitive textured?
133    pub fn is_textured(&self) -> bool {
134        match self {
135            Primitive::Line(_) => false,
136            Primitive::Rect(r) => r.is_textured(),
137            Primitive::Text(_) => false, // not in the sense of regular texture mapping
138            Primitive::QuarterPie(_) => false,
139        }
140    }
141
142    /// Is the primitive bordered?
143    pub fn is_bordered(&self) -> bool {
144        match self {
145            Primitive::Line(l) => l.is_bordered(),
146            Primitive::Rect(r) => r.is_bordered(),
147            Primitive::Text(_) => false,
148            Primitive::QuarterPie(_) => false,
149        }
150    }
151
152    /// Internal primitive info for drawing a primitive.
153    pub(crate) fn info(&self, texts: &[ExtractedText]) -> PrimitiveInfo {
154        match &self {
155            Primitive::Line(l) => l.info(),
156            Primitive::Rect(r) => r.info(),
157            Primitive::Text(t) => t.info(texts),
158            Primitive::QuarterPie(q) => q.info(),
159        }
160    }
161
162    /// Serialize a primitive and write its binary blob into the given buffer,
163    /// ready to be consumed by the GPU shader.
164    ///
165    /// Anything written here must be kept in sync format-wise with what is read
166    /// back in the shader.
167    pub(crate) fn write(
168        &self,
169        texts: &[ExtractedText],
170        prim: &mut [MaybeUninit<f32>],
171        canvas_translation: Vec2,
172        scale_factor: f32,
173    ) {
174        match &self {
175            Primitive::Line(l) => l.write(prim, canvas_translation, scale_factor),
176            Primitive::Rect(r) => r.write(prim, canvas_translation, scale_factor),
177            Primitive::Text(t) => t.write(texts, prim, canvas_translation, scale_factor),
178            Primitive::QuarterPie(q) => q.write(prim, canvas_translation, scale_factor),
179        };
180    }
181}
182
183impl From<LinePrimitive> for Primitive {
184    fn from(line: LinePrimitive) -> Self {
185        Self::Line(line)
186    }
187}
188
189impl From<RectPrimitive> for Primitive {
190    fn from(rect: RectPrimitive) -> Self {
191        Self::Rect(rect)
192    }
193}
194
195impl From<TextPrimitive> for Primitive {
196    fn from(text: TextPrimitive) -> Self {
197        Self::Text(text)
198    }
199}
200
201impl From<QuarterPiePrimitive> for Primitive {
202    fn from(qpie: QuarterPiePrimitive) -> Self {
203        Self::QuarterPie(qpie)
204    }
205}
206
207/// A line between two points, with a color and thickness.
208///
209/// This is essentially an oriented rectangle.
210#[derive(Debug, Default, Clone, Copy)]
211pub struct LinePrimitive {
212    /// The starting point of the line.
213    pub start: Vec2,
214    /// The ending point of the line.
215    pub end: Vec2,
216    /// The line color.
217    pub color: Color,
218    /// The line thickness. Must be greater than zero.
219    ///
220    /// The line shape extends equally by `thickness / 2.` on both sides of the
221    /// mathematical (infinitely thin) line joining the start and end points.
222    pub thickness: f32,
223    /// Size of the border, if any, or zero if no border. The borders always
224    /// expand inside the line. Negative values or zero mean no border.
225    pub border_width: f32,
226    /// Border color, if any (ignored if `border_width <= 0.`).
227    pub border_color: Color,
228}
229
230impl LinePrimitive {
231    /// The AABB of the line primitive.
232    pub fn aabb(&self) -> Aabb2d {
233        let dir = (self.end - self.start).normalize();
234        let tg = Vec2::new(-dir.y, dir.x);
235        let e = self.thickness / 2.;
236        let p0 = self.start + tg * e;
237        let p1 = self.start - tg * e;
238        let p2 = self.end + tg * e;
239        let p3 = self.end - tg * e;
240        let min = p0.min(p1).min(p2).min(p3);
241        let max = p0.max(p1).max(p2).max(p3);
242        Aabb2d { min, max }
243    }
244
245    /// Is the primitive bordered?
246    pub fn is_bordered(&self) -> bool {
247        self.border_width > 0.
248    }
249
250    fn info(&self) -> PrimitiveInfo {
251        PrimitiveInfo {
252            row_count: 6 + if self.is_bordered() { 2 } else { 0 },
253            sub_prim_count: 1,
254        }
255    }
256
257    fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
258        prim[0].write((self.start.x + canvas_translation.x) * scale_factor);
259        prim[1].write((self.start.y + canvas_translation.y) * scale_factor);
260        prim[2].write((self.end.x + canvas_translation.x) * scale_factor);
261        prim[3].write((self.end.y + canvas_translation.y) * scale_factor);
262        prim[4].write(bytemuck::cast(self.color.to_linear().as_u32()));
263        prim[5].write(self.thickness * scale_factor);
264        if self.is_bordered() {
265            assert_eq!(8, prim.len());
266            prim[6].write(self.border_width * scale_factor);
267            prim[7].write(bytemuck::cast(self.border_color.to_linear().as_u32()));
268        } else {
269            assert_eq!(6, prim.len());
270        }
271    }
272}
273
274/// An axis-aligned rectangle with a color, optional rounded corners, and
275/// optional texture.
276#[derive(Debug, Default, Clone, Copy)]
277pub struct RectPrimitive {
278    /// Position and size of the rectangle in its canvas space.
279    ///
280    /// For rounded rectangles, this is the AABB (the radius and borders are
281    /// included).
282    pub rect: Rect,
283    /// Rounded corners radius. Set to zero to disable rounded corners.
284    pub radius: f32,
285    /// Uniform rectangle color.
286    pub color: Color,
287    /// Optional handle to the image used for texturing the rectangle.
288    pub image: Option<AssetId<Image>>,
289    /// Image size, populated from actual texture size and scaling.
290    pub image_size: Vec2,
291    /// Scaling for the image (if any).
292    pub image_scaling: ImageScaling,
293    /// Flip the image (if any) along the horizontal axis.
294    pub flip_x: bool,
295    /// Flip the image (if any) along the vertical axis.
296    pub flip_y: bool,
297    /// Size of the border, if any, or zero if no border. The borders always
298    /// expand inside the rectangle. Negative values or zero mean no border.
299    pub border_width: f32,
300    /// Border color, if any (ignored if `border_width <= 0.`).
301    pub border_color: Color,
302}
303
304impl RectPrimitive {
305    /// Number of primitive buffer rows (4 bytes) per primitive.
306    const ROW_COUNT_BASE: u32 = 6;
307    /// Number of extra primitive buffer rows (4 bytes) per primitive to add
308    /// when textured. Those extra rows follow the base ones.
309    const ROW_COUNT_TEX: u32 = 4;
310    /// Number of extra primitive buffer rows (4 bytes) per primitive to add
311    /// when bordered. Those extra rows follow the texture ones, or the base
312    /// ones if there's no texture.
313    const ROW_COUNT_BORDER: u32 = 2;
314
315    /// Get the AABB of this rectangle.
316    pub fn aabb(&self) -> Aabb2d {
317        Aabb2d {
318            min: self.rect.min,
319            max: self.rect.max,
320        }
321    }
322
323    /// Is this primitive textured?
324    ///
325    /// True if [`RectPrimitive::image`] is `Some`.
326    pub const fn is_textured(&self) -> bool {
327        self.image.is_some()
328    }
329
330    /// Is the primitive bordered?
331    pub fn is_bordered(&self) -> bool {
332        self.border_width > 0.
333    }
334
335    #[inline]
336    fn row_count(&self) -> u32 {
337        let mut rows = Self::ROW_COUNT_BASE;
338        if self.is_textured() {
339            rows += Self::ROW_COUNT_TEX;
340        }
341        if self.is_bordered() {
342            rows += Self::ROW_COUNT_BORDER;
343        }
344        rows
345    }
346
347    fn info(&self) -> PrimitiveInfo {
348        PrimitiveInfo {
349            row_count: self.row_count(),
350            sub_prim_count: 1,
351        }
352    }
353
354    fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
355        assert_eq!(
356            self.row_count() as usize,
357            prim.len(),
358            "Invalid buffer size {} to write RectPrimitive (needs {})",
359            prim.len(),
360            self.row_count()
361        );
362
363        let half_min = self.rect.min * (0.5 * scale_factor);
364        let half_max = self.rect.max * (0.5 * scale_factor);
365        let center = half_min + half_max + canvas_translation * scale_factor;
366        let half_size = half_max - half_min;
367        prim[0].write(center.x);
368        prim[1].write(center.y);
369        prim[2].write(half_size.x);
370        prim[3].write(half_size.y);
371        prim[4].write(self.radius * scale_factor);
372        prim[5].write(bytemuck::cast(self.color.to_linear().as_u32()));
373        let mut idx = 6;
374        if self.is_textured() {
375            prim[idx + 0].write(0.5);
376            prim[idx + 1].write(0.5);
377            prim[idx + 2].write(1. / self.image_size.x);
378            prim[idx + 3].write(1. / self.image_size.y);
379            idx += 4;
380        }
381        if self.is_bordered() {
382            prim[idx + 0].write(self.border_width * scale_factor);
383            prim[idx + 1].write(bytemuck::cast(self.border_color.to_linear().as_u32()));
384        }
385    }
386}
387
388/// A reference to a text with a color.
389///
390/// The text primitive is not stored directly inside this struct. Instead, the
391/// struct stores an [`id`] field indexing the text into its [`Canvas`]. This
392/// extra indirection allows storing all texts together for convenience, as they
393/// require extra pre-processing compared to other primitives.
394///
395/// [`id`]: crate::canvas::TextPrimitive::id
396#[derive(Debug, Clone, Copy)]
397pub struct TextPrimitive {
398    /// Unique ID of the text inside its owner [`Canvas`].
399    pub id: u32,
400    /// TODO - Vec2 instead?
401    pub rect: Rect,
402}
403
404impl TextPrimitive {
405    /// Number of elements used by each single glyph in the primitive element
406    /// buffer.
407    pub const ROW_PER_GLYPH: u32 = RectPrimitive::ROW_COUNT_BASE + RectPrimitive::ROW_COUNT_TEX;
408
409    /// Get the AABB of this text.
410    pub fn aabb(&self, canvas: &ExtractedCanvas) -> Aabb2d {
411        let text = &canvas.texts[self.id as usize];
412        let mut aabb = Aabb2d {
413            min: self.rect.min,
414            max: self.rect.max,
415        };
416        trace!("Text #{:?} aabb={:?}", self.id, aabb);
417        for glyph in &text.glyphs {
418            aabb.min = aabb.min.min(self.rect.min + glyph.offset);
419            aabb.max = aabb.max.max(self.rect.min + glyph.offset + glyph.size);
420            trace!(
421                "  > add glyph offset={:?} size={:?}, new aabb {:?}",
422                glyph.offset,
423                glyph.size,
424                aabb
425            );
426        }
427        aabb
428    }
429
430    fn info(&self, texts: &[ExtractedText]) -> PrimitiveInfo {
431        let index = self.id as usize;
432        if index < texts.len() {
433            let glyph_count = texts[index].glyphs.len() as u32;
434            PrimitiveInfo {
435                row_count: Self::ROW_PER_GLYPH,
436                sub_prim_count: glyph_count,
437            }
438        } else {
439            PrimitiveInfo {
440                row_count: 0,
441                sub_prim_count: 0,
442            }
443        }
444    }
445
446    fn write(
447        &self,
448        texts: &[ExtractedText],
449        prim: &mut [MaybeUninit<f32>],
450        canvas_translation: Vec2,
451        scale_factor: f32,
452    ) {
453        let index = self.id as usize;
454        let glyphs = &texts[index].glyphs;
455        let glyph_count = glyphs.len();
456        assert_eq!(glyph_count * Self::ROW_PER_GLYPH as usize, prim.len());
457        let mut ip = 0;
458        //let inv_scale_factor = 1. / scale_factor;
459        for i in 0..glyph_count {
460            let x = glyphs[i].offset.x + (self.rect.min.x + canvas_translation.x) * scale_factor;
461            let y = glyphs[i].offset.y + (self.rect.min.y + canvas_translation.y) * scale_factor;
462            let hw = glyphs[i].size.x / 2.0;
463            let hh = glyphs[i].size.y / 2.0;
464
465            // let x = x * inv_scale_factor;
466            // let y = y * inv_scale_factor;
467            // let hw = hw * inv_scale_factor;
468            // let hh = hh * inv_scale_factor;
469
470            // Glyph position is center of rect, we need bottom-left corner
471            //let x = x - w / 2.;
472            //let y = y - h / 2.;
473
474            // FIXME - hard-coded texture size
475            let uv_x = glyphs[i].uv_rect.min.x / 1024.0;
476            let uv_y = glyphs[i].uv_rect.min.y / 1024.0;
477            let uv_w = glyphs[i].uv_rect.max.x / 1024.0 - uv_x;
478            let uv_h = glyphs[i].uv_rect.max.y / 1024.0 - uv_y;
479
480            // Glyph UV is flipped vertically
481            // let uv_y = uv_y + uv_h;
482            // let uv_h = -uv_h;
483
484            // center pos
485            // we round() here to work around a bug: if the pixel rect is not aligned on the
486            // screen pixel grid, the UV coordinates may end up being < 0.5 or >
487            // w + 0.5, which then bleeds into adjacent pixels. it looks like
488            // the rasterizing of the glyphs already adds 1 pixel border, so we should
489            // remove that border in the SDF rect, so that we never sample the
490            // texture beyond half that 1 px border, which would linearly blend
491            // with the next pixel (outside the glyph rect).
492            prim[ip + 0].write(x.round() + hw);
493            prim[ip + 1].write(y.round() + hh);
494
495            // half size
496            prim[ip + 2].write(hw);
497            prim[ip + 3].write(hh);
498
499            // radius
500            prim[ip + 4].write(0.);
501
502            // color
503            prim[ip + 5].write(bytemuck::cast(glyphs[i].color));
504
505            // uv_offset (at center pos)
506            prim[ip + 6].write(uv_x + uv_w / 2.0);
507            prim[ip + 7].write(uv_y + uv_h / 2.0);
508
509            // uv_scale
510            prim[ip + 8].write(1.0 / 1024.0);
511            prim[ip + 9].write(1.0 / 1024.0);
512
513            ip += Self::ROW_PER_GLYPH as usize;
514        }
515    }
516}
517
518#[derive(Debug, Clone, Copy)]
519pub struct QuarterPiePrimitive {
520    /// Origin of the pie.
521    pub origin: Vec2,
522    /// Radii of the (elliptical) pie.
523    pub radii: Vec2,
524    /// Uniform rectangle color.
525    pub color: Color,
526    /// Flip the quarter pie along the horizontal axis.
527    pub flip_x: bool,
528    /// Flip the quarter pie along the vertical axis.
529    pub flip_y: bool,
530}
531
532impl Default for QuarterPiePrimitive {
533    fn default() -> Self {
534        Self {
535            origin: Vec2::ZERO,
536            radii: Vec2::ONE,
537            color: Color::default(),
538            flip_x: false,
539            flip_y: false,
540        }
541    }
542}
543
544impl QuarterPiePrimitive {
545    /// Number of primitive buffer rows (4 bytes) per primitive.
546    const ROW_COUNT: u32 = 5;
547
548    pub fn aabb(&self) -> Aabb2d {
549        Aabb2d {
550            min: self.origin - self.radii,
551            max: self.origin + self.radii,
552        }
553    }
554
555    /// The pie center.
556    pub fn center(&self) -> Vec3 {
557        self.origin.extend(0.)
558    }
559
560    #[inline]
561    const fn row_count(&self) -> u32 {
562        Self::ROW_COUNT
563    }
564
565    fn info(&self) -> PrimitiveInfo {
566        PrimitiveInfo {
567            row_count: self.row_count(),
568            sub_prim_count: 1,
569        }
570    }
571
572    fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
573        assert_eq!(self.row_count() as usize, prim.len());
574        let radii_mask = BVec2::new(self.flip_x, self.flip_y);
575        let signed_radii = Vec2::select(radii_mask, -self.radii, self.radii);
576        prim[0].write((self.origin.x + canvas_translation.x) * scale_factor);
577        prim[1].write((self.origin.y + canvas_translation.y) * scale_factor);
578        prim[2].write(signed_radii.x * scale_factor);
579        prim[3].write(signed_radii.y * scale_factor);
580        prim[4].write(bytemuck::cast(self.color.to_linear().as_u32()));
581    }
582}
583
584/// Drawing surface for 2D graphics.
585///
586/// This component should attached to the same entity as a [`Camera`] and an
587/// [`OrthographicProjection`].
588///
589/// By default the dimensions of the canvas are automatically computed and
590/// updated based on that projection.
591#[derive(Component)]
592pub struct Canvas {
593    /// The canvas dimensions relative to its origin.
594    ///
595    /// Currently ignored.
596    rect: Rect,
597    /// Optional background color to clear the canvas with.
598    ///
599    /// This only has an effect starting from the next [`clear()`] call. If a
600    /// background color is set, it's used to clear the canvas each frame.
601    /// Otherwise, the canvas retains its default transparent black color (0.0,
602    /// 0.0, 0.0, 0.0).
603    ///
604    /// [`clear()`]: crate::Canvas::clear
605    pub background_color: Option<Color>,
606    /// Collection of drawn primitives.
607    primitives: Vec<Primitive>,
608    /// Collection of allocated texts.
609    pub(crate) text_layouts: Vec<TextLayout>,
610    /// Atlas layout. Needs to be a separate asset resource due to Bevy's API
611    /// only.
612    pub(crate) atlas_layout: Handle<TextureAtlasLayout>,
613}
614
615impl Default for Canvas {
616    fn default() -> Self {
617        Self {
618            rect: Rect::default(),
619            background_color: None,
620            primitives: vec![],
621            text_layouts: vec![],
622            atlas_layout: Handle::default(),
623        }
624    }
625}
626
627impl Canvas {
628    /// Create a new canvas with given dimensions.
629    ///
630    /// FIXME - Currently the rectangle is ignored; all canvases are
631    /// full-screen.
632    pub fn new(rect: Rect) -> Self {
633        Self { rect, ..default() }
634    }
635
636    /// Change the dimensions of the canvas.
637    ///
638    /// This is called automatically if the [`Canvas`] is on the same entity as
639    /// an [`OrthographicProjection`].
640    pub fn set_rect(&mut self, rect: Rect) {
641        // if let Some(color) = self.background_color {
642        //     if self.rect != rect {
643        //         TODO - clear new area if any? or resize the clear() rect?!
644        //     }
645        // }
646        self.rect = rect;
647    }
648
649    /// Get the dimensions of the canvas relative to its origin.
650    ///
651    /// FIXME - Currently this is always [`OrthographicProjection::area`].
652    pub fn rect(&self) -> Rect {
653        self.rect
654    }
655
656    /// Clear the canvas, discarding all primitives previously drawn on it.
657    ///
658    /// If the canvas has a [`background_color`], this clears the canvas to that
659    /// color.
660    ///
661    /// [`background_color`]: Canvas::background_color
662    pub fn clear(&mut self) {
663        self.primitives.clear();
664        self.text_layouts.clear(); // FIXME - really?
665
666        if let Some(color) = self.background_color {
667            self.draw(RectPrimitive {
668                rect: self.rect,
669                color,
670                ..default()
671            });
672        }
673    }
674
675    /// Draw a new primitive onto the canvas.
676    ///
677    /// This is a lower level entry point to canvas drawing; in general, you
678    /// should prefer acquiring a [`RenderContext`] via [`render_context()`]
679    /// and using it to draw primitives.
680    ///
681    /// [`render_context()`]: crate::canvas::Canvas::render_context
682    #[inline]
683    pub fn draw<'a>(&'a mut self, prim: impl Into<Primitive>) -> ShapeRef<'a> {
684        let prim = prim.into();
685        self.primitives.push(prim);
686        let sref = ShapeRef {
687            prim: self.primitives.last_mut().unwrap(),
688        };
689        sref
690    }
691
692    /// Acquire a new render context to draw on this canvas.
693    pub fn render_context(&mut self) -> RenderContext {
694        RenderContext::new(self)
695    }
696
697    pub(crate) fn finish(&mut self) {
698        //
699    }
700
701    pub(crate) fn finish_layout(&mut self, mut layout: TextLayout) -> u32 {
702        let id = self.text_layouts.len() as u32;
703        trace!("finish_layout() for text #{}", id);
704        layout.id = id;
705        self.text_layouts.push(layout);
706        id
707    }
708
709    // Workaround for Extract phase without mut access to MainWorld Canvas
710    pub(crate) fn buffer(&self) -> &Vec<Primitive> {
711        &self.primitives
712    }
713
714    pub(crate) fn text_layouts(&self) -> &[TextLayout] {
715        &self.text_layouts[..]
716    }
717
718    pub(crate) fn text_layouts_mut(&mut self) -> &mut [TextLayout] {
719        &mut self.text_layouts[..]
720    }
721
722    pub(crate) fn has_text(&self) -> bool {
723        !self.text_layouts.is_empty()
724    }
725}
726
727/// Update the dimensions of any [`Canvas`] component attached to the same
728/// entity as as an [`OrthographicProjection`] component.
729///
730/// This runs in the [`PreUpdate`] schedule.
731///
732/// [`PreUpdate`]: bevy::app::PreUpdate
733pub fn update_canvas_from_ortho_camera(mut query: Query<(&mut Canvas, &OrthographicProjection)>) {
734    trace!("PreUpdate: update_canvas_from_ortho_camera()");
735    for (mut canvas, ortho) in query.iter_mut() {
736        trace!("ortho canvas rect = {:?}", ortho.area);
737        canvas.set_rect(ortho.area);
738    }
739}
740
741/// Configuration for tile-based rendering.
742///
743/// Currently unused.
744#[derive(Default, Clone, Copy, Component)]
745pub struct TileConfig {}
746
747#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)]
748#[repr(C)]
749pub(crate) struct OffsetAndCount {
750    /// Base index into [`Tiles::primitives`].
751    pub offset: u32,
752    /// Number of consecutive primitive offsets in [`Tiles::primitives`].
753    pub count: u32,
754}
755
756/// Packed primitive index and extra data.
757///
758/// Contains a primitive index packed inside a `u32` alongside other bits
759/// necessary to drive the shader code:
760/// - Index of the first row in the primitive buffer.
761/// - Kind of primitive.
762/// - Is the primitive textured?
763/// - Is the primitive bordered (has a border)?
764#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
765#[repr(transparent)]
766pub(crate) struct PackedPrimitiveIndex(pub u32);
767
768impl PackedPrimitiveIndex {
769    /// Create a new packed index from individual values.
770    pub fn new(index: u32, kind: GpuPrimitiveKind, textured: bool, bordered: bool) -> Self {
771        let textured = (textured as u32) << 31;
772        let bordered = (bordered as u32) << 27;
773        let value = (index & 0x07FF_FFFF) | (kind as u32) << 28 | textured | bordered;
774        Self(value)
775    }
776}
777
778#[derive(Clone, Copy)]
779struct AssignedTile {
780    pub tile_index: i32,
781    pub prim_index: PackedPrimitiveIndex,
782}
783
784/// Component storing per-tile draw data.
785///
786/// This component is automatically added to any [`Camera`] and [`Canvas`]
787/// component pair missing it. It stores per-tile runtime data for rendering the
788/// canvas primitives. Most users can ignore it entirely.
789#[derive(Default, Clone, Component)]
790pub struct Tiles {
791    /// Tile size, in pixels. Currently hard-coded to 8x8 pixels.
792    pub(crate) tile_size: UVec2,
793    /// Dimensions of the canvas, in number of tiles.
794    ///
795    /// 4K, 8x8 => 129'600 tiles
796    /// 1080p, 8x8 => 32'400 tiles
797    pub(crate) dimensions: UVec2,
798    /// Flattened list of primitive indices for each tile. The start of a tile
799    /// is at element [`OffsetAndCount::offset`], and the tile contains
800    /// [`OffsetAndCount::count`] consecutive primitive offsets, each offset
801    /// being the start of the primitive into the primitive buffer of the
802    /// canvas.
803    pub(crate) primitives: Vec<PackedPrimitiveIndex>,
804    /// Offset and count of primitives per tile, into [`Tiles::primitives`].
805    pub(crate) offset_and_count: Vec<OffsetAndCount>,
806    /// Local cache saved frame-to-frame to avoid allocations.
807    assigned_tiles: Vec<AssignedTile>,
808}
809
810impl Tiles {
811    /// Update the tile data based on the current screen (canvas) size.
812    ///
813    /// This recalculates the dimensions of the various buffers and reallocate
814    /// them, to prepare for tiled drawing.
815    pub fn update_size(&mut self, screen_size: UVec2) {
816        // We force a 8x8 pixel tile, which works well with 32- and 64- waves.
817        self.tile_size = UVec2::new(8, 8);
818
819        self.dimensions = (screen_size.as_vec2() / self.tile_size.as_vec2())
820            .ceil()
821            .as_uvec2();
822
823        assert!(self.dimensions.x * self.tile_size.x >= screen_size.x);
824        assert!(self.dimensions.y * self.tile_size.y >= screen_size.y);
825
826        self.primitives.clear();
827        self.offset_and_count.clear();
828        self.offset_and_count
829            .reserve(self.dimensions.x as usize * self.dimensions.y as usize);
830
831        trace!(
832            "Resized Tiles at tile_size={:?} dim={:?} and cleared buffers",
833            self.tile_size,
834            self.dimensions
835        );
836    }
837
838    /// Assign the given primitives to tiles.
839    ///
840    /// This performs the actual binning of primitives into one or more tiles.
841    /// This assumes the various tile buffers are appropriately sized and
842    /// allocated by a previous call to [`update_size()`].
843    ///
844    /// [`update_size()`]: crate::canvas::Tiles::update_size
845    pub(crate) fn assign_to_tiles(&mut self, primitives: &[PreparedPrimitive], screen_size: Vec2) {
846        let tile_size = self.tile_size.as_vec2();
847
848        let oc_extra = self.dimensions.x as usize * self.dimensions.y as usize;
849        self.offset_and_count.reserve(oc_extra);
850
851        // Some semi-random guesswork of average tile overlapping count per primitive,
852        // so we don't start from a stupidly small allocation.
853        self.assigned_tiles.reserve(primitives.len() * 4);
854
855        // Loop over primitives and find tiles they overlap
856        for prim in primitives {
857            // Calculate bounds in terms of tile indices, clamped to the size of the screen
858            let uv_min = (prim.aabb.min.clamp(Vec2::ZERO, screen_size) / tile_size)
859                .floor()
860                .as_ivec2();
861            let mut uv_max = (prim.aabb.max.clamp(Vec2::ZERO, screen_size) / tile_size)
862                .ceil()
863                .as_ivec2();
864            if prim.aabb.max.x == tile_size.x * uv_max.x as f32 {
865                // We ignore tiles which only have a shared edge and no actualy surface overlap
866                uv_max.x -= 1;
867            }
868            if prim.aabb.max.y == tile_size.y * uv_max.y as f32 {
869                // We ignore tiles which only have a shared edge and no actualy surface overlap
870                uv_max.y -= 1;
871            }
872
873            self.assigned_tiles
874                .reserve((uv_max.y - uv_min.y + 1) as usize * (uv_max.x - uv_min.x + 1) as usize);
875
876            // Loop on tiles overlapping this primitive. This is generally only a handful,
877            // unless the primitive covers a large part of the screen.
878            for ty in uv_min.y..=uv_max.y {
879                let base_tile_index = ty * self.dimensions.x as i32;
880                for tx in uv_min.x..=uv_max.x {
881                    let tile_index = base_tile_index + tx;
882                    self.assigned_tiles.push(AssignedTile {
883                        tile_index,
884                        prim_index: prim.prim_index,
885                    });
886                }
887            }
888        }
889
890        // Sort the primitive<->tile mapping by tile index. Note that the sort MUST BE
891        // STABLE, to preserve the order of primitives, which preserves what is drawn on
892        // top of what.
893        self.assigned_tiles.sort_by_key(|at| at.tile_index);
894
895        // Build the offset and count list
896        self.primitives.reserve(self.assigned_tiles.len());
897        let mut ti = -1;
898        let mut offset = 0;
899        let mut count = 0;
900        for at in &self.assigned_tiles {
901            if at.tile_index != ti {
902                if count > 0 {
903                    // Write previous tile
904                    self.offset_and_count.push(OffsetAndCount {
905                        offset: offset as u32,
906                        count,
907                    });
908                }
909                // Write empty tile(s)
910                for _ in ti + 1..at.tile_index {
911                    self.offset_and_count.push(OffsetAndCount {
912                        offset: offset as u32,
913                        count: 0,
914                    });
915                }
916                offset = self.primitives.len() as u32;
917                count = 0;
918                ti = at.tile_index;
919            }
920
921            self.primitives.push(at.prim_index);
922            count += 1;
923        }
924        // Write last pending tile
925        if count > 0 {
926            self.offset_and_count.push(OffsetAndCount {
927                offset: offset as u32,
928                count,
929            });
930        }
931        // Write empty tile(s) at the end
932        for _ in ti + 1..oc_extra as i32 {
933            self.offset_and_count.push(OffsetAndCount {
934                offset: offset as u32,
935                count: 0,
936            });
937        }
938
939        // Clear scratch buffer for next call
940        self.assigned_tiles.clear();
941    }
942}
943
944/// Ensure any active [`Camera`] component with a [`Canvas`] component also has
945/// associated [`TileConfig`] and [`Tiles`] components.
946pub fn spawn_missing_tiles_components(
947    mut commands: Commands,
948    cameras: Query<(Entity, Option<&TileConfig>, &Camera), (With<Canvas>, Without<Tiles>)>,
949) {
950    for (entity, config, camera) in &cameras {
951        if !camera.is_active {
952            continue;
953        }
954
955        let config = config.copied().unwrap_or_default();
956        commands.entity(entity).insert((Tiles::default(), config));
957    }
958}
959
960pub fn resize_tiles_to_camera_render_target(
961    mut views: Query<(&Camera, &TileConfig, &mut Tiles), With<Canvas>>,
962) {
963    // Loop on all camera views
964    for (camera, _tile_config, tiles) in &mut views {
965        let Some(screen_size) = camera.physical_viewport_size() else {
966            continue;
967        };
968
969        // Resize tile storage to fit the viewport size
970        let tiles = tiles.into_inner();
971        tiles.update_size(screen_size);
972    }
973}
974
975pub fn allocate_atlas_layouts(
976    mut query: Query<&mut Canvas>,
977    mut layouts: ResMut<Assets<TextureAtlasLayout>>,
978) {
979    for mut canvas in query.iter_mut() {
980        // FIXME
981        let size = UVec2::splat(1024);
982
983        // FIXME - also check for resize...
984        if canvas.atlas_layout == Handle::<TextureAtlasLayout>::default() {
985            canvas.atlas_layout = layouts.add(TextureAtlasLayout::new_empty(size));
986        }
987    }
988}
989
990/// Calculate the width of a fixed-aspect rectangle given a content height.
991fn aspect_width(size: Vec2, content_height: f32) -> f32 {
992    size.x.max(0.) / size.y.max(1.) * content_height.max(0.)
993}
994
995/// Calculate the size of a rectangle such that its width fits in the given
996/// content, and its height either stretches to that content or it keeps its
997/// aspect ratio (and therefore gets clipped).
998fn fit_width(size: Vec2, content_size: Vec2, stretch_height: bool) -> Vec2 {
999    Vec2::new(
1000        content_size.x,
1001        if stretch_height {
1002            content_size.y
1003        } else {
1004            aspect_height(size, content_size.x)
1005        },
1006    )
1007}
1008
1009/// Calculate the height of a fixed-aspect rectangle given a content width.
1010fn aspect_height(size: Vec2, content_width: f32) -> f32 {
1011    size.y.max(0.) / size.x.max(1.) * content_width.max(0.)
1012}
1013
1014/// Calculate the size of a rectangle such that its height fits in the given
1015/// content, and its width either stretches to that content or it keeps its
1016/// aspect ratio (and therefore gets clipped).
1017fn fit_height(size: Vec2, content_size: Vec2, stretch_width: bool) -> Vec2 {
1018    Vec2::new(
1019        if stretch_width {
1020            content_size.x
1021        } else {
1022            aspect_width(size, content_size.y)
1023        },
1024        content_size.y,
1025    )
1026}
1027
1028/// Calculate the size of a rectangle such that both its width and height fit in
1029/// the given content, and the other direction either stretches to that content
1030/// or it keeps its aspect ratio. This ensures the returned rectangle covers the
1031/// content, possibly clipping in one direction to do so.
1032fn fit_any(size: Vec2, content_size: Vec2, stretch_other: bool) -> Vec2 {
1033    let aspect = size.x.max(0.) / size.y.max(1.);
1034    let content_aspect = content_size.x.max(0.) / content_size.y.max(1.);
1035    if aspect >= content_aspect {
1036        fit_height(size, content_size, stretch_other)
1037    } else {
1038        fit_width(size, content_size, stretch_other)
1039    }
1040}
1041
1042/// Process all images drawn onto all canvases.
1043///
1044/// This calculates the proper image size given the content rectangle size and
1045/// the window scale factor, applying any image scaling as specified during the
1046/// draw call.
1047pub fn process_images(
1048    images: Res<Assets<Image>>,
1049    q_window: Query<&Window, With<PrimaryWindow>>,
1050    mut q_canvas: Query<&mut Canvas>,
1051) {
1052    // TODO - handle multi-window
1053    let Ok(primary_window) = q_window.get_single() else {
1054        return;
1055    };
1056    let scale_factor = primary_window.scale_factor() as f32;
1057
1058    for mut canvas in q_canvas.iter_mut() {
1059        for prim in &mut canvas.primitives {
1060            let Primitive::Rect(rect) = prim else {
1061                continue;
1062            };
1063            let Some(id) = rect.image else {
1064                continue;
1065            };
1066            if let Some(image) = images.get(id) {
1067                let image_size = Vec2::new(
1068                    image.texture_descriptor.size.width as f32,
1069                    image.texture_descriptor.size.height as f32,
1070                );
1071                let content_size = rect.rect.size() * scale_factor;
1072                rect.image_size = match rect.image_scaling {
1073                    ImageScaling::Uniform(ratio) => image_size * ratio,
1074                    ImageScaling::FitWidth(stretch_height) => {
1075                        fit_width(image_size, content_size, stretch_height)
1076                    }
1077                    ImageScaling::FitHeight(stretch_width) => {
1078                        fit_height(image_size, content_size, stretch_width)
1079                    }
1080                    ImageScaling::Fit(stretch_other) => {
1081                        fit_any(image_size, content_size, stretch_other)
1082                    }
1083                    ImageScaling::Stretch => content_size,
1084                }
1085            } else {
1086                warn!("Unknown image asset ID {:?}; skipped.", id);
1087                rect.image = None;
1088            }
1089        }
1090    }
1091}
1092
1093#[cfg(test)]
1094mod tests {
1095    use super::*;
1096
1097    #[test]
1098    fn tiles() {
1099        let mut tiles = Tiles::default();
1100        tiles.update_size(UVec2::new(32, 64));
1101        assert_eq!(tiles.dimensions, UVec2::new(4, 8));
1102        assert!(tiles.primitives.is_empty());
1103        assert!(tiles.offset_and_count.is_empty());
1104        assert_eq!(tiles.offset_and_count.capacity(), 32);
1105
1106        let prim_index = PackedPrimitiveIndex::new(42, GpuPrimitiveKind::Line, true, false);
1107        tiles.assign_to_tiles(
1108            &[PreparedPrimitive {
1109                // 8 x 16, exactly aligned on the tile grid => 2 tiles exactly
1110                aabb: Aabb2d {
1111                    min: Vec2::new(8., 16.),
1112                    max: Vec2::new(16., 32.),
1113                },
1114                prim_index,
1115            }],
1116            // Large screen size, no effect in this test
1117            Vec2::new(256., 128.),
1118        );
1119
1120        assert_eq!(tiles.primitives.len(), 2);
1121        assert_eq!(tiles.primitives[0], prim_index);
1122        assert_eq!(tiles.primitives[1], prim_index);
1123
1124        assert_eq!(tiles.offset_and_count.len(), 32);
1125        for (idx, oc) in tiles.offset_and_count.iter().enumerate() {
1126            if idx == 9 || idx == 13 {
1127                assert_eq!(oc.count, 1);
1128                assert_eq!(oc.offset, if idx == 9 { 0 } else { 1 });
1129            } else {
1130                assert_eq!(oc.count, 0);
1131            }
1132        }
1133    }
1134
1135    #[test]
1136    fn aspect() {
1137        // Aspect ratios
1138        assert_eq!(aspect_width(Vec2::ZERO, 0.), 0.);
1139        assert_eq!(aspect_height(Vec2::ZERO, 0.), 0.);
1140        assert_eq!(aspect_width(Vec2::ZERO, 1.), 0.);
1141        assert_eq!(aspect_height(Vec2::ZERO, 1.), 0.);
1142        assert_eq!(aspect_width(Vec2::ONE, 0.), 0.);
1143        assert_eq!(aspect_height(Vec2::ONE, 0.), 0.);
1144
1145        // Expand to fit
1146        assert_eq!(aspect_width(Vec2::new(256., 64.), 128.), 512.);
1147        assert_eq!(aspect_height(Vec2::new(256., 64.), 512.), 128.);
1148
1149        // Shrink to fit
1150        assert_eq!(aspect_width(Vec2::new(256., 128.), 64.), 128.);
1151        assert_eq!(aspect_height(Vec2::new(256., 64.), 128.), 32.);
1152    }
1153
1154    #[test]
1155    fn fit() {
1156        // Fit to zero-sized content is always zero
1157        assert_eq!(fit_width(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
1158        assert_eq!(fit_height(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
1159        assert_eq!(fit_any(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
1160        assert_eq!(fit_width(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
1161        assert_eq!(fit_height(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
1162        assert_eq!(fit_any(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
1163        assert_eq!(fit_width(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
1164        assert_eq!(fit_height(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
1165        assert_eq!(fit_any(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
1166        assert_eq!(fit_width(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
1167        assert_eq!(fit_height(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
1168        assert_eq!(fit_any(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
1169
1170        // Fit zero-sized (size is ignored in fit direction, only content matters)
1171        assert_eq!(fit_width(Vec2::ZERO, Vec2::ONE, false), Vec2::X);
1172        assert_eq!(fit_height(Vec2::ZERO, Vec2::ONE, false), Vec2::Y);
1173        assert_eq!(fit_width(Vec2::ZERO, Vec2::ONE, true), Vec2::ONE);
1174        assert_eq!(fit_height(Vec2::ZERO, Vec2::ONE, true), Vec2::ONE);
1175
1176        // Expand to fit
1177        assert_eq!(
1178            fit_width(Vec2::new(256., 64.), Vec2::new(512., 32.), false),
1179            Vec2::new(512., 128.)
1180        );
1181        assert_eq!(
1182            fit_height(Vec2::new(256., 64.), Vec2::new(128., 128.), false),
1183            Vec2::new(512., 128.)
1184        );
1185        assert_eq!(
1186            fit_width(Vec2::new(256., 64.), Vec2::new(512., 32.), true),
1187            Vec2::new(512., 32.)
1188        );
1189        assert_eq!(
1190            fit_height(Vec2::new(256., 64.), Vec2::new(128., 128.), true),
1191            Vec2::new(128., 128.)
1192        );
1193
1194        // Shrink to fit
1195        assert_eq!(
1196            fit_width(Vec2::new(256., 64.), Vec2::new(128., 128.), false),
1197            Vec2::new(128., 32.)
1198        );
1199        assert_eq!(
1200            fit_height(Vec2::new(256., 64.), Vec2::new(512., 32.), false),
1201            Vec2::new(128., 32.)
1202        );
1203        assert_eq!(
1204            fit_width(Vec2::new(256., 64.), Vec2::new(128., 128.), true),
1205            Vec2::new(128., 128.)
1206        );
1207        assert_eq!(
1208            fit_height(Vec2::new(256., 64.), Vec2::new(512., 32.), true),
1209            Vec2::new(512., 32.)
1210        );
1211    }
1212}