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