//! Experimental cold-grid candidates; no window or input automation. use iced::advanced::{ graphics::geometry::Renderer as _, renderer::{Headless, Renderer as _}, }; use iced::{ Color, Point, Rectangle, Renderer, Size, Vector, widget::canvas::{Frame, Geometry, Path}, }; use std::{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); #[derive(Debug, Clone, Copy)] enum Candidate { Original, Template, Column, Square, } fn draw(r: &Renderer, size: Size, zoom: f32, pan: Vector, candidate: Candidate) -> Geometry { let mut f = Frame::new(r, size); f.fill_rectangle(Point::ORIGIN, size, BG); let step = (24. * zoom).max(14.); let ox = pan.x.rem_euclid(step); let oy = pan.y.rem_euclid(step); let circle = Path::circle(Point::ORIGIN, 0.65); for x in 0..(size.width / step) as i32 + 1 { if matches!(candidate, Candidate::Column) { let p = Path::new(|p| { for y in 0..(size.height / step) as i32 + 1 { p.circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65); p.close(); } }); f.fill(&p, DOT); continue; } for y in 0..(size.height / step) as i32 + 1 { let center = Point::new(ox + x as f32 * step, oy + y as f32 * step); match candidate { Candidate::Original => f.fill(&Path::circle(center, 0.65), DOT), Candidate::Template => f.with_save(|f| { f.translate(Vector::new(center.x, center.y)); f.fill(&circle, DOT); }), Candidate::Square => f.fill_rectangle( center - Vector::new(0.575, 0.575), Size::new(1.15, 1.15), DOT, ), Candidate::Column => unreachable!(), } } } f.into_geometry() } fn pixels(r: &mut Renderer, size: Size, g: Geometry) -> Vec { r.reset(Rectangle::with_size(size)); r.draw_geometry(g); Headless::screenshot(r, Size::new(size.width as u32, size.height as u32), 1., BG) } fn main() { let n = std::env::args() .nth(1) .map(|s| s.parse::().unwrap()) .unwrap_or(20) .max(1); 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={} n={n}; construction only, unoptimized linked dependencies; 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), ] { println!("size={}x{} zoom={zoom}", size.width, size.height); for candidate in [ Candidate::Original, Candidate::Template, Candidate::Column, Candidate::Square, ] { for pan in [ Vector::new(-13.25, 9.75), Vector::new(0., 0.), Vector::new(6.125, -8.5), ] { let base = draw(&r, size, zoom, pan, Candidate::Original); let base = pixels(&mut r, size, base); let test = draw(&r, size, zoom, pan, candidate); let test = pixels(&mut r, size, test); let changed = base .chunks_exact(4) .zip(test.chunks_exact(4)) .filter(|(a, b)| a != b) .count(); let max = base .iter() .zip(&test) .map(|(a, b)| a.abs_diff(*b)) .max() .unwrap_or(0); println!( "{candidate:?} pan={},{} changed_pixels={changed} max_channel_delta={max}", pan.x, pan.y ); } let mut ms = Vec::new(); for i in 0..n + 3 { let pan = Vector::new(-13.25 + i as f32 * 0.25, 9.75); let now = Instant::now(); let g = black_box(draw(&r, size, zoom, pan, candidate)); let elapsed = now.elapsed().as_secs_f64() * 1000.; drop(g); if i >= 3 { ms.push(elapsed); } } ms.sort_by(f64::total_cmp); println!( "{candidate:?} changing_pan n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}", ms[((n - 1) as f64 * 0.5).ceil() as usize], ms[((n - 1) as f64 * 0.95).ceil() as usize], ms[n - 1] ); } } }