Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd8e6c1bda | ||
|
|
2ca84676ee | ||
|
|
336720559b | ||
|
|
636093aae6 |
@@ -42,6 +42,7 @@ mod port_labels;
|
||||
pub use port_labels::PortLabels;
|
||||
#[path = "diagram/focus.rs"]
|
||||
pub(crate) mod focus;
|
||||
mod grid;
|
||||
pub(crate) mod group_gesture;
|
||||
mod multiselect;
|
||||
#[cfg(test)]
|
||||
@@ -71,6 +72,7 @@ pub struct DiagramCanvas<'a> {
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
grid: grid::Grid,
|
||||
modifiers: iced::keyboard::Modifiers,
|
||||
group: Option<multiselect::Press>,
|
||||
work: Option<Arc<crate::app::allocation_drag::Press>>,
|
||||
@@ -1251,23 +1253,10 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
bounds: Rectangle,
|
||||
_: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let background = state
|
||||
.grid
|
||||
.draw(renderer, bounds.size(), self.zoom, self.pan);
|
||||
let mut f = Frame::new(renderer, bounds.size());
|
||||
f.fill_rectangle(
|
||||
Point::ORIGIN,
|
||||
bounds.size(),
|
||||
Color::from_rgb8(246, 248, 251),
|
||||
);
|
||||
let step = (24. * self.zoom).max(14.);
|
||||
let ox = self.pan.x.rem_euclid(step);
|
||||
let oy = self.pan.y.rem_euclid(step);
|
||||
for x in 0..(bounds.width / step) as i32 + 1 {
|
||||
for y in 0..(bounds.height / step) as i32 + 1 {
|
||||
f.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
style::LINE,
|
||||
);
|
||||
}
|
||||
}
|
||||
f.translate(self.pan);
|
||||
f.scale(self.zoom);
|
||||
let scene = self.scene(state);
|
||||
@@ -1670,7 +1659,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
vec![f.into_geometry(), overlay.into_geometry()]
|
||||
vec![background, f.into_geometry(), overlay.into_geometry()]
|
||||
}
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//! Screen-space background geometry, independent of the model and pointer.
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Grid {
|
||||
geometry: canvas::Cache,
|
||||
camera: Cell<Option<(f32, Vector)>>,
|
||||
#[cfg(test)]
|
||||
builds: Cell<usize>,
|
||||
}
|
||||
|
||||
impl Grid {
|
||||
pub(super) fn draw(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
size: Size,
|
||||
zoom: f32,
|
||||
pan: Vector,
|
||||
) -> canvas::Geometry {
|
||||
// Cache::draw handles exact size changes. Raw camera equality is
|
||||
// deliberately conservative: do not quantize fractional antialiasing.
|
||||
if self.camera.replace(Some((zoom, pan))) != Some((zoom, pan)) {
|
||||
self.geometry.clear();
|
||||
}
|
||||
self.geometry.draw(renderer, size, |frame| {
|
||||
#[cfg(test)]
|
||||
self.builds.set(self.builds.get() + 1);
|
||||
paint(frame, size, zoom, pan);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.fill_rectangle(Point::ORIGIN, size, Color::from_rgb8(246, 248, 251));
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
style::LINE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,378 @@
|
||||
use super::*;
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
|
||||
async fn renderer() -> Renderer {
|
||||
<Renderer as Headless>::new(iced::Font::DEFAULT, 16.into(), None)
|
||||
.await
|
||||
.expect("grid tests require the supported WGPU headless renderer")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cache_reuses_unchanged_inputs_and_rebuilds_for_each_camera_or_size_change() {
|
||||
let renderer = renderer().await;
|
||||
let grid = Grid::default();
|
||||
let original = Size::new(320., 240.);
|
||||
let threshold = 14. / 24.;
|
||||
// Each row changes exactly one dependency from its predecessor. Returning
|
||||
// to old dimensions must invalidate too; this cache holds one viewport.
|
||||
let cases = [
|
||||
("initial", original, 1., Vector::ZERO),
|
||||
("positive x pan", original, 1., Vector::new(24., 0.)),
|
||||
("negative x pan", original, 1., Vector::new(-24., 0.)),
|
||||
("fractional x pan", original, 1., Vector::new(-24.25, 0.)),
|
||||
("positive y pan", original, 1., Vector::new(-24.25, 18.)),
|
||||
("negative y pan", original, 1., Vector::new(-24.25, -18.)),
|
||||
("fractional y pan", original, 1., Vector::new(-24.25, -18.5)),
|
||||
("return to zero pan", original, 1., Vector::ZERO),
|
||||
("zoom at zero pan", original, 1.25, Vector::ZERO),
|
||||
(
|
||||
"below spacing threshold",
|
||||
original,
|
||||
threshold - 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
("at spacing threshold", original, threshold, Vector::ZERO),
|
||||
(
|
||||
"above spacing threshold",
|
||||
original,
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
(
|
||||
"width only",
|
||||
Size::new(321., 240.),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
("return width", original, threshold + 0.001, Vector::ZERO),
|
||||
(
|
||||
"height only",
|
||||
Size::new(320., 241.),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
("return height", original, threshold + 0.001, Vector::ZERO),
|
||||
(
|
||||
"fractional width",
|
||||
Size::new(320.25, 240.),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
(
|
||||
"fractional height",
|
||||
Size::new(320.25, 240.5),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
];
|
||||
for (index, (label, size, zoom, pan)) in cases.into_iter().enumerate() {
|
||||
let _ = grid.draw(&renderer, size, zoom, pan);
|
||||
assert_eq!(grid.builds.get(), index + 1, "{label} must rebuild");
|
||||
for _ in 0..3 {
|
||||
let _ = grid.draw(&renderer, size, zoom, pan);
|
||||
assert_eq!(grid.builds.get(), index + 1, "{label} must reuse");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Independent reference copied from the former DiagramCanvas::draw background
|
||||
// pass. Do not call grid::paint here: a shared implementation would hide changes
|
||||
// in spacing, phase, radius, color, or coverage from the parity comparison.
|
||||
fn legacy_background(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.fill_rectangle(Point::ORIGIN, size, Color::from_rgb8(246, 248, 251));
|
||||
let spacing = (24. * zoom).max(14.);
|
||||
let offset_x = pan.x.rem_euclid(spacing);
|
||||
let offset_y = pan.y.rem_euclid(spacing);
|
||||
for column in 0..(size.width / spacing) as i32 + 1 {
|
||||
for row in 0..(size.height / spacing) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(
|
||||
Point::new(
|
||||
offset_x + column as f32 * spacing,
|
||||
offset_y + row as f32 * spacing,
|
||||
),
|
||||
0.65,
|
||||
),
|
||||
Color::from_rgb(0.86, 0.89, 0.91),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn foreground(frame: &mut Frame, zoom: f32, pan: Vector, changed: bool) {
|
||||
frame.translate(pan);
|
||||
frame.scale(zoom);
|
||||
// Crossing paths pass behind opaque cards. The long paths also cross the
|
||||
// screen-space warning, making geometry order observable.
|
||||
for (start, end, color) in [
|
||||
(Point::new(0., 20.), Point::new(300., 210.), style::TEAL),
|
||||
(Point::new(0., 210.), Point::new(300., 20.), style::BOUNDARY),
|
||||
] {
|
||||
frame.stroke(
|
||||
&Path::line(start, end),
|
||||
Stroke::default().with_color(color).with_width(3.),
|
||||
);
|
||||
}
|
||||
let shift = if changed { 19.5 } else { 0. };
|
||||
frame.fill_rectangle(
|
||||
Point::new(12., 92.),
|
||||
Size::new(270., 35.),
|
||||
Color::from_rgba(0.1, 0.6, 0.5, 0.25),
|
||||
);
|
||||
for (position, size, title) in [
|
||||
(Point::new(32. + shift, 58.), Size::new(116., 72.), "Sensor"),
|
||||
(Point::new(172., 139.), Size::new(118., 66.), "Controller"),
|
||||
] {
|
||||
frame.fill_rectangle(position, size, Color::WHITE);
|
||||
frame.stroke(
|
||||
&Path::rectangle(position, size),
|
||||
Stroke::default().with_color(style::BOUNDARY).with_width(1.),
|
||||
);
|
||||
frame.fill_text(Text {
|
||||
content: title.into(),
|
||||
position: position + Vector::new(8., 12.),
|
||||
color: style::INK,
|
||||
size: 13.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
// Selection paint and handles remain live foreground data, including when
|
||||
// the underlying background cache is reused after an edit.
|
||||
frame.stroke(
|
||||
&Path::rectangle(Point::new(29. + shift, 55.), Size::new(122., 78.)),
|
||||
Stroke::default().with_color(style::TEAL).with_width(2.),
|
||||
);
|
||||
frame.fill_rectangle(Point::new(26. + shift, 52.), Size::new(6., 6.), style::TEAL);
|
||||
frame.fill_text(Text {
|
||||
content: if changed {
|
||||
"Edited connection"
|
||||
} else {
|
||||
"Connection"
|
||||
}
|
||||
.into(),
|
||||
position: Point::new(116., 113.),
|
||||
color: style::INK,
|
||||
size: 11.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn warning(frame: &mut Frame, size: Size) {
|
||||
frame.fill_rectangle(
|
||||
Point::new(8., 8.),
|
||||
Size::new((size.width - 16.).max(0.), 30.),
|
||||
Color::WHITE,
|
||||
);
|
||||
frame.fill_text(Text {
|
||||
content: "1 connection route needs attention".into(),
|
||||
position: Point::new(16., 15.),
|
||||
color: style::MUTED,
|
||||
size: 12.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn legacy_geometry(
|
||||
renderer: &Renderer,
|
||||
size: Size,
|
||||
zoom: f32,
|
||||
pan: Vector,
|
||||
changed: bool,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let mut combined = Frame::new(renderer, size);
|
||||
legacy_background(&mut combined, size, zoom, pan);
|
||||
foreground(&mut combined, zoom, pan, changed);
|
||||
let mut overlay = Frame::new(renderer, size);
|
||||
warning(&mut overlay, size);
|
||||
vec![combined.into_geometry(), overlay.into_geometry()]
|
||||
}
|
||||
|
||||
fn cached_geometry(
|
||||
grid: &Grid,
|
||||
renderer: &Renderer,
|
||||
size: Size,
|
||||
zoom: f32,
|
||||
pan: Vector,
|
||||
changed: bool,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let background = grid.draw(renderer, size, zoom, pan);
|
||||
let mut content = Frame::new(renderer, size);
|
||||
foreground(&mut content, zoom, pan, changed);
|
||||
let mut overlay = Frame::new(renderer, size);
|
||||
warning(&mut overlay, size);
|
||||
vec![background, content.into_geometry(), overlay.into_geometry()]
|
||||
}
|
||||
|
||||
fn pixels(renderer: &mut Renderer, size: Size, geometry: Vec<canvas::Geometry>) -> Vec<u8> {
|
||||
// Keep physical screenshot dimensions and renderer viewport consistent even
|
||||
// for fractional canvas bounds; the geometry itself keeps its exact size.
|
||||
let physical = Size::new(size.width.ceil() as u32, size.height.ceil() as u32);
|
||||
renderer.reset(Rectangle::with_size(Size::new(
|
||||
physical.width as f32,
|
||||
physical.height as f32,
|
||||
)));
|
||||
for layer in geometry {
|
||||
renderer.draw_geometry(layer);
|
||||
}
|
||||
let pixels = Headless::screenshot(renderer, physical, 1., style::BG);
|
||||
assert_eq!(
|
||||
pixels.len(),
|
||||
physical.width as usize * physical.height as usize * 4
|
||||
);
|
||||
pixels
|
||||
}
|
||||
|
||||
fn assert_same_pixels(expected: &[u8], actual: &[u8], label: &str) {
|
||||
assert_eq!(expected.len(), actual.len(), "{label}: dimensions");
|
||||
let differing = expected
|
||||
.as_chunks::<4>()
|
||||
.0
|
||||
.iter()
|
||||
.zip(actual.as_chunks::<4>().0)
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
assert_eq!(differing, 0, "{label}: changed RGBA pixels");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cached_background_preserves_pixels_and_live_foreground_across_camera_and_size_changes() {
|
||||
let mut renderer = renderer().await;
|
||||
let grid = Grid::default();
|
||||
let original = Size::new(320., 240.);
|
||||
let threshold = 14. / 24.;
|
||||
for (label, size, zoom, pan) in [
|
||||
("origin", original, 1., Vector::ZERO),
|
||||
("positive pan", original, 1., Vector::new(17., 11.)),
|
||||
("negative pan", original, 1., Vector::new(-17., -11.)),
|
||||
("fractional pan", original, 1., Vector::new(-17.25, 11.5)),
|
||||
("zoom zero pan", original, 1.25, Vector::ZERO),
|
||||
("below threshold", original, threshold - 0.001, Vector::ZERO),
|
||||
("at threshold", original, threshold, Vector::ZERO),
|
||||
("above threshold", original, threshold + 0.001, Vector::ZERO),
|
||||
("width resize", Size::new(327., 240.), 1., Vector::ZERO),
|
||||
("height resize", Size::new(327., 247.), 1., Vector::ZERO),
|
||||
("return size", original, 1., Vector::ZERO),
|
||||
(
|
||||
"fractional bounds",
|
||||
Size::new(319.25, 239.5),
|
||||
1.,
|
||||
Vector::new(-0.5, 0.25),
|
||||
),
|
||||
] {
|
||||
let old = legacy_geometry(&renderer, size, zoom, pan, false);
|
||||
let expected = pixels(&mut renderer, size, old);
|
||||
let layers = cached_geometry(&grid, &renderer, size, zoom, pan, false);
|
||||
let actual = pixels(&mut renderer, size, layers);
|
||||
assert_same_pixels(&expected, &actual, label);
|
||||
let builds = grid.builds.get();
|
||||
let reused = cached_geometry(&grid, &renderer, size, zoom, pan, false);
|
||||
let reused = pixels(&mut renderer, size, reused);
|
||||
assert_same_pixels(&expected, &reused, label);
|
||||
assert_eq!(grid.builds.get(), builds, "{label}: screenshot reuse");
|
||||
|
||||
let edited_old = legacy_geometry(&renderer, size, zoom, pan, true);
|
||||
let edited_expected = pixels(&mut renderer, size, edited_old);
|
||||
assert_ne!(
|
||||
expected, edited_expected,
|
||||
"{label}: edit must visibly change foreground"
|
||||
);
|
||||
let edited = cached_geometry(&grid, &renderer, size, zoom, pan, true);
|
||||
let edited = pixels(&mut renderer, size, edited);
|
||||
assert_same_pixels(&edited_expected, &edited, label);
|
||||
assert_eq!(
|
||||
grid.builds.get(),
|
||||
builds,
|
||||
"{label}: foreground edit must reuse grid"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn actual_canvas_keeps_background_cached_while_selection_and_revision_change() {
|
||||
use super::super::{Context, DiagramCanvas, State};
|
||||
let mut renderer = renderer().await;
|
||||
let mut project = Project::empty();
|
||||
let a = project
|
||||
.add_element(Kind::Entity, Layer::Oa, "Sensor", None)
|
||||
.unwrap();
|
||||
let b = project
|
||||
.add_element(Kind::Actor, Layer::Oa, "Controller", None)
|
||||
.unwrap();
|
||||
let diagram = project.ensure_diagram(Layer::Oa);
|
||||
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Overview;
|
||||
let node = project.place(diagram, a, Position { x: 20., y: 50. });
|
||||
project.place(diagram, b, Position { x: 100., y: 160. });
|
||||
project.relate(RelationKind::CommunicationMean, "Link", a, b);
|
||||
let mut canvas = DiagramCanvas {
|
||||
selection: None,
|
||||
allocation: None,
|
||||
project: &project,
|
||||
view_index: None,
|
||||
diagram,
|
||||
selected: a,
|
||||
navigation_focus: None,
|
||||
zoom: 0.75,
|
||||
pan: Vector::ZERO,
|
||||
context: Context {
|
||||
document: project.id,
|
||||
diagram,
|
||||
revision: 0,
|
||||
epoch: 0,
|
||||
},
|
||||
selected_bend: None,
|
||||
selected_attachment: None,
|
||||
editable: true,
|
||||
placing: false,
|
||||
connection: None,
|
||||
};
|
||||
let state = State::default();
|
||||
let size = Size::new(360., 280.);
|
||||
let bounds = Rectangle::with_size(size);
|
||||
let draw = |canvas: &DiagramCanvas<'_>, renderer: &Renderer, bounds| {
|
||||
canvas::Program::draw(
|
||||
canvas,
|
||||
&state,
|
||||
renderer,
|
||||
&crate::style::theme(),
|
||||
bounds,
|
||||
mouse::Cursor::Unavailable,
|
||||
)
|
||||
};
|
||||
let layers = draw(&canvas, &renderer, bounds);
|
||||
assert_eq!(layers.len(), 3, "background, scene, warning overlay");
|
||||
let original = pixels(&mut renderer, size, layers);
|
||||
assert_eq!(state.grid.builds.get(), 1);
|
||||
canvas.selected = b;
|
||||
let layers = draw(&canvas, &renderer, bounds);
|
||||
let selected = pixels(&mut renderer, size, layers);
|
||||
assert_ne!(original, selected, "selection must remain live");
|
||||
assert_eq!(state.grid.builds.get(), 1);
|
||||
let mut changed = project.clone();
|
||||
changed
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&node)
|
||||
.unwrap()
|
||||
.position
|
||||
.x += 30.;
|
||||
canvas.project = &changed;
|
||||
canvas.context.revision += 1;
|
||||
let layers = draw(&canvas, &renderer, bounds);
|
||||
let moved = pixels(&mut renderer, size, layers);
|
||||
assert_ne!(selected, moved, "new model revision must remain live");
|
||||
assert_eq!(state.grid.builds.get(), 1);
|
||||
canvas.pan.x = -0.25;
|
||||
let _ = draw(&canvas, &renderer, bounds);
|
||||
assert_eq!(state.grid.builds.get(), 2);
|
||||
let _ = draw(
|
||||
&canvas,
|
||||
&renderer,
|
||||
Rectangle::with_size(Size::new(361., 280.)),
|
||||
);
|
||||
assert_eq!(state.grid.builds.get(), 3);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
*.log -text
|
||||
*.rs -text
|
||||
@@ -0,0 +1,153 @@
|
||||
# Cold construction and pan alternatives
|
||||
|
||||
Follow-up to [the stationary-camera cache](README.md), for issue 44. Application
|
||||
source remains `336720559bcec511949caa8a42bee8a55c3822da`, with evidence checkpoint
|
||||
`2ca84676ee4dc30846208881a27d90a901482085`. These standalone experiments change no
|
||||
application code, dependencies, event handling or release. Native interaction
|
||||
remains paused; no result below establishes native FPS.
|
||||
|
||||
## Question and method
|
||||
|
||||
The existing cache removes repeated construction at identical camera inputs but
|
||||
misses during pan/zoom. Can simpler geometry or renderer-level translation remove
|
||||
that cost without changing the grid's appearance? Use the same unoptimized Iced
|
||||
0.14 WGPU dependencies and Windows environment recorded in `context.json`.
|
||||
Compile the new drivers with the same `rustc`/extern/linker arguments described
|
||||
in README, replacing the driver filename and output executable. No desktop window
|
||||
is created and no input is sent. Run `cold_grid_probe` with argument `20`; phase
|
||||
drivers take no arguments.
|
||||
|
||||
The cold driver compares original circles, one reusable origin circle transformed
|
||||
for each fill, one path per column, and direct 1.15-unit squares. Every candidate
|
||||
also constructs one origin-circle template; this small common overhead is retained.
|
||||
Three warmups precede 20 changing-pan samples. Construction ends before geometry
|
||||
destruction, rendering and readback. Percentiles use sorted index `ceil((n-1)*p)`.
|
||||
Fixed candidate ordering and uncontrolled OS load limit small timing comparisons.
|
||||
|
||||
## Cold construction results
|
||||
|
||||
`cold_grid_probe.rs` produces `grid-cold-01.log`. Median milliseconds:
|
||||
|
||||
| Canvas, zoom | Original | Reused circle | Column batch | Square |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| 1900x660, 1.0 | 61.0564 | 55.3975 | 54.3594 | 3.5527 |
|
||||
| 1900x660, 0.5 | 215.8919 | 162.3073 | 158.5968 | 7.5699 |
|
||||
| 1440x900, 1.0 | 60.1683 | 43.3860 | 60.8198 | 2.0340 |
|
||||
| 1440x900, 0.5 | 165.2049 | 133.3420 | 143.1489 | 6.8178 |
|
||||
|
||||
Both circle alternatives exactly match original pixels in all 12 sampled cases
|
||||
(four integer canvas/zoom pairs, three pan offsets, scale 1). This does not prove
|
||||
equivalence at fractional zoom, other DPI or other camera inputs. Squares match
|
||||
eight of those cases but differ at the third pan offset in each canvas/zoom pair:
|
||||
4,266–13,184 changed pixels, maximum channel difference 7. Their different shape
|
||||
and area are an explicit appearance change, not a pixel-preserving optimization.
|
||||
|
||||
Reusing circles and column batches leave substantial cold cost; the experiment
|
||||
does not justify adding them to production as the solution to low FPS. The square
|
||||
alternative is much faster in this scope, but is not adopted without evaluating
|
||||
its appearance and the actual interaction result. The previous all-grid single
|
||||
path experiment remains rejected for its much higher construction cost.
|
||||
|
||||
## Source explanation and independent review
|
||||
|
||||
A fresh read-only critic checked the probes and actual local Iced source. Each
|
||||
`Path::circle` allocates/builds a path and converts its arc into quadratic segments.
|
||||
Each WGPU `Frame::fill` allocates a boxed geometry builder and invokes Lyon fill
|
||||
tessellation. Same-color fills already share a solid mesh buffer: this is not
|
||||
one GPU draw call per dot. Frame translation transforms paths before tessellation,
|
||||
so path reuse alone does not reuse triangles.
|
||||
|
||||
Renderer-level `with_translation` can reuse cached triangles. Stock Canvas cannot
|
||||
attach a separate transform to each returned geometry; application adoption would
|
||||
need a small background widget or wrapper, with layout, clipping, lifecycle and
|
||||
interaction checks. Scaling the grid to handle zoom is unsuitable because the
|
||||
dot radius must remain fixed in screen units.
|
||||
|
||||
The critic also identified two important controls. Original opaque background
|
||||
geometry must not silently become screenshot-clear color: MSAA/alpha composition
|
||||
can change quantized pixels. Also, `iced_wgpu::triangle::Storage::prepare` rewrites
|
||||
cached mesh data when its transform changes. Construction/command-recording time
|
||||
therefore excludes real preparation/upload costs. The later probe measures a
|
||||
rendering/readback path separately; neither timer represents native presentation.
|
||||
|
||||
Primary sources: Iced 0.14 `iced_graphics/src/geometry/path{,/builder}.rs`,
|
||||
`iced_wgpu/src/{geometry,triangle}.rs`, `iced_core/src/renderer.rs`, and
|
||||
`iced_widget/src/canvas.rs`. The reviewer changed no files and did not execute the
|
||||
probes; execution and interpretation remain the parent's responsibility.
|
||||
|
||||
## Renderer-translated phase cache
|
||||
|
||||
`phase_grid_probe_v1.rs` / `grid-phase-01.log` cache zero-phase circles once for
|
||||
each size/spacing pair, translate by `pan.rem_euclid(step)`, and externally clip
|
||||
to the original viewport. The original nonnegative row/column indices and fixed
|
||||
screen-space radius are retained. Cache clip bounds include the radius margins.
|
||||
This first prototype uses screenshot clear color instead of opaque background
|
||||
geometry; its widespread one-level pixel differences are not accepted as parity.
|
||||
Its compiler emitted one duplicate Renderer-import warning, retained in the
|
||||
original driver. Later drivers remove that import and compile with `-D warnings`.
|
||||
|
||||
`phase_grid_opaque_probe.rs` / `grid-phase-opaque-01.log` add an oversized opaque
|
||||
background to the same cached geometry as the circles. Its bounds extend from
|
||||
`(-step,-step)` to `(width+2*step,height+2*step)`, covering the viewport at every
|
||||
positive remainder without adding extra dot rows or columns. This directly
|
||||
compares against the unchanged legacy painter. A proposed separate-background
|
||||
control was not executed; no result is claimed for it.
|
||||
|
||||
Of 75 comparisons (five size/zoom pairs, five offsets, three DPI scales), 73 are
|
||||
byte-exact. All 60 cases at integer canvas sizes and spacing 14 or 24 are exact.
|
||||
At 301.5x211.25, zoom 0.73, pan (-13.25,9.75), scale 1 changes 12 pixels by at
|
||||
most one channel level; scale 2 changes one pixel by at most six levels. These
|
||||
are failures of exact parity and are preserved, not hidden behind a tolerance.
|
||||
|
||||
The targeted `phase_grid_diagnostic.rs` / `grid-phase-diagnostic-01.log` locates
|
||||
all 12 scale-1 differences at the rightmost physical column x=301. The scale-2
|
||||
single-pixel difference does not recur. That diagnostic skips earlier large
|
||||
cases and their rendering/timing sequence: it does not exonerate the earlier
|
||||
failure or establish that every residual difference is at an edge.
|
||||
|
||||
The replay driver's first compilation failed with E0689 because the new control's
|
||||
`Vector::new(-13.25,9.75)` left its numeric type ambiguous before `rem_euclid`.
|
||||
An explicit `Vector<f32>` annotation corrects the experiment harness. No probe
|
||||
executed during that failed attempt; no application source changed.
|
||||
|
||||
`phase_grid_replay.rs` preserves the full opaque probe's rendering/timing sequence
|
||||
and adds coordinate diagnostics without extra renders until that sequence ends.
|
||||
`grid-phase-replay-01.log` reproduces both failures: the 12 right-edge pixels and
|
||||
one **interior** pixel at physical (149,263), where legacy RGBA is
|
||||
`[240,243,246,255]` and cached RGBA is `[246,248,251,255]`. The latter cannot be
|
||||
dismissed as viewport-edge rounding. Afterwards, all 12 same-renderer repeat
|
||||
controls (three repetitions of old/old and cached/cached at two scales) are
|
||||
byte-exact; the control cache builds once. This finite control does not establish
|
||||
universal determinism, but supports investigating differing geometry coverage
|
||||
instead of treating the reproduced pixel as random readback noise.
|
||||
|
||||
Cold construction plus command recording takes 68.42–173.72ms median for the four
|
||||
large viewports; cached translation takes 0.0008–0.0011ms. Each per-size/spacing
|
||||
cache builds exactly once through all pan comparisons and measurements. Zoom
|
||||
and resize through a single retained cache are not tested by these drivers.
|
||||
The labels containing `submit` mean renderer command recording, not GPU submission.
|
||||
|
||||
Two selected cases include construction, recording, backend preparation and GPU
|
||||
readback in a separate timer (two warmups, eight samples, sorted-index median):
|
||||
|
||||
| Canvas, zoom | Original p50 ms | Phase cache p50 ms |
|
||||
| --- | ---: | ---: |
|
||||
| 1900x660, 1.0 | 259.9265 | 212.6536 |
|
||||
| 1440x900, 0.5 | 337.2560 | 170.3780 |
|
||||
|
||||
These support a benefit within the headless path, but retain significant renderer
|
||||
and readback cost and variable outliers. They are not native frame times. Sample
|
||||
size, sequential ordering and uncontrolled machine load prohibit a precise
|
||||
general speedup claim. All driver/log hashes and execution mapping are in
|
||||
`cold-pan-context.json`.
|
||||
|
||||
The phase candidate removes the measured pan tessellation cost, but does not
|
||||
meet exact visual preservation. Do not adopt it as an invisible optimization.
|
||||
The small circle rewrites leave the main cost and squares change appearance.
|
||||
Further work should investigate the reproduced interior coverage difference or
|
||||
compare optimized dependency/build profiles before adding a custom widget.
|
||||
Any adopted rendering candidate must then verify a real mounted scene at a
|
||||
nonzero widget origin, foreground ordering, viewport clipping, pan/zoom/resize
|
||||
cache lifecycle and unchanged event behavior. Native interaction/FPS verification
|
||||
remains a separate required gate. No application implementation is changed by
|
||||
these experiments.
|
||||
@@ -0,0 +1,180 @@
|
||||
# Architecture background geometry cache
|
||||
|
||||
Scope: [issue 44](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/44).
|
||||
Production/test candidate: `636093aae67aa48fa1d35e403a205227e81d04de`, based on
|
||||
`c0908f101ac376afb8021e60e454e2a176e49d45`. The base includes the separately
|
||||
unaccepted outgoing-drag contribution for issue 40. This is a contribution
|
||||
experiment and repair, not an integrated release or full FPS acceptance.
|
||||
|
||||
## Question and evidence
|
||||
|
||||
The [preceding core probe](../architecture-perf-20260925/README.md) measured
|
||||
approximately 4.26ms median for preparation/projection/routing in an eight-card
|
||||
approximation. It did not reproduce the reported severe slowdown. Source review
|
||||
then identified repeated grid construction on every canvas redraw, despite the
|
||||
grid's independence from model edits, selection and pointer position.
|
||||
|
||||
The standalone headless Iced probe copies the former grid loop: background fill,
|
||||
then one radius-0.65 circle per grid point, with the same spacing, pan remainder
|
||||
and colors. It measures geometry construction independently from offscreen
|
||||
rendering/readback, using the actual compiled development-profile Iced renderer.
|
||||
It creates no desktop window and sends no input. Three warmups precede 20 samples
|
||||
per phase. The timer includes construction and returning geometry; returned
|
||||
geometry destruction occurs afterwards. Sorted index `ceil((n-1)*p)` supplies
|
||||
reported percentiles. Renderer startup is outside the timed phases.
|
||||
|
||||
`grid-performance-01.log`, produced by `grid_render_probe_v1.rs`, records:
|
||||
|
||||
| Canvas, zoom | Dots | Fresh geometry p50 ms | Cache retrieval p50 ms |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| 1900x660, 1.0 | 2,240 | 67.8095 | 0.0001 |
|
||||
| 1900x660, 0.5 | 6,528 | 192.5581 | 0.0001 |
|
||||
| 1440x900, 1.0 | 2,318 | 66.2206 | 0.0002 |
|
||||
| 1440x900, 0.5 | 6,695 | 219.1771 | 0.0002 |
|
||||
|
||||
Cold and warm cached grid pixels equal the uncached result byte for byte in all
|
||||
four cases. Cache misses remain comparable to fresh construction; caching does
|
||||
not improve continuously changing camera inputs. Offscreen screenshot/readback
|
||||
cost remains substantial and variable. It must not be converted to native FPS.
|
||||
|
||||
## Candidate comparison and chosen change
|
||||
|
||||
1. Rebuild each circle every draw: exact current appearance, but the measured
|
||||
repeated CPU cost alone is large in this development build.
|
||||
2. Batch circles into one path: keeps the same pixels in this experiment but is
|
||||
materially slower. `grid_render_probe.rs` and `grid-performance-02.log` retain
|
||||
this rejected candidate. Its construction medians range from 274.66ms to
|
||||
1,225.53ms, versus 63.82–178.24ms for the original loop in the same run.
|
||||
3. Cache background geometry while camera and size stay identical: selected.
|
||||
It preserves the original painter and removes its repeated construction from
|
||||
stationary-camera interactions. It adds a small local cache, not a second
|
||||
geometry implementation or a model-level cache.
|
||||
|
||||
`diagram/grid.rs` owns an Iced geometry cache and an exact raw `(zoom, pan)` key.
|
||||
Iced's cache checks exact frame size. Camera changes explicitly clear it. The
|
||||
colors and dot radius remain compile-time constants; future runtime styling
|
||||
must invalidate this cache too. The conservative key rebuilds after a whole
|
||||
grid-period pan and after zoom changes within the minimum-spacing clamp. No
|
||||
fractional coordinates are rounded or tolerance-compared.
|
||||
|
||||
Drawing order becomes `[cached background and dots, live diagram, warnings]`.
|
||||
The opaque background is in the first geometry, so it cannot cover the dots.
|
||||
Existing pan/zoom transforms still apply only to the live model geometry.
|
||||
The cache lives in the retained canvas State; model revisions and selections
|
||||
do not invalidate it. Existing focus/suspension tree resets cause a safe cold
|
||||
rebuild. No routing, model semantics, hit testing or gesture admission changes.
|
||||
|
||||
`production_grid_probe.rs` imports the actual candidate cache. Its exploratory
|
||||
run 03 was performed before the source commit and formatting; the raw log is
|
||||
retained as local precommit evidence, not relabeled as a binary from the commit.
|
||||
The same source is supplied for reproduction at the identified candidate.
|
||||
|
||||
The separately recompiled run 04 imports the committed `636093aa` cache and ran
|
||||
after the full desktop test process exited, before Clippy/build compilation.
|
||||
`grid-performance-04.log` records exact baseline/cached pixels and these medians:
|
||||
|
||||
| Canvas, zoom | Fresh geometry ms | Production cache hit ms | Changing-pan rebuild ms |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| 1900x660, 1.0 | 61.8281 | 0.0002 | 59.2995 |
|
||||
| 1900x660, 0.5 | 158.8603 | 0.0002 | 163.4359 |
|
||||
| 1440x900, 1.0 | 50.5385 | 0.0002 | 53.5840 |
|
||||
| 1440x900, 0.5 | 168.4092 | 0.0002 | 175.2457 |
|
||||
|
||||
These results support the same bounded conclusion as the exploratory trials.
|
||||
The run record preserves linked-library, driver and log hashes; timing noise
|
||||
and screenshot readback remain explicit limitations.
|
||||
Run 04 directly compares the production cache's cold pixels; its generic
|
||||
cold-and-warm marker refers to the Iced-cache control. Warm production-cache
|
||||
pixel parity is established separately by the committed grid tests.
|
||||
|
||||
## Verification and boundaries
|
||||
|
||||
Three committed headless tests check:
|
||||
|
||||
- Cache construction count through 18 exact camera/size changes, including
|
||||
negative/fractional pan, zero-pan zoom changes, the minimum-spacing transition,
|
||||
independent width/height resizing and return to previous dimensions.
|
||||
- Exact RGBA equality against an independent copy of the legacy loop and
|
||||
unsplit geometry. The scenes contain opaque cards, crossing edges, text,
|
||||
translucent overdraw, selection handles and a warning overlay. Foreground
|
||||
edits must visibly change pixels while retaining the background cache.
|
||||
- The actual `DiagramCanvas` with one retained State: selection and model
|
||||
revision changes must alter rendered pixels without rebuilding the grid;
|
||||
pan and resize must rebuild it. This is drawing/scene-invalidation evidence,
|
||||
not an event-driven native gesture test.
|
||||
|
||||
Fresh read-only independent review found no concrete cache-key, lifetime,
|
||||
ordering or test-oracle defect. It checked the corresponding Iced 0.14 cache
|
||||
and mesh-layer implementations. This review does not establish execution.
|
||||
At `636093aa`, `grid-tests-01.log` passes all three targeted tests in 2.97s.
|
||||
`grid-desktop-01.log` passes the full desktop suite in 362.14s: 1,543 passed,
|
||||
zero failed, seven existing manual/diagnostic tests ignored, none filtered out.
|
||||
All 1,550 distinct test names were counted; `context.json` lists the seven
|
||||
ignored cases and records raw log hashes. This is desktop regression evidence;
|
||||
it does not relabel the ignored checks as passing or establish native FPS.
|
||||
The [independent review](independent-review.md) states its exact scope.
|
||||
|
||||
The initial strict Clippy run (`grid-clippy-01.log`) failed on two uses of
|
||||
`chunks_exact(4)` in the new test helper. Test-only successor
|
||||
`336720559bcec511949caa8a42bee8a55c3822da` uses `as_chunks::<4>()`; it leaves
|
||||
production inputs unchanged. The original full-suite result remains associated
|
||||
with `636093aa`, not relabeled as execution at the successor.
|
||||
At the successor, strict all-target Clippy passes in 2m01s
|
||||
(`grid-clippy-02.log`) and the three affected grid tests pass again in 4.13s
|
||||
(`grid-tests-02.log`). No warning suppression or test exclusion was introduced.
|
||||
|
||||
The normal Windows GUI build at `33672055` also passes in 2m15s
|
||||
(`desktop-build-02.log`). The executable and required `libunwind.dll` are staged
|
||||
locally for later native verification, but this candidate has not been launched.
|
||||
The executable SHA-256 is
|
||||
`78548e93d4b57130ed507ed0f29eddbdbafb4721b38b0cd6d06101d5324114dd`;
|
||||
`context.json` records both staged file hashes. Staging is not publication of a
|
||||
new runnable release or native acceptance.
|
||||
|
||||
Remaining limits: the user's exact saved scene is unavailable; these results do
|
||||
not establish end-to-end native FPS. Continuous pan/zoom still pays the cold
|
||||
construction cost. Native desktop verification remains paused after user Escape.
|
||||
Neither issue 40 nor issue 44 is closed by this headless experiment. Linux/WSL
|
||||
remains deferred by the user. No published release has been replaced.
|
||||
|
||||
## Environment and reproduction
|
||||
|
||||
The subsequent [cold-construction and pan experiments](COLD-PAN.md) compare
|
||||
additional approaches, including a renderer-translated cache. They preserve
|
||||
their own pixel differences, timing limits and adoption gates; they do not
|
||||
change the production implementation verified above.
|
||||
|
||||
Windows 10.0.26200, Rust 1.98.1, x86_64-pc-windows-gnullvm, development profile
|
||||
with debug information disabled. The exposed CPU is Intel Core i7-10870H with
|
||||
4 cores/threads; Windows reports VMware SVGA 3D, driver 9.17.11.4. The headless
|
||||
renderer identifies itself as `wgpu`; the probe does not identify its selected
|
||||
adapter or equate it with the desktop presenter's adapter. Other OS workloads
|
||||
were not controlled, so measured outliers are preserved.
|
||||
|
||||
At the candidate source, the normal gates are:
|
||||
|
||||
```powershell
|
||||
cargo test --locked --offline -p oa-desktop diagram::grid::tests -- --test-threads=1
|
||||
cargo test --locked --offline -p oa-desktop -- --test-threads=1
|
||||
cargo clippy --locked --offline -p oa-desktop --all-targets -- -D warnings
|
||||
cargo build --locked --offline -p oa-desktop --bin arcadia-studio
|
||||
```
|
||||
|
||||
For the standalone probes, first build the desktop dependencies in that profile.
|
||||
Compile with `rustc --edition=2024 -C opt-level=0`, supplying `--extern iced=...`
|
||||
and `--extern tokio=...` for the matching rlibs, plus `-L dependency=...` for
|
||||
their directory. On this toolchain the link also needs `-C linker=clang.exe`
|
||||
and `-L native=.../windows_x86_64_gnullvm-0.52.6/lib`. The original inputs were
|
||||
`libiced-594f7d417d69ed72.rlib` and `libtokio-5e0ca96735e3073a.rlib` from
|
||||
`target/debug/deps`. Run the resulting executable with argument `20`.
|
||||
The production probe's relative module path requires compiling it at its
|
||||
versioned location. Keep linker and runtime DLLs on PATH.
|
||||
|
||||
The first experimental compile failed because the configured futures crate
|
||||
lacked its executor feature and an inherent screenshot method shadowed the
|
||||
Headless trait method. Using the existing Tokio runtime and explicitly calling
|
||||
`Headless::screenshot` corrected those driver errors. A second link failed for
|
||||
the Windows import library search path; `grid-build-02.log` retains that failure.
|
||||
Adding the native library path allowed subsequent probes to run. These failures
|
||||
occurred in the experimental driver, before execution; they are not passing
|
||||
application checks.
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"application_source": "336720559bcec511949caa8a42bee8a55c3822da",
|
||||
"base_evidence": "2ca84676ee4dc30846208881a27d90a901482085",
|
||||
"production_changes": false,
|
||||
"environment": "Same unoptimized Windows Iced/Tokio artifacts as context.json",
|
||||
"logs": [
|
||||
{
|
||||
"driver": "cold_grid_probe.rs",
|
||||
"exit_code": 0,
|
||||
"log": "grid-cold-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_probe_v1.rs",
|
||||
"exit_code": 0,
|
||||
"compile_warning": "Unused duplicate Renderer trait import; retained in original driver",
|
||||
"log": "grid-phase-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_opaque_probe.rs",
|
||||
"exit_code": 0,
|
||||
"compile_warnings_denied": true,
|
||||
"log": "grid-phase-opaque-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_diagnostic.rs",
|
||||
"exit_code": 0,
|
||||
"compile_warnings_denied": true,
|
||||
"log": "grid-phase-diagnostic-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_replay.rs",
|
||||
"log": "grid-phase-replay-01.log",
|
||||
"exit_code": 0,
|
||||
"compile_warnings_denied": true,
|
||||
"initial_compile": "E0689 ambiguous Vector scalar before rem_euclid, fixed with Vector<f32>; no execution on failed attempt"
|
||||
}
|
||||
],
|
||||
"sha256": {
|
||||
"cold_grid_probe.rs": "a4cc6543229ae4901d54061823a3b05ec6fe3b988f245df12898e013dbb93323",
|
||||
"phase_grid_probe_v1.rs": "c4293e8bb022fae5bffe67002a6f7c867549f2fa8a865b107f39070655bc57fc",
|
||||
"phase_grid_opaque_probe.rs": "43ac5be462ca31f9d1c49554291e08b60bac9de38face7138eaa850e4dcc0ab9",
|
||||
"phase_grid_diagnostic.rs": "8de3c980ac65eef4fcb32b1b121d14aca35b93c704c5b19a440ad9dc7042248c",
|
||||
"grid-cold-01.log": "ca5cac0be409bafcea2c154d42998cbd622c9e898892574bcd5a2df0c1a70886",
|
||||
"grid-phase-01.log": "e0b2e2a3e24d642e1b6c2b1b418f24c952399758159de051f7c1ba7efbb39c4a",
|
||||
"grid-phase-opaque-01.log": "2716cbeb589f4b23a48575680022231c0b94b56ba9c06ec1adbf9e3e6d07f7cd",
|
||||
"grid-phase-diagnostic-01.log": "1dbb0f745169b88c56a21683dba6797a3dbbe753e8dd742bc9ecc8ca24788886",
|
||||
"phase_grid_replay.rs": "96e62ba5d08f7e26d9705d3f90e3785151597b526293439345b59ed842de4971",
|
||||
"grid-phase-replay-01.log": "040034be1b8eb3d4fc010bbe34c5ded47a953b8295bcf2f9440507d1a149da1f"
|
||||
},
|
||||
"native_fps": "unverified"
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
//! 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<u8> {
|
||||
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::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
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={} 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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"source": "636093aae67aa48fa1d35e403a205227e81d04de",
|
||||
"target": "x86_64-pc-windows-gnullvm",
|
||||
"rustc": "1.98.1",
|
||||
"rust_llvm": "22.1.8",
|
||||
"linker_clang": "23.1.1",
|
||||
"profile": "dev/test unoptimized debug=0",
|
||||
"incremental": false,
|
||||
"build_jobs": 1,
|
||||
"full_test_count": 1550,
|
||||
"passed": 1543,
|
||||
"ignored": 7,
|
||||
"failed": 0,
|
||||
"ignored_tests": [
|
||||
"app::source_rename::timing_tests::accepted_release1_prepare_commit_sync_and_confirm_timing",
|
||||
"app::source_rename::timing_tests::layout_timing_tests::accepted_release1_constructor_vs_reuse_through_next_highlight",
|
||||
"app::source_symbols::tests::source_symbols_100_modules_1000_definitions_10000_uses",
|
||||
"behavior::scenario::spacing::controller::timing_tests::saved_spacing_capture_prepare_20_lanes_1000_messages_3_views",
|
||||
"source_annotations::anchor_tests::final_multi_glyph_cluster_anchor_uses_logical_trailing_advance_edge",
|
||||
"source_annotations::anchor_tests::mixed_direction_omitted_separator_has_one_logical_predecessor_anchor",
|
||||
"source_annotations::performance_tests::annotation_projection_cost_distributions"
|
||||
],
|
||||
"log_sha256": {
|
||||
"grid-tests-01.log": "ed76ce90e9246dfddf1d3a96d6a1b91d0d0719701b1d09ddb639a1277c089612",
|
||||
"grid-desktop-01.log": "2000cf03685d6fde73c3dede90f84153a45c6ad0a5c2886a2bc09dda98ba63c3",
|
||||
"grid-performance-04.log": "c492aba0bbe600fa1c2a7f0ac52e5706d29cb67aca95fa3a31641ff7f90c2412",
|
||||
"grid-clippy-01.log": "2498a3d0be9738b8e43ae588c23d6e48f531fdbfc5f47d2f79c1a2f0f1b5bdc1",
|
||||
"grid-clippy-02.log": "69de93cce0c53f37f3c82eb28a28c1610548b30fec4680f02b9b7b118606040f",
|
||||
"grid-tests-02.log": "54c1d72402310be8b3026886dfda0339aa1a81c76b8165c40c97ea075524bff2",
|
||||
"desktop-build-02.log": "e39d1eaaceb8a5805a486c204d6eca7e1ce2b9b39b232e770ab0ad33cbe0e860"
|
||||
},
|
||||
"native_acceptance": "unverified",
|
||||
"source_clean_after_full_test": true,
|
||||
"timing_run": {
|
||||
"source": "636093aae67aa48fa1d35e403a205227e81d04de",
|
||||
"samples": 20,
|
||||
"warmups": 3,
|
||||
"probe_exe_sha256": "79c2e0b64cfacf24b7ee10d8690969d961753945f45d16a743ea750b9eb049d0",
|
||||
"probe_source_sha256": "e6efe1e510164444912d05a96e49c55d791b7046102013458a5ad94bd474a6cd",
|
||||
"grid_git_blob_sha256": "5e7c54d1b4da2e2c082f8ef011a3755f8808fcd67f1babf3be5448cd94240e6c",
|
||||
"iced_rlib_sha256": "9fec4bea1a5ac7e1ae75d786b76e3dbe97a6f63b30d11b6753f268c2ad9efbc6",
|
||||
"tokio_rlib_sha256": "78587ae94762065914b25065c85d9cb66106b71619ec7b65f5e16cdeb689abb9"
|
||||
},
|
||||
"test_only_successor": {
|
||||
"source": "336720559bcec511949caa8a42bee8a55c3822da",
|
||||
"change": "chunks_exact(4) replaced by as_chunks::<4>() in the pixel test helper",
|
||||
"targeted_passed": 3,
|
||||
"targeted_failed": 0,
|
||||
"targeted_filtered": 1547,
|
||||
"strict_clippy": "passed",
|
||||
"normal_build": "passed"
|
||||
},
|
||||
"staged_candidate": {
|
||||
"source": "336720559bcec511949caa8a42bee8a55c3822da",
|
||||
"launched": false,
|
||||
"files_sha256": {
|
||||
"arcadia-studio.exe": "78548e93d4b57130ed507ed0f29eddbdbafb4721b38b0cd6d06101d5324114dd",
|
||||
"libunwind.dll": "aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 2m 15s
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
|
||||
Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
error: using `chunks_exact` with a constant chunk size
|
||||
--> crates\oa-desktop\src\diagram\grid\tests.rs:231:10
|
||||
|
|
||||
231 | .chunks_exact(4)
|
||||
| ^^^^^^^^^^^^^^^ help: consider using `as_chunks` instead: `as_chunks::<4>().0.iter()`
|
||||
|
|
||||
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
|
||||
= note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`
|
||||
|
||||
error: using `chunks_exact` with a constant chunk size
|
||||
--> crates\oa-desktop\src\diagram\grid\tests.rs:232:21
|
||||
|
|
||||
232 | .zip(actual.chunks_exact(4))
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `as_chunks::<4>()` instead
|
||||
--> crates\oa-desktop\src\diagram\grid\tests.rs:232:21
|
||||
|
|
||||
232 | .zip(actual.chunks_exact(4))
|
||||
| ^^^^^^^^^^^^^^^
|
||||
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
|
||||
|
||||
error: could not compile `oa-desktop` (bin "arcadia-studio" test) due to 2 previous errors
|
||||
@@ -0,0 +1,2 @@
|
||||
Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 2m 01s
|
||||
@@ -0,0 +1,69 @@
|
||||
renderer=wgpu n=20; construction only, unoptimized linked dependencies; NOT native FPS
|
||||
size=1900x660 zoom=1
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=61.0564 p95_ms=111.2137 max_ms=111.2137
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=55.3975 p95_ms=73.8790 max_ms=73.8790
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=54.3594 p95_ms=69.6607 max_ms=69.6607
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=4266 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=3.5527 p95_ms=5.6911 max_ms=5.6911
|
||||
size=1900x660 zoom=0.5
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=215.8919 p95_ms=253.9666 max_ms=253.9666
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=162.3073 p95_ms=212.2833 max_ms=212.2833
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=158.5968 p95_ms=449.6066 max_ms=449.6066
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=12784 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=7.5699 p95_ms=13.3416 max_ms=13.3416
|
||||
size=1440x900 zoom=1
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=60.1683 p95_ms=88.4967 max_ms=88.4967
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=43.3860 p95_ms=53.1866 max_ms=53.1866
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=60.8198 p95_ms=80.8932 max_ms=80.8932
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=4440 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=2.0340 p95_ms=4.0620 max_ms=4.0620
|
||||
size=1440x900 zoom=0.5
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=165.2049 p95_ms=203.5331 max_ms=203.5331
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=133.3420 p95_ms=143.9790 max_ms=143.9790
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=143.1489 p95_ms=155.3680 max_ms=155.3680
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=13184 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=6.8178 p95_ms=10.8775 max_ms=10.8775
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=67.8095 p95_ms=77.2227 max_ms=77.2227
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=171.4916 p95_ms=231.2029 max_ms=231.2029
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=99.1916 p95_ms=125.0517 max_ms=125.0517
|
||||
cache_forced_miss_geometry: n=20 p50_ms=65.6279 p95_ms=75.6531 max_ms=75.6531
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=192.5581 p95_ms=237.9956 max_ms=237.9956
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0009 max_ms=0.0009
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=328.0257 p95_ms=498.0908 max_ms=498.0908
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=108.5102 p95_ms=119.6021 max_ms=119.6021
|
||||
cache_forced_miss_geometry: n=20 p50_ms=183.2740 p95_ms=212.6681 max_ms=212.6681
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=66.2206 p95_ms=89.5746 max_ms=89.5746
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0010 max_ms=0.0010
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=194.1484 p95_ms=445.2525 max_ms=445.2525
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=113.8430 p95_ms=152.8658 max_ms=152.8658
|
||||
cache_forced_miss_geometry: n=20 p50_ms=81.6705 p95_ms=200.7735 max_ms=200.7735
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=219.1771 p95_ms=270.8434 max_ms=270.8434
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=387.7051 p95_ms=600.4906 max_ms=600.4906
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=119.9417 p95_ms=157.2978 max_ms=157.2978
|
||||
cache_forced_miss_geometry: n=20 p50_ms=193.8187 p95_ms=222.6229 max_ms=222.6229
|
||||
@@ -0,0 +1,41 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=63.8161 p95_ms=74.3558 max_ms=74.3558
|
||||
batched_geometry: n=20 p50_ms=280.9291 p95_ms=361.9218 max_ms=361.9218
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0004 max_ms=0.0004
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=203.0484 p95_ms=314.9968 max_ms=314.9968
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=109.2162 p95_ms=470.8047 max_ms=470.8047
|
||||
cache_forced_miss_geometry: n=20 p50_ms=73.1544 p95_ms=112.9079 max_ms=112.9079
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=178.2363 p95_ms=204.1913 max_ms=204.1913
|
||||
batched_geometry: n=20 p50_ms=1225.5328 p95_ms=1791.5758 max_ms=1791.5758
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0008 max_ms=0.0008
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=281.4035 p95_ms=308.5933 max_ms=308.5933
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=100.8220 p95_ms=114.5026 max_ms=114.5026
|
||||
cache_forced_miss_geometry: n=20 p50_ms=179.6717 p95_ms=319.2162 max_ms=319.2162
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=67.1604 p95_ms=76.3298 max_ms=76.3298
|
||||
batched_geometry: n=20 p50_ms=274.6607 p95_ms=551.4707 max_ms=551.4707
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=168.4817 p95_ms=294.9456 max_ms=294.9456
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=92.0521 p95_ms=105.3303 max_ms=105.3303
|
||||
cache_forced_miss_geometry: n=20 p50_ms=70.1733 p95_ms=83.6053 max_ms=83.6053
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=177.7526 p95_ms=205.8862 max_ms=205.8862
|
||||
batched_geometry: n=20 p50_ms=1012.6393 p95_ms=1103.8729 max_ms=1103.8729
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0006 max_ms=0.0006
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=293.3138 p95_ms=315.0118 max_ms=315.0118
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=110.6309 p95_ms=127.6823 max_ms=127.6823
|
||||
cache_forced_miss_geometry: n=20 p50_ms=195.3297 p95_ms=327.8771 max_ms=327.8771
|
||||
@@ -0,0 +1,45 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=70.2238 p95_ms=101.8816 max_ms=101.8816
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0005 max_ms=0.0005
|
||||
production_changing_pan_geometry: n=20 p50_ms=74.2265 p95_ms=150.4436 max_ms=150.4436
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=179.3216 p95_ms=272.4518 max_ms=272.4518
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=108.8949 p95_ms=138.2430 max_ms=138.2430
|
||||
cache_forced_miss_geometry: n=20 p50_ms=73.2982 p95_ms=229.4395 max_ms=229.4395
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=190.5914 p95_ms=245.6332 max_ms=245.6332
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0004 max_ms=0.0004
|
||||
production_changing_pan_geometry: n=20 p50_ms=200.1888 p95_ms=243.7068 max_ms=243.7068
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=356.6151 p95_ms=438.3689 max_ms=438.3689
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=151.2161 p95_ms=236.6296 max_ms=236.6296
|
||||
cache_forced_miss_geometry: n=20 p50_ms=198.4648 p95_ms=312.0322 max_ms=312.0322
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=83.2017 p95_ms=284.6453 max_ms=284.6453
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0010 max_ms=0.0010
|
||||
production_changing_pan_geometry: n=20 p50_ms=61.6783 p95_ms=79.8293 max_ms=79.8293
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=168.7804 p95_ms=206.0726 max_ms=206.0726
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=114.7579 p95_ms=130.0840 max_ms=130.0840
|
||||
cache_forced_miss_geometry: n=20 p50_ms=103.6019 p95_ms=215.1691 max_ms=215.1691
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=202.9283 p95_ms=247.2353 max_ms=247.2353
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0004 max_ms=0.0004
|
||||
production_changing_pan_geometry: n=20 p50_ms=223.5437 p95_ms=243.7359 max_ms=243.7359
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=299.4320 p95_ms=359.2924 max_ms=359.2924
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=113.6960 p95_ms=180.7261 max_ms=180.7261
|
||||
cache_forced_miss_geometry: n=20 p50_ms=191.9370 p95_ms=294.2203 max_ms=294.2203
|
||||
@@ -0,0 +1,45 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=61.8281 p95_ms=68.9849 max_ms=68.9849
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
production_changing_pan_geometry: n=20 p50_ms=59.2995 p95_ms=69.6670 max_ms=69.6670
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0029 max_ms=0.0029
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=136.3939 p95_ms=160.0453 max_ms=160.0453
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=82.0448 p95_ms=105.5853 max_ms=105.5853
|
||||
cache_forced_miss_geometry: n=20 p50_ms=52.1529 p95_ms=58.0832 max_ms=58.0832
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=158.8603 p95_ms=169.1878 max_ms=169.1878
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
production_changing_pan_geometry: n=20 p50_ms=163.4359 p95_ms=262.5349 max_ms=262.5349
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=226.3661 p95_ms=247.0635 max_ms=247.0635
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=78.5761 p95_ms=99.7537 max_ms=99.7537
|
||||
cache_forced_miss_geometry: n=20 p50_ms=154.7299 p95_ms=177.8883 max_ms=177.8883
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=50.5385 p95_ms=57.4277 max_ms=57.4277
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
production_changing_pan_geometry: n=20 p50_ms=53.5840 p95_ms=60.8569 max_ms=60.8569
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0005 max_ms=0.0005
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=124.3827 p95_ms=140.3617 max_ms=140.3617
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=75.7445 p95_ms=97.7246 max_ms=97.7246
|
||||
cache_forced_miss_geometry: n=20 p50_ms=54.7114 p95_ms=61.8326 max_ms=61.8326
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=168.4092 p95_ms=186.4184 max_ms=186.4184
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
production_changing_pan_geometry: n=20 p50_ms=175.2457 p95_ms=205.4313 max_ms=205.4313
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=253.4003 p95_ms=420.1746 max_ms=420.1746
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=85.1261 p95_ms=107.0600 max_ms=107.0600
|
||||
cache_forced_miss_geometry: n=20 p50_ms=155.4512 p95_ms=163.8325 max_ms=163.8325
|
||||
@@ -0,0 +1,96 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=1900x660 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=2212 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=2133 max_channel_delta=1
|
||||
scale=1 pan=23.875,23.75 changed_pixels=2133 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=4424 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=8745 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=2212 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=4266 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=6557 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=8848 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=4266 max_channel_delta=1
|
||||
scale=2 pan=23.875,23.75 changed_pixels=10665 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=6478 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=66.6997 p95_ms=77.3888 max_ms=77.3888
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0010 p95_ms=0.0017 max_ms=0.0017
|
||||
cache_builds=1
|
||||
size=1900x660 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=6392 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=6392 max_channel_delta=1
|
||||
scale=1 pan=13.875,13.75 changed_pixels=6345 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=12960 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=12781 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=9588 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=11288 max_channel_delta=1
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=9494 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=12801 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=25568 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=12784 max_channel_delta=1
|
||||
scale=2 pan=13.875,13.75 changed_pixels=31725 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=19170 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=189.4570 p95_ms=221.8240 max_ms=221.8240
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0007 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=2280 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=2220 max_channel_delta=1
|
||||
scale=1 pan=23.875,23.75 changed_pixels=2220 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=4522 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=9000 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=2280 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=4403 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=6742 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=9120 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=4440 max_channel_delta=1
|
||||
scale=2 pan=23.875,23.75 changed_pixels=11026 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=6683 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=66.8644 p95_ms=93.9216 max_ms=93.9216
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=6592 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=6592 max_channel_delta=1
|
||||
scale=1 pan=13.875,13.75 changed_pixels=6528 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=13260 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=13306 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=9952 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=11488 max_channel_delta=1
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=9792 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=13209 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=26368 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=13184 max_channel_delta=1
|
||||
scale=2 pan=13.875,13.75 changed_pixels=32640 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=19686 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=202.5700 p95_ms=482.7433 max_ms=482.7433
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0011 p95_ms=0.0041 max_ms=0.0041
|
||||
cache_builds=1
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=263 max_channel_delta=1
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=251 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=250 max_channel_delta=1
|
||||
scale=1 pan=17.395,17.27 changed_pixels=238 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=241 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=379 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=395 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=332 max_channel_delta=1
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=289 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=372 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=759 max_channel_delta=1
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=626 max_channel_delta=6
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=731 max_channel_delta=1
|
||||
scale=2 pan=17.395,17.27 changed_pixels=576 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=784 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=6.6260 p95_ms=21.1743 max_ms=21.1743
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0010 p95_ms=0.0012 max_ms=0.0012
|
||||
cache_builds=1
|
||||
@@ -0,0 +1,32 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
difference_xy=301,26 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,27 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,62 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,80 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,97 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,115 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,132 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,150 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,167 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,184 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,185 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,202 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=12 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=6.1755 p95_ms=8.4025 max_ms=8.4025
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0011 p95_ms=0.0019 max_ms=0.0019
|
||||
cache_builds=1
|
||||
@@ -0,0 +1,100 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=1900x660 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=72.0271 p95_ms=99.9725 max_ms=99.9725
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0011 p95_ms=0.0015 max_ms=0.0015
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=259.9265 min_ms=253.3506 max_ms=281.4865
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=212.6536 min_ms=184.5191 max_ms=299.2711
|
||||
size=1900x660 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=167.4776 p95_ms=186.8968 max_ms=186.8968
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=68.4232 p95_ms=97.6147 max_ms=97.6147
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0458 max_ms=0.0458
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=173.7177 p95_ms=215.3188 max_ms=215.3188
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0009 max_ms=0.0009
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=337.2560 min_ms=319.0543 max_ms=366.4956
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=170.3780 min_ms=144.1992 max_ms=188.4337
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=12 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=1 max_channel_delta=6
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=5.4797 p95_ms=6.8957 max_ms=6.8957
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0007 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
@@ -0,0 +1,127 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=1900x660 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=68.3424 p95_ms=88.0883 max_ms=88.0883
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0010 p95_ms=0.0012 max_ms=0.0012
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=249.7269 min_ms=216.9378 max_ms=267.1425
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=215.6865 min_ms=177.7329 max_ms=262.5719
|
||||
size=1900x660 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=190.0141 p95_ms=231.4193 max_ms=231.4193
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=72.2449 p95_ms=86.5021 max_ms=86.5021
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=209.4728 p95_ms=329.0977 max_ms=329.0977
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0009 max_ms=0.0009
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=384.7680 min_ms=347.4879 max_ms=420.2894
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=164.1611 min_ms=150.1276 max_ms=186.4567
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=12 max_channel_delta=1
|
||||
difference_xy=301,26 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,27 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,62 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,80 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,97 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,115 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,132 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,150 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,167 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,184 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,185 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,202 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=1 max_channel_delta=6
|
||||
difference_xy=149,263 expected=[240, 243, 246, 255] actual=[246, 248, 251, 255]
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=5.5680 p95_ms=7.0634 max_ms=7.0634
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0007 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
original_sequence_complete; beginning same-renderer repeat controls
|
||||
comparison=old/old repetition=1 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=1 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=1 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=1 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=2 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=2 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=2 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=2 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=3 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=3 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=3 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=3 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
control_cache_builds=1
|
||||
@@ -0,0 +1,11 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 4m 23s
|
||||
Running unittests src\main.rs (C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\arcadia_studio-c5b42126ba36cd38.exe)
|
||||
|
||||
running 3 tests
|
||||
test diagram::grid::tests::actual_canvas_keeps_background_cached_while_selection_and_revision_change ... ok
|
||||
test diagram::grid::tests::cache_reuses_unchanged_inputs_and_rebuilds_for_each_camera_or_size_change ... ok
|
||||
test diagram::grid::tests::cached_background_preserves_pixels_and_live_foreground_across_camera_and_size_changes ... ok
|
||||
|
||||
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 1547 filtered out; finished in 2.97s
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 3m 31s
|
||||
Running unittests src\main.rs (C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\arcadia_studio-c5b42126ba36cd38.exe)
|
||||
|
||||
running 3 tests
|
||||
test diagram::grid::tests::actual_canvas_keeps_background_cached_while_selection_and_revision_change ... ok
|
||||
test diagram::grid::tests::cache_reuses_unchanged_inputs_and_rebuilds_for_each_camera_or_size_change ... ok
|
||||
test diagram::grid::tests::cached_background_preserves_pixels_and_live_foreground_across_camera_and_size_changes ... ok
|
||||
|
||||
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 1547 filtered out; finished in 4.13s
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
//! Headless rendering experiment; creates no desktop window and sends no input.
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
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 paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.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);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fresh(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
paint(&mut frame, size, zoom, pan);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn batched(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
frame.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 path = Path::new(|p| {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.fill(&path, DOT);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn screenshot(renderer: &mut Renderer, size: Size, geometry: Geometry) -> Vec<u8> {
|
||||
renderer.reset(Rectangle::with_size(size));
|
||||
renderer.draw_geometry(geometry);
|
||||
Headless::screenshot(
|
||||
renderer,
|
||||
Size::new(size.width as u32, size.height as u32),
|
||||
1.,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn measure<T>(label: &str, n: usize, mut f: impl FnMut() -> T) {
|
||||
for _ in 0..3 {
|
||||
black_box(f());
|
||||
}
|
||||
let mut values = Vec::new();
|
||||
for _ in 0..n {
|
||||
let now = Instant::now();
|
||||
let value = black_box(f());
|
||||
values.push(now.elapsed().as_secs_f64() * 1000.);
|
||||
drop(value);
|
||||
}
|
||||
values.sort_by(f64::total_cmp);
|
||||
let at = |p: f64| values[((n - 1) as f64 * p).ceil() as usize];
|
||||
println!(
|
||||
"{label}: n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
at(0.5),
|
||||
at(0.95),
|
||||
values[n - 1]
|
||||
);
|
||||
}
|
||||
fn main() {
|
||||
let n = std::env::args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut renderer = runtime
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.expect("headless production renderer");
|
||||
println!(
|
||||
"renderer={} samples={n}; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation",
|
||||
renderer.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),
|
||||
] {
|
||||
let pan = Vector::new(-13.25, 9.75);
|
||||
let cache = Cache::new();
|
||||
let calls = Cell::new(0);
|
||||
let cached = |r: &Renderer| {
|
||||
cache.draw(r, size, |f| {
|
||||
calls.set(calls.get() + 1);
|
||||
paint(f, size, zoom, pan);
|
||||
})
|
||||
};
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
println!(
|
||||
"\nsize={}x{} zoom={zoom} dots={}",
|
||||
size.width,
|
||||
size.height,
|
||||
((size.width / step) as i32 + 1) * ((size.height / step) as i32 + 1)
|
||||
);
|
||||
let baseline = fresh(&renderer, size, zoom, pan);
|
||||
let baseline_pixels = screenshot(&mut renderer, size, baseline);
|
||||
let geometry = batched(&renderer, size, zoom, pan);
|
||||
let pixels = screenshot(&mut renderer, size, geometry);
|
||||
let differences = baseline_pixels
|
||||
.iter()
|
||||
.zip(&pixels)
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
println!("batched_rgba_byte_differences={differences}");
|
||||
assert_eq!(
|
||||
differences, 0,
|
||||
"batched path must preserve exact grid pixels"
|
||||
);
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
assert_eq!(calls.get(), 1);
|
||||
println!("pixel_parity=cold_and_warm_exact");
|
||||
measure("fresh_geometry", n, || fresh(&renderer, size, zoom, pan));
|
||||
measure("batched_geometry", n, || {
|
||||
batched(&renderer, size, zoom, pan)
|
||||
});
|
||||
measure("cached_geometry", n, || cached(&renderer));
|
||||
assert_eq!(calls.get(), 1, "identical camera must reuse geometry");
|
||||
measure("fresh_geometry_and_screenshot", n, || {
|
||||
let g = fresh(&renderer, size, zoom, pan);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cached_geometry_and_screenshot", n, || {
|
||||
let g = cached(&renderer);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cache_forced_miss_geometry", n, || {
|
||||
cache.clear();
|
||||
cached(&renderer)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
//! Headless rendering experiment; creates no desktop window and sends no input.
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
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 paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.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);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fresh(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
paint(&mut frame, size, zoom, pan);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn screenshot(renderer: &mut Renderer, size: Size, geometry: Geometry) -> Vec<u8> {
|
||||
renderer.reset(Rectangle::with_size(size));
|
||||
renderer.draw_geometry(geometry);
|
||||
Headless::screenshot(
|
||||
renderer,
|
||||
Size::new(size.width as u32, size.height as u32),
|
||||
1.,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn measure<T>(label: &str, n: usize, mut f: impl FnMut() -> T) {
|
||||
for _ in 0..3 {
|
||||
black_box(f());
|
||||
}
|
||||
let mut values = Vec::new();
|
||||
for _ in 0..n {
|
||||
let now = Instant::now();
|
||||
let value = black_box(f());
|
||||
values.push(now.elapsed().as_secs_f64() * 1000.);
|
||||
drop(value);
|
||||
}
|
||||
values.sort_by(f64::total_cmp);
|
||||
let at = |p: f64| values[((n - 1) as f64 * p).ceil() as usize];
|
||||
println!(
|
||||
"{label}: n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
at(0.5),
|
||||
at(0.95),
|
||||
values[n - 1]
|
||||
);
|
||||
}
|
||||
fn main() {
|
||||
let n = std::env::args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut renderer = runtime
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.expect("headless production renderer");
|
||||
println!(
|
||||
"renderer={} samples={n}; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation",
|
||||
renderer.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),
|
||||
] {
|
||||
let pan = Vector::new(-13.25, 9.75);
|
||||
let cache = Cache::new();
|
||||
let calls = Cell::new(0);
|
||||
let cached = |r: &Renderer| {
|
||||
cache.draw(r, size, |f| {
|
||||
calls.set(calls.get() + 1);
|
||||
paint(f, size, zoom, pan);
|
||||
})
|
||||
};
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
println!(
|
||||
"\nsize={}x{} zoom={zoom} dots={}",
|
||||
size.width,
|
||||
size.height,
|
||||
((size.width / step) as i32 + 1) * ((size.height / step) as i32 + 1)
|
||||
);
|
||||
let baseline = fresh(&renderer, size, zoom, pan);
|
||||
let baseline_pixels = screenshot(&mut renderer, size, baseline);
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
assert_eq!(calls.get(), 1);
|
||||
println!("pixel_parity=cold_and_warm_exact");
|
||||
measure("fresh_geometry", n, || fresh(&renderer, size, zoom, pan));
|
||||
measure("cached_geometry", n, || cached(&renderer));
|
||||
assert_eq!(calls.get(), 1, "identical camera must reuse geometry");
|
||||
measure("fresh_geometry_and_screenshot", n, || {
|
||||
let g = fresh(&renderer, size, zoom, pan);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cached_geometry_and_screenshot", n, || {
|
||||
let g = cached(&renderer);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cache_forced_miss_geometry", n, || {
|
||||
cache.clear();
|
||||
cached(&renderer)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# Independent cache and evaluator review
|
||||
|
||||
Read-only GPT-6 Astra review of
|
||||
`636093aae67aa48fa1d35e403a205227e81d04de`, 2026-09-25. The reviewer did not
|
||||
edit files, compile, launch a desktop window or control the user interface.
|
||||
|
||||
No concrete cache-key, lifetime or drawing-order defect was found. The review
|
||||
checked Iced 0.14 source as well as the application: Iced compares exact cache
|
||||
size, explicit camera changes clear geometry, and context/gesture retirement
|
||||
retains the model-independent grid. Normal canvas state survives view rebuilds;
|
||||
the focus wrapper's suspension reset causes a safe cold miss. Cached and live
|
||||
mesh submissions retain their invocation order. Background geometry has no
|
||||
text/images, and existing model transforms still apply to the live scene.
|
||||
|
||||
The evaluator uses an independent copy of the original grid painter, rather
|
||||
than calling the candidate's painter to produce its expected image. It compares
|
||||
the former combined frame with the split cached background, including opaque
|
||||
and translucent foreground, text, overlay, fractional inputs and live edits.
|
||||
The real DiagramCanvas check demonstrates changing selection/model revision
|
||||
pixels while the grid construction count remains one, then checks pan/resize
|
||||
invalidation. It exercises retained-state drawing and scene invalidation;
|
||||
event-driven native gesture acceptance is outside that test.
|
||||
|
||||
The reviewer inspected `grid-tests-01.log`: all three tests pass, none fail or
|
||||
are ignored, 1,547 are filtered, and execution takes 2.97s. Source identity is
|
||||
provided by the accompanying run record and clean tracked implementation, not
|
||||
by an embedded hash in the Cargo log. Full-suite/build/Clippy results published
|
||||
later remain separate evidence and must be read in their own logs.
|
||||
|
||||
The measurements support avoiding repeated grid construction for an unchanged
|
||||
camera. They do not establish native FPS, performance on another renderer, or
|
||||
improvement during continuously changing pan/zoom. No broad final application
|
||||
or release acceptance is claimed by this review.
|
||||
|
||||
A second read-only audit checked the full desktop log's 1,550 distinct names,
|
||||
1,543 passes and seven ignored cases against `context.json`. It verified all
|
||||
three recorded log hashes, driver-source hash, committed grid blob, linked Iced
|
||||
and Tokio libraries, timing tables, sample method and rejected-batch ranges.
|
||||
It did not independently verify the probe executable, execution chronology,
|
||||
Clippy/build gates or environment assertions. It clarified that run 04's generic
|
||||
cold-and-warm marker is not a production-cache warm-pixel check; that assertion
|
||||
belongs to the separate committed tests.
|
||||
@@ -0,0 +1,209 @@
|
||||
//! 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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
//! 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),
|
||||
] {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
//! Renderer-level phase translation experiment, not application code.
|
||||
use iced::advanced::{
|
||||
Renderer as _,
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
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: -1.,
|
||||
y: -1.,
|
||||
width: size.width + step + 2.,
|
||||
height: size.height + step + 2.,
|
||||
},
|
||||
|f| {
|
||||
builds.set(builds.get() + 1);
|
||||
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),
|
||||
] {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
//! 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<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,
|
||||
)
|
||||
}
|
||||
// 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(<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),
|
||||
] {
|
||||
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<f32> = 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);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//! Headless rendering experiment; creates no desktop window and sends no input.
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
use iced::widget::canvas;
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
|
||||
// Compile the actual candidate cache, not a second implementation of its key.
|
||||
#[path = "../../../crates/oa-desktop/src/diagram/grid.rs"]
|
||||
mod grid;
|
||||
mod style {
|
||||
pub const LINE: iced::Color = iced::Color::from_rgb(0.86, 0.89, 0.91);
|
||||
}
|
||||
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
|
||||
fn paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.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);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fresh(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
paint(&mut frame, size, zoom, pan);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn batched(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
frame.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 path = Path::new(|p| {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.fill(&path, DOT);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn screenshot(renderer: &mut Renderer, size: Size, geometry: Geometry) -> Vec<u8> {
|
||||
renderer.reset(Rectangle::with_size(size));
|
||||
renderer.draw_geometry(geometry);
|
||||
Headless::screenshot(
|
||||
renderer,
|
||||
Size::new(size.width as u32, size.height as u32),
|
||||
1.,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn measure<T>(label: &str, n: usize, mut f: impl FnMut() -> T) {
|
||||
for _ in 0..3 {
|
||||
black_box(f());
|
||||
}
|
||||
let mut values = Vec::new();
|
||||
for _ in 0..n {
|
||||
let now = Instant::now();
|
||||
let value = black_box(f());
|
||||
values.push(now.elapsed().as_secs_f64() * 1000.);
|
||||
drop(value);
|
||||
}
|
||||
values.sort_by(f64::total_cmp);
|
||||
let at = |p: f64| values[((n - 1) as f64 * p).ceil() as usize];
|
||||
println!(
|
||||
"{label}: n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
at(0.5),
|
||||
at(0.95),
|
||||
values[n - 1]
|
||||
);
|
||||
}
|
||||
fn main() {
|
||||
let n = std::env::args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut renderer = runtime
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.expect("headless production renderer");
|
||||
println!(
|
||||
"renderer={} samples={n}; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation",
|
||||
renderer.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),
|
||||
] {
|
||||
let pan = Vector::new(-13.25, 9.75);
|
||||
let cache = Cache::new();
|
||||
let calls = Cell::new(0);
|
||||
let cached = |r: &Renderer| {
|
||||
cache.draw(r, size, |f| {
|
||||
calls.set(calls.get() + 1);
|
||||
paint(f, size, zoom, pan);
|
||||
})
|
||||
};
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
println!(
|
||||
"\nsize={}x{} zoom={zoom} dots={}",
|
||||
size.width,
|
||||
size.height,
|
||||
((size.width / step) as i32 + 1) * ((size.height / step) as i32 + 1)
|
||||
);
|
||||
let baseline = fresh(&renderer, size, zoom, pan);
|
||||
let baseline_pixels = screenshot(&mut renderer, size, baseline);
|
||||
let geometry = batched(&renderer, size, zoom, pan);
|
||||
let pixels = screenshot(&mut renderer, size, geometry);
|
||||
let differences = baseline_pixels
|
||||
.iter()
|
||||
.zip(&pixels)
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
println!("batched_rgba_byte_differences={differences}");
|
||||
assert_eq!(
|
||||
differences, 0,
|
||||
"batched path must preserve exact grid pixels"
|
||||
);
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
assert_eq!(calls.get(), 1);
|
||||
println!("pixel_parity=cold_and_warm_exact");
|
||||
measure("fresh_geometry", n, || fresh(&renderer, size, zoom, pan));
|
||||
let production = grid::Grid::default();
|
||||
let g = production.draw(&renderer, size, zoom, pan);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, g));
|
||||
measure("production_cached_geometry", n, || {
|
||||
production.draw(&renderer, size, zoom, pan)
|
||||
});
|
||||
let mut tick = 0;
|
||||
measure("production_changing_pan_geometry", n, || {
|
||||
tick += 1;
|
||||
production.draw(
|
||||
&renderer,
|
||||
size,
|
||||
zoom,
|
||||
pan + Vector::new(tick as f32 * 0.25, 0.),
|
||||
)
|
||||
});
|
||||
measure("cached_geometry", n, || cached(&renderer));
|
||||
assert_eq!(calls.get(), 1, "identical camera must reuse geometry");
|
||||
measure("fresh_geometry_and_screenshot", n, || {
|
||||
let g = fresh(&renderer, size, zoom, pan);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cached_geometry_and_screenshot", n, || {
|
||||
let g = cached(&renderer);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cache_forced_miss_geometry", n, || {
|
||||
cache.clear();
|
||||
cached(&renderer)
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user