1use alloc::vec;
27use alloc::vec::Vec;
28
29use super::cmd::{cmd_bbox, cmd_parts, pos_mix, Cmd};
30use super::consts::L_COUNT;
31use super::Ui;
32
33const CELL_SHIFT: u32 = 5; const CELL: i32 = 32;
35pub(crate) const DRECTS_MAX: usize = 128;
37const COPY_RECTS_MORE: usize = 16;
40const HASH_K: u64 = 0x9E37_79B9_7F4A_7C15;
41const DCLIP_MAX: usize = super::ctx::UICLIP_MAX;
44const DMG_MAX: usize = 32;
46#[derive(Clone, Copy, Default)]
52pub(crate) struct Dmg {
53 win: u32,
54 x0: i32,
55 y0: i32,
56 x1: i32,
57 y1: i32,
58}
59
60pub(crate) const IMG_MIN_PX: i64 = 256 * 256;
62pub(crate) const TOP_BLOCKED: u8 = 255;
64
65#[derive(Clone, Copy)]
68#[cfg_attr(not(any(windows, jfgui_glibc, target_os = "android", target_arch = "wasm32")), allow(dead_code))] pub(crate) struct ImgSeen {
70 pub(crate) index: u32,
71 pub(crate) vis: [i32; 4],
72}
73
74#[derive(Clone, Copy, Default)]
76pub(crate) struct Cvs {
77 id: u32,
78 win: u32,
79 frame: u32,
80 rect: [i32; 4],
81}
82
83#[derive(Default)]
85pub(crate) struct W {
86 pub(crate) cells: Option<Vec<u64>>,
87 pub(crate) cells_prev: Option<Vec<u64>>,
88 pub(crate) cols: u32,
89 pub(crate) rows: u32,
90 pub(crate) full: bool,
91 pub(crate) split: bool,
94 pub(crate) busy: bool,
96 pub(crate) forced: bool,
98 pub(crate) n0: i32,
100 pub(crate) pre: [Vec<u64>; L_COUNT - 1],
103 pub(crate) pre_prev: [Vec<u64>; L_COUNT - 1],
104 pub(crate) top: Vec<u8>,
106 pub(crate) imgs: Vec<ImgSeen>,
107 pub(crate) canv: Vec<(u32, [i32; 4])>,
109}
110
111impl W {
112 #[cfg_attr(not(any(windows, jfgui_glibc, target_os = "android", target_arch = "wasm32")), allow(dead_code))] pub(crate) fn through(&self, now: bool, l: usize) -> &[u64] {
116 let (pre, all) = if now { (&self.pre_prev, &self.cells_prev) } else { (&self.pre, &self.cells) };
117 match pre[l.min(L_COUNT - 1)..].iter().find(|k| !k.is_empty()) {
118 Some(k) => k,
119 None => all.as_deref().unwrap_or(&[]),
120 }
121 }
122}
123
124struct DClip {
126 c: [i32; 4],
127 stack: Vec<[i32; 4]>,
128 over: u32,
130}
131
132impl DClip {
133 fn new() -> DClip {
135 DClip { c: [-0x4000_0000, -0x4000_0000, 0x4000_0000, 0x4000_0000], stack: Vec::new(), over: 0 }
136 }
137
138 fn push(&mut self, r: [i32; 4]) {
140 if self.stack.len() >= DCLIP_MAX {
141 self.over += 1;
142 return;
143 }
144 self.stack.push(self.c);
145 let c = &mut self.c;
146 c[0] = c[0].max(r[0]);
147 c[1] = c[1].max(r[1]);
148 c[2] = c[2].min(r[0].wrapping_add(r[2]));
149 c[3] = c[3].min(r[1].wrapping_add(r[3]));
150 }
151
152 fn pop(&mut self) {
153 if self.over != 0 {
154 self.over -= 1;
155 } else if let Some(c) = self.stack.pop() {
156 self.c = c;
157 }
158 }
159
160 fn cut(&self, bb: &mut [i32; 4]) -> bool {
162 bb[0] = bb[0].max(self.c[0]);
163 bb[1] = bb[1].max(self.c[1]);
164 bb[2] = bb[2].min(self.c[2]);
165 bb[3] = bb[3].min(self.c[3]);
166 bb[2] > bb[0] && bb[3] > bb[1]
167 }
168}
169
170fn rects_build(out: &mut Vec<[i32; 4]>, cols: i32, rows: i32, dirty: impl Fn(usize) -> bool) -> Option<u32> {
174 out.clear();
175 let mut cells = 0u32;
176 for row in 0..rows {
177 let at = |col: i32| dirty((row * cols + col) as usize);
178 let mut col = 0;
179 while col < cols {
180 if !at(col) {
181 col += 1;
182 continue;
183 }
184 let mut end = col + 1;
186 while end < cols && at(end) {
187 end += 1;
188 }
189 cells += (end - col) as u32;
190 let (x, w, y) = (col << CELL_SHIFT, (end - col) << CELL_SHIFT, row << CELL_SHIFT);
192 if let Some(r) = out.iter_mut().find(|r| r[0] == x && r[2] == w && r[1] + r[3] == y) {
193 r[3] += CELL;
194 } else if out.len() < DRECTS_MAX {
195 out.push([x, y, w, CELL]);
196 } else {
197 return None;
198 }
199 col = end;
200 }
201 }
202 Some(cells)
203}
204
205impl Ui {
206 pub fn dirty_force_full(&mut self) {
208 self.wp.dirty.full = true;
209 }
210
211 #[allow(dead_code)] pub(crate) fn dirty_free(&mut self) {
214 let d = &mut self.wp.dirty;
215 d.cells = None;
216 d.cells_prev = None;
217 d.cols = 0;
218 d.rows = 0;
219 d.full = true;
220 }
221
222 fn cells_ensure(&mut self, w: i32, h: i32) {
224 let cols = (w as u32).wrapping_add(CELL as u32 - 1) >> CELL_SHIFT;
225 let rows = (h as u32).wrapping_add(CELL as u32 - 1) >> CELL_SHIFT;
226 let d = &mut self.wp.dirty;
227 if cols == d.cols && rows == d.rows {
228 return;
229 }
230 d.cols = cols;
231 d.rows = rows;
232 let n = cols as usize * rows as usize;
233 d.cells = Some(vec![0; n]);
234 d.cells_prev = Some(vec![0; n]);
235 d.pre = Default::default();
236 d.pre_prev = Default::default();
237 d.full = true;
238 }
239
240 pub(crate) fn dmg_canvas_seen(&mut self, id: u32) {
244 let e = Cvs { id, win: self.uiw_cur, frame: self.frame, rect: self.wp.ctx.last_rect };
245 let (mut old, mut age) = (0, 0u32);
246 for (i, c) in self.cvs.iter_mut().enumerate() {
247 if c.id == id {
248 *c = e;
249 return;
250 }
251 let a = self.frame.wrapping_sub(c.frame);
252 if a > age {
253 (old, age) = (i, a);
254 }
255 }
256 if age >= 2 {
257 self.cvs[old] = e;
258 } else {
259 self.cvs.push(e);
260 }
261 }
262
263 pub fn canvas_damage(&mut self, id: u32, x: i32, y: i32, w: i32, h: i32) {
266 if id == 0 {
267 return;
268 }
269 let Some(e) = self.cvs.iter().find(|e| e.id == id).copied() else { return };
271 let r = e.rect;
272 let x0 = x.wrapping_add(r[0]).max(r[0]);
273 let x1 = w.wrapping_add(x.wrapping_add(r[0])).min(r[0].wrapping_add(r[2]));
274 let y0 = y.wrapping_add(r[1]).max(r[1]);
275 let y1 = h.wrapping_add(y.wrapping_add(r[1])).min(r[1].wrapping_add(r[3]));
276 if x0 >= x1 || y0 >= y1 {
277 return;
278 }
279 let full = self.dmg.len() >= DMG_MAX;
280 if let Some(d) = self.dmg.iter_mut().rev().find(|d| d.win == e.win).filter(|_| full) {
281 d.x0 = d.x0.min(x0);
283 d.y0 = d.y0.min(y0);
284 d.x1 = d.x1.max(x1);
285 d.y1 = d.y1.max(y1);
286 } else {
287 self.dmg.push(Dmg { win: e.win, x0, y0, x1, y1 }); }
289 self.invalidate();
290 }
291
292 fn dmg_take(&mut self, n: i32, w: i32, h: i32) -> i32 {
295 let mut n = n;
296 let cur = self.uiw_cur;
297 let mut keep = Vec::new();
298 for d in core::mem::take(&mut self.dmg) {
299 if d.win != cur {
300 keep.push(d);
301 continue;
302 }
303 if n < 0 {
304 continue; }
306 if n as usize >= DRECTS_MAX {
307 n = -1;
308 continue;
309 }
310 let x = d.x0.max(0);
311 let rw = d.x1.min(w).wrapping_sub(x);
312 let y = d.y0.max(0);
313 let rh = d.y1.min(h).wrapping_sub(y);
314 if rw > 0 && rh > 0 {
315 self.drects.push([x, y, rw, rh]);
316 n += 1;
317 }
318 }
319 self.dmg = keep;
320 n
321 }
322
323 pub(crate) fn dirty_compute(&mut self, w: i32, h: i32) -> i32 {
326 self.cells_ensure(w, h);
327 {
328 let d = &mut self.wp.dirty;
329 d.forced = d.full;
330 d.top.clear();
331 d.imgs.clear();
332 for k in d.pre.iter_mut() {
333 k.clear();
334 }
335 }
336 if self.gp_canvas_wanted() {
337 self.gp_canvas_begin(); }
339 let mut sc = self.sc_pass(w, h);
340 if self.wp.dirty.cells.is_none() || self.wp.dirty.cells_prev.is_none() {
341 self.sc_plan(sc, 0, 0, false);
342 return self.dirty_full_take(w, h);
343 }
344 let cols = self.wp.dirty.cols as i32;
345 let rows = self.wp.dirty.rows as i32;
346 {
348 let Ui { wp, .. } = self;
349 let d = &mut wp.dirty;
350 let split = cfg!(any(windows, jfgui_glibc, target_os = "android", target_arch = "wasm32")) && d.split; let cells = d.cells.as_mut().unwrap();
352 cells.fill(0);
353 let grids = sc.on();
354 for (l, layer) in wp.cmd.layers.iter().enumerate() {
355 if split && l > 0 && !layer.is_empty() {
356 d.pre[l - 1].extend_from_slice(cells);
357 }
358 let mut dc = DClip::new();
359 for (i, c) in layer.iter().enumerate() {
360 if grids {
361 sc.reach(l, i, dc.c, dc.over == 0);
362 }
363 match c {
364 Cmd::Clip { x, y, w, h } => {
365 dc.push([*x, *y, *w, *h]);
366 if grids {
367 sc.clip(l, i, [*x, *y, x.wrapping_add(*w), y.wrapping_add(*h)]);
368 }
369 continue;
370 }
371 Cmd::Unclip => {
372 dc.pop();
373 if grids {
374 sc.unclip(l, i);
375 }
376 continue;
377 }
378 _ => {}
379 }
380 let Some(bb0) = cmd_bbox(c) else { continue };
381 let mut bb = bb0;
383 let vis = dc.cut(&mut bb);
384 if !vis && !grids {
385 continue;
386 }
387 let parts = cmd_parts(c);
390 let mut hh = pos_mix(parts.0, parts.1 as i64, parts.2 as i64);
391 hh ^= (dc.c[0] as u32 as u64) | (dc.c[1] as u32 as u64) << 32;
392 hh = hh.wrapping_mul(HASH_K);
393 hh ^= (dc.c[2] as u32 as u64) | (dc.c[3] as u32 as u64) << 32;
394 hh = hh.wrapping_mul(HASH_K);
395 let mut apart = None;
397 if split && l == 0 && !d.canv.is_empty() && dc.over == 0 && d.imgs.len() < TOP_BLOCKED as usize - 1 && matches!(c, Cmd::Custom { .. }) {
398 if let Some(&(_, r)) = d.canv.iter().find(|x| x.0 == i as u32) {
399 let v = [bb[0].max(r[0]), bb[1].max(r[1]), bb[2].min(r[0] + r[2]), bb[3].min(r[1] + r[3])];
400 hh = pos_mix(0x4341_4E56, v[0] as i64, v[1] as i64) ^ ((v[2] as u32 as u64) | (v[3] as u32 as u64) << 32).wrapping_mul(HASH_K);
401 apart = Some(v);
402 }
403 }
404 if grids {
405 sc.cmd(l, i, c, parts, bb0, dc.c, dc.over == 0, hh);
406 }
407 if !vis {
408 continue;
409 }
410 let c0 = (bb[0] >> CELL_SHIFT).max(0);
412 let c1 = ((bb[2] - 1) >> CELL_SHIFT).min(cols - 1);
413 let r0 = (bb[1] >> CELL_SHIFT).max(0);
414 let r1 = ((bb[3] - 1) >> CELL_SHIFT).min(rows - 1);
415 let mut tag = 0;
417 let seen = match apart {
418 Some(v) => (v[0] < v[2] && v[1] < v[3]).then_some(v),
419 None => (matches!(c, Cmd::Image { .. }) && (bb[2] - bb[0]) as i64 * (bb[3] - bb[1]) as i64 >= IMG_MIN_PX).then_some(bb),
420 };
421 if let Some(vis) = seen.filter(|_| split && l == 0 && dc.over == 0 && d.imgs.len() < TOP_BLOCKED as usize - 1) {
422 if d.top.is_empty() {
423 d.top.resize(cols as usize * rows as usize, 0);
424 }
425 d.imgs.push(ImgSeen { index: i as u32, vis });
426 tag = d.imgs.len() as u8;
427 }
428 let track = l == 0 && !d.top.is_empty();
429 let mut r = r0;
430 while r <= r1 {
431 let mut k = c0;
432 while k <= c1 {
433 let i = (r * cols + k) as usize;
434 cells[i] = (cells[i].rotate_left(7) ^ hh).wrapping_mul(HASH_K);
435 if track {
436 let t = &mut d.top[i];
437 *t = if *t != 0 { TOP_BLOCKED } else { tag };
438 }
439 k += 1;
440 }
441 r += 1;
442 }
443 }
444 }
445 }
446 let pending = self.dmg.iter().any(|d| d.win == self.uiw_cur);
448 let split = cfg!(any(windows, jfgui_glibc, target_os = "android", target_arch = "wasm32")) && self.wp.dirty.split;
451 let apart = split && {
452 let quads = (!self.wp.dirty.imgs.is_empty() || self.wp.dirty.busy) && {
453 let images = self.gp_plan_images();
454 self.gp_plan_canvases() | images
455 };
456 quads || self.wp.dirty.busy || self.wp.cmd.layers[1..].iter().any(|l| !l.is_empty())
457 };
458 let allowed = !self.wp.dirty.full && !pending && !apart;
459 let mut plan = self.sc_plan(sc, cols, rows, allowed);
460 let want = plan.as_ref().is_some_and(|p| self.sc_want(p));
461 let force = self.verify_dirty || self.m.touch.on;
463 let (built, copy) = {
464 let Ui { wp, drects, .. } = &mut *self;
465 let d = &wp.dirty;
466 let (cur, prev) = (d.cells.as_ref().unwrap(), d.cells_prev.as_ref().unwrap());
467 let changed = |i: usize| cur[i] != prev[i];
468 let mut built = rects_build(drects, cols, rows, changed);
469 let mut copy = false;
474 if let Some(p) = plan.as_mut().filter(|_| want) {
475 let copied = p.settle(cols as usize, changed);
476 let total = cols as u32 * rows as u32;
477 let draw = built.filter(|&n| n * 5 <= total * 3).unwrap_or(total);
478 let mut alt = Vec::new();
479 let cv = &p.covered;
480 if let Some(n) = rects_build(&mut alt, cols, rows, |i| changed(i) && cv[i] == 0) {
481 if copied > 0 && (force || (copied * 4 >= draw as usize && alt.len() <= drects.len() + COPY_RECTS_MORE)) {
482 *drects = alt;
483 built = Some(n);
484 copy = true;
485 }
486 }
487 }
488 (built, copy)
489 };
490 if !copy {
491 plan = None;
492 }
493 let Some(dirty_cells) = built else {
494 self.dirty_swap();
495 return self.dirty_full_take(w, h);
496 };
497 let n0 = self.drects.len() as i32;
499 self.wp.dirty.n0 = n0;
500 let n = self.dmg_take(n0, w, h);
501 if n < 0 {
502 self.wp.dirty.forced = true;
503 self.dirty_swap();
504 return self.dirty_full_take(w, h);
505 }
506 for r in self.drects.iter_mut() {
508 let mw = w.wrapping_sub(r[0]);
509 if r[2] > mw {
510 r[2] = mw;
511 }
512 let mh = h.wrapping_sub(r[1]);
513 if r[3] > mh {
514 r[3] = mh;
515 }
516 }
517 let total = (cols as u32).wrapping_mul(rows as u32).wrapping_mul(3);
519 if (dirty_cells.wrapping_mul(5) > total && !(force && plan.is_some())) || self.wp.dirty.full {
520 self.dirty_swap();
521 return self.dirty_full_take(w, h);
522 }
523 if let Some(p) = plan {
525 self.sc_copy(&p, cols, rows);
526 }
527 self.dirty_swap();
528 n
529 }
530
531 fn dirty_full_take(&mut self, w: i32, h: i32) -> i32 {
533 if self.dmg.iter().any(|d| d.win == self.uiw_cur) {
534 self.wp.dirty.forced = true; }
536 self.wp.dirty.full = false;
537 self.drects.clear();
538 self.dmg_take(0, w, h);
539 self.drects.clear();
540 -1
541 }
542
543 fn dirty_swap(&mut self) {
545 let d = &mut self.wp.dirty;
546 core::mem::swap(&mut d.cells, &mut d.cells_prev);
547 core::mem::swap(&mut d.pre, &mut d.pre_prev);
548 }
549}