//! Full-sequence opaque phase replay with diagnostics and repeat controls; 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, ) } // Diagnostics only: this performs no rendering and does not mutate the renderer. fn log_coordinates(expected: &[u8], actual: &[u8], size: Size, scale: f32) { assert_eq!(expected.len(), actual.len()); let physical_width = (size.width * scale).ceil() as usize; for (index, (a, b)) in expected .as_chunks::<4>() .0 .iter() .zip(actual.as_chunks::<4>().0) .enumerate() .filter(|(_, (a, b))| a != b) .take(20) { println!( "difference_xy={},{} expected={a:?} actual={b:?}", index % physical_width, index / physical_width ); } } fn report_control(label: &str, expected: &[u8], actual: &[u8], size: Size, scale: f32) { assert_eq!(expected.len(), actual.len()); let changed = expected .as_chunks::<4>() .0 .iter() .zip(actual.as_chunks::<4>().0) .filter(|(a, b)| a != b) .count(); let max = expected .iter() .zip(actual) .map(|(a, b)| a.abs_diff(*b)) .max() .unwrap_or(0); println!( "comparison={label} size={}x{} scale={scale} pan=-13.25,9.75 changed_pixels={changed} max_channel_delta={max}", size.width, size.height ); log_coordinates(expected, actual, size, scale); } 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), ] { 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()); 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 ); // Keep the original render/timing sequence; print diagnostics only. log_coordinates(&expected, &actual, size, scale); } } 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] ); } } } // All original cases, timings and screenshots have completed above. // Reuse the same renderer for controls. This cache is newly constructed, // then retained across all control repetitions; original per-case caches // have already gone out of scope, exactly as in the opaque probe. println!("original_sequence_complete; beginning same-renderer repeat controls"); let size = Size::new(301.5, 211.25); let step = (24f32 * 0.73).max(14.); let pan: Vector = Vector::new(-13.25, 9.75); let offset = Vector::new(pan.x.rem_euclid(step), pan.y.rem_euclid(step)); let cache = Cache::new(); let builds = Cell::new(0); for repetition in 1..=3 { for scale in [1., 2.] { r.reset(Rectangle::with_size(size)); let g = old(&r, size, step, offset); r.draw_geometry(g); let first = shot(&mut r, size, scale); r.reset(Rectangle::with_size(size)); let g = old(&r, size, step, offset); r.draw_geometry(g); let second = shot(&mut r, size, scale); report_control( &format!("old/old repetition={repetition}"), &first, &second, size, scale, ); r.reset(Rectangle::with_size(size)); let g = phase(&r, &cache, size, step, &builds); submit(&mut r, size, g, offset); let first = 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 second = shot(&mut r, size, scale); report_control( &format!("cached/cached repetition={repetition}"), &first, &second, size, scale, ); } } println!("control_cache_builds={}", builds.get()); assert_eq!(builds.get(), 1); }