Skip to main content

ui/
scrollcopy.rs

1// scrollcopy -- scroll copies: when only a scroll view's content moved, the pixels that stay
2// valid are moved inside the framebuffer; the band that came into view and whatever really
3// changed are drawn again
4//
5//   scroll_copy_begin(id, ox, oy)   the commands recorded until scroll_copy_end are the content
6//                                   of scroll view `id` and move with it; (ox, oy) = window px
7//                                   of the content's origin (its place minus the scroll offset).
8//                                   Call right after the view's clip.  Scroll views, lists, data
9//                                   views, vlist, document, code and multi-line text views do.
10//   scroll_copy_end()
11//   (dirty.rs) Pass: the content grids of a frame while dirty_compute hashes the commands;
12//   sc_plan: the window cells a copy can make valid; sc_want: copy or draw; sc_copy: the copy
13//   scopy_present(n) (render_live): the moved cells join the presented rectangles
14//   scopy_done() (render_live, after drawing): the frame's cost for sc_want
15//
16// A view whose origin moved (its clip staying) in the last RECENT frames gets a grid of 32 x 32 px cells aligned
17// at its content's origin, over its clip.  A cell's hash mixes, in drawing order, every content
18// command touching it (hash and box relative to the origin, so a moved command keeps them) and
19// every other command over the view that is flat there (a fill covering the cell's visible
20// part: window background, cards; an opaque one forgets what was below).  Anything else over
21// the cell (text or shapes of other layers, scroll bars, popups, the uneven edge of a fill) and
22// content whose pixels may change when it moves (the rasterizer works in absolute f32
23// coordinates: rounded corners, circles, lines; custom drawings, icons painted by the style or
24// an icon hook, very large or left-cut text) marks the cell bad.  When the origin moved by
25// (dx, dy) since the last frame and the view's clip stayed, a cell with its old hash that is
26// bad in neither frame shows the same pixels moved by (dx, dy): the dirty window cells covered
27// by such cells may be copied instead of drawn -- byte-identical to a full redraw
28// (--verify-dirty checks it).
29//
30// A copy reads and writes a cell's pixels: filling a flat cell again is cheaper, drawing text
31// or icons is not, and every rectangle left to draw replays the commands.  So only cells with
32// detail are copied, small gaps between drawn cells are drawn too, a copy must take over a
33// quarter of what would be drawn, and per view the measured cost of frames that copied and of
34// frames that drew decides (the other way is tried again every EXPLORE frames; the first
35// frames try both); a view that moved IDLE_MAX frames in a row with grids but without copying
36// gets none for SKIP frames.  Frames checked by --verify-dirty always copy when they can (and
37// never skip).  No copy: a horizontal move in a mirrored window, the inspector, pending canvas
38// damage, a full redraw.  `expect scrollcopy` (auto.rs) counts the frames that copied,
39// jfgui_DEBUG=1 prints them.
40
41use alloc::vec;
42use alloc::vec::Vec;
43use core::sync::atomic::Ordering;
44
45use super::cmd::{pos_mix, Cmd, ICON_PAINTED};
46use super::Ui;
47
48/// content cells: 32 x 32 px
49const CS_SHIFT: u32 = 5;
50const SEED: u64 = 0x243F_6A88_85A3_08D3;
51const HK: u64 = 0x9E37_79B9_7F4A_7C15;
52/// other commands over a view: their hashes are told apart from the content's
53const OTHER: u64 = 0x5851_F42D_4C95_7F2D;
54/// views smaller than this (px) get no grid
55const AREA_MIN: i64 = 96 * 96;
56/// grids per window and frame (further views get none)
57const GRIDS_MAX: usize = 16;
58/// a view gets grids while its origin moved within this many rendered frames
59const RECENT: u32 = 60;
60/// the way that costs more is tried again every this many frames (its cost may have changed)
61const EXPLORE: u32 = 16;
62/// views whose costs are kept per window
63const STATS_MAX: usize = 16;
64/// a view that could have copied in this many frames in a row but did not gets no grids for
65/// SKIP frames (they cost a pass over its commands)
66const IDLE_MAX: u32 = 4;
67const SKIP: u32 = 60;
68/// text and icons larger than this (px) may skip the glyph cache (drawn as paths): not copied
69const TEXT_MAX_PX: i32 = 128;
70/// copied cells between two drawn ones in a row: drawn too when at most this many
71const GAP_MAX: usize = 2;
72
73/// a scroll view of the pass: its content is records [i0, i1) of `layer`
74#[derive(Clone, Copy)]
75struct Rec {
76    id: u32,
77    layer: u8,
78    i0: u32,
79    i1: u32,
80    ox: i64,
81    oy: i64,
82    /// the (logical) clip at its start: a view that moved with its clip was laid out anew
83    clip: [i32; 4],
84}
85
86/// the mean cost (us, 0 = not known) of a view's frames that drew and that copied; frames
87/// that could copy; frames in a row that compared grids but did not copy; frames left without
88/// grids
89#[derive(Clone, Copy, Default)]
90struct Stat {
91    id: u32,
92    draw: u32,
93    copy: u32,
94    n: u32,
95    idle: u32,
96    skip: u32,
97}
98
99// the view's Stat (a new one replaces the oldest when STATS_MAX are kept)
100fn stat(v: &mut Vec<Stat>, id: u32) -> &mut Stat {
101    let k = match v.iter().position(|e| e.id == id) {
102        Some(k) => k,
103        None => {
104            if v.len() >= STATS_MAX {
105                v.remove(0);
106            }
107            v.push(Stat { id, ..Stat::default() });
108            v.len() - 1
109        }
110    };
111    &mut v[k]
112}
113
114/// a content grid: the cells of one view in one frame
115#[derive(Default)]
116pub(crate) struct Grid {
117    id: u32,
118    /// the view's clip at its content's start (x0, y0, x1, y1), cut to the window
119    r: [i32; 4],
120    ox: i64,
121    oy: i64,
122    /// the first cell (in cells from the origin), cells per row, rows
123    kx: i64,
124    ky: i64,
125    nx: usize,
126    ny: usize,
127    h: Vec<u64>,
128    bad: Vec<bool>,
129    /// content beyond flat fills (text, icons, images, shapes): worth a copy
130    det: Vec<bool>,
131    // ---- while hashing
132    layer: usize,
133    i0: usize,
134    i1: usize,
135    live: bool,
136    /// the clips opened inside the content (intersections; x0, y0, x1, y1)
137    cs: Vec<[i32; 4]>,
138}
139
140/// per-window part: the views recorded by this pass, where they were (id, origin, clip, the
141/// frame they last moved), the grids of the last frame, the cells copied by this frame (x, y,
142/// w, h; presented), the frame being drawn (its start in us, what it did with a possible copy:
143/// 0 none possible, 1 drew, 2 copied; about which view), the views' costs
144#[derive(Default)]
145pub(crate) struct W {
146    recs: Vec<Rec>,
147    open: Vec<usize>,
148    seen: Vec<(u32, i64, i64, [i32; 4], u32)>,
149    tick: u32,
150    prev: Vec<Grid>,
151    shown: Vec<[i32; 4]>,
152    t0: u64,
153    mode: u8,
154    key: u32,
155    stats: Vec<Stat>,
156    /// the views whose grids met last frame's, the views that copied (this frame)
157    tried: Vec<u32>,
158    copied: Vec<u32>,
159}
160
161impl W {
162    /// cmd_reset: a new pass records its views again
163    pub(crate) fn reset(&mut self) {
164        self.recs.clear();
165        self.open.clear();
166    }
167}
168
169/// shared: what the copies did since the last `expect scrollcopy`
170#[derive(Default)]
171pub(crate) struct S {
172    pub(crate) frames: u32,
173    pub(crate) cells: u64,
174}
175
176/// how a command may change pixels when moved
177enum Odd {
178    No,
179    All,
180    /// the four corner squares (side k) of the box
181    Corners(i32, [i32; 4]),
182}
183
184/// a command's flat part (window px, before its clip): a fill of one colour (opaque: hides
185/// what is below), or a hole where it draws nothing
186enum Flat {
187    No,
188    Fill([i32; 4], bool),
189    Hole([i32; 4]),
190}
191
192fn isect(a: [i32; 4], b: [i32; 4]) -> [i32; 4] {
193    [a[0].max(b[0]), a[1].max(b[1]), a[2].min(b[2]), a[3].min(b[3])]
194}
195
196fn empty(a: &[i32; 4]) -> bool {
197    a[2] <= a[0] || a[3] <= a[1]
198}
199
200#[inline]
201fn mix(h: u64, v: u64) -> u64 {
202    (h.rotate_left(7) ^ v).wrapping_mul(HK)
203}
204
205// content commands whose pixels may change when they move by whole pixels: the rasterizer
206// works in absolute f32 coordinates (curves), glyphs too large for the cache are paths, the
207// "natural" pen rounding differs left of 0, custom drawings and painted icons are unknown
208fn odd_moved(c: &Cmd, icons_exact: bool) -> Odd {
209    match *c {
210        Cmd::Rect { .. } | Cmd::Image { .. } | Cmd::Clip { .. } | Cmd::Unclip => Odd::No,
211        Cmd::Text { x, size, .. } => {
212            if x < 0 || size >> 8 > TEXT_MAX_PX { Odd::All } else { Odd::No }
213        }
214        Cmd::Icon { x, size, .. } => {
215            if !icons_exact || x < 0 || size >> 8 > TEXT_MAX_PX { Odd::All } else { Odd::No }
216        }
217        Cmd::RRect { x, y, w, h, r, t, .. } => {
218            // only the corner bands are paths (shapes.rs); without a radius plain fills
219            let r = (r as i32).min(w.min(h) >> 1);
220            if r <= 0 { Odd::No } else { Odd::Corners(r.max(t as i32) + 1, [x, y, x.wrapping_add(w), y.wrapping_add(h)]) }
221        }
222        Cmd::Shadow { x, y, w, h, r, depth: d, .. } => {
223            if d <= 0 {
224                return Odd::No;
225            }
226            // rings of fill_rrect(x - i, y - i + down, w + 2i, h + 2i, r + i), i = 1 .. d
227            let down = (d as u32 / 3) as i32;
228            Odd::Corners(r.max(0) + d + 2, [x - d, y - d + down, x + w + d, y + h + d + down])
229        }
230        Cmd::PanelFx { .. } | Cmd::Circle { .. } | Cmd::Line { .. } | Cmd::Custom { .. } => Odd::All,
231    }
232}
233
234// the flat part of a command (see Flat)
235fn flat(c: &Cmd) -> Flat {
236    match *c {
237        Cmd::Rect { x, y, w, h, c } => Flat::Fill([x, y, x.wrapping_add(w), y.wrapping_add(h)], c >> 24 == 255),
238        Cmd::RRect { x, y, w, h, r, t, fill, .. } => {
239            // inside the border and the corner bands: the plain fill rows of fill_rrect
240            let (r, t) = ((r as i32).min(w.min(h) >> 1), t as i32);
241            let k = if r <= 0 { t } else { r.max(t) + 1 };
242            let b = [x + k, y + k, x + w - k, y + h - k];
243            match fill >> 24 {
244                0 => Flat::Hole(b),
245                a => Flat::Fill(b, a == 255),
246            }
247        }
248        Cmd::Shadow { x, y, w, h, r, depth: d, .. } => {
249            if d <= 0 {
250                return Flat::Hole([x, y, x.wrapping_add(w), y.wrapping_add(h)]);
251            }
252            // every ring's plain fill rows (translucent)
253            let down = (d as u32 / 3) as i32;
254            let r = r.max(0);
255            Flat::Fill([x + r + 1, y + down + r + 1, x + w - r - 1, y + down + h - r - 1], false)
256        }
257        _ => Flat::No,
258    }
259}
260
261// content worth a copy: drawing it costs more than moving its pixels (not a plain fill)
262fn detail(c: &Cmd) -> bool {
263    !matches!(c, Cmd::Rect { .. } | Cmd::RRect { .. } | Cmd::Shadow { .. } | Cmd::Clip { .. } | Cmd::Unclip)
264}
265
266impl Grid {
267    // cells (grid indices [a, b) x [c, d)) touched by the window rectangle q
268    fn cells(&self, q: &[i32; 4]) -> Option<(usize, usize, usize, usize)> {
269        if empty(q) {
270            return None;
271        }
272        let a = ((q[0] as i64 - self.ox) >> CS_SHIFT) - self.kx;
273        let b = ((q[2] as i64 - 1 - self.ox) >> CS_SHIFT) - self.kx + 1;
274        let c = ((q[1] as i64 - self.oy) >> CS_SHIFT) - self.ky;
275        let d = ((q[3] as i64 - 1 - self.oy) >> CS_SHIFT) - self.ky + 1;
276        let (a, b) = (a.max(0), b.min(self.nx as i64));
277        let (c, d) = (c.max(0), d.min(self.ny as i64));
278        if a >= b || c >= d {
279            return None;
280        }
281        Some((a as usize, b as usize, c as usize, d as usize))
282    }
283
284    // the cells (grid indices [a, b) x [c, d), maybe empty) whose part inside `bound` lies inside
285    // f: a run per axis
286    fn inner(&self, f: &[i32; 4], bound: &[i32; 4]) -> (usize, usize, usize, usize) {
287        let axis = |o: i64, k: i64, n: usize, f0: i32, f1: i32, b0: i32, b1: i32| {
288            // cell i spans o + (k + i) * 32 ..+ 32; its part max(.., b0) .. min(.., b1)
289            let a = if b0 >= f0 { 0 } else { -((o - f0 as i64) >> CS_SHIFT) - k };
290            let e = if b1 <= f1 { n as i64 } else { ((f1 as i64 - o) >> CS_SHIFT) - k };
291            (a.clamp(0, n as i64) as usize, e.clamp(0, n as i64) as usize)
292        };
293        let (a, b) = axis(self.ox, self.kx, self.nx, f[0], f[2], bound[0], bound[2]);
294        let (c, d) = axis(self.oy, self.ky, self.ny, f[1], f[3], bound[1], bound[3]);
295        (a, b.max(a), c, d.max(c))
296    }
297
298    // mark the cells touched by q bad
299    fn spoil(&mut self, q: &[i32; 4]) {
300        if let Some((a, b, c, d)) = self.cells(q) {
301            for j in c..d {
302                self.bad[j * self.nx + a..j * self.nx + b].fill(true);
303            }
304        }
305    }
306
307    // a content command: its box inside the content's own clips
308    fn content(&mut self, c: &Cmd, parts: (u64, i32, i32, u32), bb: [i32; 4], icons_exact: bool) {
309        let e = match self.cs.last() {
310            Some(k) => isect(bb, *k),
311            None => bb,
312        };
313        let Some((a, b, cc, d)) = self.cells(&e) else { return };
314        let (body, x, y, sh) = parts;
315        let mut hc = pos_mix(body, x as i64 - (self.ox << sh), y as i64 - (self.oy << sh));
316        hc = mix(hc, ((e[0] as i64 - self.ox) as u64) ^ ((e[1] as i64 - self.oy) as u64) << 32);
317        hc = mix(hc, ((e[2] as i64 - self.ox) as u64) ^ ((e[3] as i64 - self.oy) as u64) << 32);
318        let det = detail(c);
319        for j in cc..d {
320            let row = j * self.nx;
321            for v in &mut self.h[row + a..row + b] {
322                *v = mix(*v, hc);
323            }
324            if det {
325                self.det[row + a..row + b].fill(true);
326            }
327        }
328        match odd_moved(c, icons_exact) {
329            Odd::No => {}
330            Odd::All => self.spoil(&e),
331            Odd::Corners(k, q) => {
332                for s in [
333                    [q[0], q[1], q[0] + k, q[1] + k],
334                    [q[2] - k, q[1], q[2], q[1] + k],
335                    [q[0], q[3] - k, q[0] + k, q[3]],
336                    [q[2] - k, q[3] - k, q[2], q[3]],
337                ] {
338                    self.spoil(&isect(s, e));
339                }
340            }
341        }
342    }
343
344    // another command over the view, drawn in `clip` (exact: all of its levels tracked)
345    fn other(&mut self, c: &Cmd, bb: [i32; 4], clip: [i32; 4], exact: bool, hw: u64) {
346        let e = isect(bb, clip);
347        let ea = isect(e, self.r);
348        let Some((a, b, cc, d)) = self.cells(&ea) else { return };
349        let hw = hw ^ OTHER;
350        let fl = if exact { flat(c) } else { Flat::No };
351        // the cells it is flat over (or draws nothing over)
352        let (ia, ib, ic, id) = match fl {
353            Flat::Fill(f, _) => self.inner(&isect(f, clip), &self.r),
354            Flat::Hole(f) => self.inner(&f, &self.r),
355            Flat::No => (0, 0, 0, 0),
356        };
357        let opaque = matches!(fl, Flat::Fill(_, true));
358        let fill = matches!(fl, Flat::Fill(..));
359        for j in cc..d {
360            let row = j * self.nx;
361            let inj = j >= ic && j < id;
362            for i in a..b {
363                let k = row + i;
364                if !(inj && i >= ia && i < ib) {
365                    self.bad[k] = true;
366                } else if fill {
367                    if opaque {
368                        self.h[k] = SEED;
369                        self.bad[k] = false;
370                        self.det[k] = false;
371                    }
372                    self.h[k] = mix(self.h[k], hw);
373                }
374            }
375        }
376    }
377}
378
379/// the grids of one frame while dirty_compute hashes the commands (in drawing order)
380#[derive(Default)]
381pub(crate) struct Pass {
382    grids: Vec<Grid>,
383    /// grid indices by (layer, i0): where each one starts
384    starts: Vec<usize>,
385    next: usize,
386    icons_exact: bool,
387    w: i32,
388    h: i32,
389}
390
391impl Pass {
392    /// any grid to fill
393    pub(crate) fn on(&self) -> bool {
394        !self.grids.is_empty()
395    }
396
397    /// before record i of layer l (clip: the clip there, exact: all of its levels tracked):
398    /// the grids whose content starts here take their view's clip
399    pub(crate) fn reach(&mut self, l: usize, i: usize, clip: [i32; 4], exact: bool) {
400        while let Some(&g) = self.starts.get(self.next) {
401            let gr = &mut self.grids[g];
402            if (gr.layer, gr.i0) > (l, i) {
403                break;
404            }
405            self.next += 1;
406            if (gr.layer, gr.i0) < (l, i) {
407                continue; // (never reached: an empty layer part)
408            }
409            let r = isect(clip, [0, 0, self.w, self.h]);
410            if !exact || empty(&r) || ((r[2] - r[0]) as i64 * (r[3] - r[1]) as i64) < AREA_MIN {
411                continue;
412            }
413            gr.r = r;
414            gr.kx = (r[0] as i64 - gr.ox) >> CS_SHIFT;
415            gr.ky = (r[1] as i64 - gr.oy) >> CS_SHIFT;
416            gr.nx = (((r[2] as i64 - 1 - gr.ox) >> CS_SHIFT) - gr.kx + 1) as usize;
417            gr.ny = (((r[3] as i64 - 1 - gr.oy) >> CS_SHIFT) - gr.ky + 1) as usize;
418            gr.h = vec![SEED; gr.nx * gr.ny];
419            gr.bad = vec![false; gr.nx * gr.ny];
420            gr.det = vec![false; gr.nx * gr.ny];
421            gr.live = true;
422        }
423    }
424
425    /// a clip record (x0, y0, x1, y1) at record i of layer l
426    pub(crate) fn clip(&mut self, l: usize, i: usize, q: [i32; 4]) {
427        for g in self.grids.iter_mut().filter(|g| g.live && g.layer == l && i >= g.i0 && i < g.i1) {
428            let k = g.cs.last().map_or(q, |k| isect(*k, q));
429            g.cs.push(k);
430        }
431    }
432
433    /// an unclip record at record i of layer l
434    pub(crate) fn unclip(&mut self, l: usize, i: usize) {
435        for g in self.grids.iter_mut().filter(|g| g.live && g.layer == l && i >= g.i0 && i < g.i1) {
436            g.cs.pop();
437        }
438    }
439
440    /// a drawing record i of layer l: parts (cmd_parts), its box bb (x0, y0, x1, y1), the clip
441    /// it is drawn in (exact: all levels tracked), its window hash (with the clip)
442    #[allow(clippy::too_many_arguments)]
443    pub(crate) fn cmd(&mut self, l: usize, i: usize, c: &Cmd, parts: (u64, i32, i32, u32), bb: [i32; 4], clip: [i32; 4], exact: bool, hw: u64) {
444        let icons_exact = self.icons_exact;
445        for g in self.grids.iter_mut().filter(|g| g.live) {
446            if g.layer == l && i >= g.i0 && i < g.i1 {
447                g.content(c, parts, bb, icons_exact);
448            } else {
449                g.other(c, bb, clip, exact, hw);
450            }
451        }
452    }
453}
454
455/// a planned copy: per window cell (row-major) the copy index + 1 of a copy that would make
456/// it valid (dirty_compute clears the cells it draws), each copy's view and move
457pub(crate) struct Plan {
458    pub(crate) covered: Vec<u8>,
459    moves: Vec<(u32, i32, i32)>,
460}
461
462impl Plan {
463    /// (dirty_compute) changed(i): the hash of window cell i changed (cols per row).  Unchanged
464    /// cells need no copy; a few covered cells between two drawn ones in a row are drawn too
465    /// (fewer, longer rectangles) -> the cells left to copy
466    pub(crate) fn settle(&mut self, cols: usize, changed: impl Fn(usize) -> bool) -> usize {
467        let cv = &mut self.covered;
468        for (i, t) in cv.iter_mut().enumerate() {
469            if !changed(i) {
470                *t = 0; // the pixels are there already
471            }
472        }
473        let mut left = 0;
474        for start in (0..cv.len()).step_by(cols.max(1)) {
475            let drawn = |cv: &[u8], i: usize| cv[start + i] == 0 && changed(start + i);
476            let mut i = 0;
477            while i < cols {
478                if cv[start + i] == 0 {
479                    i += 1;
480                    continue;
481                }
482                // a run of covered cells [i, e)
483                let mut e = i + 1;
484                while e < cols && cv[start + e] != 0 {
485                    e += 1;
486                }
487                if e - i <= GAP_MAX && i > 0 && e < cols && drawn(cv, i - 1) && drawn(cv, e) {
488                    cv[start + i..start + e].fill(0);
489                } else {
490                    left += e - i;
491                }
492                i = e;
493            }
494        }
495        left
496    }
497}
498
499impl Ui {
500    /// ui_scroll_copy_begin(id, ox, oy): the commands recorded until scroll_copy_end are the
501    /// content of scroll view `id`; (ox, oy) = the window px of the content's origin (its
502    /// position minus the scroll offset).  Call right after the view's clip.  When only the
503    /// origin moved between two frames, pixels still shown may be moved in the framebuffer
504    /// instead of drawn again (content that does not move with the origin is simply drawn).
505    pub fn scroll_copy_begin(&mut self, id: u32, ox: i64, oy: i64) {
506        let layer = self.wp.cmd.layer;
507        let i0 = self.wp.cmd.layers[layer].len() as u32;
508        let clip = self.wp.ctx.uiclip;
509        let s = &mut self.wp.scopy;
510        s.recs.push(Rec { id, layer: layer as u8, i0, i1: u32::MAX, ox, oy, clip });
511        s.open.push(s.recs.len() - 1);
512    }
513
514    /// ui_scroll_copy_end: the end of the content of scroll_copy_begin
515    pub fn scroll_copy_end(&mut self) {
516        let Ui { wp, .. } = self;
517        if let Some(k) = wp.scopy.open.pop() {
518            if let Some(r) = wp.scopy.recs.get_mut(k) {
519                r.i1 = wp.cmd.layers[r.layer as usize].len() as u32;
520            }
521        }
522    }
523
524    /// (dirty_compute) the grids to fill this frame: the closed views of the last pass whose
525    /// origin moved lately
526    pub(crate) fn sc_pass(&mut self, w: i32, h: i32) -> Pass {
527        let icons_exact = self.icon_hook.is_none() && !ICON_PAINTED.load(Ordering::Relaxed);
528        // (a finger scrolls every frame until the copies decide the frame rate: touch.rs)
529        let (ins, verify) = (self.m.ins.on, self.verify_dirty || self.m.touch.on);
530        let s = &mut self.wp.scopy;
531        s.shown.clear();
532        s.tick = s.tick.wrapping_add(1);
533        s.t0 = plat::time_us();
534        s.mode = 0;
535        // the last frame: views that moved since their last grid without copying idle, IDLE_MAX
536        // frames in a row and they skip SKIP frames
537        for id in core::mem::take(&mut s.tried) {
538            let copied = s.copied.contains(&id);
539            let st = stat(&mut s.stats, id);
540            st.idle = if copied { 0 } else { st.idle + 1 };
541            if st.idle >= IDLE_MAX {
542                st.idle = 0;
543                st.skip = SKIP;
544            }
545        }
546        s.copied.clear();
547        let mut p = Pass { w, h, icons_exact, ..Pass::default() };
548        // where the views are (scrolled: the origin moved, the clip stayed); those not built in
549        // this pass are forgotten
550        let mut seen = Vec::with_capacity(s.recs.len());
551        for r in s.recs.iter().filter(|r| r.i1 != u32::MAX && r.i1 > r.i0) {
552            let moved = match s.seen.iter().find(|e| e.0 == r.id) {
553                Some(e) if (e.1 == r.ox && e.2 == r.oy) || e.3 != r.clip => e.4,
554                Some(_) => s.tick,
555                None => s.tick.wrapping_sub(RECENT + 1),
556            };
557            seen.push((r.id, r.ox, r.oy, r.clip, moved));
558            let skip = match s.stats.iter_mut().find(|e| e.id == r.id) {
559                Some(st) if st.skip > 0 && !verify => {
560                    st.skip -= 1;
561                    true
562                }
563                _ => false,
564            };
565            // the overlay is drawn into the framebuffer: no copies under the inspector
566            if !ins && !skip && s.tick.wrapping_sub(moved) <= RECENT && p.grids.len() < GRIDS_MAX {
567                p.grids.push(Grid { id: r.id, ox: r.ox, oy: r.oy, layer: r.layer as usize, i0: r.i0 as usize, i1: r.i1 as usize, ..Grid::default() });
568            }
569        }
570        s.seen = seen;
571        let mut st: Vec<usize> = (0..p.grids.len()).collect();
572        st.sort_by_key(|&g| (p.grids[g].layer, p.grids[g].i0));
573        p.starts = st;
574        p
575    }
576
577    /// (dirty_compute, after hashing) compare the grids with the last frame's (they become the
578    /// last frame's): the window cells (cols x rows) whose pixels a copy can provide; None: no
579    /// copy (allowed = false: this frame copies nothing)
580    pub(crate) fn sc_plan(&mut self, p: Pass, cols: i32, rows: i32, allowed: bool) -> Option<Plan> {
581        let grids: Vec<Grid> = p
582            .grids
583            .into_iter()
584            .filter(|g| g.live)
585            .map(|mut g| {
586                g.cs = Vec::new();
587                g
588            })
589            .collect();
590        let prev = core::mem::replace(&mut self.wp.scopy.prev, grids);
591        if !allowed || prev.is_empty() {
592            return None;
593        }
594        let (w, h) = (p.w, p.h);
595        let rtl = self.rtl;
596        // (a finger: flat cells are copied too, every rectangle left to draw replays the commands)
597        let flat_too = self.m.touch.on;
598        let grids = &self.wp.scopy.prev;
599        // (grid, move, the part of the view whose pixels come from inside it)
600        let mut cand: Vec<(&Grid, &Grid, i32, i32, [i32; 4])> = Vec::new();
601        let mut tried = Vec::new();
602        for g in grids {
603            let Some(o) = prev.iter().find(|o| o.id == g.id) else { continue };
604            let (dx, dy) = (g.ox - o.ox, g.oy - o.oy);
605            let r = g.r;
606            if o.r != r || (dx == 0 && dy == 0) || dx.abs() >= (r[2] - r[0]) as i64 || dy.abs() >= (r[3] - r[1]) as i64 || (rtl && dx != 0) {
607                continue;
608            }
609            let (dx, dy) = (dx as i32, dy as i32);
610            let q = [r[0].max(r[0] + dx), r[1].max(r[1] + dy), r[2].min(r[2] + dx), r[3].min(r[3] + dy)];
611            cand.push((g, o, dx, dy, q));
612            tried.push(g.id); // (moved since its last grid: the back-off of sc_pass)
613        }
614        self.wp.scopy.tried = tried;
615        // views over each other: neither copies
616        let keep: Vec<bool> = (0..cand.len()).map(|a| (0..cand.len()).all(|b| a == b || empty(&isect(cand[a].0.r, cand[b].0.r)))).collect();
617        let mut covered = vec![0u8; cols as usize * rows as usize];
618        let mut moves = Vec::new();
619        for (m, &(g, o, dx, dy, q)) in cand.iter().enumerate() {
620            if !keep[m] {
621                continue;
622            }
623            // the content cells with their old hash, good in both frames, with detail
624            let (sx, sy) = (g.kx - o.kx, g.ky - o.ky);
625            let mut v = vec![false; g.h.len()];
626            for j in 0..g.ny {
627                let pj = j as i64 + sy;
628                if pj < 0 || pj >= o.ny as i64 {
629                    continue;
630                }
631                for i in 0..g.nx {
632                    let pi = i as i64 + sx;
633                    if pi < 0 || pi >= o.nx as i64 {
634                        continue;
635                    }
636                    let (k, pk) = (j * g.nx + i, pj as usize * o.nx + pi as usize);
637                    v[k] = !g.bad[k] && !o.bad[pk] && o.h[pk] == g.h[k];
638                }
639            }
640            // the window cells inside q made of valid content cells only, one with detail (a
641            // window cell meets one or two content cells per axis)
642            let tag = moves.len() as u8 + 1;
643            let span = |c: i32, lim: i32, o: i64, k: i64| {
644                let (a, b) = (c << 5, ((c + 1) << 5).min(lim));
645                ((((a as i64 - o) >> CS_SHIFT) - k), (((b as i64 - 1 - o) >> CS_SHIFT) - k), a, b)
646            };
647            let (c0, c1) = ((q[0] >> 5).max(0), ((q[2] + 31) >> 5).min(cols));
648            let (r0, r1) = ((q[1] >> 5).max(0), ((q[3] + 31) >> 5).min(rows));
649            let xs: Vec<(i64, i64, i32, i32)> = (c0..c1).map(|c| span(c, w, g.ox, g.kx)).collect();
650            let mut any = false;
651            for row in r0..r1 {
652                let (j0, j1, y0, y1) = span(row, h, g.oy, g.ky);
653                if y0 < q[1] || y1 > q[3] || j0 < 0 || j1 >= g.ny as i64 {
654                    continue;
655                }
656                for (n, &(i0, i1, x0, x1)) in xs.iter().enumerate() {
657                    if x0 < q[0] || x1 > q[2] || i0 < 0 || i1 >= g.nx as i64 {
658                        continue;
659                    }
660                    let ks = [j0 as usize * g.nx + i0 as usize, j0 as usize * g.nx + i1 as usize, j1 as usize * g.nx + i0 as usize, j1 as usize * g.nx + i1 as usize];
661                    if ks.iter().all(|&k| v[k]) && (flat_too || ks.iter().any(|&k| g.det[k])) {
662                        covered[(row * cols + c0 + n as i32) as usize] = tag;
663                        any = true;
664                    }
665                }
666            }
667            if any {
668                moves.push((g.id, dx, dy));
669            }
670        }
671        if moves.is_empty() {
672            return None;
673        }
674        Some(Plan { covered, moves })
675    }
676
677    /// (dirty_compute) a copy is possible this frame: take it?  Checked frames (--verify-dirty)
678    /// always do; else the view's measured costs decide (see the module header).  The frame
679    /// counts as one that drew until sc_copy copies.
680    pub(crate) fn sc_want(&mut self, plan: &Plan) -> bool {
681        let verify = self.verify_dirty || self.m.touch.on;
682        let s = &mut self.wp.scopy;
683        s.mode = 1;
684        s.key = plan.moves.first().map_or(0, |m| m.0);
685        if verify {
686            return true;
687        }
688        let st = stat(&mut s.stats, s.key);
689        st.n = st.n.wrapping_add(1);
690        if st.draw == 0 || st.copy == 0 {
691            return st.draw != 0; // the first frames: draw once, copy once
692        }
693        let better = st.copy < st.draw;
694        if st.n % EXPLORE == 0 { !better } else { better }
695    }
696
697    /// (render_live, after drawing) the cost of a frame that could copy: what it took since
698    /// dirty_compute began joins the view's mean for the way it went (not for checked frames)
699    pub(crate) fn scopy_done(&mut self) {
700        let verify = self.verify_dirty;
701        let s = &mut self.wp.scopy;
702        let mode = core::mem::take(&mut s.mode);
703        if mode == 0 || verify {
704            return;
705        }
706        let dt = plat::time_us().saturating_sub(s.t0).clamp(1, 1 << 28) as u32;
707        let key = s.key;
708        if let Some(st) = s.stats.iter_mut().find(|e| e.id == key) {
709            let v = if mode == 2 { &mut st.copy } else { &mut st.draw };
710            *v = if *v == 0 { dt } else { (*v * 3 + dt) / 4 };
711        }
712    }
713
714    /// (dirty_compute, no full redraw) do the planned copies of the covered cells: rows in the
715    /// order that reads every source before it is overwritten; the moved areas are presented
716    /// (scopy_present)
717    pub(crate) fn sc_copy(&mut self, plan: &Plan, cols: i32, rows: i32) {
718        let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
719        let stride = self.wp.rt.fb.stride as usize;
720        let debug = self.ui_debug;
721        let mut done = 0u64;
722        for (m, &(id, dx, dy)) in plan.moves.iter().enumerate() {
723            let tag = m as u8 + 1;
724            // the cells of this copy as runs per cell row (row, col0, col1)
725            let mut runs: Vec<(i32, i32, i32)> = Vec::new();
726            for row in 0..rows {
727                let cv = &plan.covered[(row * cols) as usize..((row + 1) * cols) as usize];
728                let mut col = 0;
729                while col < cols as usize {
730                    if cv[col] != tag {
731                        col += 1;
732                        continue;
733                    }
734                    let mut end = col + 1;
735                    while end < cols as usize && cv[end] == tag {
736                        end += 1;
737                    }
738                    runs.push((row, col as i32, end as i32));
739                    col = end;
740                }
741            }
742            if runs.is_empty() {
743                continue;
744            }
745            // cell rows downwards when the content moves up (the source is below), else upwards;
746            // along a row away from the source
747            if dy > 0 {
748                runs.reverse();
749            }
750            let mut bbox = [i32::MAX, i32::MAX, i32::MIN, i32::MIN];
751            let ncell: u64 = runs.iter().map(|r| (r.2 - r.1) as u64).sum();
752            let px = &mut self.wp.rt.fb.px;
753            let mut k = 0;
754            while k < runs.len() {
755                let row = runs[k].0;
756                let mut e = k;
757                while e < runs.len() && runs[e].0 == row {
758                    e += 1;
759                }
760                let mut rr: Vec<(i32, i32)> = runs[k..e].iter().map(|r| (r.1, r.2)).collect();
761                rr.sort_unstable();
762                if dx > 0 {
763                    rr.reverse();
764                }
765                let (y0, y1) = (row << 5, ((row + 1) << 5).min(h));
766                for t in 0..(y1 - y0) {
767                    let y = if dy > 0 { y1 - 1 - t } else { y0 + t };
768                    let sy = y - dy;
769                    for &(c0, c1) in &rr {
770                        let (x0, x1) = (c0 << 5, (c1 << 5).min(w));
771                        let src = sy as usize * stride + (x0 - dx) as usize * 4;
772                        let dst = y as usize * stride + x0 as usize * 4;
773                        px.copy_within(src..src + (x1 - x0) as usize * 4, dst);
774                    }
775                }
776                for &(c0, c1) in &rr {
777                    bbox = [bbox[0].min(c0 << 5), bbox[1].min(y0), bbox[2].max((c1 << 5).min(w)), bbox[3].max(y1)];
778                }
779                k = e;
780            }
781            self.wp.scopy.shown.push([bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]]);
782            self.wp.scopy.copied.push(id);
783            done += ncell;
784            if debug {
785                plat::write_stderr(alloc::format!("render: scroll copy #{:x} by {},{}: {} cells\n", id, dx, dy, ncell).as_bytes());
786            }
787        }
788        if done != 0 {
789            self.wp.scopy.mode = 2;
790            self.m.scopy.frames = self.m.scopy.frames.wrapping_add(1);
791            self.m.scopy.cells = self.m.scopy.cells.wrapping_add(done);
792        }
793    }
794
795    /// (render_live) the presented rectangles: n dirty ones (-1 = all) and the cells a scroll
796    /// copy moved (appended to drects) -> their count
797    pub(crate) fn scopy_present(&mut self, n: i32) -> i32 {
798        let s = &self.wp.scopy.shown;
799        if n < 0 || s.is_empty() {
800            return n;
801        }
802        self.drects.truncate(n as usize);
803        self.drects.extend_from_slice(s);
804        n + s.len() as i32
805    }
806}