docs: compare cold grid and translated pan rendering alternatives

This commit is contained in:
2026-09-25 00:21:10 -07:00
parent 2ca84676ee
commit fd8e6c1bda
13 changed files with 1622 additions and 0 deletions
@@ -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.
@@ -139,6 +139,11 @@ 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
@@ -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,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
@@ -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,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);
}