210 lines
7.5 KiB
Rust
210 lines
7.5 KiB
Rust
//! 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<usize>) -> 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<u8> {
|
|
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(<Renderer as Headless>::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]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|