docs: publish grid cache measurements and Windows verification
This commit is contained in:
@@ -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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user