9.1 KiB
Cold construction and pan alternatives
Follow-up to the stationary-camera cache, for issue 44. Application
source remains 336720559bcec511949caa8a42bee8a55c3822da, with evidence checkpoint
2ca84676ee4dc30846208881a27d90a901482085. These standalone experiments change no
application code, dependencies, event handling or release. Native interaction
remains paused; no result below establishes native FPS.
Question and method
The existing cache removes repeated construction at identical camera inputs but
misses during pan/zoom. Can simpler geometry or renderer-level translation remove
that cost without changing the grid's appearance? Use the same unoptimized Iced
0.14 WGPU dependencies and Windows environment recorded in context.json.
Compile the new drivers with the same rustc/extern/linker arguments described
in README, replacing the driver filename and output executable. No desktop window
is created and no input is sent. Run cold_grid_probe with argument 20; phase
drivers take no arguments.
The cold driver compares original circles, one reusable origin circle transformed
for each fill, one path per column, and direct 1.15-unit squares. Every candidate
also constructs one origin-circle template; this small common overhead is retained.
Three warmups precede 20 changing-pan samples. Construction ends before geometry
destruction, rendering and readback. Percentiles use sorted index ceil((n-1)*p).
Fixed candidate ordering and uncontrolled OS load limit small timing comparisons.
Cold construction results
cold_grid_probe.rs produces grid-cold-01.log. Median milliseconds:
| Canvas, zoom | Original | Reused circle | Column batch | Square |
|---|---|---|---|---|
| 1900x660, 1.0 | 61.0564 | 55.3975 | 54.3594 | 3.5527 |
| 1900x660, 0.5 | 215.8919 | 162.3073 | 158.5968 | 7.5699 |
| 1440x900, 1.0 | 60.1683 | 43.3860 | 60.8198 | 2.0340 |
| 1440x900, 0.5 | 165.2049 | 133.3420 | 143.1489 | 6.8178 |
Both circle alternatives exactly match original pixels in all 12 sampled cases (four integer canvas/zoom pairs, three pan offsets, scale 1). This does not prove equivalence at fractional zoom, other DPI or other camera inputs. Squares match eight of those cases but differ at the third pan offset in each canvas/zoom pair: 4,266–13,184 changed pixels, maximum channel difference 7. Their different shape and area are an explicit appearance change, not a pixel-preserving optimization.
Reusing circles and column batches leave substantial cold cost; the experiment does not justify adding them to production as the solution to low FPS. The square alternative is much faster in this scope, but is not adopted without evaluating its appearance and the actual interaction result. The previous all-grid single path experiment remains rejected for its much higher construction cost.
Source explanation and independent review
A fresh read-only critic checked the probes and actual local Iced source. Each
Path::circle allocates/builds a path and converts its arc into quadratic segments.
Each WGPU Frame::fill allocates a boxed geometry builder and invokes Lyon fill
tessellation. Same-color fills already share a solid mesh buffer: this is not
one GPU draw call per dot. Frame translation transforms paths before tessellation,
so path reuse alone does not reuse triangles.
Renderer-level with_translation can reuse cached triangles. Stock Canvas cannot
attach a separate transform to each returned geometry; application adoption would
need a small background widget or wrapper, with layout, clipping, lifecycle and
interaction checks. Scaling the grid to handle zoom is unsuitable because the
dot radius must remain fixed in screen units.
The critic also identified two important controls. Original opaque background
geometry must not silently become screenshot-clear color: MSAA/alpha composition
can change quantized pixels. Also, iced_wgpu::triangle::Storage::prepare rewrites
cached mesh data when its transform changes. Construction/command-recording time
therefore excludes real preparation/upload costs. The later probe measures a
rendering/readback path separately; neither timer represents native presentation.
Primary sources: Iced 0.14 iced_graphics/src/geometry/path{,/builder}.rs,
iced_wgpu/src/{geometry,triangle}.rs, iced_core/src/renderer.rs, and
iced_widget/src/canvas.rs. The reviewer changed no files and did not execute the
probes; execution and interpretation remain the parent's responsibility.
Renderer-translated phase cache
phase_grid_probe_v1.rs / grid-phase-01.log cache zero-phase circles once for
each size/spacing pair, translate by pan.rem_euclid(step), and externally clip
to the original viewport. The original nonnegative row/column indices and fixed
screen-space radius are retained. Cache clip bounds include the radius margins.
This first prototype uses screenshot clear color instead of opaque background
geometry; its widespread one-level pixel differences are not accepted as parity.
Its compiler emitted one duplicate Renderer-import warning, retained in the
original driver. Later drivers remove that import and compile with -D warnings.
phase_grid_opaque_probe.rs / grid-phase-opaque-01.log add an oversized opaque
background to the same cached geometry as the circles. Its bounds extend from
(-step,-step) to (width+2*step,height+2*step), covering the viewport at every
positive remainder without adding extra dot rows or columns. This directly
compares against the unchanged legacy painter. A proposed separate-background
control was not executed; no result is claimed for it.
Of 75 comparisons (five size/zoom pairs, five offsets, three DPI scales), 73 are byte-exact. All 60 cases at integer canvas sizes and spacing 14 or 24 are exact. At 301.5x211.25, zoom 0.73, pan (-13.25,9.75), scale 1 changes 12 pixels by at most one channel level; scale 2 changes one pixel by at most six levels. These are failures of exact parity and are preserved, not hidden behind a tolerance.
The targeted phase_grid_diagnostic.rs / grid-phase-diagnostic-01.log locates
all 12 scale-1 differences at the rightmost physical column x=301. The scale-2
single-pixel difference does not recur. That diagnostic skips earlier large
cases and their rendering/timing sequence: it does not exonerate the earlier
failure or establish that every residual difference is at an edge.
The replay driver's first compilation failed with E0689 because the new control's
Vector::new(-13.25,9.75) left its numeric type ambiguous before rem_euclid.
An explicit Vector<f32> annotation corrects the experiment harness. No probe
executed during that failed attempt; no application source changed.
phase_grid_replay.rs preserves the full opaque probe's rendering/timing sequence
and adds coordinate diagnostics without extra renders until that sequence ends.
grid-phase-replay-01.log reproduces both failures: the 12 right-edge pixels and
one interior pixel at physical (149,263), where legacy RGBA is
[240,243,246,255] and cached RGBA is [246,248,251,255]. The latter cannot be
dismissed as viewport-edge rounding. Afterwards, all 12 same-renderer repeat
controls (three repetitions of old/old and cached/cached at two scales) are
byte-exact; the control cache builds once. This finite control does not establish
universal determinism, but supports investigating differing geometry coverage
instead of treating the reproduced pixel as random readback noise.
Cold construction plus command recording takes 68.42–173.72ms median for the four
large viewports; cached translation takes 0.0008–0.0011ms. Each per-size/spacing
cache builds exactly once through all pan comparisons and measurements. Zoom
and resize through a single retained cache are not tested by these drivers.
The labels containing submit mean renderer command recording, not GPU submission.
Two selected cases include construction, recording, backend preparation and GPU readback in a separate timer (two warmups, eight samples, sorted-index median):
| Canvas, zoom | Original p50 ms | Phase cache p50 ms |
|---|---|---|
| 1900x660, 1.0 | 259.9265 | 212.6536 |
| 1440x900, 0.5 | 337.2560 | 170.3780 |
These support a benefit within the headless path, but retain significant renderer
and readback cost and variable outliers. They are not native frame times. Sample
size, sequential ordering and uncontrolled machine load prohibit a precise
general speedup claim. All driver/log hashes and execution mapping are in
cold-pan-context.json.
The phase candidate removes the measured pan tessellation cost, but does not meet exact visual preservation. Do not adopt it as an invisible optimization. The small circle rewrites leave the main cost and squares change appearance. Further work should investigate the reproduced interior coverage difference or compare optimized dependency/build profiles before adding a custom widget. Any adopted rendering candidate must then verify a real mounted scene at a nonzero widget origin, foreground ordering, viewport clipping, pan/zoom/resize cache lifecycle and unchanged event behavior. Native interaction/FPS verification remains a separate required gate. No application implementation is changed by these experiments.