Skip to main content

ui/
canvas.rs

1// canvas -- a canvas whose zoom and pan go through GPU textures (G3c; docs/GPU.md "Leinwände")
2//
3// The CPU stays the truth: a canvas records its Cmd::Custom as ever (ui.canvas, ui.cmd_custom), its
4// Draw paints what the view shows.  Right after it the widget tells the library what that drawing
5// is made of:
6//
7//   ui.canvas_gpu_wanted() -> bool     is it worth building a CanvasDesc?  (Never on other targets,
8//                                      without a GPU output, in headless runs.)
9//   ui.canvas_gpu(CanvasDesc { .. })   the custom command recorded last shows this content through
10//                                      this view
11//   ui.canvas_gpu_damage(id, r)        the content rectangle r (x0, y0, x1, y1, content units)
12//                                      changed: its tiles are drawn again when they are needed
13//
14// While the view moves and the content stays, the content comes from tiles (TILE x TILE px GPU
15// textures, drawn once by CanvasContent::draw_view, kept under a budget) that the render pass draws
16// with the transform: no raster, no upload of the view.  A short time after the last change the
17// view is drawn once more by the CPU: at rest the picture is byte for byte the CPU path's.  An edit
18// goes the CPU's way as without a GPU and costs no tile.
19//
20// Everywhere else, and with CanvasGpu::Cpu, ui.canvas_gpu does nothing and the Draw paints every
21// frame; the widget's code stays the same on every target.
22
23use alloc::rc::Rc;
24use alloc::vec::Vec;
25
26use crate::cmd::{Cmd, Gfx};
27use crate::Ui;
28
29/// how a canvas may use the GPU while its view moves
30#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
31#[non_exhaustive]
32pub enum CanvasGpu {
33    /// the CPU path in every frame, as without a GPU
34    Cpu,
35    /// moves by whole pixels at the same scale come from the tiles; another scale is drawn by the
36    /// CPU at once.  Pixel-exact when the content's raster does not depend on its place
37    Pan,
38    /// also zoom: the tiles are scaled while the gesture runs (a little soft or coarse), the CPU
39    /// draws the picture sharp a moment after it
40    #[default]
41    Zoom,
42}
43
44/// the view of a canvas: the content point (u, v) shows at the window pixel position
45/// (ox + u * scale, oy + v * scale)
46#[derive(Clone, Copy, PartialEq, Debug, Default)]
47pub struct CanvasView {
48    pub scale: f64,
49    pub ox: f64,
50    pub oy: f64,
51}
52
53/// what a canvas shows, drawable through any view (at render time)
54pub trait CanvasContent {
55    /// draw the content through view v into g (origin 0, the clip says what needs pixels, rect = the
56    /// target's rectangle): what the canvas's Draw paints there at this view, without what lies
57    /// over it, every pixel opaque
58    fn draw_view(&self, g: &mut Gfx, v: CanvasView, rect: [i32; 4]);
59}
60
61/// a canvas for ui.canvas_gpu
62pub struct CanvasDesc {
63    /// its identity in the window (the tiles live as long as it is drawn; ui.canvas_gpu_damage)
64    pub id: u32,
65    /// its name for jfgui_GPU_CANVAS_CPU / jfgui_GPU_CANVAS_PAN ("image", "diagram", ...)
66    pub name: &'static str,
67    /// the window rectangle x, y, w, h the content fills, opaque (inside the custom command's box)
68    pub rect: [i32; 4],
69    pub view: CanvasView,
70    /// stands for everything the content shows: another value drops its tiles
71    pub ver: u32,
72    pub content: Rc<dyn CanvasContent>,
73    pub gpu: CanvasGpu,
74    /// the raster does not depend on the place: a move by whole pixels must match byte for byte
75    /// (jfgui_GPU_VERIFY fails on a difference; else it is counted, GpuOutput::canvas_diff_*)
76    pub exact_pan: bool,
77}
78
79/// a canvas of the frame: its command
80#[cfg_attr(not(any(windows, jfgui_glibc, target_os = "android", target_arch = "wasm32")), allow(dead_code))] // (read by gpu_out.rs)
81pub(crate) struct Rec {
82    pub(crate) layer: u8,
83    pub(crate) index: u32,
84    pub(crate) d: CanvasDesc,
85}
86
87/// the canvases recorded in the live window's frame
88#[derive(Default)]
89pub(crate) struct W {
90    #[cfg_attr(not(any(windows, jfgui_glibc, target_os = "android", target_arch = "wasm32")), allow(dead_code))] // (read by gpu_out.rs)
91    pub(crate) recs: Vec<Rec>,
92}
93
94impl Ui {
95    /// is the GPU output there to use a CanvasDesc?  A widget without one saves building it
96    pub fn canvas_gpu_wanted(&self) -> bool {
97        self.gp_canvas_wanted()
98    }
99
100    /// the custom command recorded last shows this content through this view (module header)
101    pub fn canvas_gpu(&mut self, d: CanvasDesc) {
102        if d.gpu == CanvasGpu::Cpu || !self.gp_canvas_wanted() {
103            return;
104        }
105        let l = self.wp.cmd.layer as usize;
106        let list = &self.wp.cmd.layers[l];
107        if let Some(Cmd::Custom { f: Some(_), .. }) = list.last() {
108            let index = list.len() as u32 - 1;
109            self.wp.canv.recs.push(Rec { layer: l as u8, index, d });
110        }
111    }
112
113    /// the content rectangle x0, y0, x1, y1 (content units) of canvas id changed: its tiles go
114    pub fn canvas_gpu_damage(&mut self, id: u32, r: [f64; 4]) {
115        if self.gp_canvas_wanted() {
116            self.gp_canvas_damage(id, r);
117        }
118    }
119}