docs: publish grid cache measurements and Windows verification

This commit is contained in:
2026-09-25 00:03:44 -07:00
parent 336720559b
commit 2ca84676ee
18 changed files with 2545 additions and 0 deletions
@@ -0,0 +1,2 @@
*.log -text
*.rs -text
@@ -0,0 +1,175 @@
# 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
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,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
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,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,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)
});
}
}