//! Renderer-level phase translation experiment, not application code. use iced::advanced::{Renderer as _, graphics::geometry::Renderer as _, renderer::Headless}; use iced::{ Color, Point, Rectangle, Renderer, Size, Vector, widget::canvas::{Cache, Frame, Geometry, Path}, }; use std::{cell::Cell, hint::black_box, time::Instant}; const BG: Color = Color::from_rgb8(246, 248, 251); const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91); fn dots(f: &mut Frame, size: Size, step: f32, phase: Vector) { for x in 0..(size.width / step) as i32 + 1 { for y in 0..(size.height / step) as i32 + 1 { f.fill( &Path::circle( Point::new(phase.x + x as f32 * step, phase.y + y as f32 * step), 0.65, ), DOT, ); } } } fn old(r: &Renderer, size: Size, step: f32, phase: Vector) -> Geometry { let mut f = Frame::new(r, size); f.fill_rectangle(Point::ORIGIN, size, BG); dots(&mut f, size, step, phase); f.into_geometry() } fn phase(r: &Renderer, cache: &Cache, size: Size, step: f32, builds: &Cell) -> Geometry { cache.draw_with_bounds( r, Rectangle { x: -step, y: -step, width: size.width + 3. * step, height: size.height + 3. * step, }, |f| { builds.set(builds.get() + 1); f.fill_rectangle( Point::new(-step, -step), Size::new(size.width + 3. * step, size.height + 3. * step), BG, ); dots(f, size, step, Vector::ZERO); }, ) } fn submit(r: &mut Renderer, size: Size, g: Geometry, pan: Vector) { r.with_layer(Rectangle::with_size(size), |r| { r.with_translation(pan, |r| r.draw_geometry(g)) }); } fn shot(r: &mut Renderer, size: Size, scale: f32) -> Vec { Headless::screenshot( r, Size::new( (size.width * scale).ceil() as u32, (size.height * scale).ceil() as u32, ), scale, BG, ) } fn main() { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap(); let mut r = rt .block_on(::new( iced::Font::DEFAULT, 16.into(), None, )) .unwrap(); println!( "renderer={}; phase-cache prototype; NOT native FPS", r.name() ); for (size, zoom) in [ (Size::new(1900., 660.), 1.), (Size::new(1900., 660.), 0.5), (Size::new(1440., 900.), 1.), (Size::new(1440., 900.), 0.5), (Size::new(301.5, 211.25), 0.73), ] { if size.width != 301.5 { continue; } let step = (24f32 * zoom).max(14.); let cache = Cache::new(); let builds = Cell::new(0); println!( "size={}x{} zoom={zoom} step={step}", size.width, size.height ); for scale in [1., 1.25, 2.] { for pan in [ Vector::ZERO, Vector::new(-13.25, 9.75), Vector::new(6.125, -8.5), Vector::new(step - 0.125, step - 0.25), Vector::new(-0.125, 0.125), ] { let offset = Vector::new(pan.x.rem_euclid(step), pan.y.rem_euclid(step)); r.reset(Rectangle::with_size(size)); let g = old(&r, size, step, offset); r.draw_geometry(g); let expected = shot(&mut r, size, scale); r.reset(Rectangle::with_size(size)); let g = phase(&r, &cache, size, step, &builds); submit(&mut r, size, g, offset); let actual = shot(&mut r, size, scale); assert_eq!(expected.len(), actual.len()); for (index, (a, b)) in expected .chunks_exact(4) .zip(actual.chunks_exact(4)) .enumerate() .filter(|(_, (a, b))| a != b) .take(20) { println!( "difference_xy={},{} expected={a:?} actual={b:?}", index % (size.width * scale).ceil() as usize, index / (size.width * scale).ceil() as usize ); } let changed = expected .chunks_exact(4) .zip(actual.chunks_exact(4)) .filter(|(a, b)| a != b) .count(); let max = expected .iter() .zip(&actual) .map(|(a, b)| a.abs_diff(*b)) .max() .unwrap_or(0); println!( "scale={scale} pan={},{} changed_pixels={changed} max_channel_delta={max}", pan.x, pan.y ); } } assert_eq!(builds.get(), 1); for cached in [false, true] { let mut ms = Vec::new(); for i in 0..23 { r.reset(Rectangle::with_size(size)); let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75); let now = Instant::now(); let g = if cached { phase(&r, &cache, size, step, &builds) } else { old(&r, size, step, offset) }; if cached { submit(&mut r, size, g, offset) } else { r.draw_geometry(g) }; let elapsed = black_box(now.elapsed().as_secs_f64() * 1000.); if i >= 3 { ms.push(elapsed); } } ms.sort_by(f64::total_cmp); println!( "cached={cached} construct_and_submit changing_pan n=20 p50_ms={:.4} p95_ms={:.4} max_ms={:.4}", ms[10], ms[19], ms[19] ); } println!("cache_builds={}", builds.get()); assert_eq!(builds.get(), 1); // Includes backend prepare/upload and readback; still not native presentation. if size.width == 1900. && zoom == 1. || size.width == 1440. && zoom == 0.5 { for cached in [false, true] { let mut ms = Vec::new(); for i in 0..10 { r.reset(Rectangle::with_size(size)); let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75); let now = Instant::now(); let g = if cached { phase(&r, &cache, size, step, &builds) } else { old(&r, size, step, offset) }; if cached { submit(&mut r, size, g, offset); } else { r.draw_geometry(g); } let pixels = black_box(shot(&mut r, size, 1.)); let elapsed = now.elapsed().as_secs_f64() * 1000.; drop(pixels); if i >= 2 { ms.push(elapsed); } } ms.sort_by(f64::total_cmp); println!( "cached={cached} construct_submit_screenshot changing_pan n=8 p50_ms={:.4} min_ms={:.4} max_ms={:.4}", ms[4], ms[0], ms[7] ); } } } }