1#[cfg(not(target_arch = "wasm32"))]
194use alloc::boxed::Box;
195use alloc::collections::BTreeMap;
196use alloc::format;
197use alloc::rc::{Rc, Weak};
198use alloc::string::String;
199use alloc::sync::Arc;
200use alloc::vec;
201use alloc::vec::Vec;
202use core::sync::atomic::{AtomicU32, Ordering};
203
204use gpu::{
205 Access, Adapter, BindEntry, BindGroup, BindGroupDesc, BindGroupLayout, Binding, BindingKind, Buffer, BufferDesc, BufferUsage,
206 ColorAttachment, ColorTarget, Cull, DataLayout, Device, DeviceDesc, Format, GpuError, Instance, Layout, Level, Load, MemoryKind,
207 PipelineLayoutDesc, PresentMode, RenderPassDesc, RenderPipeline, RenderPipelineDesc, Resource, ShaderModule, ShaderStage, Stages,
208 Swapchain, SwapchainDesc, Texture, TextureDesc, TextureRegion, TextureUsage, Topology, TryAcquire,
209};
210use rt::sync::SpinLock;
211
212use super::auto::{next_word, skip_ws, word_eq};
213use super::canvas::{CanvasGpu, CanvasView};
214use super::cmd::{render_layer, Cmd, DrawRef, Gfx};
215use super::consts::*;
216use super::dirty::TOP_BLOCKED;
217use super::gpu_view::{Entry, GpuContent, Own, Target, ViewTex, GPU_VIEW_FORMAT};
218use super::{App, GpuOutput, Mods, Ui};
219use gfx::{Dc, Surface, FMT_A8, FMT_XRGB};
220use text::Text;
221
222const DEFAULT_ON: bool = true;
225const DEFAULT_ON_LINUX: bool = true;
228const DEFAULT_ON_BROWSER: bool = true;
232const DEFAULT_ON_APPLE: bool = false;
235const PRESENT: PresentMode = PresentMode::Mailbox;
239const IMAGES: u32 = 2;
241const MIN_PRESENT_US: u64 = if cfg!(target_arch = "wasm32") { 0 } else { 4_000 };
244const STABLE_US: u64 = 100_000;
246const RETRY_US: u64 = 2_000;
248const JOB_US: u64 = 8_000;
250#[cfg(not(target_arch = "wasm32"))]
253const WORKER_STACK: usize = if cfg!(windows) { 2 << 20 } else { 8 << 20 };
254const RETRY_FIRST_MS: u64 = 2_000;
256const RETRY_MAX_MS: u64 = 300_000;
257const BIG_UPLOAD: u64 = 128 << 10;
259const TRIM_US: u64 = 1_200_000;
260const REMOTE_LOOK_US: u64 = 10_000_000;
262const KEPT_US: u64 = 500_000;
264const UPPER: usize = L_COUNT - 1;
266const CELL: i32 = 32;
268const CELL_ROW: usize = CELL as usize * 4;
269const CELL_BYTES: usize = CELL as usize * CELL_ROW;
270const NO_BLOCK: u32 = u32::MAX;
271const LAYER_STEP: i32 = 256;
274const LAYER_SPARE: i32 = 128;
275const LAYER_SHRINK: i64 = 4;
277const LINGER_US: u64 = 1_000_000;
279const IMAGE_BUDGET: u64 = 256 << 20;
281const IMAGE_STEP_BYTES: u64 = 4 << 20;
283const IMAGES_KEPT: usize = 64;
285const IMAGE_UNUSED_US: u64 = 30_000_000;
287const TILE: i32 = 256;
289const TILE_BORDER: i32 = 1;
290const TILE_TEX: i32 = TILE + 2 * TILE_BORDER;
291const TILE_BYTES: u64 = TILE_TEX as u64 * TILE_TEX as u64 * 4;
292const CANVAS_BUDGET: u64 = 128 << 20;
294const FILL_US: u64 = 5_000;
296const SETTLE_US: u64 = 180_000;
298const CANVAS_UNUSED_US: u64 = 30_000_000;
300const K_MIN: f64 = 0.55;
302const K_MAX: f64 = 1.9;
303const PHASE_TOL: f64 = 1.0 / 64.0;
305const LEVELS_MAX: usize = 6;
307const SHOW_TRIES: u32 = 300;
309const VIEW_REST_US: u64 = 180_000;
311const VIEW_HOLD_US: u64 = 100_000;
314
315#[cfg(not(target_arch = "wasm32"))]
318mod spv {
319 include!("gpu_out_spv.rs");
320}
321
322pub const GPU_OUT_WGSL: &str = include_str!("gpu_out.wgsl");
324#[cfg(not(target_arch = "wasm32"))]
325pub const GPU_OUT_VS: &[u32] = spv::VS;
326#[cfg(not(target_arch = "wasm32"))]
327pub const GPU_OUT_FS: &[u32] = spv::FS;
328#[cfg(not(target_arch = "wasm32"))]
329pub const GPU_OUT_FS_IMAGE: &[u32] = spv::FS_IMAGE;
330#[cfg(not(target_arch = "wasm32"))]
331pub const GPU_OUT_FS_TILE: &[u32] = spv::FS_TILE;
332
333#[derive(Clone, Copy, PartialEq, Eq, Debug)]
334enum Stage {
335 Cpu,
336 Job,
337 Switch,
338 Gpu,
339}
340
341struct Tex {
342 t: Texture,
343 group: BindGroup,
344 w: i32,
345 h: i32,
346}
347
348#[derive(Default)]
350struct Store {
351 idx: Vec<u32>,
352 px: Vec<u8>,
353 free: Vec<u32>,
354 used: u32,
355}
356
357impl Store {
358 fn has(&self, i: usize) -> bool {
359 self.idx.get(i).is_some_and(|&b| b != NO_BLOCK)
360 }
361
362 fn get(&self, i: usize) -> &[u8] {
363 &self.px[self.idx[i] as usize * CELL_BYTES..][..CELL_BYTES]
364 }
365
366 fn empty(&mut self) {
368 self.idx.clear();
369 self.px.clear();
370 self.free.clear();
371 self.used = 0;
372 }
373
374 fn reserve(&mut self, n: usize) {
376 self.px.reserve_exact((n + self.used as usize).saturating_mul(CELL_BYTES).saturating_sub(self.px.len()));
377 }
378
379 fn put(&mut self, i: usize, cells: usize) -> &mut [u8] {
381 if self.idx.len() != cells {
382 self.empty();
383 self.idx.resize(cells, NO_BLOCK);
384 }
385 let mut b = self.idx[i];
386 if b == NO_BLOCK {
387 b = self.free.pop().unwrap_or_else(|| {
388 let n = (self.px.len() / CELL_BYTES) as u32;
389 self.px.resize(self.px.len() + CELL_BYTES, 0);
390 n
391 });
392 self.idx[i] = b;
393 self.used += 1;
394 }
395 &mut self.px[b as usize * CELL_BYTES..][..CELL_BYTES]
396 }
397
398 fn drop_cell(&mut self, i: usize) {
399 if self.has(i) {
400 self.free.push(self.idx[i]);
401 self.idx[i] = NO_BLOCK;
402 self.used -= 1;
403 }
404 }
405}
406
407struct LayerTex {
409 t: Texture,
410 group: BindGroup,
411 ox: i32,
412 oy: i32,
413 w: i32,
414 h: i32,
415 fresh: bool, }
417
418#[derive(Default)]
421struct Upper {
422 tex: Option<LayerTex>,
423 rects: Vec<[i32; 4]>,
424 store: Store,
425}
426
427struct ImageTex {
429 _t: Texture,
430 group: BindGroup,
431}
432
433struct ImageLoad {
435 t: Texture,
436 group: BindGroup,
437 row: i32,
438}
439
440struct Image {
442 key: usize,
443 weak: Weak<Surface>, ver: u32,
445 bytes: u64,
446 acc: u64,
447 opaque: Option<bool>,
448 tex: Option<Rc<ImageTex>>,
449 load: Option<ImageLoad>,
450 tick: u64,
451 used_us: u64,
452}
453
454struct TileTex {
456 t: Texture,
457 group: BindGroup,
458 fresh: core::cell::Cell<bool>, }
460
461struct Tile {
462 tex: Rc<TileTex>,
463 tick: u64,
464}
465
466struct TileLevel {
468 scale: f64,
469 fx: f64,
470 fy: f64,
471 tiles: BTreeMap<(i32, i32), Tile>,
472}
473
474struct Canvas {
476 win: usize,
477 id: u32,
478 ver: u32,
479 view: CanvasView,
481 prev: CanvasView,
482 known: bool,
483 damaged: bool,
484 apart: bool,
486 until: u64,
487 moved: bool,
488 levels: Vec<TileLevel>,
489}
490
491struct TileDraw {
494 tex: Rc<TileTex>,
495 b: [i32; 4],
496 place: [i32; 4],
497}
498
499enum QuadSrc {
503 Image { tex: Rc<ImageTex>, src: Rc<Surface>, w: i32, h: i32, step: [u32; 2] },
504 Canvas { f: DrawRef, w: i32, h: i32, vis: [i32; 4], tiles: Vec<TileDraw>, copy: bool, loose: bool },
505 View { tex: Rc<ViewTex>, f: DrawRef, w: i32, h: i32, vis: [i32; 4] },
507}
508
509struct Quad {
511 x: i32,
512 y: i32,
513 src: QuadSrc,
514 rects: Vec<[i32; 4]>,
515}
516
517#[derive(Default)]
519struct Split {
520 upper: [Upper; UPPER],
521 active: bool,
523 linger_at: u64,
525 clean: bool,
527 drawn: bool,
529 err: Option<GpuError>,
530 m: Vec<u8>,
533 nr: Vec<u8>,
534 rects: Vec<[i32; 4]>,
535 cols: usize,
538 rows: usize,
539 hole: Vec<u8>,
540 owed: Vec<bool>,
541 stale: Vec<bool>,
542 any_owed: bool,
543 any_stale: bool,
544 quads: Vec<Quad>,
545 part: Vec<bool>,
548 redraw: Vec<bool>,
549 moved: bool,
550}
551
552impl Split {
553 fn reset(&mut self) {
555 *self = Split::default();
556 }
557}
558
559struct Win {
561 pw: i32,
562 stage: Stage,
563 sc: Option<Swapchain>, surf: Option<gpu::Surface>, tex: Option<Tex>,
566 rb: Option<Buffer>, #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
568 rb_px: Vec<u8>,
569 rb_keep: Option<Vec<u8>>, full: bool, undef: bool, pending: bool, hidden: bool, test_hold: bool, apart_before: bool, last_present: u64,
577 job: u32, lost: bool, size: (i32, i32),
580 stable_at: u64,
581 fail_at: u64, fail_ms: u64,
583 out: GpuOutput,
584 sp: Split,
585 #[cfg(target_arch = "wasm32")]
586 verify_wait: Vec<Verify>,
587}
588
589#[cfg(target_arch = "wasm32")]
591struct Verify {
592 rb: Buffer,
593 fb: Surface,
594 loose: Vec<[i32; 4]>,
595 late: Vec<[i32; 4]>,
599}
600
601#[cfg(target_arch = "wasm32")]
603struct Probe {
604 job: u32,
605 win: usize,
606 core: Core,
607 built: Built,
608 rb: Buffer,
609 px: Vec<u8>,
610 at: u64,
611}
612
613struct Core {
614 inst: Instance,
615 dev: Device,
616 bgl: BindGroupLayout,
617 pipe: RenderPipeline,
618 pipe_image: RenderPipeline,
619 pipe_tile: RenderPipeline,
620 format: Format,
621 name: String,
622}
623
624enum Dev {
625 None,
626 Starting(u32),
627 Ready(Core),
628 Retry(u64),
629 Never(String),
630}
631
632struct Built {
633 surf: gpu::Surface,
634 sc: Swapchain,
635 tex: Texture,
636 group: BindGroup,
637}
638
639enum Done {
640 Device { job: u32, win: usize, r: gpu::Result<(Core, Built)> },
641 Window { job: u32, win: usize, r: gpu::Result<Built> },
642}
643
644struct Jobs {
645 done: SpinLock<Vec<Done>>,
646 running: AtomicU32,
647}
648
649pub(crate) struct S {
651 want: bool,
652 verify: bool,
653 trace: bool,
654 script: bool,
655 skip: bool,
656 dev: Dev,
657 wins: Vec<Option<Win>>,
658 jobs: Arc<Jobs>,
659 threads: Vec<plat::Thread>,
660 next_job: u32,
661 retry_ms: u64,
662 ready_at: u64,
663 trim_at: u64,
664 t0: u64,
665 script_pw: [i32; super::consts::WIN_MAX],
666 script_asked: [(i32, i32); super::consts::WIN_MAX],
667 layers_on: bool,
668 images_on: bool,
669 images: Vec<Image>,
670 image_budget: u64,
671 image_bytes: u64,
672 image_uploads: u64,
673 image_hits: u64,
674 image_evicted: u64,
675 tick: u64,
676 look_at: u64,
677 canvases_on: bool,
678 canvases: Vec<Canvas>,
679 canvas_cpu: String,
680 canvas_pan: String,
681 canvas_zoom: String,
682 canvas_budget: u64,
683 canvas_settle_us: u64,
684 canvas_unused_us: u64,
685 tile_bytes: u64,
686 tile_free: Vec<Rc<TileTex>>,
687 tile_sf: Surface,
688 tile_fills: u64,
689 tile_fill_us: u64,
690 tile_evicted: u64,
691 canvas_settles: u64,
692 canvas_free_at: u64,
693 said_ok: bool, tile_marked: u64, diff_marked: u64, dev_gen: u32,
698 views_on: bool,
700 want3d: bool,
701 #[cfg(target_arch = "wasm32")]
702 web_inst: Option<Instance>, #[cfg(target_arch = "wasm32")]
704 probe: Option<Probe>,
705}
706
707impl Default for S {
708 fn default() -> S {
709 S {
710 want: false,
711 verify: false,
712 trace: false,
713 script: false,
714 skip: false,
715 dev: Dev::None,
716 wins: Vec::new(),
717 #[allow(clippy::arc_with_non_send_sync)]
719 jobs: Arc::new(Jobs { done: SpinLock::new(Vec::new()), running: AtomicU32::new(0) }),
720 threads: Vec::new(),
721 next_job: 1,
722 retry_ms: 0,
723 ready_at: 0,
724 trim_at: 0,
725 t0: 0,
726 script_pw: [-1; super::consts::WIN_MAX],
727 script_asked: [(0, 0); super::consts::WIN_MAX],
728 layers_on: false,
729 images_on: false,
730 images: Vec::new(),
731 image_budget: IMAGE_BUDGET,
732 image_bytes: 0,
733 image_uploads: 0,
734 image_hits: 0,
735 image_evicted: 0,
736 tick: 0,
737 look_at: 0,
738 canvases_on: false,
739 canvases: Vec::new(),
740 canvas_cpu: String::new(),
741 canvas_pan: String::new(),
742 canvas_zoom: String::new(),
743 canvas_budget: CANVAS_BUDGET,
744 canvas_settle_us: SETTLE_US,
745 canvas_unused_us: CANVAS_UNUSED_US,
746 tile_bytes: 0,
747 tile_free: Vec::new(),
748 tile_sf: Surface::default(),
749 tile_fills: 0,
750 tile_fill_us: 0,
751 tile_evicted: 0,
752 canvas_settles: 0,
753 canvas_free_at: 0,
754 said_ok: false,
755 tile_marked: 0,
756 diff_marked: 0,
757 dev_gen: 0,
758 views_on: true,
759 want3d: false,
760 #[cfg(target_arch = "wasm32")]
761 web_inst: None,
762 #[cfg(target_arch = "wasm32")]
763 probe: None,
764 }
765 }
766}
767
768fn env_on(name: &str) -> bool {
769 plat::env(name).is_some_and(|v| v == "1" || v.eq_ignore_ascii_case("on"))
770}
771
772fn wanted() -> bool {
773 let on = match plat::env("jfgui_GPU").as_deref() {
774 Some("0") | Some("off") => false,
775 Some("1") | Some("on") => true,
776 _ if cfg!(target_arch = "wasm32") => DEFAULT_ON_BROWSER && !plat::args().iter().any(|a| a == "--agent"),
778 _ if cfg!(target_vendor = "apple") => DEFAULT_ON_APPLE,
779 _ => if cfg!(windows) { DEFAULT_ON } else { DEFAULT_ON_LINUX }, };
781 let output = !plat::env("jfgui_GPU_OUTPUT").is_some_and(|v| v == "0" || v.eq_ignore_ascii_case("off"));
783 on && output && !plat::win_remote_session()
784}
785
786fn ms(a: u64, b: u64) -> f64 {
787 b.saturating_sub(a) as f64 / 1000.0
788}
789
790impl S {
791 fn setup(&mut self) {
792 self.verify = env_on("jfgui_GPU_VERIFY");
793 self.trace = env_on("jfgui_GPU_TRACE");
794 self.t0 = plat::time_us();
795 self.retry_ms = plat::env("jfgui_GPU_RETRY_MS").and_then(|v| v.parse().ok()).unwrap_or(RETRY_FIRST_MS);
796 let off = |name: &str| plat::env(name).is_some_and(|v| v == "0" || v.eq_ignore_ascii_case("off"));
797 self.layers_on = !off("jfgui_GPU_LAYERS");
798 self.images_on = self.layers_on && !off("jfgui_GPU_IMAGES");
799 if let Some(mb) = plat::env("jfgui_GPU_IMAGE_MB").and_then(|v| v.parse::<u64>().ok()) {
800 self.image_budget = mb << 20;
801 }
802 self.canvases_on = self.layers_on && !off("jfgui_GPU_CANVAS");
803 self.canvas_cpu = plat::env("jfgui_GPU_CANVAS_CPU").unwrap_or_default();
804 self.canvas_pan = plat::env("jfgui_GPU_CANVAS_PAN").unwrap_or_default();
805 self.canvas_zoom = plat::env("jfgui_GPU_CANVAS_ZOOM").unwrap_or_default();
806 if let Some(mb) = plat::env("jfgui_GPU_CANVAS_MB").and_then(|v| v.parse::<u64>().ok()) {
807 self.canvas_budget = mb << 20;
808 }
809 if let Some(ms) = plat::env("jfgui_GPU_CANVAS_SETTLE_MS").and_then(|v| v.parse::<u64>().ok()) {
810 self.canvas_settle_us = ms * 1000;
811 }
812 self.views_on = self.layers_on && !off("jfgui_GPU_VIEWS");
813 }
814
815 fn canvas_mode(&self, name: &str, gpu: CanvasGpu) -> CanvasGpu {
817 let named = |list: &str| list.split(',').any(|n| n == name);
818 if named(&self.canvas_cpu) {
819 CanvasGpu::Cpu
820 } else if named(&self.canvas_zoom) {
821 CanvasGpu::Zoom
822 } else if named(&self.canvas_pan) {
823 CanvasGpu::Pan
824 } else {
825 gpu
826 }
827 }
828
829 fn canvases_gone(&mut self) {
831 for c in self.canvases.iter_mut() {
832 c.levels.clear();
833 c.apart = false;
834 c.moved = false;
835 }
836 self.tile_free.clear();
837 self.tile_bytes = 0;
838 self.tile_sf = Surface::default();
839 self.canvas_free_at = 0;
840 }
841
842 fn tile_texture(&mut self, tick: u64) -> Option<Rc<TileTex>> {
845 if self.tile_bytes + TILE_BYTES <= self.canvas_budget {
846 if let Some(k) = self.tile_free.iter().position(|t| Rc::strong_count(t) == 1) {
847 return Some(self.tile_free.swap_remove(k));
848 }
849 }
850 while self.tile_bytes + TILE_BYTES > self.canvas_budget {
852 if self.tile_free.pop().is_some() {
853 self.tile_bytes -= TILE_BYTES; continue;
855 }
856 let mut old: Option<(usize, usize, (i32, i32), u64)> = None;
857 for (ci, c) in self.canvases.iter().enumerate() {
858 for (li, l) in c.levels.iter().enumerate() {
859 for (key, t) in l.tiles.iter() {
860 if t.tick != tick && old.is_none_or(|o| t.tick < o.3) {
861 old = Some((ci, li, *key, t.tick));
862 }
863 }
864 }
865 }
866 let (ci, li, key, _) = old?;
867 self.canvases[ci].levels[li].tiles.remove(&key)?;
868 self.tile_evicted += 1;
869 self.tile_bytes -= TILE_BYTES;
870 }
871 let Dev::Ready(core) = &self.dev else { return None };
872 let t = core.dev.create_texture(&TextureDesc::d2(TILE_TEX as u32, TILE_TEX as u32, Format::Bgra8Unorm, TextureUsage::COPY_DST | TextureUsage::SAMPLED)).ok()?;
873 let group = core.dev.create_bind_group(&BindGroupDesc {
874 layout: &core.bgl,
875 entries: &[BindEntry { binding: 0, resource: Resource::Texture(t.view()) }],
876 }).ok()?;
877 self.tile_bytes += TILE_BYTES;
878 Some(Rc::new(TileTex { t, group, fresh: core::cell::Cell::new(true) }))
879 }
880
881 fn tiles_free(&mut self, tiles: BTreeMap<(i32, i32), Tile>) {
883 self.tile_free.extend(tiles.into_values().map(|t| t.tex));
884 }
885
886 fn evict(&mut self, k: usize) {
888 let e = &mut self.images[k];
889 if e.tex.take().is_some() | e.load.take().is_some() {
890 self.image_bytes -= e.bytes;
891 self.image_evicted += 1;
892 self.trim_at = plat::time_us() + TRIM_US;
893 }
894 }
895
896 fn images_gone(&mut self) {
898 for e in self.images.iter_mut() {
899 e.tex = None;
900 e.load = None;
901 }
902 self.image_bytes = 0;
903 }
904
905 fn desc(&self) -> SwapchainDesc {
906 SwapchainDesc { present: PRESENT, copy_src: self.verify, min_images: IMAGES, ..SwapchainDesc::DEFAULT }
907 }
908
909 fn note(&self, what: &str) {
910 if self.trace {
911 plat::write_stderr(format!("gpu: {:9.2} ms {what}\n", ms(self.t0, plat::time_us())).as_bytes());
912 }
913 }
914
915 fn unshown(&self, i: usize) -> bool {
918 self.wins.get(i).and_then(|w| w.as_ref()).is_some_and(|w| w.stage == Stage::Gpu && w.pending)
919 }
920
921 fn win(&mut self, i: usize) -> Option<&mut Win> {
922 self.wins.get_mut(i).and_then(|w| w.as_mut())
923 }
924
925 #[cfg(target_arch = "wasm32")]
927 fn spawn(&mut self, f: impl FnOnce() -> Done + 'static) -> bool {
928 let d = f();
929 self.jobs.done.lock().push(d);
930 true
931 }
932
933 #[cfg(target_arch = "wasm32")]
937 fn web_start(&mut self) -> bool {
938 if self.probe.is_some() {
939 self.web_probe();
940 return true;
941 }
942 if !matches!(self.dev, Dev::None | Dev::Starting(WEB_PREPARING)) {
943 return matches!(self.dev, Dev::Starting(_));
944 }
945 let Some((i, pw)) = self.wins.iter().enumerate().find_map(|(i, w)| w.as_ref().filter(|w| w.stage == Stage::Cpu).map(|w| (i, w.pw))) else {
946 return true;
947 };
948 if self.web_inst.is_none() {
949 self.note("start: adapter and device asked for");
950 match Instance::new() {
951 Ok(inst) => {
952 self.web_inst = Some(inst);
953 self.dev = Dev::Starting(WEB_PREPARING);
954 }
955 Err(e) => {
956 self.note(&format!("no device: {e}"));
957 if let Some(w) = self.win(i) {
958 w.out.error = Some(format!("{e}"));
959 }
960 self.dev = Dev::Never(format!("{e}"));
961 return true;
962 }
963 }
964 }
965 let inst = self.web_inst.as_ref().map(|x| x.prepare());
966 match inst {
967 Some(Ok(true)) => {}
968 Some(Err(e)) => {
969 self.note(&format!("no device: {e}"));
970 self.web_inst = None;
971 if let Some(w) = self.win(i) {
972 w.out.error = Some(format!("{e}"));
973 }
974 self.dev = if matches!(e, GpuError::Failed(_)) {
975 let at = plat::time_us() + self.retry_ms * 1000;
976 self.retry_ms = (self.retry_ms * 2).min(RETRY_MAX_MS);
977 Dev::Retry(at)
978 } else {
979 Dev::Never(format!("{e}")) };
981 return true;
982 }
983 _ => return true,
984 }
985 let Some(nw) = plat::win_gpu_child(pw) else { return true };
987 let job = self.next_job;
988 self.next_job += 1;
989 let desc = self.desc();
990 let trace = self.trace;
991 let t0 = plat::time_us();
992 let r = build_device(self.web_inst.take(), nw, pw, None, desc, trace, self.want3d);
993 self.note(&format!("device, pipelines, swapchain: {:.2} ms on this thread", ms(t0, plat::time_us())));
994 if let Some(w) = self.win(i) {
995 w.stage = Stage::Job;
996 w.job = job;
997 }
998 self.dev = Dev::Starting(job);
999 match r.and_then(|(core, built)| {
1000 let (rb, px) = probe_start(&core)?;
1001 Ok(Probe { job, win: i, core, built, rb, px, at: plat::time_us() })
1002 }) {
1003 Ok(p) => self.probe = Some(p),
1004 Err(e) => self.jobs.done.lock().push(Done::Device { job, win: i, r: Err(e) }),
1005 }
1006 true
1007 }
1008
1009 #[cfg(target_arch = "wasm32")]
1011 fn web_probe(&mut self) {
1012 let Some(p) = &self.probe else { return };
1013 let mut got = vec![0u8; p.px.len()];
1014 let r = match p.rb.read(0, &mut got) {
1015 Err(GpuError::NotReady) if plat::time_us() < p.at + PROBE_US => return,
1016 Err(GpuError::NotReady) => Err(GpuError::Timeout),
1017 Err(e) => Err(e),
1018 Ok(()) if got == p.px => Ok(()),
1019 Ok(()) => Err(GpuError::Unsupported("the browser's WebGPU draws other pixels")),
1020 };
1021 let Some(p) = self.probe.take() else { return };
1022 self.note(&format!("the small picture: {:?}, {:.2} ms", r, ms(p.at, plat::time_us())));
1023 let (job, win) = (p.job, p.win);
1024 self.jobs.done.lock().push(Done::Device { job, win, r: r.map(|_| (p.core, p.built)) });
1025 }
1026
1027 #[cfg(not(target_arch = "wasm32"))]
1029 fn spawn(&mut self, f: impl FnOnce() -> Done + Send + 'static) -> bool {
1030 let jobs = self.jobs.clone();
1031 jobs.running.fetch_add(1, Ordering::AcqRel);
1032 let j = jobs.clone();
1033 let stack = plat::env("jfgui_GPU_WORKER_KB").and_then(|v| v.parse::<usize>().ok()).map_or(WORKER_STACK, |k| k << 10);
1035 let t = plat::gpu_thread_spawn(stack, Box::new(move || {
1036 let d = f();
1037 j.done.lock().push(d);
1038 j.running.fetch_sub(1, Ordering::AcqRel);
1039 }));
1040 match t {
1041 Some(t) => {
1042 self.threads.push(t);
1043 true
1044 }
1045 None => {
1046 jobs.running.fetch_sub(1, Ordering::AcqRel);
1047 false
1048 }
1049 }
1050 }
1051}
1052
1053#[cfg(target_arch = "wasm32")]
1057const WEB_PREPARING: u32 = 0;
1058#[cfg(target_arch = "wasm32")]
1060const PROBE_US: u64 = 5_000_000;
1061
1062#[cfg(target_arch = "wasm32")]
1065fn probe_start(core: &Core) -> gpu::Result<(Buffer, Vec<u8>)> {
1066 const N: u32 = 8;
1067 const ROW: usize = 256;
1068 let dev = &core.dev;
1069 let src = dev.create_texture(&TextureDesc::d2(N, N, Format::Bgra8Unorm, TextureUsage::COPY_DST | TextureUsage::SAMPLED))?;
1070 let group = dev.create_bind_group(&BindGroupDesc {
1071 layout: &core.bgl,
1072 entries: &[BindEntry { binding: 0, resource: Resource::Texture(src.view()) }],
1073 })?;
1074 let target = dev.create_texture(&TextureDesc::d2(N, N, core.format, TextureUsage::COLOR_TARGET | TextureUsage::COPY_SRC))?;
1075 let rb = dev.create_buffer(&BufferDesc { size: (ROW * N as usize) as u64, usage: BufferUsage::NONE, memory: MemoryKind::Readback })?;
1076 let mut px = vec![0u8; (N * N * 4) as usize];
1078 let mut want = vec![0u8; ROW * N as usize];
1079 for y in 0..N as usize {
1080 for x in 0..N as usize {
1081 let t = [(x * 30 + 1) as u8, (y * 30 + 2) as u8, ((x ^ y) * 17 + 3) as u8, 0x55];
1082 px[(y * N as usize + x) * 4..][..4].copy_from_slice(&t);
1083 let rgba = core.format == Format::Rgba8Unorm;
1084 let c = if rgba { [t[2], t[1], t[0], 255] } else { [t[0], t[1], t[2], 255] };
1085 want[y * ROW + x * 4..][..4].copy_from_slice(&c);
1086 }
1087 }
1088 dev.queue().write_texture_rects(&src, &[[0, 0, N as i32, N as i32]], &px, N * 4, Layout::Undefined, Layout::Sampled)?;
1089 let mut e = dev.encoder()?;
1090 e.begin_render(&RenderPassDesc {
1091 color: &[ColorAttachment { view: target.view(), load: Load::Discard, clear: [0.0; 4], store: true, before: Layout::Undefined, after: Layout::CopySrc }],
1092 depth: None,
1093 });
1094 let half = N / 2;
1095 e.set_render_pipeline(&core.pipe);
1096 e.set_bind_group(0, &group);
1097 e.push_constants(0, &[0i32; 4]);
1098 e.set_scissor(0, 0, N, half);
1099 e.draw(3, 1, 0, 0);
1100 e.set_render_pipeline(&core.pipe_image);
1101 e.set_bind_group(0, &group);
1102 e.push_constants(0, &[0i32, 0, 65536, 65536]);
1103 e.set_scissor(0, half, half, half);
1104 e.draw(3, 1, 0, 0);
1105 e.set_render_pipeline(&core.pipe_tile);
1106 e.set_bind_group(0, &group);
1107 e.set_scissor(half, half, half, half);
1108 e.draw(3, 1, 0, 0);
1109 e.end_render();
1110 e.copy_texture_to_buffer(TextureRegion::mip(&target, 0), &rb, DataLayout { offset: 0, bytes_per_row: ROW as u32, rows_per_image: 0 });
1111 dev.queue().submit([e.finish()?])?;
1112 Ok((rb, want))
1113}
1114
1115fn build_window(dev: &Device, bgl: &BindGroupLayout, surf: gpu::Surface, old: Option<Swapchain>, desc: &SwapchainDesc) -> gpu::Result<Built> {
1117 let sc = match old {
1118 Some(mut sc) => {
1119 sc.rebuild()?;
1120 sc
1121 }
1122 None => dev.create_swapchain(&surf, desc)?,
1123 };
1124 let (w, h) = sc.size();
1125 if w == 0 || h == 0 {
1126 return Err(GpuError::NotReady); }
1128 let tex = dev.create_texture(&TextureDesc::d2(w, h, Format::Bgra8Unorm, TextureUsage::COPY_DST | TextureUsage::SAMPLED))?;
1129 let group = dev.create_bind_group(&BindGroupDesc {
1130 layout: bgl,
1131 entries: &[BindEntry { binding: 0, resource: Resource::Texture(tex.view()) }],
1132 })?;
1133 Ok(Built { surf, sc, tex, group })
1134}
1135
1136fn fast_adapters(inst: &Instance) -> gpu::Result<Vec<Adapter>> {
1140 if plat::env("jfgui_GPU_ADAPTER").is_some_and(|s| !s.is_empty()) {
1141 return inst.adapter().map(|a| vec![a]);
1142 }
1143 let list: Vec<Adapter> = inst.adapters()?.into_iter().filter(|a| a.info().missing.is_none() && gpu::low_power_rank(a.info().kind, false).is_some()).collect();
1144 if list.is_empty() { Err(GpuError::NoAdapter) } else { Ok(list) }
1145}
1146
1147#[allow(clippy::too_many_arguments)]
1150fn build_device(prepared: Option<Instance>, nw: plat::NativeWindow, pw: i32, display: Option<(u32, u32)>, desc: SwapchainDesc, trace: bool, want3d: bool) -> gpu::Result<(Core, Built)> {
1151 let t0 = plat::time_us();
1152 #[cfg(target_os = "android")]
1153 if env_on("jfgui_GPU_TEST_DIE") {
1154 rt::os::exit(3); }
1156 let inst = match prepared {
1157 Some(inst) => inst, None => Instance::new()?,
1159 };
1160 let t1 = plat::time_us();
1161 let surf = gpu::Surface::native(&inst, nw, pw)?;
1162 let list: Vec<Adapter> = if want3d { fast_adapters(&inst)? } else { inst.adapters_low_power(display)? };
1163 let adapter = list.into_iter().find(|a| a.can_present(&surf)).ok_or(GpuError::NoAdapter)?;
1164 let t2 = plat::time_us();
1165 let dev = adapter.create_device(&DeviceDesc { max_level: if want3d { Level::Voll } else { Level::Basis } })?;
1166 let t3 = plat::time_us();
1167 let bgl = dev.create_bind_group_layout(&[Binding { binding: 0, kind: BindingKind::Texture, stages: Stages::FRAGMENT }])?;
1168 let built = build_window(&dev, &bgl, surf, None, &desc)?;
1169 let t4 = plat::time_us();
1170 let format = built.sc.format();
1171 #[cfg(not(target_arch = "wasm32"))]
1172 let (vs, fs, fs_image, fs_tile) = (ShaderModule::from_spirv(&dev, spv::VS)?, ShaderModule::from_spirv(&dev, spv::FS)?,
1173 ShaderModule::from_spirv(&dev, spv::FS_IMAGE)?, ShaderModule::from_spirv(&dev, spv::FS_TILE)?);
1174 #[cfg(target_arch = "wasm32")]
1176 let (vs, fs, fs_image, fs_tile) = {
1177 let m = ShaderModule::from_wgsl(&dev, GPU_OUT_WGSL)?;
1178 (m.clone(), m.clone(), m.clone(), m)
1179 };
1180 let layout = dev.create_pipeline_layout(&PipelineLayoutDesc { groups: &[&bgl], push_constants: 16 })?;
1181 let pipeline = |fragment: &'_ ShaderModule, entry: &'static str| {
1182 dev.create_render_pipeline(&RenderPipelineDesc {
1183 layout: &layout,
1184 vertex: ShaderStage::new(&vs, "vs"),
1185 fragment: Some(ShaderStage::new(fragment, entry)),
1186 buffers: &[],
1187 topology: Topology::Triangles,
1188 cull: Cull::None,
1189 front_cw: false,
1190 targets: &[ColorTarget { format, blend: None, write_mask: 0xF }],
1191 depth: None,
1192 })
1193 };
1194 let pipe = pipeline(&fs, "fs")?;
1195 let pipe_image = pipeline(&fs_image, "fs_image")?;
1196 let pipe_tile = pipeline(&fs_tile, "fs_tile")?;
1197 let t5 = plat::time_us();
1198 if trace {
1199 plat::write_stderr(format!(
1200 "gpu: worker: instance {:.2} ms, adapter {:.2}, device {:.2}, swapchain + texture {:.2}, pipeline {:.2} ({})\n",
1201 ms(t0, t1), ms(t1, t2), ms(t2, t3), ms(t3, t4), ms(t4, t5), adapter.info().name).as_bytes());
1202 }
1203 let name = adapter.info().name.clone();
1204 Ok((Core { inst, dev, bgl, pipe, pipe_image, pipe_tile, format, name }, built))
1205}
1206
1207fn upload(w: &mut Win, core: &Core, fb: &Surface, rects: Option<&[[i32; 4]]>, trim_at: &mut u64) -> gpu::Result<()> {
1211 let Some(tex) = &w.tex else { return Ok(()) };
1212 let all = [[0, 0, fb.w, fb.h]];
1213 let (rs, before) = match rects {
1214 Some(r) if !w.full && !w.undef => (r, Layout::Sampled),
1215 _ => (&all[..], Layout::Undefined),
1217 };
1218 core.dev.queue().write_texture_rects(&tex.t, rs, &fb.px, fb.stride as u32, before, Layout::Sampled)?;
1219 let bytes: u64 = rs.iter().map(|r| r[2].max(0) as u64 * r[3].max(0) as u64 * 4).sum();
1220 w.out.upload_bytes += bytes;
1221 w.out.upload_base += bytes;
1222 if let Some(sc) = &w.sc {
1223 sc.damage(if rects.is_some() && before == Layout::Sampled { Some(rs) } else { None });
1224 }
1225 if bytes > BIG_UPLOAD {
1226 *trim_at = plat::time_us() + TRIM_US;
1227 }
1228 w.full = false;
1229 w.undef = false;
1230 w.pending = true;
1231 Ok(())
1232}
1233
1234enum Shown {
1235 Done,
1236 Wait,
1238 NoArea,
1239 Stale,
1240}
1241
1242fn show(w: &mut Win, core: &Core, fb: &Surface) -> gpu::Result<Shown> {
1244 let (Some(sc), Some(tex)) = (w.sc.as_mut(), w.tex.as_ref()) else { return Ok(Shown::Stale) };
1245 let fr = match sc.try_acquire()? {
1246 TryAcquire::Frame(f) => f,
1247 TryAcquire::NotReady => return Ok(Shown::Wait),
1248 TryAcquire::NoArea => return Ok(Shown::NoArea),
1249 TryAcquire::Stale => return Ok(Shown::Stale),
1250 };
1251 let sc = &*sc;
1252 #[cfg(target_arch = "wasm32")]
1254 let rb_frame = match &w.rb {
1255 Some(_) => Some(core.dev.create_buffer(&BufferDesc { size: (fb.w as u64 * 4 + 255 & !255) * fb.h as u64, usage: BufferUsage::NONE, memory: MemoryKind::Readback })?),
1256 None => None,
1257 };
1258 #[cfg(target_arch = "wasm32")]
1259 let rb_now = rb_frame.as_ref();
1260 #[cfg(not(target_arch = "wasm32"))]
1261 let rb_now = w.rb.as_ref();
1262 let row_bytes = if cfg!(target_arch = "wasm32") { (fb.w as u32 * 4 + 255) & !255 } else { 0 };
1263 let mut e = core.dev.encoder()?;
1264 let after = if rb_now.is_some() { Layout::CopySrc } else { Layout::Present };
1265 e.begin_render(&RenderPassDesc {
1266 color: &[ColorAttachment { view: sc.view(&fr), load: Load::Discard, clear: [0.0; 4], store: true, before: Layout::Undefined, after }],
1267 depth: None,
1268 });
1269 e.set_render_pipeline(&core.pipe);
1270 e.set_bind_group(0, &tex.group);
1271 e.push_constants(0, &[0i32; 4]);
1272 e.draw(3, 1, 0, 0);
1273 let scissor = |e: &mut gpu::CommandEncoder, r: &[i32; 4]| e.set_scissor(r[0] as u32, r[1] as u32, r[2] as u32, r[3] as u32);
1274 if !w.sp.quads.is_empty() {
1276 for q in &w.sp.quads {
1277 match &q.src {
1278 QuadSrc::Image { tex, step, .. } => {
1279 e.set_render_pipeline(&core.pipe_image);
1280 e.set_bind_group(0, &tex.group);
1281 e.push_constants(0, &[q.x, q.y, step[0] as i32, step[1] as i32]);
1282 for r in &q.rects {
1283 scissor(&mut e, r);
1284 e.draw(3, 1, 0, 0);
1285 }
1286 }
1287 QuadSrc::View { tex, .. } => {
1288 let Some(group) = &tex.group else { continue };
1290 e.set_render_pipeline(&core.pipe);
1291 e.set_bind_group(0, group);
1292 e.push_constants(0, &[q.x, q.y, 0, 0]);
1293 for r in &q.rects {
1294 scissor(&mut e, r);
1295 e.draw(3, 1, 0, 0);
1296 }
1297 }
1298 QuadSrc::Canvas { tiles, copy, .. } => {
1299 e.set_render_pipeline(if *copy { &core.pipe } else { &core.pipe_tile });
1300 for t in tiles {
1301 let mut bound = false;
1302 for r in &q.rects {
1303 let (x0, y0) = (t.b[0].max(r[0]), t.b[1].max(r[1]));
1304 let (x1, y1) = (t.b[2].min(r[0] + r[2]), t.b[3].min(r[1] + r[3]));
1305 if x0 < x1 && y0 < y1 {
1306 if !bound {
1307 bound = true;
1308 e.set_bind_group(0, &t.tex.group);
1309 e.push_constants(0, &t.place);
1310 }
1311 scissor(&mut e, &[x0, y0, x1 - x0, y1 - y0]);
1312 e.draw(3, 1, 0, 0);
1313 }
1314 }
1315 }
1316 }
1317 }
1318 }
1319 e.set_render_pipeline(&core.pipe);
1320 }
1321 for u in w.sp.upper.iter().filter(|u| !u.rects.is_empty()) {
1323 if let Some(t) = &u.tex {
1324 e.set_bind_group(0, &t.group);
1325 e.push_constants(0, &[t.ox, t.oy, 0, 0]);
1326 for r in &u.rects {
1327 scissor(&mut e, r);
1328 e.draw(3, 1, 0, 0);
1329 }
1330 }
1331 }
1332 e.end_render();
1333 if let Some(rb) = rb_now {
1334 let img = sc.texture(&fr);
1335 e.copy_texture_to_buffer(TextureRegion::mip(img, 0), rb, DataLayout { bytes_per_row: row_bytes, ..DataLayout::default() });
1336 e.texture_barrier(img, Layout::CopySrc, Layout::Present);
1337 e.barrier(Access::COPY_WRITE, Access::HOST_READ);
1338 }
1339 let cmd = e.finish()?;
1340 let id = sc.present(fr, [cmd])?;
1341 w.pending = false;
1342 w.last_present = plat::time_us();
1343 w.out.frames += 1;
1344 #[cfg(target_arch = "wasm32")]
1345 {
1346 let _ = id;
1347 if let Some(rb) = rb_frame {
1348 let mut snap = Surface::new(fb.w, fb.h, FMT_XRGB);
1349 let n = snap.px.len();
1350 snap.px.copy_from_slice(&fb.px[..n]);
1351 let loose = w.sp.quads.iter().filter(|q| matches!(q.src, QuadSrc::Canvas { loose: true, .. })).flat_map(|q| q.rects.iter().copied()).collect();
1352 let late = w.sp.quads.iter().filter(|q| matches!(q.src, QuadSrc::View { .. })).flat_map(|q| q.rects.iter().copied()).collect();
1353 w.verify_wait.push(Verify { rb, fb: snap, loose, late });
1354 }
1355 }
1356 #[cfg(not(target_arch = "wasm32"))]
1357 if let Some(rb) = &w.rb {
1358 core.dev.wait(id, 5_000_000_000)?;
1359 sc.flush()?;
1360 let n = fb.w as usize * fb.h as usize * 4;
1361 w.rb_px.resize(n, 0);
1362 rb.read(0, &mut w.rb_px[..n])?;
1363 let swap_rb = core.format == Format::Rgba8Unorm;
1364 if let Some(keep) = w.rb_keep.as_mut() {
1365 keep.clear();
1366 keep.extend_from_slice(&w.rb_px[..n]);
1367 }
1368 for q in &w.sp.quads {
1370 if matches!(q.src, QuadSrc::Canvas { loose: true, .. }) {
1371 loose_compare(&mut w.rb_px, fb, &q.rects, swap_rb, &mut w.out);
1372 }
1373 }
1374 let (x, y, bad) = compare(&w.rb_px, fb, swap_rb);
1375 w.out.verified += 1;
1376 if bad != 0 {
1377 w.out.verify_bad += 1;
1378 plat::write_stderr(format!("gpu: the presented image differs from the framebuffer in {bad} pixels, first at {x}, {y}\n").as_bytes());
1379 }
1380 }
1381 Ok(Shown::Done)
1382}
1383
1384#[cfg(target_arch = "wasm32")]
1386fn verify_poll(w: &mut Win, core: &Core) {
1387 let swap_rb = core.format == Format::Rgba8Unorm;
1388 while let Some(v) = w.verify_wait.first_mut() {
1389 let row = v.fb.w as usize * 4;
1390 let padded = (row + 255) & !255;
1391 let mut raw = vec![0u8; padded * v.fb.h as usize];
1392 match v.rb.read(0, &mut raw) {
1393 Err(GpuError::NotReady) => return,
1394 Err(_) => {
1395 w.verify_wait.clear();
1396 return;
1397 }
1398 Ok(()) => {}
1399 }
1400 let mut img: Vec<u8> = Vec::with_capacity(row * v.fb.h as usize);
1401 for r in raw.chunks_exact(padded) {
1402 img.extend_from_slice(&r[..row]);
1403 }
1404 loose_compare(&mut img, &v.fb, &v.loose, swap_rb, &mut w.out);
1405 let mut ignore = GpuOutput::default();
1407 loose_compare(&mut img, &v.fb, &v.late, swap_rb, &mut ignore);
1408 let (x, y, bad) = compare(&img, &v.fb, swap_rb);
1409 w.out.verified += 1;
1410 if bad != 0 {
1411 w.out.verify_bad += 1;
1412 plat::write_stderr(format!("gpu: the presented image differs from the framebuffer in {bad} pixels, first at {x}, {y}\n").as_bytes());
1413 }
1414 w.verify_wait.remove(0);
1415 }
1416}
1417
1418fn compare(img: &[u8], fb: &Surface, swap_rb: bool) -> (i32, i32, u64) {
1422 let (mut fx, mut fy, mut bad) = (0, 0, 0u64);
1423 let row = fb.w as usize * 4;
1424 for y in 0..fb.h as usize {
1425 let a = &img[y * row..y * row + row];
1426 let b = &fb.px[y * fb.stride as usize..y * fb.stride as usize + row];
1427 for (x, (p, q)) in a.chunks_exact(4).zip(b.chunks_exact(4)).enumerate() {
1429 let same = if swap_rb { p[0] == q[2] && p[1] == q[1] && p[2] == q[0] } else { p[..3] == q[..3] };
1430 if !same || p[3] != 255 {
1431 if bad == 0 {
1432 fx = x as i32;
1433 fy = y as i32;
1434 }
1435 bad += 1;
1436 }
1437 }
1438 }
1439 (fx, fy, bad)
1440}
1441
1442fn loose_compare(img: &mut [u8], fb: &Surface, rects: &[[i32; 4]], swap_rb: bool, out: &mut GpuOutput) {
1445 let row = fb.w as usize * 4;
1446 let (mut n, mut sum, mut top) = (0u64, 0u64, 0u32);
1447 for r in rects {
1448 for y in r[1] as usize..(r[1] + r[3]) as usize {
1449 let a = &mut img[y * row + r[0] as usize * 4..y * row + (r[0] + r[2]) as usize * 4];
1450 let b = &fb.px[y * fb.stride as usize + r[0] as usize * 4..y * fb.stride as usize + (r[0] + r[2]) as usize * 4];
1451 for (p, q) in a.chunks_exact_mut(4).zip(b.chunks_exact(4)) {
1452 let c = if swap_rb { [q[2], q[1], q[0]] } else { [q[0], q[1], q[2]] };
1453 let d = (0..3).map(|i| p[i].abs_diff(c[i]) as u32).max().unwrap_or(0);
1454 if d != 0 {
1455 n += 1;
1456 sum += d as u64;
1457 top = top.max(d);
1458 p[..3].copy_from_slice(&c);
1459 }
1460 }
1461 }
1462 }
1463 out.canvas_diff_frames += (n != 0) as u64;
1464 out.canvas_diff_px += n;
1465 out.canvas_diff_sum += sum;
1466 out.canvas_diff_max = out.canvas_diff_max.max(top);
1467}
1468
1469fn flr(x: f64) -> f64 {
1471 let t = x as i64 as f64;
1472 if t > x { t - 1.0 } else { t }
1473}
1474
1475fn rnd(x: f64) -> f64 {
1476 flr(x + 0.5)
1477}
1478
1479fn level_for(c: &mut Canvas, v: CanvasView, zoom: bool) -> usize {
1483 let zoom = zoom && c.prev.scale != v.scale;
1484 let near = |l: &TileLevel, v: CanvasView| {
1485 let (dx, dy) = (v.ox - l.fx, v.oy - l.fy);
1486 (dx - rnd(dx)).abs() < PHASE_TOL && (dy - rnd(dy)).abs() < PHASE_TOL
1487 };
1488 let mut best: Option<(usize, f64)> = None;
1489 for (n, l) in c.levels.iter().enumerate() {
1490 let k = v.scale / l.scale;
1491 if k == 1.0 {
1492 if near(l, v) {
1493 return n;
1494 }
1495 } else if zoom && (K_MIN..=K_MAX).contains(&k) {
1496 let far = if k < 1.0 { 1.0 / k } else { k };
1497 if best.is_none_or(|b| far < b.1) {
1498 best = Some((n, far));
1499 }
1500 }
1501 }
1502 if let Some(b) = best {
1503 return b.0;
1504 }
1505 let p = c.prev;
1506 let from = if zoom && p.scale > 0.0 && (K_MIN..=K_MAX).contains(&(v.scale / p.scale)) { p } else { v };
1507 c.levels.push(TileLevel { scale: from.scale, fx: from.ox - flr(from.ox), fy: from.oy - flr(from.oy), tiles: BTreeMap::new() });
1508 c.levels.len() - 1
1509}
1510
1511fn cell_box(i: usize, cols: usize, w: i32, h: i32) -> [i32; 4] {
1515 let (c, r) = ((i % cols) as i32, (i / cols) as i32);
1516 [c * CELL, r * CELL, ((c + 1) * CELL).min(w), ((r + 1) * CELL).min(h)]
1517}
1518
1519fn cell_save(fb: &Surface, b: [i32; 4], to: &mut [u8]) {
1520 let n = (b[2] - b[0]) as usize * 4;
1521 for y in b[1]..b[3] {
1522 let s = y as usize * fb.stride as usize + b[0] as usize * 4;
1523 let d = (y - b[1]) as usize * CELL_ROW;
1524 to[d..d + n].copy_from_slice(&fb.px[s..s + n]);
1525 }
1526}
1527
1528fn cell_load(fb: &mut Surface, b: [i32; 4], from: &[u8]) {
1529 let n = (b[2] - b[0]) as usize * 4;
1530 for y in b[1]..b[3] {
1531 let d = y as usize * fb.stride as usize + b[0] as usize * 4;
1532 let s = (y - b[1]) as usize * CELL_ROW;
1533 fb.px[d..d + n].copy_from_slice(&from[s..s + n]);
1534 }
1535}
1536
1537fn cell_rects(out: &mut Vec<[i32; 4]>, cols: usize, rows: usize, w: i32, h: i32, f: impl Fn(usize) -> bool) {
1540 out.clear();
1541 for row in 0..rows {
1542 let mut c = 0;
1543 while c < cols {
1544 if !f(row * cols + c) {
1545 c += 1;
1546 continue;
1547 }
1548 let mut e = c + 1;
1549 while e < cols && f(row * cols + e) {
1550 e += 1;
1551 }
1552 let (x, rw, y) = (c as i32 * CELL, (e as i32 * CELL).min(w) - c as i32 * CELL, row as i32 * CELL);
1553 let rh = (y + CELL).min(h) - y;
1554 if let Some(r) = out.iter_mut().find(|r| r[0] == x && r[2] == rw && r[1] + r[3] == y) {
1555 r[3] += rh;
1556 } else {
1557 out.push([x, y, rw, rh]);
1558 }
1559 c = e;
1560 }
1561 }
1562}
1563
1564fn quad_blit(fb: &mut Surface, q: &Quad, b: [i32; 4], text: &mut Text) {
1566 let mut dc = Dc::new(fb.w, fb.h);
1567 dc.clip_push(b[0], b[1], b[2] - b[0], b[3] - b[1]);
1568 match &q.src {
1569 QuadSrc::Image { src, w, h, .. } => dc.blit_scaled(fb, src, q.x, q.y, *w, *h),
1570 QuadSrc::Canvas { f, w, h, vis, .. } | QuadSrc::View { f, w, h, vis, .. } => {
1572 dc.clip_push(vis[0], vis[1], vis[2] - vis[0], vis[3] - vis[1]);
1573 dc.clip_push(q.x, q.y, *w, *h);
1574 f.draw(&mut Gfx { dc: &mut dc, sf: fb, text }, [q.x, q.y, *w, *h]);
1575 dc.clip_pop();
1576 dc.clip_pop();
1577 }
1578 }
1579}
1580
1581fn surface_opaque(sf: &Surface) -> bool {
1583 if sf.fmt == FMT_XRGB {
1584 return true;
1585 }
1586 let (row, stride) = (sf.w as usize * 4, sf.stride as usize);
1587 sf.fmt != FMT_A8 && sf.px.chunks(stride).take(sf.h as usize).all(|r| r[..row].chunks_exact(4).all(|p| p[3] == 255))
1588}
1589
1590fn image_texture(core: &Core, sf: &Surface) -> gpu::Result<ImageLoad> {
1592 let t = core.dev.create_texture(&TextureDesc::d2(sf.w as u32, sf.h as u32, Format::Bgra8Unorm, TextureUsage::COPY_DST | TextureUsage::SAMPLED))?;
1593 let group = core.dev.create_bind_group(&BindGroupDesc {
1594 layout: &core.bgl,
1595 entries: &[BindEntry { binding: 0, resource: Resource::Texture(t.view()) }],
1596 })?;
1597 Ok(ImageLoad { t, group, row: 0 })
1598}
1599
1600fn layer_texture(core: &Core, bb: [i32; 4], fw: i32, fh: i32) -> gpu::Result<LayerTex> {
1602 let size = |n: i32, lim: i32| ((n + LAYER_SPARE + LAYER_STEP - 1) / LAYER_STEP * LAYER_STEP).min(lim);
1603 let (w, h) = (size(bb[2] - bb[0], fw), size(bb[3] - bb[1], fh));
1604 let ox = (bb[0] - (w - (bb[2] - bb[0])) / 2).clamp(0, fw - w);
1605 let oy = (bb[1] - (h - (bb[3] - bb[1])) / 2).clamp(0, fh - h);
1606 let t = core.dev.create_texture(&TextureDesc::d2(w as u32, h as u32, Format::Bgra8Unorm, TextureUsage::COPY_DST | TextureUsage::SAMPLED))?;
1607 let group = core.dev.create_bind_group(&BindGroupDesc {
1608 layout: &core.bgl,
1609 entries: &[BindEntry { binding: 0, resource: Resource::Texture(t.view()) }],
1610 })?;
1611 Ok(LayerTex { t, group, ox, oy, w, h, fresh: true })
1612}
1613
1614fn view_detach(e: &mut Entry) {
1618 if e.devgen == 0 {
1619 return;
1620 }
1621 if let Some(c) = e.content.upgrade() {
1622 if let Ok(mut c) = c.try_borrow_mut() {
1623 c.detach();
1624 }
1625 }
1626 e.info.detaches += 1;
1627 e.devgen = 0;
1628 e.output = false;
1629 e.refused = 0;
1630 e.tex = None;
1631 e.tried = None;
1632 e.apart = false;
1633 e.moved = false;
1634 e.rest_at = 0;
1635 e.held_at = 0;
1636 e.held_key = None;
1637 e.sh.c.borrow_mut().gpu = None;
1638}
1639
1640fn view_frame(e: &mut Entry, content: &Rc<core::cell::RefCell<dyn GpuContent>>, dev: &Device, bgl: Option<&BindGroupLayout>, size: (u32, u32)) -> gpu::Result<()> {
1646 let (w, h) = size;
1647 if e.tex.as_ref().is_none_or(|t| (t.w, t.h) != size) {
1648 e.tex = None;
1649 e.sh.c.borrow_mut().gpu = None;
1650 let t = dev.create_texture(&TextureDesc::d2(w, h, GPU_VIEW_FORMAT, TextureUsage::COLOR_TARGET | TextureUsage::SAMPLED | TextureUsage::COPY_SRC))?;
1651 let group = match bgl {
1652 Some(l) => Some(dev.create_bind_group(&BindGroupDesc { layout: l, entries: &[BindEntry { binding: 0, resource: Resource::Texture(t.view()) }] })?),
1653 None => None,
1654 };
1655 let stride = (w * 4 + 255) & !255;
1656 e.sh.c.borrow_mut().gpu = Some(Target {
1657 dev: dev.clone(),
1658 t: t.clone(),
1659 #[cfg(not(target_arch = "wasm32"))]
1661 rb: dev.create_buffer(&BufferDesc { size: stride as u64 * h as u64, usage: BufferUsage::NONE, memory: MemoryKind::Readback })?,
1662 stride,
1663 #[cfg(target_arch = "wasm32")]
1664 wait: None,
1665 });
1666 e.tex = Some(Rc::new(ViewTex { t, group, w, h, fresh: core::cell::Cell::new(true) }));
1667 }
1668 let Some(tex) = e.tex.clone() else { return Err(GpuError::Invalid("no target")) };
1669 let fresh = tex.fresh.get();
1670 if let Err(err) = view_draw(&tex, content, dev, size, if fresh { Layout::Undefined } else { Layout::Sampled }) {
1671 if fresh {
1674 e.tex = None;
1675 e.sh.c.borrow_mut().gpu = None;
1676 }
1677 return Err(err);
1678 }
1679 tex.fresh.set(false);
1680 e.sh.c.borrow_mut().frame += 1;
1681 Ok(())
1682}
1683
1684fn view_draw(tex: &ViewTex, content: &Rc<core::cell::RefCell<dyn GpuContent>>, dev: &Device, size: (u32, u32), before: Layout) -> gpu::Result<()> {
1686 let mut c = content.try_borrow_mut().map_err(|_| GpuError::Invalid("the view's content is borrowed"))?;
1687 let mut enc = dev.encoder()?;
1688 let target = ColorAttachment { view: tex.t.view(), load: Load::Clear, clear: [0.0, 0.0, 0.0, 1.0], store: true, before, after: Layout::Sampled };
1689 c.draw(&mut enc, target, size)?;
1690 let id = dev.queue().submit([enc.finish()?])?;
1691 c.submitted(id);
1692 Ok(())
1693}
1694
1695impl Ui {
1698 pub(crate) fn gp_window_mode(&mut self) {
1700 let want3d = self.desc.flags & AF_GPU_3D != 0;
1701 let s = &mut self.m.gout;
1702 s.want = wanted();
1703 s.want3d = want3d;
1704 s.setup();
1705 }
1706
1707 pub(crate) fn gp_present(&mut self, cur: u32, pw: i32, n: i32) -> bool {
1709 if !self.m.gout.want {
1710 return false;
1711 }
1712 self.gp_poll();
1713 let t0 = plat::time_us();
1714 let i = cur as usize;
1715 if self.m.gout.verify {
1716 self.gp_settle(i); }
1718 let s = &mut self.m.gout;
1719 let Dev::Ready(core) = &s.dev else { return false };
1720 let Some(Some(w)) = s.wins.get_mut(i) else { return false };
1721 if w.stage != Stage::Gpu {
1722 return false;
1723 }
1724 if let Some(e) = w.sp.err.take() {
1725 self.gp_failed(i, e);
1726 return true;
1727 }
1728 let fb = &self.wp.rt.fb;
1729 let fits = w.tex.as_ref().is_some_and(|t| (t.w, t.h) == (fb.w, fb.h));
1730 if !fits {
1731 self.gp_leave(i, "the size changed");
1732 return true;
1733 }
1734 if plat::win_state(pw) & plat::WST_MINIMIZED != 0 {
1735 self.gp_flatten(i); return true;
1737 }
1738 let rects = if n < 0 { None } else { Some(&self.drects[..n as usize]) };
1739 let soon = (t0 < w.last_present + MIN_PRESENT_US && !s.script) || cfg!(target_arch = "wasm32");
1742 let apart = w.sp.drawn || w.sp.active || !w.sp.quads.is_empty();
1744 let before = core::mem::replace(&mut w.apart_before, apart);
1745 if apart || before {
1746 if let Some(sc) = &w.sc {
1747 sc.damage(None);
1748 }
1749 }
1750 let up = if core::mem::take(&mut w.sp.drawn) { Ok(()) } else { upload(w, core, fb, rects, &mut s.trim_at) };
1752 let r = up.and_then(|_| if soon || w.test_hold { Ok(Shown::Wait) } else { show(w, core, fb) });
1753 w.out.present_us += plat::time_us().saturating_sub(t0);
1754 match r {
1755 Ok(Shown::Done) | Ok(Shown::Wait) => {}
1756 Ok(Shown::NoArea) => {
1757 self.gp_flatten(i);
1758 return true;
1759 }
1760 Ok(Shown::Stale) => {
1761 self.gp_leave(i, "stale");
1762 return true;
1763 }
1764 Err(e) => {
1765 self.gp_failed(i, e);
1766 return true;
1767 }
1768 }
1769 plat::present_account(t0, true);
1770 true
1771 }
1772
1773 pub(crate) fn gp_cpu_done(&mut self, cur: u32, pw: i32) {
1775 let s = &mut self.m.gout;
1776 if !s.want {
1777 return;
1778 }
1779 let i = cur as usize;
1780 if s.wins.len() <= i {
1781 s.wins.resize_with(i + 1, || None);
1782 }
1783 let now = plat::time_us();
1784 let w = s.wins[i].get_or_insert_with(|| Win {
1785 pw,
1786 stage: Stage::Cpu,
1787 sc: None,
1788 surf: None,
1789 tex: None,
1790 rb: None,
1791 rb_px: Vec::new(),
1792 rb_keep: None,
1793 full: true,
1794 undef: true,
1795 pending: false,
1796 hidden: false,
1797 test_hold: false,
1798 apart_before: false,
1799 last_present: 0,
1800 job: 0,
1801 lost: false,
1802 size: (0, 0),
1803 stable_at: now,
1804 fail_at: 0,
1805 fail_ms: 0,
1806 out: GpuOutput::default(),
1807 sp: Split::default(),
1808 #[cfg(target_arch = "wasm32")]
1809 verify_wait: Vec::new(),
1810 });
1811 w.pw = pw;
1812 w.out.cpu_frames += 1;
1813 let size = (self.wp.rt.fb.w, self.wp.rt.fb.h);
1814 if w.size != size {
1815 w.size = size;
1816 w.stable_at = now;
1817 }
1818 }
1819
1820 fn gp_leave(&mut self, i: usize, why: &str) {
1823 self.gp_settle(i);
1824 if let Some(p) = self.uiw_parts_mut(i as u32) {
1825 p.dirty.busy = false;
1826 }
1827 let Some(fb) = self.uiw_parts(i as u32).map(|p| &p.rt.fb) else { return };
1828 let size = (fb.w, fb.h);
1829 let Some(Some(w)) = self.m.gout.wins.get(i) else { return };
1830 let pw = w.pw;
1831 let t0 = plat::time_us();
1832 plat::win_present(pw, &Ui::frame_of(fb), None);
1833 plat::win_gpu_child_show(pw, false);
1834 let now = plat::time_us();
1835 let s = &mut self.m.gout;
1836 s.note(&format!("window {i}: back to the CPU path ({why}): {:.2} ms", ms(t0, now)));
1837 let Some(w) = s.win(i) else { return };
1838 w.stage = Stage::Cpu;
1839 w.tex = None;
1840 w.sp.reset();
1841 w.pending = false;
1842 w.size = size;
1843 w.stable_at = now;
1844 w.out.leaves += 1;
1845 #[cfg(target_arch = "wasm32")]
1847 {
1848 w.sc = None;
1849 w.surf = None;
1850 }
1851 w.out.cpu_frames += 1;
1852 }
1853
1854 fn gp_failed(&mut self, i: usize, e: GpuError) {
1857 plat::log_warn("gpu", &format!("output: {e}"));
1858 self.m.gout.note(&format!("window {i}: {e}"));
1859 if matches!(e, GpuError::SurfaceLost) {
1860 self.gp_leave(i, "surface lost");
1861 let now = plat::time_us();
1862 if let Some(w) = self.m.gout.win(i) {
1863 w.sc = None;
1864 w.surf = None;
1865 w.fail_ms = (w.fail_ms * 2).clamp(RETRY_FIRST_MS, RETRY_MAX_MS);
1866 w.fail_at = now + w.fail_ms * 1000;
1867 }
1868 return;
1869 }
1870 let n = self.m.gout.wins.len();
1871 for k in 0..n {
1872 let on = self.m.gout.wins[k].as_ref().is_some_and(|w| w.stage == Stage::Gpu);
1873 if on {
1874 self.gp_leave(k, "failure");
1875 }
1876 if let Some(w) = self.m.gout.win(k) {
1877 w.stage = Stage::Cpu;
1878 w.sc = None;
1879 w.surf = None;
1880 w.tex = None;
1881 w.rb = None;
1882 w.pending = false;
1883 w.sp.reset();
1884 w.out.error = Some(format!("{e}"));
1885 }
1886 }
1887 if matches!(self.m.gout.dev, Dev::Ready(_)) {
1889 let devgen = self.m.gout.dev_gen;
1890 self.gp_views_gone(Some(devgen));
1891 }
1892 let s = &mut self.m.gout;
1893 s.images_gone();
1894 s.canvases_gone();
1895 let now = plat::time_us();
1896 if matches!(e, GpuError::Invalid(_) | GpuError::Unsupported(_)) {
1897 s.dev = Dev::Never(format!("{e}"));
1898 return;
1899 }
1900 if now.saturating_sub(s.ready_at) > 60_000_000 {
1902 s.retry_ms = plat::env("jfgui_GPU_RETRY_MS").and_then(|v| v.parse().ok()).unwrap_or(RETRY_FIRST_MS);
1903 }
1904 s.dev = Dev::Retry(now + s.retry_ms * 1000);
1905 s.retry_ms = (s.retry_ms * 2).min(RETRY_MAX_MS);
1906 }
1907
1908 fn gp_poll(&mut self) {
1910 #[cfg(target_arch = "wasm32")]
1911 {
1912 let s = &mut self.m.gout;
1913 s.web_probe();
1914 if let Dev::Ready(core) = &s.dev {
1915 for w in s.wins.iter_mut().flatten() {
1916 verify_poll(w, core);
1917 }
1918 }
1919 }
1920 let done = core::mem::take(&mut *self.m.gout.jobs.done.lock());
1921 for d in done {
1922 match d {
1923 Done::Device { job, win, r } => {
1924 if !matches!(self.m.gout.dev, Dev::Starting(j) if j == job) {
1925 continue;
1926 }
1927 match r {
1928 Ok((core, built)) => {
1929 let s = &mut self.m.gout;
1930 s.note(&format!("device ready: {} ({:?})", core.name, core.dev.level()));
1931 s.dev = Dev::Ready(core);
1932 s.dev_gen += 1; s.ready_at = plat::time_us();
1934 self.gp_built(win, built);
1935 if !self.m.gview.views.is_empty() {
1936 self.invalidate(); }
1938 }
1939 Err(e) => {
1940 let s = &mut self.m.gout;
1941 s.note(&format!("no device: {e}"));
1942 match e {
1943 GpuError::DeviceLost | GpuError::OutOfMemory | GpuError::Timeout | GpuError::Failed(_) | GpuError::SurfaceLost
1944 | GpuError::NotReady => {
1945 s.dev = Dev::Retry(plat::time_us() + s.retry_ms * 1000);
1946 s.retry_ms = (s.retry_ms * 2).min(RETRY_MAX_MS);
1947 plat::gpu_out_state(plat::GPU_STATE_UNKNOWN);
1948 }
1949 _ => {
1950 s.dev = Dev::Never(format!("{e}")); plat::gpu_out_state(plat::GPU_STATE_NEVER);
1952 }
1953 }
1954 if let Some(w) = s.win(win) {
1955 w.stage = Stage::Cpu;
1956 w.lost = false;
1957 w.out.error = Some(format!("{e}"));
1958 #[cfg(target_arch = "wasm32")]
1960 plat::win_gpu_child_show(w.pw, false);
1961 }
1962 }
1963 }
1964 }
1965 Done::Window { job, win, r } => {
1966 let live = self.m.gout.win(win).is_some_and(|w| w.job == job && w.stage == Stage::Job);
1967 if !live {
1968 continue;
1969 }
1970 match r {
1971 Ok(b) => self.gp_built(win, b),
1972 Err(e) => {
1973 let now = plat::time_us();
1974 let s = &mut self.m.gout;
1975 s.note(&format!("window {win}: no swapchain: {e}"));
1976 if let Some(w) = s.win(win) {
1977 w.stage = Stage::Cpu;
1978 w.stable_at = now;
1979 if !core::mem::take(&mut w.lost) {
1980 w.out.error = Some(format!("{e}"));
1981 w.fail_ms = (w.fail_ms * 2).clamp(RETRY_FIRST_MS, RETRY_MAX_MS);
1982 w.fail_at = now + w.fail_ms * 1000;
1983 }
1984 }
1985 }
1986 }
1987 }
1988 }
1989 }
1990 let s = &mut self.m.gout;
1991 if !s.threads.is_empty() && s.jobs.running.load(Ordering::Acquire) == 0 {
1992 for t in s.threads.drain(..) {
1993 t.join();
1994 }
1995 }
1996 }
1997
1998 fn gp_built(&mut self, i: usize, b: Built) {
2000 let verify = self.m.gout.verify;
2001 let s = &mut self.m.gout;
2002 let Dev::Ready(core) = &s.dev else { return };
2003 let (dev, format) = (core.dev.clone(), core.format);
2004 let Some(w) = s.win(i) else { return };
2005 if core::mem::take(&mut w.lost) {
2006 w.stage = Stage::Cpu; return;
2008 }
2009 if b.sc.format() != format {
2010 w.stage = Stage::Cpu;
2011 w.fail_at = u64::MAX;
2012 return;
2013 }
2014 let (tw, th) = b.sc.size();
2015 let tex = Tex { t: b.tex, group: b.group, w: tw as i32, h: th as i32 };
2016 w.surf = Some(b.surf);
2017 w.sc = Some(b.sc);
2018 w.tex = Some(tex);
2019 w.full = true;
2020 w.undef = true;
2021 w.rb = None;
2022 if verify {
2023 let size = if cfg!(target_arch = "wasm32") { 4 } else { tw as u64 * th as u64 * 4 };
2025 w.rb = dev.create_buffer(&BufferDesc { size, usage: BufferUsage::NONE, memory: MemoryKind::Readback }).ok();
2026 }
2027 w.stage = Stage::Switch;
2028 if !unseen(w) {
2029 self.gp_switch(i);
2030 }
2031 }
2032
2033 fn gp_switch(&mut self, i: usize) {
2035 let t0 = plat::time_us();
2036 let mut s = core::mem::take(&mut self.m.gout);
2037 let mut failed = None;
2038 let mut again = false;
2039 let fb = self.uiw_parts(i as u32).map(|p| &p.rt.fb);
2040 if let (Dev::Ready(core), Some(Some(w)), Some(fb)) = (&s.dev, s.wins.get_mut(i), fb) {
2041 if w.tex.as_ref().is_none_or(|t| (t.w, t.h) != (fb.w, fb.h)) || !plat::win_gpu_child_alone(w.pw) {
2042 w.stage = Stage::Cpu; w.stable_at = t0;
2044 again = true;
2045 } else {
2046 let ready = w.sc.as_mut().map_or(Ok(true), |sc| sc.prepare());
2047 let shown = match ready {
2048 Ok(true) => upload(w, core, fb, None, &mut s.trim_at).and_then(|_| show(w, core, fb)),
2049 Ok(false) => Ok(Shown::Wait), Err(e) => Err(e),
2051 };
2052 match shown {
2053 Ok(Shown::Done) => {
2054 plat::win_gpu_child_show(w.pw, true);
2055 plat::win_release_kept(w.pw);
2056 w.stage = Stage::Gpu;
2057 w.out.switches += 1;
2058 w.out.error = None;
2059 if !core::mem::replace(&mut s.said_ok, true) {
2060 plat::gpu_out_state(plat::GPU_STATE_OK);
2061 }
2062 }
2063 Ok(Shown::Wait) => w.pending = true,
2064 Ok(Shown::NoArea) | Ok(Shown::Stale) => {
2065 w.stage = Stage::Cpu;
2066 w.stable_at = t0;
2067 }
2068 Err(e) => failed = Some(e),
2069 }
2070 }
2071 }
2072 self.m.gout = s;
2073 if let Some(e) = failed {
2074 self.gp_failed(i, e);
2075 } else if !again {
2076 let t1 = plat::time_us();
2077 let on = self.m.gout.wins.get(i).and_then(|w| w.as_ref()).is_some_and(|w| w.stage == Stage::Gpu);
2078 if on {
2079 self.m.gout.note(&format!("window {i}: switched to the swapchain ({:.2} ms)", ms(t0, t1)));
2080 }
2081 }
2082 }
2083
2084 pub(crate) fn gp_tick(&mut self) {
2086 if !self.m.gout.want {
2087 return;
2088 }
2089 self.gp_poll();
2090 #[cfg(target_arch = "wasm32")]
2092 {
2093 let e = if let Dev::Ready(core) = &self.m.gout.dev { core.dev.poll().err() } else { None };
2094 let e = e.or(plat::win_gpu_child_blank().then_some(GpuError::Unsupported("the canvas shows nothing")));
2096 if let Some(e) = e {
2097 self.gp_failed(0, e);
2098 return;
2099 }
2100 }
2101 let n = self.m.gout.wins.len();
2102 let now = plat::time_us();
2103 self.gp_canvas_tick(now);
2104 self.gp_views_tick(now);
2105 for w in self.m.gout.wins.iter_mut().flatten() {
2106 if w.sp.linger_at != 0 && now >= w.sp.linger_at {
2107 w.sp.linger_at = 0;
2108 for u in w.sp.upper.iter_mut().filter(|u| u.rects.is_empty()) {
2109 u.tex = None;
2110 u.store = Store::default();
2111 }
2112 self.m.gout.trim_at = now + TRIM_US; }
2114 }
2115 for i in 0..n {
2116 let Some(w) = self.m.gout.wins[i].as_ref() else { continue };
2117 if matches!(w.stage, Stage::Gpu | Stage::Switch) && w.sc.as_ref().is_some_and(|sc| sc.busy()) {
2119 if let Some(Err(e)) = w.sc.as_ref().map(|sc| sc.flush()) {
2120 self.gp_failed(i, e);
2121 continue;
2122 }
2123 }
2124 let Some(w) = self.m.gout.wins[i].as_ref() else { continue };
2125 match w.stage {
2126 Stage::Switch if !unseen(w) => self.gp_switch(i),
2127 Stage::Gpu if unseen(w) => self.gp_leave(i, "hidden"),
2128 Stage::Gpu if !plat::win_gpu_child_alone(w.pw) => self.gp_leave(i, "something else shows"),
2130 Stage::Gpu if w.pending && !w.test_hold && plat::time_us() >= w.last_present + MIN_PRESENT_US => self.gp_retry(i),
2131 _ => {}
2132 }
2133 }
2134 self.gp_views_wake(plat::time_us());
2135 self.gp_jobs();
2136 let s = &mut self.m.gout;
2137 if s.trim_at != 0 && plat::time_us() >= s.trim_at {
2138 s.trim_at = 0;
2139 if let Dev::Ready(core) = &s.dev {
2140 let _ = core.dev.poll();
2141 }
2142 }
2143 }
2144
2145 fn gp_retry(&mut self, i: usize) {
2148 if self.m.gout.verify {
2149 self.gp_settle(i);
2150 }
2151 let mut s = core::mem::take(&mut self.m.gout);
2152 let mut r = Ok(Shown::Done);
2153 let fb = self.uiw_parts(i as u32).map(|p| &p.rt.fb);
2154 if let (Dev::Ready(core), Some(Some(w)), Some(fb)) = (&s.dev, s.wins.get_mut(i), fb) {
2155 if plat::win_state(w.pw) & plat::WST_MINIMIZED == 0 {
2156 let t0 = plat::time_us();
2157 r = if w.full { upload(w, core, fb, None, &mut s.trim_at) } else { Ok(()) }.and_then(|_| show(w, core, fb));
2158 plat::present_account(t0, false);
2159 }
2160 }
2161 self.m.gout = s;
2162 match r {
2163 Ok(Shown::Stale) => self.gp_leave(i, "stale"),
2164 Err(e) => self.gp_failed(i, e),
2165 _ => {}
2166 }
2167 }
2168
2169 fn gp_jobs(&mut self) {
2171 let now = plat::time_us();
2172 let s = &mut self.m.gout;
2173 if plat::win_in_sizemove() {
2174 return;
2175 }
2176 match s.dev {
2177 Dev::None => {}
2178 Dev::Retry(at) if at <= now => s.dev = Dev::None,
2179 _ => {}
2180 }
2181 #[cfg(target_arch = "wasm32")]
2182 if s.web_start() {
2183 return;
2184 }
2185 if matches!(s.dev, Dev::None) {
2186 if plat::win_remote_session() {
2187 s.dev = Dev::Retry(now + REMOTE_LOOK_US); return;
2189 }
2190 let Some((i, pw)) = s.wins.iter().enumerate().find_map(|(i, w)| w.as_ref().filter(|w| w.stage == Stage::Cpu).map(|w| (i, w.pw))) else {
2191 return;
2192 };
2193 let Some(nw) = plat::win_gpu_child(pw) else {
2194 if !plat::win_gpu_child_later(pw) {
2195 s.dev = Dev::Never(String::from("no child window"));
2196 }
2197 return;
2198 };
2199 let display = plat::win_display_adapter(pw);
2200 let desc = s.desc();
2201 let job = s.next_job;
2202 s.next_job += 1;
2203 let (trace, want3d) = (s.trace, s.want3d);
2204 s.note("start: instance, device, swapchain on a worker");
2205 plat::gpu_out_state(plat::GPU_STATE_TRYING);
2206 let ok = s.spawn(move || Done::Device { job, win: i, r: build_device(None, nw, pw, display, desc, trace, want3d) });
2207 if ok {
2208 s.dev = Dev::Starting(job);
2209 if let Some(w) = s.win(i) {
2210 w.stage = Stage::Job;
2211 w.job = job;
2212 }
2213 } else {
2214 s.dev = Dev::Never(String::from("no thread"));
2215 }
2216 return;
2217 }
2218 let Dev::Ready(core) = &s.dev else { return };
2219 let (dev, bgl, inst) = (core.dev.clone(), core.bgl.clone(), core.inst.clone());
2220 let n = s.wins.len();
2221 let desc = s.desc();
2222 for i in 0..n {
2223 let Some(w) = s.wins[i].as_ref() else { continue };
2224 if w.stage != Stage::Cpu || unseen(w) || now < w.fail_at || now < w.stable_at + STABLE_US {
2225 continue;
2226 }
2227 let pw = w.pw;
2228 if plat::win_state(pw) & plat::WST_MINIMIZED != 0 || plat::win_size(pw) != w.size {
2229 continue;
2230 }
2231 let Some(nw) = plat::win_gpu_child(pw) else { continue };
2232 let job = s.next_job;
2233 s.next_job += 1;
2234 let Some(w) = s.wins[i].as_mut() else { continue };
2235 let old = w.sc.take();
2236 let surf = match w.surf.take() {
2237 Some(x) => {
2238 x.track_size(); x
2240 }
2241 None => match gpu::Surface::native(&inst, nw, pw) {
2242 Ok(x) => x,
2243 Err(_) => {
2244 w.fail_at = u64::MAX;
2245 continue;
2246 }
2247 },
2248 };
2249 w.stage = Stage::Job;
2250 w.job = job;
2251 #[allow(clippy::redundant_locals)] let desc = desc;
2253 let (d2, b2) = (dev.clone(), bgl.clone());
2254 s.note(&format!("window {i}: swapchain on a worker"));
2255 let ok = s.spawn(move || Done::Window { job, win: i, r: build_window(&d2, &b2, surf, old, &desc) });
2256 if !ok {
2257 if let Some(w) = s.win(i) {
2258 w.stage = Stage::Cpu;
2259 w.fail_at = u64::MAX;
2260 }
2261 }
2262 }
2263 }
2264
2265 pub(crate) fn gp_deadline(&self) -> Option<u64> {
2267 let s = &self.m.gout;
2268 if !s.want || s.wins.is_empty() {
2269 return None;
2270 }
2271 let now = plat::time_us();
2272 let mut d = u64::MAX;
2273 if s.jobs.running.load(Ordering::Acquire) != 0 {
2274 d = now + JOB_US;
2275 }
2276 match s.dev {
2277 Dev::None if s.wins.iter().flatten().next().is_some_and(|w| !plat::win_gpu_child_later(w.pw)) => d = now,
2278 Dev::Retry(at) => d = d.min(at),
2279 _ => {}
2280 }
2281 if s.trim_at != 0 {
2282 d = d.min(s.trim_at);
2283 }
2284 if !s.script {
2285 for c in s.canvases.iter().filter(|c| c.apart) {
2286 d = d.min(c.until);
2287 }
2288 }
2289 if s.canvas_free_at != 0 {
2290 d = d.min(s.canvas_free_at);
2291 }
2292 for e in self.m.gview.views.iter() {
2295 if e.rest_at != 0 {
2296 d = d.min(e.rest_at);
2297 }
2298 if e.held_at != 0 && !e.woke {
2299 d = d.min(if s.unshown(e.win) { e.held_at + VIEW_HOLD_US } else { now });
2300 }
2301 }
2302 let ready = matches!(s.dev, Dev::Ready(_));
2303 let moving = plat::win_in_sizemove();
2304 for w in s.wins.iter().flatten() {
2305 if w.sp.linger_at != 0 {
2306 d = d.min(w.sp.linger_at);
2307 }
2308 let minimised = plat::win_state(w.pw) & plat::WST_MINIMIZED != 0;
2309 let on_way = w.sc.as_ref().is_some_and(|sc| sc.busy());
2311 match w.stage {
2312 Stage::Switch if !minimised && !unseen(w) => d = d.min(now + RETRY_US),
2313 Stage::Gpu if on_way => d = d.min(now + RETRY_US.min(1000)),
2314 Stage::Gpu if w.pending && !minimised && !w.test_hold => {
2315 let retry = (w.last_present + MIN_PRESENT_US).max(now + RETRY_US.min(1000));
2316 d = d.min(w.sc.as_ref().map_or(retry, |sc| sc.next_look(retry)));
2317 }
2318 Stage::Cpu if ready && !minimised && !moving && !unseen(w) && w.fail_at != u64::MAX && !plat::win_gpu_child_later(w.pw) => {
2319 let settle = if plat::win_size(w.pw) == w.size { 1000 } else { 16_000 };
2321 d = d.min((w.stable_at + STABLE_US).max(w.fail_at).max(now + settle));
2322 }
2323 _ => {}
2324 }
2325 }
2326 if d == u64::MAX { None } else { Some(d) }
2327 }
2328
2329 #[cfg(target_os = "android")]
2334 pub(crate) fn gp_surface_gone(&mut self) -> bool {
2335 if !self.m.gout.want {
2336 return false;
2337 }
2338 let mut waits = false;
2339 for i in 0..self.m.gout.wins.len() {
2340 let Some(stage) = self.m.gout.wins[i].as_ref().map(|w| w.stage) else { continue };
2341 if stage == Stage::Gpu {
2342 self.gp_leave(i, "the surface went");
2343 waits = true;
2344 }
2345 let Some(w) = self.m.gout.win(i) else { continue };
2346 if stage == Stage::Job {
2347 w.lost = true; } else {
2349 w.stage = Stage::Cpu;
2350 }
2351 w.sc = None;
2352 w.surf = None;
2353 w.tex = None;
2354 w.rb = None;
2355 w.pending = false;
2356 w.sp.reset();
2357 }
2358 waits
2359 }
2360
2361 fn gp_wait_jobs(&mut self, ms: u64) {
2364 let dl = plat::time_us() + ms * 1000;
2365 while self.m.gout.jobs.running.load(Ordering::Acquire) != 0 && plat::time_us() < dl {
2366 plat::wait(plat::time_us() + 1000);
2367 }
2368 self.gp_poll();
2369 }
2370
2371 pub(crate) fn gp_window_gone(&mut self, i: u32) {
2373 let i = i as usize;
2374 let views = &mut self.m.gview.views;
2376 for e in views.iter_mut().filter(|e| e.win == i) {
2377 view_detach(e);
2378 }
2379 views.retain(|e| e.win != i);
2380 if self.m.gout.wins.get(i).and_then(|w| w.as_ref()).is_none() {
2381 return;
2382 }
2383 if self.m.gout.jobs.running.load(Ordering::Acquire) != 0 {
2384 self.gp_wait_jobs(3000);
2385 }
2386 if let Some(w) = self.m.gout.wins.get_mut(i) {
2387 *w = None;
2388 }
2389 let s = &mut self.m.gout;
2390 for k in (0..s.canvases.len()).rev() {
2391 if s.canvases[k].win == i {
2392 let c = s.canvases.remove(k);
2393 let n: usize = c.levels.iter().map(|l| l.tiles.len()).sum();
2394 s.tile_bytes -= n as u64 * TILE_BYTES;
2395 }
2396 }
2397 }
2398
2399 pub(crate) fn gp_window_show(&mut self, i: u32, show: bool) {
2402 let i = i as usize;
2403 let Some(stage) = self.m.gout.wins.get(i).and_then(|w| w.as_ref()).map(|w| w.stage) else { return };
2404 if !show && stage == Stage::Gpu {
2405 self.gp_leave(i, "hidden");
2406 }
2407 let now = plat::time_us();
2408 if let Some(w) = self.m.gout.win(i) {
2409 w.hidden = !show;
2410 w.stable_at = now;
2411 }
2412 }
2413
2414 pub(crate) fn gp_shutdown(&mut self) {
2417 self.gp_views_gone(None);
2419 self.m.gview.own = Own::None;
2420 if !self.m.gout.want {
2421 return;
2422 }
2423 let t0 = plat::time_us();
2424 self.gp_wait_jobs(2000);
2425 let s = &mut self.m.gout;
2426 for w in s.wins.iter().flatten() {
2428 plat::win_show(w.pw, plat::PWS_HIDE);
2429 }
2430 if env_on("jfgui_GPU_VALIDATE") {
2433 s.wins.clear();
2434 s.images.clear();
2435 s.canvases.clear();
2436 s.tile_free.clear();
2437 s.dev = Dev::None;
2438 } else {
2439 core::mem::forget(core::mem::take(&mut s.wins));
2440 core::mem::forget(core::mem::take(&mut s.images));
2441 core::mem::forget(core::mem::take(&mut s.canvases));
2442 core::mem::forget(core::mem::take(&mut s.tile_free));
2443 core::mem::forget(core::mem::replace(&mut s.dev, Dev::None));
2444 }
2445 s.want = false;
2446 s.note(&format!("shut down: {:.2} ms", ms(t0, plat::time_us())));
2447 }
2448
2449 pub fn gpu_output(&self, win: u32) -> GpuOutput {
2451 let s = &self.m.gout;
2452 let mut o = s.wins.get(win as usize).and_then(|w| w.as_ref()).map_or_else(GpuOutput::default, |w| {
2453 let mut o = w.out.clone();
2454 o.on = w.stage == Stage::Gpu;
2455 o.building = matches!(w.stage, Stage::Job | Stage::Switch);
2456 o.pending = w.pending;
2457 for u in &w.sp.upper {
2458 if let Some(t) = &u.tex {
2459 o.layer_textures += !u.rects.is_empty() as u32;
2460 o.layer_bytes += t.w as u64 * t.h as u64 * 4;
2461 }
2462 o.layer_store += u.store.px.capacity() as u64;
2463 }
2464 o.image_quads = w.sp.quads.iter().filter(|q| matches!(q.src, QuadSrc::Image { .. })).count() as u32;
2465 o.view_quads = w.sp.quads.iter().filter(|q| matches!(q.src, QuadSrc::View { .. })).count() as u32;
2466 o.canvas_quads = w.sp.quads.len() as u32 - o.image_quads - o.view_quads;
2467 o
2468 });
2469 o.wanted = s.want;
2470 o.image_textures = s.images.iter().filter(|e| e.tex.is_some()).count() as u32;
2471 o.image_bytes = s.image_bytes;
2472 o.image_uploads = s.image_uploads;
2473 o.image_hits = s.image_hits;
2474 o.image_evicted = s.image_evicted;
2475 o.canvas_apart = s.canvases.iter().filter(|c| c.apart && c.win == win as usize).count() as u32;
2476 o.canvas_tiles = s.canvases.iter().flat_map(|c| c.levels.iter()).map(|l| l.tiles.len() as u32).sum();
2477 o.canvas_bytes = s.tile_bytes;
2478 o.canvas_fills = s.tile_fills;
2479 o.canvas_fill_us = s.tile_fill_us;
2480 o.canvas_evicted = s.tile_evicted;
2481 o.canvas_settles = s.canvas_settles;
2482 match &s.dev {
2483 Dev::Ready(core) => {
2484 o.adapter = core.name.clone();
2485 o.reserved = core.dev.memory_reserved();
2486 o.present_mode = s.wins.get(win as usize).and_then(|w| w.as_ref()).and_then(|w| w.sc.as_ref()).map_or("", |sc| match sc.present_mode() {
2487 PresentMode::Fifo => "fifo",
2488 PresentMode::Mailbox => "mailbox",
2489 PresentMode::Immediate => "immediate",
2490 });
2491 }
2492 Dev::Never(why) => {
2493 o.never = true;
2494 if o.error.is_none() {
2495 o.error = Some(why.clone());
2496 }
2497 }
2498 _ => {}
2499 }
2500 o
2501 }
2502
2503 pub fn gpu_test_lose(&mut self) {
2506 if let Dev::Ready(core) = &self.m.gout.dev {
2507 core.dev.test_lose();
2508 }
2509 }
2510
2511 pub fn gpu_test_hold(&mut self, win: u32, on: bool) {
2514 if let Some(w) = self.m.gout.win(win as usize) {
2515 w.test_hold = on;
2516 }
2517 }
2518
2519 pub fn gpu_test_image_budget(&mut self, bytes: u64) {
2521 self.m.gout.image_budget = bytes;
2522 }
2523
2524 pub fn gpu_test_canvas_budget(&mut self, bytes: u64) {
2526 self.m.gout.canvas_budget = bytes;
2527 }
2528
2529 pub fn gpu_test_canvas_unused(&mut self, ms: u64) {
2531 self.m.gout.canvas_unused_us = ms * 1000;
2532 }
2533
2534 pub fn gpu_canvas_settle(&mut self) {
2536 let mut any = false;
2537 let s = &mut self.m.gout;
2538 for c in s.canvases.iter_mut().filter(|c| c.apart) {
2539 c.apart = false;
2540 any = true;
2541 s.canvas_settles += 1;
2542 }
2543 if any {
2544 self.invalidate();
2545 }
2546 }
2547
2548 pub fn gpu_settle(&mut self) {
2550 self.gp_settle_all();
2551 }
2552
2553 pub(crate) fn gp_split_wanted(&self) -> bool {
2557 let s = &self.m.gout;
2558 s.want && s.layers_on && s.wins.get(self.uiw_cur as usize).and_then(|w| w.as_ref()).is_some_and(|w| w.stage == Stage::Gpu)
2559 }
2560
2561 fn gp_can_split(&self, i: usize) -> bool {
2564 let Some(Some(w)) = self.m.gout.wins.get(i) else { return false };
2565 let fb = &self.wp.rt.fb;
2566 !self.m.ins.on && !w.full && !w.undef && w.tex.as_ref().is_some_and(|t| (t.w, t.h) == (fb.w, fb.h)) && plat::win_state(w.pw) & plat::WST_MINIMIZED == 0
2567 }
2568
2569 pub(crate) fn gp_plan_images(&mut self) -> bool {
2572 let i = self.uiw_cur as usize;
2573 if !self.gp_can_split(i) {
2574 return true; }
2576 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
2577 let opaque_dc = self.dc.opac == 255;
2578 let Ui { m, wp, .. } = self;
2579 let s = &mut m.gout;
2580 s.tick += 1;
2581 let (tick, now) = (s.tick, plat::time_us());
2582 let d = &wp.dirty;
2583 let (cols, rows) = (d.cols as usize, d.rows as usize);
2584 let total = cols * rows;
2585 let Some(Some(w)) = s.wins.get_mut(i) else { return false };
2586 let sp = &mut w.sp;
2587 if (sp.cols, sp.rows) != (cols, rows) || sp.hole.len() != total {
2588 (sp.cols, sp.rows) = (cols, rows);
2589 sp.hole = vec![0; total];
2590 sp.owed = vec![false; total];
2591 sp.stale = vec![false; total];
2592 sp.part = vec![false; total];
2593 sp.redraw = vec![false; total];
2594 sp.any_owed = false;
2595 sp.any_stale = false;
2596 }
2597 sp.hole.fill(0);
2598 sp.part.fill(false);
2599 let mut quads = core::mem::take(&mut sp.quads);
2600 quads.clear();
2601 let (cur, prev) = (d.cells.as_deref().unwrap_or(&[]), d.cells_prev.as_deref().unwrap_or(&[]));
2603 let mut k = 0;
2604 while k < s.images.len() {
2605 if s.images[k].weak.strong_count() == 0 {
2606 s.evict(k);
2607 s.images.remove(k);
2608 } else {
2609 k += 1;
2610 }
2611 }
2612 if s.images_on && opaque_dc {
2613 for (tag0, seen) in d.imgs.iter().enumerate() {
2614 let tag = tag0 as u8 + 1;
2615 let Some(Cmd::Image { x, y, w: iw, h: ih, sf, ver }) = wp.cmd.layers[0].get(seen.index as usize) else { continue };
2616 if sf.w <= 0 || sf.h <= 0 || sf.px.is_empty() || sf.fmt == FMT_A8 {
2617 continue;
2618 }
2619 let key = Rc::as_ptr(sf) as usize;
2620 let vis = seen.vis;
2621 let (c0, c1) = (((vis[0] + CELL - 1) >> 5).max(0) as usize, (((vis[2].min(fw) + (if vis[2] >= fw { CELL - 1 } else { 0 })) >> 5).max(0) as usize).min(cols));
2623 let (r0, r1) = (((vis[1] + CELL - 1) >> 5).max(0) as usize, (((vis[3].min(fh) + (if vis[3] >= fh { CELL - 1 } else { 0 })) >> 5).max(0) as usize).min(rows));
2624 let mut changed = 0u64;
2625 let mut any = false;
2626 for r in r0..r1 {
2627 for c in c0..c1 {
2628 let k = r * cols + c;
2629 if d.top.get(k) == Some(&tag) {
2630 any = true;
2631 if cur.get(k) != prev.get(k) {
2632 changed += CELL_BYTES as u64;
2633 }
2634 }
2635 }
2636 }
2637 if !any {
2638 continue;
2639 }
2640 let mut q = 0;
2642 while q < s.images.len() {
2643 if s.images[q].key == key && s.images[q].ver != *ver {
2644 s.evict(q);
2645 s.images.remove(q);
2646 } else {
2647 q += 1;
2648 }
2649 }
2650 let at = match s.images.iter().position(|e| e.key == key) {
2651 Some(at) => {
2652 s.images[at].acc += changed;
2653 at
2654 }
2655 None => {
2656 if s.images.len() >= IMAGES_KEPT {
2657 let Some(old) = (0..s.images.len()).min_by_key(|&k| s.images[k].tick) else { continue };
2658 s.evict(old);
2659 s.images.remove(old);
2660 }
2661 s.images.push(Image {
2662 key,
2663 weak: Rc::downgrade(sf),
2664 ver: *ver,
2665 bytes: sf.w as u64 * sf.h as u64 * 4,
2666 acc: 0,
2667 opaque: None,
2668 tex: None,
2669 load: None,
2670 tick,
2671 used_us: now,
2672 });
2673 s.images.len() - 1
2674 }
2675 };
2676 s.images[at].tick = tick;
2677 s.images[at].used_us = now;
2678 let e = &s.images[at];
2680 if e.tex.is_none() && e.load.is_none() && e.acc >= e.bytes && e.bytes <= s.image_budget {
2681 if let Dev::Ready(core) = &s.dev {
2682 let lim = core.dev.limits().max_texture_2d as i32;
2683 let opaque = e.opaque.unwrap_or_else(|| surface_opaque(sf));
2684 s.images[at].opaque = Some(opaque);
2685 if opaque && sf.w <= lim && sf.h <= lim {
2686 let need = s.images[at].bytes;
2687 while s.image_bytes + need > s.image_budget {
2688 let old = (0..s.images.len()).filter(|&k| (s.images[k].tex.is_some() || s.images[k].load.is_some()) && s.images[k].tick != tick).min_by_key(|&k| s.images[k].tick);
2689 let Some(old) = old else { break };
2690 s.evict(old);
2691 }
2692 if s.image_bytes + need <= s.image_budget {
2693 if let Dev::Ready(core) = &s.dev {
2694 match image_texture(core, sf) {
2697 Ok(load) => {
2698 s.images[at].load = Some(load);
2699 s.image_bytes += need;
2700 }
2701 Err(_) => s.images[at].opaque = Some(false),
2702 }
2703 }
2704 }
2705 }
2706 }
2707 }
2708 if let (Dev::Ready(core), Some(load)) = (&s.dev, s.images[at].load.as_mut()) {
2710 let t0 = plat::time_us();
2711 let rows = ((IMAGE_STEP_BYTES / (sf.w as u64 * 4)).max(1) as i32).min(sf.h - load.row);
2712 let before = if load.row == 0 { Layout::Undefined } else { Layout::Sampled };
2713 match core.dev.queue().write_texture_rects(&load.t, &[[0, load.row, sf.w, rows]], &sf.px, sf.stride as u32, before, Layout::Sampled) {
2714 Ok(()) => {
2715 load.row += rows;
2716 let done = load.row >= sf.h;
2717 let bytes = sf.w as u64 * rows as u64 * 4;
2718 s.trim_at = plat::time_us() + TRIM_US;
2719 if let Some(Some(w)) = s.wins.get_mut(i) {
2720 w.out.upload_bytes += bytes;
2721 }
2722 if done {
2723 if let Some(load) = s.images[at].load.take() {
2724 s.images[at].tex = Some(Rc::new(ImageTex { _t: load.t, group: load.group }));
2725 s.image_uploads += 1;
2726 }
2727 s.note(&format!("image {} x {} as a texture, the last part {:.2} ms", sf.w, sf.h, ms(t0, plat::time_us())));
2728 }
2729 }
2730 Err(_) => {
2731 s.evict(at);
2732 s.images[at].opaque = Some(false);
2733 }
2734 }
2735 }
2736 let Some(tex) = s.images[at].tex.clone() else { continue };
2737 if quads.len() >= TOP_BLOCKED as usize - 1 {
2738 continue;
2739 }
2740 s.image_hits += 1;
2741 let Some(Some(w)) = s.wins.get_mut(i) else { continue };
2742 let sp = &mut w.sp;
2743 let q = quads.len() as u8 + 1;
2744 for r in r0..r1 {
2745 for c in c0..c1 {
2746 let k = r * cols + c;
2747 if d.top.get(k) == Some(&tag) {
2748 sp.hole[k] = q;
2749 }
2750 }
2751 }
2752 let mut rects = Vec::new();
2753 let hole = &sp.hole;
2754 cell_rects(&mut rects, cols, rows, fw, fh, |k| hole[k] == q);
2755 let step = [(((sf.w as u64) << 16) / *iw as u64) as u32, (((sf.h as u64) << 16) / *ih as u64) as u32];
2756 quads.push(Quad { x: *x, y: *y, src: QuadSrc::Image { tex, src: sf.clone(), w: *iw, h: *ih, step }, rects });
2757 }
2758 }
2759 if now >= s.look_at && !s.images.is_empty() {
2761 s.look_at = now + IMAGE_UNUSED_US / 4;
2762 for k in 0..s.images.len() {
2763 if now.saturating_sub(s.images[k].used_us) > IMAGE_UNUSED_US {
2764 s.evict(k);
2765 }
2766 }
2767 }
2768 let Some(Some(w)) = s.wins.get_mut(i) else { return false };
2769 w.sp.quads = quads;
2770 !w.sp.quads.is_empty()
2771 }
2772
2773 pub(crate) fn gp_canvas_wanted(&self) -> bool {
2775 let s = &self.m.gout;
2776 s.want && s.canvases_on
2777 }
2778
2779 pub(crate) fn gp_canvas_steady(&self, id: u32, ver: u32) -> bool {
2782 let i = self.uiw_cur as usize;
2783 let s = &self.m.gout;
2784 s.want && s.canvases_on && !self.rtl && self.gp_split_wanted() && s.canvases.iter().find(|c| c.win == i && c.id == id).is_some_and(|c| c.ver == ver && !c.damaged)
2785 }
2786
2787 pub(crate) fn gp_canvas_begin(&mut self) {
2790 let i = self.uiw_cur as usize;
2791 let can = self.wp.dirty.split && !self.rtl && self.dc.opac == 255 && self.gp_can_split(i);
2792 let now = plat::time_us();
2793 let Ui { m, wp, .. } = self;
2794 let s = &mut m.gout;
2795 let (settle, unused) = (s.canvas_settle_us, s.canvas_unused_us);
2796 wp.dirty.canv.clear();
2797 for rec in wp.canv.recs.iter() {
2798 let d = &rec.d;
2799 let mode = s.canvas_mode(d.name, d.gpu);
2800 if mode == CanvasGpu::Cpu {
2801 continue;
2802 }
2803 let ci = s.canvases.iter().position(|c| c.win == i && c.id == d.id).unwrap_or_else(|| {
2804 s.canvases.push(Canvas {
2805 win: i,
2806 id: d.id,
2807 ver: d.ver,
2808 view: d.view,
2809 prev: d.view,
2810 known: false,
2811 damaged: false,
2812 apart: false,
2813 until: 0,
2814 moved: false,
2815 levels: Vec::new(),
2816 });
2817 s.canvases.len() - 1
2818 });
2819 let new_ver = s.canvases[ci].ver != d.ver;
2820 if new_ver {
2821 let levels = core::mem::take(&mut s.canvases[ci].levels);
2822 for l in levels {
2823 s.tiles_free(l.tiles);
2824 }
2825 }
2826 let c = &mut s.canvases[ci];
2827 let changed = new_ver || c.damaged;
2828 let moved = c.known && c.view != d.view && d.view.scale > 0.0 && d.view.scale < 1.0e6;
2829 let (dx, dy) = (d.view.ox - c.view.ox, d.view.oy - c.view.oy);
2830 let whole = d.view.scale == c.view.scale && (dx - rnd(dx)).abs() < PHASE_TOL && (dy - rnd(dy)).abs() < PHASE_TOL;
2831 c.moved = false;
2832 if changed || !can || rec.layer != 0 {
2833 c.apart = false; } else if moved {
2835 c.apart = mode == CanvasGpu::Zoom || whole;
2836 if c.apart {
2837 c.moved = true;
2838 c.until = now + settle;
2839 s.canvas_free_at = c.until + unused;
2840 }
2841 }
2842 let c = &mut s.canvases[ci];
2843 if moved {
2844 c.prev = c.view;
2845 }
2846 c.ver = d.ver;
2847 c.view = d.view;
2848 c.damaged = false;
2849 c.known = true;
2850 if c.apart {
2851 wp.dirty.canv.push((rec.index, d.rect));
2852 }
2853 }
2854 }
2855
2856 pub(crate) fn gp_plan_canvases(&mut self) -> bool {
2859 let i = self.uiw_cur as usize;
2860 if self.wp.dirty.canv.is_empty() {
2861 return false;
2862 }
2863 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
2864 let Ui { m, wp, text, .. } = self;
2865 let font = text.get_font();
2866 let s = &mut m.gout;
2867 s.tick += 1;
2868 let (tick, t0) = (s.tick, plat::time_us());
2869 let fill_end = if s.script { u64::MAX } else { t0 + FILL_US };
2870 let d = &wp.dirty;
2871 let (cols, rows) = (d.cols as usize, d.rows as usize);
2872 let Some(Some(w)) = s.wins.get_mut(i) else { return false };
2873 let mut quads = core::mem::take(&mut w.sp.quads);
2874 let mut sf = core::mem::take(&mut s.tile_sf);
2875 if sf.px.is_empty() {
2876 sf = Surface::new(TILE_TEX, TILE_TEX, FMT_XRGB);
2877 }
2878 for (tag0, seen) in d.imgs.iter().enumerate() {
2879 let Some(Cmd::Custom { x, y, w: cw, h: ch, f: Some(f), .. }) = wp.cmd.layers[0].get(seen.index as usize) else { continue };
2880 let Some(rec) = wp.canv.recs.iter().find(|r| r.layer == 0 && r.index == seen.index) else { continue };
2881 let Some(ci) = s.canvases.iter().position(|c| c.win == i && c.id == rec.d.id) else { continue };
2882 if quads.len() >= TOP_BLOCKED as usize - 1 {
2883 continue;
2884 }
2885 let (tag, vis, v) = (tag0 as u8 + 1, seen.vis, rec.d.view);
2886 let zoom = s.canvas_mode(rec.d.name, rec.d.gpu) == CanvasGpu::Zoom;
2887 let (moved, li) = {
2888 let c = &mut s.canvases[ci];
2889 (c.moved, level_for(c, v, zoom))
2890 };
2891 let mut li = li;
2892 if s.canvases[ci].levels.len() > LEVELS_MAX {
2893 let old = s.canvases[ci].levels.remove(0);
2894 s.tiles_free(old.tiles);
2895 li -= 1;
2896 }
2897 let (lscale, fx, fy) = {
2899 let l = &s.canvases[ci].levels[li];
2900 (l.scale, l.fx, l.fy)
2901 };
2902 let k = v.scale / lscale;
2903 let (dx, dy) = (v.ox - fx, v.oy - fy);
2904 let (rx, ry) = (rnd(dx), rnd(dy));
2905 let copy = k == 1.0 && (dx - rx).abs() < PHASE_TOL && (dy - ry).abs() < PHASE_TOL;
2906 let t = TILE as f64;
2907 let range = |w0: i32, w1: i32, o: f64, f: f64| -> (i32, i32) {
2908 let g = |w: f64| (w - o) / k + f;
2909 (flr(g(w0 as f64 - 1.0) / t) as i32, flr(g(w1 as f64 + 1.0) / t) as i32)
2910 };
2911 let (ix0, ix1) = range(vis[0], vis[2], v.ox, fx);
2912 let (iy0, iy1) = range(vis[1], vis[3], v.oy, fy);
2913 let edge = |g: f64, o: f64, f: f64| -> i32 { (flr(o + (g - f) * k - 0.5) + 1.0) as i32 };
2915 let mut tiles = Vec::new();
2916 let mut missing: Vec<[i32; 4]> = Vec::new();
2917 for iy in iy0..=iy1 {
2918 for ix in ix0..=ix1 {
2919 let (gx, gy) = (ix as f64 * t, iy as f64 * t);
2920 let (b, place) = if copy {
2921 let (x0, y0) = (rx as i32 + ix * TILE, ry as i32 + iy * TILE);
2922 ([x0, y0, x0 + TILE, y0 + TILE], [x0 - TILE_BORDER, y0 - TILE_BORDER, 0, 0])
2923 } else {
2924 let b = [edge(gx, v.ox, fx), edge(gy, v.oy, fy), edge(gx + t, v.ox, fx), edge(gy + t, v.oy, fy)];
2925 let at = |g: f64, o: f64, f: f64| rnd((o + (g - TILE_BORDER as f64 - f) * k) * 256.0) as i32;
2926 let step = rnd(65536.0 / k) as i32;
2927 (b, [at(gx, v.ox, fx), at(gy, v.oy, fy), step, step])
2928 };
2929 if b[0] >= vis[2] || b[2] <= vis[0] || b[1] >= vis[3] || b[3] <= vis[1] || b[0] >= b[2] || b[1] >= b[3] {
2930 continue;
2931 }
2932 if let Some(tile) = s.canvases[ci].levels[li].tiles.get_mut(&(ix, iy)) {
2933 tile.tick = tick;
2934 tiles.push(TileDraw { tex: tile.tex.clone(), b, place });
2935 continue;
2936 }
2937 let t1 = plat::time_us();
2938 let tex = if t1 < fill_end { s.tile_texture(tick) } else { None };
2939 let Some(tex) = tex else {
2940 missing.push(b);
2941 continue;
2942 };
2943 let tv = CanvasView { scale: lscale, ox: fx - gx + TILE_BORDER as f64, oy: fy - gy + TILE_BORDER as f64 };
2944 let mut dc = Dc::new(TILE_TEX, TILE_TEX);
2945 dc.begin(&sf);
2946 rec.d.content.draw_view(&mut Gfx { dc: &mut dc, sf: &mut sf, text: &mut *text }, tv, [0, 0, TILE_TEX, TILE_TEX]);
2947 dc.end();
2948 let Dev::Ready(core) = &s.dev else {
2949 missing.push(b);
2950 continue;
2951 };
2952 let before = if tex.fresh.replace(false) { Layout::Undefined } else { Layout::Sampled };
2953 if core.dev.queue().write_texture_rects(&tex.t, &[[0, 0, TILE_TEX, TILE_TEX]], &sf.px, sf.stride as u32, before, Layout::Sampled).is_err() {
2954 missing.push(b); continue;
2956 }
2957 s.tile_fills += 1;
2958 s.tile_fill_us += plat::time_us().saturating_sub(t1);
2959 s.trim_at = plat::time_us() + TRIM_US;
2960 if let Some(Some(w)) = s.wins.get_mut(i) {
2961 w.out.upload_bytes += TILE_BYTES;
2962 }
2963 tiles.push(TileDraw { tex: tex.clone(), b, place });
2964 s.canvases[ci].levels[li].tiles.insert((ix, iy), Tile { tex, tick });
2965 }
2966 }
2967 let Some(Some(w)) = s.wins.get_mut(i) else { continue };
2969 let sp = &mut w.sp;
2970 if sp.hole.len() != cols * rows {
2971 continue;
2972 }
2973 let q = quads.len() as u8 + 1;
2974 let span = |a: i32, b: i32, n: usize| ((a.max(0) >> 5) as usize, (((b + CELL - 1).max(0) >> 5) as usize).min(n));
2975 let (c0, c1) = span(vis[0], vis[2], cols);
2976 let (r0, r1) = span(vis[1], vis[3], rows);
2977 for r in r0..r1 {
2978 for c in c0..c1 {
2979 let k = r * cols + c;
2980 if d.top.get(k) == Some(&tag) {
2981 let b = cell_box(k, cols, fw, fh);
2982 sp.hole[k] = q;
2983 sp.part[k] = b[0] < vis[0] || b[1] < vis[1] || b[2] > vis[2] || b[3] > vis[3];
2984 if moved {
2985 sp.owed[k] = true;
2986 sp.stale[k] = true;
2987 }
2988 }
2989 if moved {
2990 sp.redraw[k] = true;
2991 }
2992 }
2993 }
2994 for b in &missing {
2995 let (c0, c1) = span(b[0].max(vis[0]), b[2].min(vis[2]), cols);
2996 let (r0, r1) = span(b[1].max(vis[1]), b[3].min(vis[3]), rows);
2997 for r in r0..r1 {
2998 for c in c0..c1 {
2999 let k = r * cols + c;
3000 if sp.hole[k] == q {
3001 sp.hole[k] = 0;
3002 sp.part[k] = false;
3003 }
3004 }
3005 }
3006 }
3007 sp.moved |= moved;
3008 w.out.canvas_frames += moved as u64;
3009 let mut rects = Vec::new();
3010 let hole = &sp.hole;
3011 cell_rects(&mut rects, cols, rows, fw, fh, |k| hole[k] == q);
3012 for r in rects.iter_mut() {
3013 let (x0, y0) = (r[0].max(vis[0]), r[1].max(vis[1]));
3014 let (x1, y1) = ((r[0] + r[2]).min(vis[2]), (r[1] + r[3]).min(vis[3]));
3015 *r = [x0, y0, x1 - x0, y1 - y0];
3016 }
3017 rects.retain(|r| r[2] > 0 && r[3] > 0);
3018 if !rects.is_empty() {
3019 let loose = !(rec.d.exact_pan && copy);
3020 quads.push(Quad { x: *x, y: *y, src: QuadSrc::Canvas { f: f.clone(), w: *cw, h: *ch, vis, tiles, copy, loose }, rects });
3021 }
3022 }
3023 s.tile_sf = sf;
3024 text.set_font(font.0, font.1);
3025 if let Some(Some(w)) = s.wins.get_mut(i) {
3026 w.sp.quads = quads;
3027 }
3028 true
3029 }
3030
3031 fn gp_canvas_tick(&mut self, now: u64) {
3034 let s = &mut self.m.gout;
3035 if !s.script && s.canvases.iter().any(|c| c.apart && now >= c.until) {
3036 for c in s.canvases.iter_mut().filter(|c| c.apart && now >= c.until) {
3037 c.apart = false;
3038 s.canvas_settles += 1;
3039 }
3040 self.invalidate();
3041 }
3042 let s = &mut self.m.gout;
3043 if s.canvas_free_at != 0 && now >= s.canvas_free_at {
3044 s.canvases_gone();
3045 s.trim_at = now + TRIM_US;
3046 }
3047 }
3048
3049 pub(crate) fn gp_canvas_damage(&mut self, id: u32, r: [f64; 4]) {
3051 let i = self.uiw_cur as usize;
3052 let S { canvases, tile_free, .. } = &mut self.m.gout;
3053 let Some(c) = canvases.iter_mut().find(|c| c.win == i && c.id == id) else { return };
3054 c.damaged = true;
3055 for l in c.levels.iter_mut() {
3056 let t = TILE as f64;
3057 let cell = |u: f64, f: f64, m: f64| flr((u * l.scale + f + m) / t) as i32;
3058 let (ix0, ix1) = (cell(r[0], l.fx, -2.0), cell(r[2], l.fx, 2.0));
3059 let (iy0, iy1) = (cell(r[1], l.fy, -2.0), cell(r[3], l.fy, 2.0));
3060 l.tiles.retain(|&(ix, iy), tile| {
3061 let hit = ix >= ix0 && ix <= ix1 && iy >= iy0 && iy <= iy1;
3062 if hit {
3063 tile_free.push(tile.tex.clone());
3064 }
3065 !hit
3066 });
3067 }
3068 }
3069
3070 fn gp_view_device(&mut self, now: u64) -> Option<(Device, u32, bool)> {
3076 let Mods { gout: s, gview, .. } = &mut self.m;
3077 if let (true, Dev::Ready(core)) = (s.want, &s.dev) {
3078 return Some((core.dev.clone(), s.dev_gen, true));
3079 }
3080 if let Own::Ready { dev, devgen, .. } = &gview.own {
3081 return Some((dev.clone(), *devgen, false));
3082 }
3083 if s.want && !matches!(s.dev, Dev::Never(_)) {
3084 return None;
3085 }
3086 match gview.own {
3087 Own::Never(_) => return None,
3088 Own::Retry(at) if now < at => return None,
3089 _ => {}
3090 }
3091 if cfg!(target_arch = "wasm32") {
3093 gview.own = Own::Never(String::from("the browser: views draw on the output's device"));
3094 return None;
3095 }
3096 let t0 = plat::time_us();
3097 let r = Instance::new().and_then(|inst| {
3098 let a = inst.adapter()?;
3099 let dev = a.create_device(&DeviceDesc::DEFAULT)?;
3100 Ok((inst, a.info().name.clone(), dev))
3101 });
3102 match r {
3103 Ok((inst, name, dev)) => {
3104 s.dev_gen += 1;
3105 s.note(&format!("views: a device of their own: {name} ({:?}), {:.2} ms", dev.level(), ms(t0, plat::time_us())));
3106 gview.own = Own::Ready { _inst: inst, dev: dev.clone(), devgen: s.dev_gen };
3107 Some((dev, s.dev_gen, false))
3108 }
3109 Err(e) => {
3110 s.note(&format!("views: no device of their own: {e}"));
3111 gview.own = match e {
3112 GpuError::DeviceLost | GpuError::OutOfMemory | GpuError::Timeout | GpuError::Failed(_) | GpuError::NotReady => Own::Retry(now + RETRY_FIRST_MS * 1000),
3113 _ => Own::Never(format!("{e}")), };
3115 None
3116 }
3117 }
3118 }
3119
3120 pub(crate) fn gp_views_begin(&mut self) {
3125 if self.wp.canv.views.is_empty() {
3126 return;
3127 }
3128 self.m.gview.views.retain(|e| e.content.strong_count() > 0);
3131 let i = self.uiw_cur as usize;
3132 let now = plat::time_us();
3133 let dev = self.gp_view_device(now);
3134 let pw = self.uiw_pw(i as u32);
3135 let unseen = pw >= 0 && plat::win_state(pw) & (plat::WST_MINIMIZED | plat::WST_HIDDEN) != 0;
3137 let can = self.wp.dirty.split && !self.rtl && self.dc.opac == 255 && self.m.gout.views_on && self.gp_can_split(i);
3138 let unshown = self.m.gout.unshown(i);
3140 let mut failed: Option<(GpuError, bool, u32)> = None;
3141 let Ui { m, wp, .. } = self;
3142 let Mods { gout: s, gview, .. } = m;
3143 let bgl = match &s.dev {
3144 Dev::Ready(core) if s.want => Some(core.bgl.clone()),
3145 _ => None,
3146 };
3147 let lim = dev.as_ref().map_or(0, |d| d.0.limits().max_texture_2d);
3148 for rec in wp.canv.views.iter_mut() {
3149 rec.apart = false;
3150 let Some(Cmd::Custom { x, y, w, h, ver, .. }) = wp.cmd.layers[rec.layer as usize].get_mut(rec.index as usize) else { continue };
3151 let Some(e) = gview.views.iter_mut().find(|e| e.win == i && e.id == rec.id) else { continue };
3152 let Some(content) = e.content.upgrade() else { continue };
3153 if let Some((d, devgen, out)) = &dev {
3155 if e.devgen != *devgen && e.refused != *devgen {
3156 if e.devgen != 0 {
3157 view_detach(e);
3158 }
3159 let r = match content.try_borrow_mut() {
3160 Ok(mut c) => c.attach(d),
3161 Err(_) => Err(GpuError::Invalid("the view's content is borrowed")),
3162 };
3163 match r {
3164 Ok(()) => {
3165 e.devgen = *devgen;
3166 e.output = *out;
3167 e.level = Some(d.level());
3168 e.info.attaches += 1;
3169 e.info.error = None;
3170 s.note(&format!("view {}: attached to device {devgen}", e.id));
3171 }
3172 Err(err) => {
3173 e.refused = *devgen;
3174 e.info.error = Some(format!("{err}"));
3175 }
3176 }
3177 }
3178 }
3179 let attached = e.devgen != 0 && dev.as_ref().is_some_and(|d| d.1 == e.devgen);
3180 let size = ((*w).max(0) as u32, (*h).max(0) as u32);
3181 e.info.size = size;
3182 e.moved = core::mem::take(&mut e.arrived);
3184 if e.moved {
3185 e.rest_at = now + VIEW_REST_US;
3186 }
3187 let key = (rec.ver, size, e.devgen);
3189 let due = attached && !unseen && size.0 > 0 && size.1 > 0 && size.0 <= lim && size.1 <= lim && e.tried != Some(key);
3190 e.woke = false;
3193 let hold = due && unshown && !(e.held_at != 0 && now >= e.held_at + VIEW_HOLD_US);
3194 if hold {
3195 if e.held_key != Some(key) {
3196 e.held_key = Some(key);
3197 e.info.held += 1;
3198 }
3199 if e.held_at == 0 {
3200 e.held_at = now;
3201 }
3202 } else {
3203 e.held_at = 0;
3204 e.held_key = None;
3205 }
3206 if due && !hold {
3207 e.tried = Some(key);
3208 let (d, _, out) = dev.as_ref().unwrap_or_else(|| unreachable!());
3209 let t0 = plat::time_us();
3210 match view_frame(e, &content, d, if *out { bgl.as_ref() } else { None }, size) {
3211 Ok(()) => {
3212 e.moved = true;
3213 e.rest_at = now + VIEW_REST_US;
3214 e.info.error = None;
3215 }
3216 Err(err) => {
3217 e.info.error = Some(format!("{err}"));
3218 if matches!(err, GpuError::DeviceLost | GpuError::OutOfMemory | GpuError::Failed(_) | GpuError::Timeout | GpuError::SurfaceLost) {
3219 failed = Some((err, *out, e.devgen));
3220 }
3221 }
3222 }
3223 e.info.draw_us += plat::time_us().saturating_sub(t0);
3224 }
3225 let frame = e.sh.c.borrow().frame;
3227 let fits = e.tex.as_ref().is_some_and(|t| (t.w, t.h) == size && t.group.is_some());
3228 let apart = can && attached && e.output && rec.layer == 0 && fits && frame != 0;
3229 rec.apart = apart;
3230 e.apart = apart;
3231 if apart {
3232 wp.dirty.canv.push((rec.index, [*x, *y, *w, *h]));
3233 }
3234 let read = if cfg!(target_arch = "wasm32") { e.sh.c.borrow().image_frame as u32 } else { 0 };
3237 *ver = rec.ver.wrapping_mul(0x9E37_79B9) ^ (frame as u32).wrapping_mul(0x85EB_CA6B) ^ (frame >> 32) as u32 ^ read.wrapping_mul(0xC2B2_AE35);
3238 }
3239 if let Own::Ready { devgen, .. } = gview.own {
3241 if !gview.views.iter().any(|e| e.devgen == devgen) {
3242 gview.own = Own::None;
3243 s.note("views: their own device goes");
3244 }
3245 }
3246 if let Some((err, out, devgen)) = failed {
3247 if out {
3248 self.gp_failed(i, err);
3250 self.wp.dirty.split = false;
3251 self.wp.dirty.canv.clear();
3252 for rec in self.wp.canv.views.iter_mut() {
3253 rec.apart = false;
3254 }
3255 } else {
3256 self.gp_views_gone(Some(devgen));
3257 self.m.gview.own = Own::Retry(now + RETRY_FIRST_MS * 1000);
3258 }
3259 }
3260 }
3261
3262 pub(crate) fn gp_plan_views(&mut self) -> bool {
3266 let i = self.uiw_cur as usize;
3267 if !self.wp.canv.views.iter().any(|v| v.apart) {
3268 return false;
3269 }
3270 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
3271 let Ui { m, wp, .. } = self;
3272 let Mods { gout: s, gview, .. } = m;
3273 let d = &wp.dirty;
3274 let (cols, rows) = (d.cols as usize, d.rows as usize);
3275 let Some(Some(w)) = s.wins.get_mut(i) else { return false };
3276 let sp = &mut w.sp;
3277 if sp.hole.len() != cols * rows {
3278 return false; }
3280 let mut quads = core::mem::take(&mut sp.quads);
3281 let span = |a: i32, b: i32, n: usize| ((a.max(0) >> 5) as usize, (((b + CELL - 1).max(0) >> 5) as usize).min(n));
3282 let mut any = false;
3283 for rec in wp.canv.views.iter().filter(|v| v.apart) {
3284 let Some(e) = gview.views.iter().find(|e| e.win == i && e.id == rec.id) else { continue };
3285 let Some(tex) = e.tex.clone() else { continue };
3286 let moved = e.moved;
3287 let Some(Cmd::Custom { x, y, w: cw, h: ch, .. }) = wp.cmd.layers[0].get(rec.index as usize) else { continue };
3288 let seen = d.imgs.iter().position(|s| s.index == rec.index);
3289 let q = quads.len() as u8 + 1;
3290 let Some(tag0) = seen.filter(|_| quads.len() < TOP_BLOCKED as usize - 1) else {
3291 if moved {
3293 let (c0, c1) = span(*x, x + cw, cols);
3294 let (r0, r1) = span(*y, y + ch, rows);
3295 for r in r0..r1 {
3296 sp.redraw[r * cols + c0..r * cols + c1.max(c0)].fill(true);
3297 }
3298 sp.moved = true;
3299 }
3300 continue;
3301 };
3302 let (tag, vis) = (tag0 as u8 + 1, d.imgs[tag0].vis);
3303 let (c0, c1) = span(vis[0], vis[2], cols);
3304 let (r0, r1) = span(vis[1], vis[3], rows);
3305 for r in r0..r1 {
3306 for c in c0..c1 {
3307 let k = r * cols + c;
3308 if d.top.get(k) == Some(&tag) {
3309 let b = cell_box(k, cols, fw, fh);
3310 sp.hole[k] = q;
3311 sp.part[k] = b[0] < vis[0] || b[1] < vis[1] || b[2] > vis[2] || b[3] > vis[3];
3312 if moved {
3313 sp.owed[k] = true;
3314 sp.stale[k] = true;
3315 }
3316 }
3317 if moved {
3318 sp.redraw[k] = true;
3319 }
3320 }
3321 }
3322 sp.moved |= moved;
3323 let mut rects = Vec::new();
3324 let hole = &sp.hole;
3325 cell_rects(&mut rects, cols, rows, fw, fh, |k| hole[k] == q);
3326 for r in rects.iter_mut() {
3327 let (x0, y0) = (r[0].max(vis[0]), r[1].max(vis[1]));
3328 let (x1, y1) = ((r[0] + r[2]).min(vis[2]), (r[1] + r[3]).min(vis[3]));
3329 *r = [x0, y0, x1 - x0, y1 - y0];
3330 }
3331 rects.retain(|r| r[2] > 0 && r[3] > 0);
3332 if !rects.is_empty() {
3333 quads.push(Quad { x: *x, y: *y, src: QuadSrc::View { tex, f: e.draw.clone(), w: *cw, h: *ch, vis }, rects });
3334 any = true;
3335 }
3336 }
3337 w.sp.quads = quads;
3338 any
3339 }
3340
3341 pub(crate) fn gp_views_gone(&mut self, devgen: Option<u32>) {
3344 for e in self.m.gview.views.iter_mut() {
3345 if e.devgen != 0 && devgen.is_none_or(|g| g == e.devgen) {
3346 view_detach(e);
3347 }
3348 }
3349 }
3350
3351 fn gp_views_tick(&mut self, now: u64) {
3354 let views = &mut self.m.gview.views;
3355 if views.is_empty() {
3356 return;
3357 }
3358 views.retain(|e| e.content.strong_count() > 0);
3359 #[cfg(target_arch = "wasm32")]
3364 {
3365 let Mods { gout, gview, .. } = &mut self.m;
3366 let mut came = false;
3367 for e in gview.views.iter_mut() {
3368 let mut c = e.sh.c.borrow_mut();
3369 let Some(g) = c.gpu.as_ref() else { continue };
3370 if g.wait.is_none() {
3371 if e.rest_at == 0 && c.image_frame != c.frame && c.error.is_none() {
3374 c.read();
3375 }
3376 continue;
3377 }
3378 let before = c.image_frame;
3379 c.read();
3380 if c.image_frame == before {
3381 continue;
3382 }
3383 e.arrived = true;
3384 e.rest_at = now + VIEW_REST_US;
3385 came = true;
3386 let Some(Some(w)) = gout.wins.get_mut(e.win) else { continue };
3387 let sp = &mut w.sp;
3388 let mine = Rc::as_ptr(&e.draw) as *const ();
3389 if let Some(q) = sp.quads.iter().position(|q| matches!(&q.src, QuadSrc::View { f, .. } if Rc::as_ptr(f) as *const () == mine)) {
3390 let tag = q as u8 + 1;
3391 for (owed, hole) in sp.owed.iter_mut().zip(sp.hole.iter()) {
3392 if *hole == tag {
3393 *owed = true;
3394 sp.any_owed = true;
3395 }
3396 }
3397 }
3398 }
3399 if came {
3400 self.invalidate();
3401 }
3402 }
3403 let views = &mut self.m.gview.views;
3404 let mut rest: Vec<usize> = Vec::new();
3405 for e in views.iter_mut().filter(|e| e.rest_at != 0 && now >= e.rest_at) {
3406 e.rest_at = 0;
3407 if !rest.contains(&e.win) {
3408 rest.push(e.win);
3409 }
3410 }
3411 for i in rest {
3412 self.gp_settle(i);
3413 }
3414 }
3415
3416 fn gp_views_wake(&mut self, now: u64) {
3420 let Mods { gout, gview, .. } = &mut self.m;
3421 let mut wake = false;
3422 for e in gview.views.iter_mut().filter(|e| e.held_at != 0 && !e.woke) {
3423 if !gout.unshown(e.win) || now >= e.held_at + VIEW_HOLD_US {
3424 e.woke = true;
3425 wake = true;
3426 }
3427 }
3428 if wake {
3429 self.invalidate();
3430 }
3431 }
3432
3433 pub(crate) fn gp_split_begin(&mut self, n: i32) -> bool {
3436 if !self.wp.dirty.split {
3437 return false;
3438 }
3439 let i = self.uiw_cur as usize;
3440 let can = self.gp_can_split(i);
3441 let upper_now = self.wp.cmd.layers[1..].iter().any(|l| !l.is_empty());
3442 let Some(w) = self.m.gout.win(i) else { return false };
3443 let sp = &mut w.sp;
3444 let kept = sp.active || !sp.quads.is_empty() || sp.any_stale || sp.moved;
3445 if !can {
3446 if kept {
3447 self.gp_flatten(i);
3448 }
3449 if let Some(w) = self.m.gout.win(i).filter(|_| upper_now) {
3450 w.sp.clean = false; }
3452 self.wp.dirty.busy = false;
3453 return false;
3454 }
3455 if !kept && !upper_now {
3456 sp.clean = true; self.wp.dirty.busy = false;
3458 return false;
3459 }
3460 let stale = sp.any_stale && sp.stale.iter().zip(sp.hole.iter()).any(|(&a, &h)| a && h == 0);
3461 if n == 0 && !stale && !sp.moved {
3462 return false; }
3464 if self.wp.rt.present_n == 0 {
3465 self.wp.rt.present_n = -1;
3466 }
3467 true
3468 }
3469
3470 pub(crate) fn gp_split_draw(&mut self, n: i32) {
3474 let icon_style = (self.style, self.color(T_ACCENT), self.theme_dark, self.color(T_TEXT_DISABLED), self.wp.rt.dpi as u32);
3475 let i = self.uiw_cur as usize;
3476 let Ui { m, wp, dc, text, icon_hook, drects, .. } = self;
3477 let s = &mut m.gout;
3478 let S { dev, wins, trim_at, .. } = s;
3479 let (Dev::Ready(core), Some(Some(w))) = (&*dev, wins.get_mut(i)) else {
3480 for l in wp.cmd.layers.iter() {
3481 render_layer(l, 0, dc, &mut wp.rt.fb, text, icon_hook, icon_style);
3482 }
3483 return;
3484 };
3485 let d = &wp.dirty;
3486 let (cols, rows) = (d.cols as usize, d.rows as usize);
3487 let total = cols * rows;
3488 let fb = &mut wp.rt.fb;
3489 let (fw, fh) = (fb.w, fb.h);
3490 let Win { sp, tex: base, out, pending, .. } = w;
3491 if sp.hole.len() != total {
3492 (sp.cols, sp.rows) = (cols, rows);
3493 sp.hole = vec![0; total];
3494 sp.owed = vec![false; total];
3495 sp.stale = vec![false; total];
3496 sp.part = vec![false; total];
3497 sp.redraw = vec![false; total];
3498 }
3499 let Split { upper, m: lowest, nr, rects, hole, owed, stale, quads, linger_at, part, redraw, .. } = sp;
3500 lowest.clear();
3502 lowest.resize(total, L_COUNT as u8);
3503 nr.clear();
3504 nr.resize(total, 0);
3505 let all = d.forced || (!sp.active && !sp.clean);
3506 let now: [&[u64]; L_COUNT] = core::array::from_fn(|l| d.through(true, l));
3507 let was: [&[u64]; L_COUNT] = core::array::from_fn(|l| d.through(false, l));
3508 for k in 0..total {
3509 let mut low = L_COUNT;
3510 if all || was[0].len() != total {
3511 low = 0;
3512 } else {
3513 for l in 0..L_COUNT {
3514 if now[l][k] != was[l][k] {
3515 low = l;
3516 break;
3517 }
3518 }
3519 }
3520 if stale[k] && hole[k] == 0 {
3521 low = 0; }
3523 for l in 1..L_COUNT {
3524 if now[l][k] != now[l - 1][k] {
3525 nr[k] |= 1 << l;
3526 }
3527 }
3528 if redraw[k] && (hole[k] == 0 || nr[k] != 0) {
3531 low = 0;
3532 }
3533 lowest[k] = low as u8;
3534 }
3535 if n >= 0 {
3537 for r in drects.iter().take(n as usize).skip(d.n0.max(0) as usize) {
3538 let (c0, c1) = ((r[0].max(0) >> 5) as usize, (((r[0] + r[2] + CELL - 1) >> 5).max(0) as usize).min(cols));
3539 let (r0, r1) = ((r[1].max(0) >> 5) as usize, (((r[1] + r[3] + CELL - 1) >> 5).max(0) as usize).min(rows));
3540 for row in r0..r1 {
3541 lowest[row * cols + c0..row * cols + c1.max(c0)].fill(0);
3542 }
3543 }
3544 }
3545 let mut err = None;
3547 for (u, l) in upper.iter_mut().zip(1..) {
3548 let bit = 1u8 << l;
3549 let mut bb = [i32::MAX, i32::MAX, i32::MIN, i32::MIN];
3550 for k in (0..total).filter(|&k| nr[k] & bit != 0) {
3551 let b = cell_box(k, cols, fw, fh);
3552 bb = [bb[0].min(b[0]), bb[1].min(b[1]), bb[2].max(b[2]), bb[3].max(b[3])];
3553 }
3554 if bb[0] >= bb[2] {
3555 if u.tex.is_some() && !u.rects.is_empty() {
3556 *linger_at = plat::time_us() + LINGER_US;
3557 }
3558 continue;
3559 }
3560 let area = (bb[2] - bb[0]) as i64 * (bb[3] - bb[1]) as i64;
3561 let fits = u.tex.as_ref().is_some_and(|t| {
3562 bb[0] >= t.ox && bb[1] >= t.oy && bb[2] <= t.ox + t.w && bb[3] <= t.oy + t.h
3563 && (t.w as i64 * t.h as i64) <= (area * LAYER_SHRINK).max((LAYER_STEP * LAYER_STEP) as i64)
3564 });
3565 if !fits {
3566 u.tex = None;
3567 match layer_texture(core, bb, fw, fh) {
3568 Ok(t) => u.tex = Some(t),
3569 Err(e) => err = Some(e),
3570 }
3571 for k in (0..total).filter(|&k| nr[k] & bit != 0) {
3572 lowest[k] = lowest[k].min(l);
3573 }
3574 }
3575 }
3576 let stride = fb.stride as usize;
3578 for l in 0..L_COUNT {
3579 let bit = 1u8 << l;
3580 if l > 0 {
3581 for k in 0..total {
3582 let low = lowest[k] as usize;
3583 if low > l {
3584 continue;
3585 }
3586 let b = cell_box(k, cols, fw, fh);
3587 if low == l {
3588 if let Some(u) = upper[l - 1..].iter().find(|u| u.store.has(k)) {
3591 cell_load(fb, b, u.store.get(k));
3592 owed[k] = false;
3593 }
3594 }
3595 if nr[k] & bit != 0 {
3596 let st = &upper[l - 1].store;
3597 if !st.has(k) && st.free.is_empty() && st.px.len() + CELL_BYTES > st.px.capacity() {
3598 let n = (k..total).filter(|&c| nr[c] & bit != 0 && !st.has(c)).count();
3600 upper[l - 1].store.reserve(n);
3601 }
3602 if owed[k] && hole[k] != 0 {
3603 if let Some(q) = quads.get(hole[k] as usize - 1) {
3604 quad_blit(fb, q, b, text);
3605 }
3606 owed[k] = false;
3607 }
3608 cell_save(fb, b, upper[l - 1].store.put(k, total));
3609 }
3610 }
3611 }
3612 let is_base = l == 0;
3613 cell_rects(rects, cols, rows, fw, fh, |k| (lowest[k] as usize) <= l && (if is_base { hole[k] == 0 || part[k] } else { nr[k] & bit != 0 }));
3614 for r in rects.iter() {
3615 dc.clip_push(r[0], r[1], r[2], r[3]);
3616 render_layer(&wp.cmd.layers[l], 0, dc, fb, text, icon_hook, icon_style);
3617 dc.clip_pop();
3618 }
3619 let bytes: u64 = rects.iter().map(|r| r[2] as u64 * r[3] as u64 * 4).sum();
3620 if is_base {
3621 out.base_px += bytes / 4;
3622 for k in 0..total {
3623 if lowest[k] == 0 {
3624 let h = hole[k] != 0;
3625 owed[k] = h;
3626 stale[k] = h;
3627 }
3628 }
3629 if err.is_none() {
3630 if let Some(t) = base.as_ref() {
3631 if let Err(e) = core.dev.queue().write_texture_rects(&t.t, rects, &fb.px, stride as u32, Layout::Sampled, Layout::Sampled) {
3632 err = Some(e);
3633 }
3634 }
3635 }
3636 out.upload_base += bytes;
3637 } else {
3638 let u = &mut upper[l - 1];
3639 if err.is_none() {
3640 if let Some(t) = u.tex.as_mut() {
3641 let before = if t.fresh { Layout::Undefined } else { Layout::Sampled };
3642 match core.dev.queue().write_texture_rects_at(&t.t, rects, (t.ox, t.oy), &fb.px, stride as u32, before, Layout::Sampled) {
3643 Ok(()) => t.fresh = t.fresh && rects.is_empty(),
3644 Err(e) => err = Some(e),
3645 }
3646 }
3647 }
3648 for k in 0..total {
3649 if nr[k] & bit == 0 {
3650 u.store.drop_cell(k);
3651 }
3652 }
3653 if u.store.used == 0 {
3654 u.store.empty();
3655 }
3656 cell_rects(&mut u.rects, cols, rows, fw, fh, |k| nr[k] & bit != 0);
3657 }
3658 out.upload_bytes += bytes;
3659 if bytes > BIG_UPLOAD {
3660 *trim_at = plat::time_us() + TRIM_US;
3661 }
3662 }
3663 if sp.moved {
3665 for k in 0..total {
3666 if sp.redraw[k] && sp.hole[k] != 0 && sp.nr[k] == 0 {
3667 sp.owed[k] = true;
3668 }
3669 }
3670 }
3671 sp.any_owed = sp.owed.iter().any(|&v| v);
3672 sp.any_stale = sp.stale.iter().any(|&v| v);
3673 sp.redraw.fill(false);
3674 sp.moved = false;
3675 sp.active = sp.upper.iter().any(|u| !u.rects.is_empty());
3676 sp.clean = true;
3677 sp.drawn = true;
3678 sp.err = err;
3679 *pending = true;
3680 out.split_frames += 1;
3681 wp.dirty.busy = sp.active || !sp.quads.is_empty();
3682 }
3683
3684 pub(crate) fn gp_frame_done(&mut self, t0: u64, n: i32, split: bool) {
3686 let i = self.uiw_cur as usize;
3687 let (fw, fh) = (self.wp.rt.fb.w as u64, self.wp.rt.fb.h as u64);
3688 let px = if split {
3689 0
3690 } else if n < 0 {
3691 fw * fh
3692 } else {
3693 self.drects.iter().take(n as usize).map(|r| r[2].max(0) as u64 * r[3].max(0) as u64).sum()
3694 };
3695 if let Some(w) = self.m.gout.win(i) {
3696 w.out.render_us += plat::time_us().saturating_sub(t0);
3697 w.out.base_px += px;
3698 }
3699 }
3700
3701 fn gp_settle(&mut self, i: usize) {
3703 let Some(Some(w)) = self.m.gout.wins.get_mut(i) else { return };
3704 if !w.sp.any_owed {
3705 return;
3706 }
3707 let sp = &mut w.sp;
3708 let (mut owed, hole, quads) = (core::mem::take(&mut sp.owed), core::mem::take(&mut sp.hole), core::mem::take(&mut sp.quads));
3709 let (cols, rows) = (sp.cols, sp.rows);
3710 sp.any_owed = false;
3711 let font = self.text.get_font();
3712 let cur = self.uiw_cur as usize;
3713 let Ui { wp, m, text, .. } = self;
3714 let parts = if i == cur { Some(wp) } else { m.win.recs[i & (WIN_MAX - 1)].save.as_deref_mut() };
3715 if let Some(p) = parts {
3716 let fb = &mut p.rt.fb;
3717 let (fw, fh) = (fb.w, fb.h);
3718 let mut rects = Vec::new();
3719 for (q, quad) in quads.iter().enumerate() {
3720 let tag = q as u8 + 1;
3721 cell_rects(&mut rects, cols, rows, fw, fh, |k| owed[k] && hole[k] == tag);
3722 for r in &rects {
3723 quad_blit(fb, quad, [r[0], r[1], r[0] + r[2], r[1] + r[3]], text);
3724 }
3725 }
3726 }
3727 self.text.set_font(font.0, font.1);
3728 owed.fill(false);
3729 let Some(Some(w)) = self.m.gout.wins.get_mut(i) else { return };
3730 (w.sp.owed, w.sp.hole, w.sp.quads) = (owed, hole, quads);
3731 }
3732
3733 pub(crate) fn gp_settle_all(&mut self) {
3735 for i in 0..self.m.gout.wins.len() {
3736 self.gp_settle(i);
3737 }
3738 }
3739
3740 fn gp_flatten(&mut self, i: usize) {
3743 self.gp_settle(i);
3744 let s = &mut self.m.gout;
3745 let trim = plat::time_us() + TRIM_US;
3746 let mut freed = false;
3747 if let Some(Some(w)) = s.wins.get_mut(i) {
3748 freed = w.sp.active;
3749 w.sp.reset();
3750 w.full = true;
3751 w.pending = true;
3752 }
3753 if freed {
3754 s.trim_at = trim;
3755 }
3756 if let Some(p) = self.uiw_parts_mut(i as u32) {
3757 p.dirty.busy = false;
3758 }
3759 }
3760
3761 pub(crate) fn gp_repaired(&mut self) {
3763 let i = self.uiw_cur as usize;
3764 if self.m.gout.wins.get(i).and_then(|w| w.as_ref()).is_some_and(|w| w.stage == Stage::Gpu) {
3765 self.gp_flatten(i);
3766 }
3767 }
3768
3769 pub(crate) fn gp_scripted(&self) -> bool {
3773 self.m.gout.script
3774 }
3775
3776 pub(crate) fn gp_script_pw(&mut self, cur: u32) -> i32 {
3780 let i = cur as usize & (super::consts::WIN_MAX - 1);
3781 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
3782 let s = &mut self.m.gout;
3783 let mut pw = s.script_pw[i];
3784 if pw < 0 {
3785 let off = 40 + 48 * i as i32;
3786 let owner = if cfg!(windows) { plat::PW_NOOWNER } else { s.script_pw.iter().copied().find(|&p| p >= 0).unwrap_or(plat::PW_NOOWNER) };
3788 pw = plat::win_create("gpu output test", 320, 240, plat::PWF_SYSFRAME | plat::PWF_RESIZABLE, owner, off, off);
3789 if pw < 0 {
3790 return -1;
3791 }
3792 s.script_pw[i] = pw;
3793 plat::win_test(plat::WT_NOANIM, pw, [0; 4]);
3794 plat::win_show(pw, plat::PWS_SHOWNA);
3795 }
3796 if plat::win_size(pw) != (fw, fh) && s.script_asked[i] != (fw, fh) {
3798 s.script_asked[i] = (fw, fh);
3799 plat::win_set_rect(pw, plat::PW_KEEP, plat::PW_KEEP, fw, fh);
3800 }
3801 script_pump();
3802 pw
3803 }
3804
3805 fn gp_scoped(&self) -> usize {
3806 self.uiw_cur as usize
3807 }
3808
3809 fn gp_script_wait(&mut self, ms: u64) -> bool {
3811 let dl = plat::time_us() + ms * 1000;
3812 loop {
3813 script_pump();
3814 self.gp_tick();
3815 let s = &self.m.gout;
3816 let building = s.jobs.running.load(Ordering::Acquire) != 0;
3817 let all = s.wins.iter().flatten().all(|w| w.stage == Stage::Gpu || kept_at_other_size(w));
3820 if matches!(s.dev, Dev::Never(_)) {
3821 return false;
3822 }
3823 if all && !building && !s.wins.is_empty() {
3824 return true;
3825 }
3826 if plat::time_us() >= dl {
3827 return false;
3828 }
3829 plat::wait(plat::time_us() + 1000);
3830 }
3831 }
3832
3833 fn gp_skip(&mut self, why: &str) {
3834 self.m.gout.skip = true;
3835 plat::write_stdout(format!("SKIP gpu: {why}\n").as_bytes());
3836 }
3837
3838 pub(crate) fn sc_gpu(&mut self, app: &mut dyn App, a: &[u8]) {
3840 if let Some(r) = word_eq(a, "on") {
3841 self.m.gout.script = true;
3842 self.m.gout.setup();
3843 self.m.gout.want = true;
3844 self.m.gout.want3d = self.desc.flags & AF_GPU_3D != 0;
3845 self.m.gout.verify = word_eq(skip_ws(r), "fast").is_none();
3846 self.m.gout.trace = env_on("jfgui_GPU_TRACE");
3847 self.m.gout.skip = false;
3848 self.dirty_force_full();
3849 self.invalidate();
3850 self.run_passes(app);
3851 self.render();
3852 self.present();
3853 if !self.gp_script_wait(20_000) {
3854 let why = match &self.m.gout.dev {
3855 Dev::Never(w) => w.clone(),
3856 _ => {
3857 let stages: Vec<(usize, Stage)> = self.m.gout.wins.iter().enumerate().filter_map(|(i, w)| w.as_ref().map(|w| (i, w.stage))).collect();
3858 format!("the swapchain did not come: {stages:?}")
3859 }
3860 };
3861 if matches!(self.m.gout.dev, Dev::Never(_)) {
3862 self.gp_skip(&why);
3863 } else {
3864 self.fail(why.as_bytes());
3865 }
3866 }
3867 return;
3868 }
3869 if word_eq(a, "off").is_some() {
3870 self.m.gout.script = false;
3871 self.gp_wait_jobs(3000);
3872 if matches!(self.m.gout.dev, Dev::Ready(_)) {
3873 let devgen = self.m.gout.dev_gen;
3874 self.gp_views_gone(Some(devgen)); }
3876 let s = &mut self.m.gout;
3877 s.wins.clear();
3878 s.images.clear();
3879 s.image_bytes = 0;
3880 s.canvases.clear();
3881 s.tile_free.clear();
3882 s.tile_bytes = 0;
3883 s.dev = Dev::None;
3884 s.want = false;
3885 for pw in s.script_pw.iter_mut() {
3886 if *pw >= 0 {
3887 plat::win_destroy(*pw);
3888 *pw = -1;
3889 }
3890 }
3891 script_pump();
3892 return;
3893 }
3894 if !self.m.gout.script {
3895 self.fail(b"gpu on first");
3896 return;
3897 }
3898 if word_eq(a, "wait").is_some() {
3899 if self.m.gout.skip {
3900 return;
3901 }
3902 let (t, _) = super::runtime::parse_int(next_word(a));
3903 self.render();
3904 self.present();
3905 if !self.gp_script_wait(if t > 0 { t as u64 } else { 20_000 }) {
3906 self.fail(b"not on the swapchain");
3907 }
3908 return;
3909 }
3910 if word_eq(a, "lose").is_some() {
3911 self.gpu_test_lose();
3912 return;
3913 }
3914 if let Some(n) = word_eq(a, "budget") {
3915 let (v, _) = super::runtime::parse_int(skip_ws(n));
3916 self.gpu_test_image_budget(v.max(0) as u64);
3917 return;
3918 }
3919 if let Some(n) = word_eq(a, "tilebudget") {
3920 let (v, _) = super::runtime::parse_int(skip_ws(n));
3921 self.gpu_test_canvas_budget(v.max(0) as u64);
3922 return;
3923 }
3924 if let Some(r) = word_eq(a, "shot") {
3925 let r = skip_ws(r);
3926 self.gp_shot(&String::from_utf8_lossy(r).into_owned());
3927 return;
3928 }
3929 if word_eq(a, "settle").is_some() {
3930 if !self.m.gout.skip {
3931 self.gpu_canvas_settle();
3932 self.render();
3933 self.present();
3934 }
3935 return;
3936 }
3937 if word_eq(a, "hold").is_some() {
3938 let (t, _) = super::runtime::parse_int(next_word(a));
3939 let dl = plat::time_us() + t.max(0) as u64 * 1000;
3940 while plat::time_us() < dl {
3941 script_pump();
3942 self.gp_tick();
3943 plat::gpu_wait(plat::time_us() + 10_000);
3944 }
3945 return;
3946 }
3947 if word_eq(a, "print").is_some() {
3948 let o = self.gpu_output(self.gp_scoped() as u32);
3949 plat::write_stdout(format!("gpu: {o:?}\n").as_bytes());
3950 return;
3951 }
3952 let Some(r) = word_eq(a, "expect") else {
3953 self.fail(b"unknown gpu command");
3954 return;
3955 };
3956 let r = skip_ws(r);
3957 if self.m.gout.skip {
3958 return;
3959 }
3960 self.render();
3961 self.present();
3962 let i = self.gp_scoped();
3963 let needs_gpu = ["gpu", "apart", "tiles", "layers", "images"].iter().any(|w| word_eq(r, w).is_some());
3966 if needs_gpu && self.m.gout.win(i).is_some_and(|w| w.stage != Stage::Gpu && kept_at_other_size(w)) {
3967 plat::write_stdout(format!("note: gpu expect {}: the system keeps the window at another size (no result)\n", String::from_utf8_lossy(r).trim_end()).as_bytes());
3968 return;
3969 }
3970 if word_eq(r, "gpu").is_some() || word_eq(r, "cpu").is_some() {
3971 let want_gpu = word_eq(r, "gpu").is_some();
3972 let on = self.gpu_output(i as u32).on;
3973 if on != want_gpu {
3974 self.fail(if on { b"on the swapchain" } else { b"on the CPU path" });
3975 }
3976 } else if word_eq(r, "same").is_some() {
3977 self.gp_expect_same();
3978 } else if word_eq(r, "window").is_some() {
3979 self.gp_expect_pixels(i, false);
3980 } else if word_eq(r, "screen").is_some() {
3981 self.gp_expect_pixels(i, true);
3982 } else if let Some(n) = word_eq(r, "upload") {
3983 let (max, _) = super::runtime::parse_int(skip_ws(n));
3984 let bytes = self.m.gout.win(i).map_or(0, |w| core::mem::take(&mut w.out.upload_bytes));
3985 if bytes > max as u64 {
3986 self.fail(format!("{bytes} bytes uploaded").as_bytes());
3987 }
3988 } else if let Some(n) = word_eq(r, "base") {
3989 let (max, _) = super::runtime::parse_int(skip_ws(n));
3990 let bytes = self.m.gout.win(i).map_or(0, |w| core::mem::take(&mut w.out.upload_base));
3991 if bytes > max as u64 {
3992 self.fail(format!("{bytes} bytes uploaded to the base texture").as_bytes());
3993 }
3994 } else if let Some(n) = word_eq(r, "raster") {
3995 let (max, _) = super::runtime::parse_int(skip_ws(n));
3996 let px = self.m.gout.win(i).map_or(0, |w| core::mem::take(&mut w.out.base_px));
3997 if px > max as u64 {
3998 self.fail(format!("{px} pixels of the base layer drawn").as_bytes());
3999 }
4000 } else if let Some(n) = word_eq(r, "layers") {
4001 let (want, _) = super::runtime::parse_int(skip_ws(n));
4002 let have = self.gpu_output(i as u32).layer_textures;
4003 if have != want as u32 {
4004 self.fail(format!("{have} layer textures").as_bytes());
4005 }
4006 } else if let Some(n) = word_eq(r, "images") {
4007 let (want, _) = super::runtime::parse_int(skip_ws(n));
4008 let have = self.gpu_output(i as u32).image_textures;
4009 if have != want as u32 {
4010 self.fail(format!("{have} image textures").as_bytes());
4011 }
4012 } else if let Some(n) = word_eq(r, "apart") {
4013 let (want, _) = super::runtime::parse_int(skip_ws(n));
4014 let have = self.gpu_output(i as u32).canvas_apart;
4015 if have != want as u32 {
4016 self.fail(format!("{have} canvases apart").as_bytes());
4017 }
4018 } else if let Some(n) = word_eq(r, "tiles") {
4019 let (want, _) = super::runtime::parse_int(skip_ws(n));
4020 let have = self.gpu_output(i as u32).canvas_tiles;
4021 if have != want as u32 {
4022 self.fail(format!("{have} canvas tiles").as_bytes());
4023 }
4024 } else if let Some(n) = word_eq(r, "fills") {
4025 let (max, _) = super::runtime::parse_int(skip_ws(n));
4026 let s = &mut self.m.gout;
4027 let fills = s.tile_fills - core::mem::replace(&mut s.tile_marked, s.tile_fills);
4028 if fills > max as u64 {
4029 self.fail(format!("{fills} tiles drawn").as_bytes());
4030 }
4031 } else if word_eq(r, "nodiff").is_some() {
4032 let o = self.gpu_output(i as u32);
4033 let px = o.canvas_diff_px - core::mem::replace(&mut self.m.gout.diff_marked, o.canvas_diff_px);
4034 if px != 0 {
4035 self.fail(format!("{px} pixels of a canvas's tiles differ, by {} at most in this window so far", o.canvas_diff_max).as_bytes());
4036 }
4037 } else {
4038 self.fail(b"unknown gpu expectation");
4039 }
4040 }
4041
4042 fn gp_shot(&mut self, file: &str) {
4044 if self.m.gout.skip {
4045 return;
4046 }
4047 self.render();
4048 self.present();
4049 let i = self.gp_scoped();
4050 self.gp_settle_all();
4051 let mut s = core::mem::take(&mut self.m.gout);
4052 let mut raw = None;
4053 let fb = self.uiw_parts(i as u32).map(|p| &p.rt.fb);
4054 let (mut w_px, mut h_px) = (0, 0);
4055 if let (Dev::Ready(core), Some(Some(w)), Some(fb)) = (&s.dev, s.wins.get_mut(i), fb) {
4056 (w_px, h_px) = (fb.w, fb.h);
4057 w.rb_keep = Some(Vec::new());
4058 for _ in 0..SHOW_TRIES {
4059 match show(w, core, fb) {
4060 Ok(Shown::Wait) => plat::gpu_wait(plat::time_us() + 4_000),
4061 _ => break,
4062 }
4063 }
4064 raw = w.rb_keep.take().filter(|v| !v.is_empty());
4065 if core.format == Format::Rgba8Unorm {
4066 if let Some(v) = raw.as_mut() {
4067 for p in v.chunks_exact_mut(4) {
4068 p.swap(0, 2);
4069 }
4070 }
4071 }
4072 }
4073 self.m.gout = s;
4074 script_pump();
4075 let saved = raw.is_some_and(|v| {
4076 let mut sf = Surface::new(w_px, h_px, FMT_XRGB);
4077 sf.px.copy_from_slice(&v);
4078 Ui::save_png(&sf, file)
4079 });
4080 if !saved {
4081 self.fail(b"no presented image (gpu on fast reads nothing back)");
4082 }
4083 }
4084
4085 fn gp_expect_same(&mut self) {
4088 if self.m.gout.canvases.iter().any(|c| c.apart) {
4089 self.gpu_canvas_settle();
4090 self.render();
4091 self.present();
4092 }
4093 self.gp_settle_all();
4094 let mut s = core::mem::take(&mut self.m.gout);
4095 let mut msg = None;
4096 let mut any = false;
4097 for i in 0..s.wins.len() {
4098 let fb = self.uiw_parts(i as u32).map(|p| &p.rt.fb);
4099 if let (Dev::Ready(core), Some(Some(w)), Some(fb)) = (&s.dev, s.wins.get_mut(i), fb) {
4100 if w.stage != Stage::Gpu {
4101 continue;
4102 }
4103 any = true;
4104 let frames = w.out.verified;
4105 for _ in 0..SHOW_TRIES {
4106 match show(w, core, fb) {
4107 Ok(Shown::Wait) => {
4108 plat::gpu_wait(plat::time_us() + 4_000); }
4110 Ok(_) => break,
4111 Err(e) => {
4112 msg = Some(format!("{e}"));
4113 break;
4114 }
4115 }
4116 }
4117 if w.out.verified == frames && msg.is_none() {
4118 msg = Some(format!("window {i}: no frame presented"));
4119 }
4120 if w.out.verify_bad != 0 {
4121 msg = Some(format!("window {i}: {} of {} presented images differ", w.out.verify_bad, w.out.verified));
4122 w.out.verify_bad = 0;
4123 }
4124 }
4125 }
4126 if !any && msg.is_none() {
4127 if s.wins.iter().flatten().any(kept_at_other_size) {
4128 plat::write_stdout(b"note: gpu expect same: the system keeps the window at another size (no result)\n");
4129 } else {
4130 msg = Some(String::from("no window on the swapchain"));
4131 }
4132 }
4133 self.m.gout = s;
4134 self.m.gout.diff_marked = self.gpu_output(self.gp_scoped() as u32).canvas_diff_px;
4135 script_pump();
4136 if let Some(m) = msg {
4137 self.fail(m.as_bytes());
4138 }
4139 }
4140
4141 fn gp_expect_pixels(&mut self, i: usize, screen: bool) {
4143 self.gp_settle_all();
4144 let pw = self.m.gout.script_pw[i & (super::consts::WIN_MAX - 1)];
4145 if pw < 0 {
4146 self.fail(b"no window");
4147 return;
4148 }
4149 if !cfg!(windows) && screen {
4153 let mut s = core::mem::take(&mut self.m.gout);
4154 let fb = self.uiw_parts(i as u32).map(|p| &p.rt.fb);
4155 if let (Dev::Ready(core), Some(Some(w)), Some(fb)) = (&s.dev, s.wins.get_mut(i), fb) {
4156 for _ in 0..SHOW_TRIES {
4157 if w.stage != Stage::Gpu || !matches!(show(w, core, fb), Ok(Shown::Wait)) {
4158 break;
4159 }
4160 plat::gpu_wait(plat::time_us() + 4_000);
4161 }
4162 }
4163 self.m.gout = s;
4164 }
4165 for _ in 0..SHOW_TRIES {
4167 let open = self.m.gout.wins.get(i).and_then(|w| w.as_ref()).is_some_and(|w| w.pending || w.sc.as_ref().is_some_and(|sc| sc.busy()));
4168 if !open {
4169 break;
4170 }
4171 script_pump();
4172 self.gp_tick();
4173 plat::gpu_wait(plat::time_us() + 4_000);
4174 }
4175 for _ in 0..12 {
4177 script_pump();
4178 self.gp_tick();
4179 plat::gpu_wait(plat::time_us() + 8_000);
4180 }
4181 let Some(fb) = self.uiw_parts(i as u32).map(|p| &p.rt.fb) else { return };
4182 let (w, h) = (fb.w, fb.h);
4183 let px = if screen && cfg!(windows) {
4184 plat::win_get_rect(pw).and_then(|r| plat::win_test_screen(r[0], r[1], w, h))
4185 } else {
4186 plat::win_test_image(pw).filter(|x| x.0 == w && x.1 == h).map(|x| x.2)
4187 };
4188 let Some(px) = px else {
4189 if cfg!(windows) {
4190 self.fail(b"no picture");
4191 } else {
4192 plat::write_stdout(b"note: gpu expect screen: no way to read the window back here (no result)\n");
4194 }
4195 return;
4196 };
4197 let mut bad = 0u64;
4198 let mut first = (0, 0);
4199 let mut some = Vec::new(); let row = w as usize * 4;
4201 for y in 0..h as usize {
4202 let a = &px[y * row..y * row + row];
4203 let b = &fb.px[y * fb.stride as usize..y * fb.stride as usize + row];
4204 for (x, (p, q)) in a.chunks_exact(4).zip(b.chunks_exact(4)).enumerate() {
4205 let corner = screen && (x < 8 || x + 8 >= w as usize) && (y < 8 || y + 8 >= h as usize);
4207 if p[..3] != q[..3] && !corner {
4208 if bad == 0 {
4209 first = (x, y);
4210 }
4211 if bad % 4096 == 0 {
4212 some.push([x as i32, y as i32, 0, 0]);
4213 }
4214 bad += 1;
4215 }
4216 }
4217 }
4218 if bad != 0 {
4219 let total = w as u64 * h as u64;
4220 if screen && some.iter().any(|&p| plat::win_test(plat::WT_FROM_POINT, pw, p) != 1) {
4222 plat::write_stdout(format!("note: gpu expect screen: {bad} of {total} pixels differ: another window lies on top of ours\n").as_bytes());
4223 } else {
4224 self.fail(format!("{bad} pixels differ, first at {}, {}", first.0, first.1).as_bytes());
4225 }
4226 }
4227 }
4228}
4229
4230fn unseen(w: &Win) -> bool {
4232 w.hidden || plat::win_state(w.pw) & plat::WST_HIDDEN != 0
4233}
4234
4235fn kept_at_other_size(w: &Win) -> bool {
4237 plat::win_size(w.pw) != w.size && plat::time_us() >= w.stable_at + KEPT_US
4238}
4239
4240fn script_pump() {
4242 plat::wait(0);
4243 let mut keep: Vec<plat::Event> = vec![];
4244 while let Some(e) = plat::evq_pop() {
4245 if e.typ == plat::E_WAKE {
4246 keep.push(e);
4247 }
4248 }
4249 for e in keep {
4250 plat::evq_push(e);
4251 }
4252}