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}