Skip to content

WhatsCanvas Performance Evaluation

Performance claims need reproducible evidence. WhatsCanvas therefore separates microbenchmarks, complete-frame measurements, rendering validation, and environment metadata instead of presenting one favorable FPS number.

Evaluation layers

Layer Target Question answered
CPU micro WhatsCanvasCoreBenchmarks Which isolated layout, font, path, recording, upload, or filter operation changed?
Complete frame WhatsCanvasPerformanceSuite How long does a representative frame take on Software, OpenGL, or Vulkan?
Focused filter WhatsCanvasImageFilterBenchmarks What is the cost and pixel workload of frosted glass and inner shadow?
Evidence JSONL + pixel hash + memory + environment Can another developer reproduce and audit the result?

The complete-frame suite is the primary public benchmark. The smaller targets remain useful when a frame regression needs to be localized.

The active bottleneck analysis, prioritized implementation backlog, and quantitative acceptance targets are tracked in Performance Optimization TODO.

A checked-in Windows i7-8700 / GTX 1060 reference run demonstrates the complete report format and preserves all raw JSONL records. It is a reproducible single-machine baseline, not a universal score or cross-library ranking. That historical run used the former 960 x 540, 11-scene matrix, so it documents earlier work but is not dimension-compatible with the current 1080p suite.

Verified hotspot optimization

The checked-in reference run also exposed two genuine hotspots: rounded images were routed through full clip masks, and Software/Vulkan shadows processed much more of the canvas than the shadow could affect. The optimized implementation uses native uniform-rounded image coverage, crops shadow work to expanded silhouette bounds, keeps Vulkan blur on the GPU, defers temporary target reclamation until GPU completion, and batches path-shadow silhouette passes.

The following candidate was rerun on 2026-07-26 on the exact reference machine and driver, in Release, at 960 x 540 with the standard 30 + 5 frame profile. The baseline remains checked in rather than being overwritten, so these deltas remain auditable.

Backend Scene Reference median Optimized median Delta
OpenGL image_grid 191.084 ms 0.533 ms -99.7%
Software image_grid 94.119 ms 42.938 ms -54.4%
Vulkan image_grid 47.344 ms 0.290 ms -99.4%
OpenGL shadow_grid 11.764 ms 10.571 ms -10.1%
Software shadow_grid 1003.717 ms 70.411 ms -93.0%
Vulkan shadow_grid 1129.557 ms 14.947 ms -98.7%

These results do not show Vulkan as generally slower than OpenGL: image_grid is faster on Vulkan in this run. The remaining shadow_grid difference, 14.947 ms versus 10.571 ms, is concentrated in 36 small render-target and Gaussian-filter jobs. On this GTX 1060 driver their Vulkan render-pass, descriptor, and command-buffer fixed costs remain more visible than the OpenGL driver's internal scheduling.

Software and OpenGL shadow hashes are unchanged. Image hashes intentionally changed at rounded edges when clip-mask rasterization was replaced by native shader coverage. Vulkan shadow output changed when the CPU blur was replaced by the GPU path; its OpenGL comparison has a maximum channel difference of 1. The optimized revision passed all 64 Release CTest entries, including all 22 Vulkan tests, Software golden images, OpenGL/Vulkan filter pixel parity, text, Unicode, examples, API documentation, and installed-package consumers.

Verified 1080p stress optimization

The 1080p stress scenes exposed a second set of bottlenecks that smaller scenes hid: OpenGL recreated sprite GPU objects during every flush, text recorded one command per atlas glyph, repeated translated shapes rebuilt identical meshes, and Software rasterization paid triangle interpolation costs for simple quads and uniform anti-aliased interiors.

The candidate was measured on 2026-07-27 on the same Windows i7-8700 / GTX 1060 machine in Release. Each scene ran in a fresh process at 1920 x 1080. The standard profile uses 30 timed frames after 5 warmup frames; the more variable Software text result uses the thorough 120 + 20 profile.

Backend Scene Before median Optimized median Delta Commands / draws
OpenGL geometry_stress 43.890 ms 24.759 ms -43.6% 2,305 / 2,305
OpenGL text_stress 893.640 ms 13.459 ms -98.5% 577 / 194
Vulkan geometry_stress 50.940 ms 25.990 ms -49.0% 2,305 / 1
Vulkan text_stress 34.910 ms 16.134 ms -53.8% 577 / 577
Software geometry_stress 157.090 ms 103.599 ms -34.1% 2,305 / 2,305
Software text_stress 79.960 ms 60.348 ms -24.5% 577 / 577

OpenGL now retains one sprite batch's program, VAO, and buffers across frames. Glyph runs sharing atlas and render state use one compact image-batch command. Vulkan lowers compatible solid geometry to one primitive without losing per-vertex color or analytic-AA coverage. Translation-normalized fill and AA meshes are reused under byte-bounded LRU caches, and Software has direct raster paths for axis-aligned image quads and uniform interior triangles.

OpenGL and Vulkan validation hashes stayed stable through these optimizations. Software's normalized translated geometry and direct quad interpolation can differ from the former two-triangle arithmetic by at most one channel value; the measured comparison changed 0.001543% of pixels with maximum channel delta 1 and mean channel delta 0.000005. Text output converges to the same validation hash across Software, OpenGL, and Vulkan.

The subsequent batching pass reduced OpenGL geometry_stress from 2,305 GPU draws to 9 by flattening compatible 2D affine transforms into bounded per-vertex batches. Standard medians observed during the pass ranged from 19.777 ms to 25.565 ms, with the previous 24.759 ms result inside that range, so the draw-count reduction is verified but no stable frame-time improvement is claimed. The pixel hash remained 44121eb5a074425f.

Vulkan now combines compatible atlas/image quads using packed RGBA8 per-vertex tints and reuses identical sampled-image descriptor sets within a frame. text_stress fell from 577 GPU draws to 194 while retaining pixel hash 6554c1da7b50ade0. Its final serial standard median was 16.320 ms versus the previous 16.134 ms, a 1.2% difference within run variability; this is likewise a structural batching improvement, not a timing-speedup claim.

Standard scene matrix

The default resolution is 1920 x 1080. Every scene is deterministic and produces a fixed validation-frame hash after timing has completed. The default matches a widely deployed desktop display workload while --width and --height remain available for controlled investigations.

Scene Coverage Cache mode Operations/frame
solid_rects Dense filled rectangles and command submission churn 576
rounded_ui Rounded UI surfaces and anti-aliased edges churn 120
path_cached Repeated complex path geometry hot 160
path_churn Per-frame path construction and tessellation churn 160
geometry_stress 2,304 mixed rectangles, rounded rectangles, circles, ovals, and custom paths churn 2,304
image_grid Reused RGBA texture scaling and sampling hot 96
clip_layers Nested clips, transforms, and layers churn 144
shadow_grid Shape shadows with varied radii churn 36
text_cached Repeated shaped text and glyph-atlas reuse hot 120
text_churn Changing text content and glyph lookup pressure churn 120
text_stress 576 multilingual text calls, expanding to roughly 8,000 cached glyph commands hot 576
contract_text_latin 576 fixed Roboto Latin calls for cross-library comparison hot 576
frosted_glass Backdrop capture, blur, and layer composition hot 4
inner_shadow Filtered controls with inner shadows hot 24

Hot and churn variants are deliberate. A renderer should show both steady-state cache efficiency and the cost of changing content. The stress scenes are also deliberately large enough to expose command recording, glyph-atlas switching, batching, tessellation, and submission bottlenecks that small UI samples can hide.

Parameterized workload matrix

Fixed scenes are useful regression anchors, but a single object count can be overfit. geometry_stress, image_grid, and text_stress therefore also provide an opt-in parameterized path. Their default behavior and historical pixel hashes remain unchanged unless workload options are supplied.

Option Meaning
--workload stable Geometry, resources, and command topology remain stable between frames
--workload dynamic-data Positions, colors, and text selection change while topology remains stable
--workload dynamic-structure Primitive, texture, text, and render-state selection can change each frame
--operations N Draw operations in the selected stress scene
--seed N Deterministic content seed recorded in JSONL
--texture-count N Image resource cardinality, from 1 through 256
--rounded-ratio 0..1 Fraction of image operations using rounded coverage
--state-change-rate 0..1 Deterministic fraction of operations changing blend state
--text-length N ASCII characters in each generated text sample

The matrix runner launches every workload and seed in a fresh Release process, then reports the median of process medians. It preserves raw JSONL and writes JSON, CSV, Markdown, and per-backend log-log SVG scaling charts:

python scripts\run_benchmark_matrix.py `
  --executable build\Release\WhatsCanvasPerformanceSuite.exe `
  --backend opengl --backend vulkan `
  --preset standard --profile standard `
  --output-dir build\performance-matrix

The smoke preset covers all three categories and stable/structure-changing paths with one seed. standard uses three operation scales, all three workload modes, and three seeds. thorough expands the scale range and uses five seeds. Use --dry-run to audit every command before a long measurement.

One matrix case remains directly reproducible:

build\Release\WhatsCanvasPerformanceSuite.exe `
  --backend vulkan --profile standard --scene image_grid `
  --workload dynamic-structure --operations 1024 --seed 2003 `
  --texture-count 32 --rounded-ratio 0.5 `
  --state-change-rate 0.125 `
  --output build\image-grid-dynamic.jsonl

Reports include scale, dynamic mode, texture cardinality, rounded ratio, state change rate, text length, process range, synchronized frame timing, throughput, draw calls, and draw reduction. This makes stable-cache wins visible without hiding dynamic topology or high-state-churn costs.

The native parameterized matrix evaluates WhatsCanvas backends and revisions. For external libraries whose adapters implement the same parameter semantics, the cross-library matrix runner applies the pixel-quality contract and an independent ABBA comparison to every matrix cell:

python scripts\run_cross_library_matrix.py `
  --reference "whatscanvas=build/Release/WhatsCanvasPerformanceSuite.exe --backend opengl" `
  --adapter "nanovg=build/Release/WhatsCanvasNanoVGBenchmarkAdapter.exe --backend opengl" `
  --preset standard --profile standard `
  --repetitions 4 `
  --output-dir build\cross-library-matrix

standard contains 27 cells: three scenes, three operation scales, and three change modes. Its default workload seed is 1001; use --seeds 1001,2003,3001 to test content sensitivity. ABBA repetition controls process noise separately from content diversity. The default two ABBA blocks use four fresh processes per renderer per cell; use --repetitions 8 for a publishable four-block run. The runner preserves each cell's raw JSONL and capture, then writes aggregate JSON, CSV, and Markdown with quality status and 95% confidence verdicts.

The first checked Windows i7-8700 / GTX 1060 run passed all 27 quality gates. With two ABBA blocks per cell, WhatsCanvas OpenGL was conclusively faster in 12 cells, NanoVG GL3 in 12, and 3 crossed the paired-ratio 95% confidence boundary. The breakdown was 2/9 WhatsCanvas wins in geometry, 3/9 in images, and 7/9 in text. Stable single-texture images and most text workloads favored WhatsCanvas; dynamic multi-texture/state image streams and medium/high-scale dynamic geometry favored NanoVG. See the raw parameter-matrix baseline for every cell and all 216 process JSONL records.

Post-optimization parameter matrix (cac08c1)

The same 27-cell Standard matrix was rerun after ordered eight-slot OpenGL multi-texture batching, a dedicated batch sampler, and one shared path attribute/index upload stream. Each cell used two ABBA blocks and four fresh processes per renderer. All 27 quality gates passed. WhatsCanvas won 12 cells, NanoVG won 11, and 4 were inconclusive:

Category WhatsCanvas faster NanoVG faster Inconclusive
Geometry 2 6 1
Images 6 3 0
Text 4 2 3
Total 12 11 4

The image result changed materially rather than moving inside noise. All three stable and all three dynamic-data image cells now favor WhatsCanvas. At 1,024 operations, dynamic-data fell from 4.273 ms to 0.773 ms while NanoVG measured 1.667 ms. The remaining conclusive NanoVG wins are:

Scene Mode Operations WhatsCanvas NanoVG WhatsCanvas gap
geometry_stress dynamic-structure 256 1.254 ms 0.748 ms 65.8% slower
geometry_stress dynamic-data 1,024 1.890 ms 1.575 ms 20.1% slower
geometry_stress dynamic-structure 1,024 2.912 ms 1.661 ms 72.4% slower
geometry_stress stable 4,096 6.619 ms 4.542 ms 44.8% slower
geometry_stress dynamic-data 4,096 6.636 ms 4.711 ms 38.8% slower
geometry_stress dynamic-structure 4,096 9.425 ms 5.647 ms 65.6% slower
image_grid dynamic-structure 64 0.494 ms 0.411 ms 21.0% slower
image_grid dynamic-structure 256 0.997 ms 0.779 ms 28.6% slower
image_grid dynamic-structure 1,024 2.338 ms 1.652 ms 37.6% slower
contract_text_latin stable 1,024 5.905 ms 5.874 ms 0.7% slower
contract_text_latin dynamic-data 1,024 6.178 ms 5.898 ms 4.5% slower

This changes the optimization priority. Large and structurally changing geometry is the dominant gap. Dynamic-structure images are next: their deliberate blend barriers and changing texture sets still produce many small batches even though ordinary multi-texture streams no longer do. The two text losses are small and lower priority; the 1,024-operation dynamic-structure text cell still favors WhatsCanvas, 6.652 ms versus 8.794 ms.

The renderer counters locate those gaps more precisely:

Workload Record Submit Draws Structural evidence
Geometry dynamic-structure, 256 0.180 ms 1.050 ms 46 State/topology changes already fragment a small frame
Geometry dynamic-structure, 1,024 0.680 ms 2.250 ms 237 Both compilation and small submissions scale
Geometry dynamic-structure, 4,096 2.810 ms 7.010 ms 950 97,636 vertices and about 2.1 MiB of path upload
Geometry stable, 4,096 2.730 ms 4.280 ms 2 About 2.0 MiB upload: draw count is not the main cost
Images dynamic-structure, 1,024 0.310 ms 2.060 ms 283 Real blend barriers dominate submit after multi-texture batching
Text dynamic-data, 1,024 4.140 ms 2.080 ms 2 Remaining text gap is primarily record-side work

These counters are from representative WhatsCanvas processes in the same matrix. They are diagnostic, not additional independent samples. They rule out one generic fix: geometry needs less frame compilation and attribute expansion, image dynamic-structure needs cheaper barrier-bounded batches, and high-count text needs record-path profiling rather than another draw-call optimization.

Current parameter matrix

The Standard matrix was rerun after multi-packet topology reuse, GPU shape parameters for large affine batches, compact on-demand path gradient storage, persistent OpenGL sprite-sequence state, short-path allocation cleanup, and redundant GL-state removal. The same two-block ABBA schedule and four fresh processes per renderer were used for every cell. All 27 quality gates passed:

Category WhatsCanvas faster NanoVG faster Inconclusive
Geometry 8 0 1
Images 9 0 0
Text 9 0 0
Total 26 0 1

This run closes every previous conclusive image, text, and geometry loss. Reusing the sprite program, VAO, projection, sampler uniforms, sampler bindings, and unchanged texture slots across an ordered image sequence makes even the deliberately fragmented image workload competitive without reordering transparent draws:

Scene Mode Operations WhatsCanvas NanoVG Result
geometry_stress stable 4,096 3.971 ms 5.417 ms WhatsCanvas 26.7% faster
geometry_stress dynamic-data 4,096 3.893 ms 5.175 ms WhatsCanvas 24.8% faster
image_grid dynamic-structure 1,024 1.328 ms 1.869 ms WhatsCanvas 28.9% faster
contract_text_latin dynamic-structure 1,024 6.886 ms 10.132 ms WhatsCanvas 32.0% faster

The two previously slower geometry cells now favor WhatsCanvas:

Mode Operations WhatsCanvas NanoVG Result
dynamic-structure 1,024 1.730 ms 1.906 ms WhatsCanvas 9.2% faster
dynamic-structure 4,096 5.751 ms 6.029 ms WhatsCanvas 4.6% faster

At 256 operations the same workload remains statistically inconclusive (0.788 versus 0.811 ms). The implementation does not weaken AA, reorder blend barriers, or branch on benchmark sizes. It removes general short-path costs: Path reserves the common compact verb count, contour extraction moves storage and reserves from the known verb count, and simple fills consume the parsed contour directly. OpenGL also caches the unchanged additive blend equation and returns immediately for an already-empty clip/scissor state.

Profiles

Profile Timed frames Warmup frames Intended use
quick 3 1 Build/schema/readback smoke only
standard 30 5 Normal local comparison and reference reports
thorough 120 20 Stable release investigation on dedicated hardware

Each scene also records one pre-warmup cold frame. For official runs, the provided runner launches each scene in a fresh process. This prevents a prior text scene's glyph atlas, a prior path scene's caches, or process peak RSS from changing the next scene's result.

Metrics

  • record_*_ms: beginFrame plus public Canvas draw calls.
  • submit_*_ms: endFrame plus backend completion. OpenGL uses glFinish; Vulkan waits for its queue, so reported GPU time is not deferred work.
  • total_*_ms: complete synchronized frame time.
  • median, p90, p95, p99, mean, min, max, stddev, cv: distribution statistics over measured frames.
  • cold_total_ms: first frame before warmup.
  • fps: 1000 / total_median_ms; this is throughput, not display refresh.
  • operations_per_second: declared scene operations divided by median time.
  • readback_ms: separately timed RGBA readback, excluded from frame timing.
  • pixel_hash: fixed-frame RGBA hash used to detect missing or changed output.
  • rss_*, peak_rss_bytes, private_or_virtual_bytes: process memory observations. The last field follows the closest portable OS counter and is deliberately not presented as identical private-memory semantics everywhere.
  • command_count, draw_call_count, cache bytes, filter/pass/pixel counts, and render-target statistics: public Canvas::RenderStats diagnostics.
  • flush_cpu_ns: Renderer flush wall time. frame_compile_cpu_ns isolates command-to-packet lowering where a FrameCompiler path is active, while device_execution_cpu_ns isolates device-command execution.
  • gpu_time_available and gpu_time_ns: a delayed, non-blocking backend timer result. OpenGL uses a three-query ring; unsupported backends report false rather than substituting CPU time. GPU timing is disabled by default; pass --gpu-timing for a diagnostic run so comparative baselines do not pay timer-query overhead.
  • compiled_packet_count, compiled_vertex_bytes, and compiled_index_bytes: the submitted compact-packet footprint.
  • tracked_resource_bytes: the sum of glyph-atlas, pooled render-target, tessellation, stroke, and bitmap-text cache bytes owned by WhatsCanvas.

Pixel equality is not a perceptual quality score. A fast blank frame can be fast for the wrong reason, so every result must have a non-empty readback and a stable hash, while visual regression remains a separate quality gate.

Build and run

Use Release; Debug results are not suitable for performance claims.

cmake -S . -B build -DWHATSCANVAS_BUILD_BENCHMARKS=ON
cmake --build build --config Release --target WhatsCanvasPerformanceSuite
.\scripts\run_performance_suite.ps1 `
  -Profile standard `
  -Backends software,opengl,vulkan `
  -OutputDir build/performance-results
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
  -DWHATSCANVAS_BUILD_BENCHMARKS=ON
cmake --build build --target WhatsCanvasPerformanceSuite
PROFILE=standard BACKENDS="software opengl vulkan" \
  OUTPUT_DIR=build/performance-results \
  ./scripts/run_performance_suite.sh

The runner defaults to Software because it is available on every supported host. Request only GPU backends compiled into the current build. To run one case directly:

build\Release\WhatsCanvasPerformanceSuite.exe `
  --backend opengl --profile standard --scene text_cached `
  --output build\text-cached.jsonl `
  --capture-dir build\performance-captures

Use --list-scenes to inspect scene names. --frames, --warmup, --width, and --height override a profile for investigation. Workload options are recorded in every JSONL result, but custom settings should not be mixed into a fixed-scene standard reference report; use the matrix runner for parameterized publication.

Compare revisions

Capture baseline and candidate results on the same idle machine with the same driver, power mode, build type, backend, dimensions, and profile:

python scripts\compare_performance.py `
  build\perf-baseline build\perf-candidate `
  --regression-threshold 10 `
  --output build\performance-comparison.md

Optional gates:

python scripts\compare_performance.py `
  build\perf-baseline build\perf-candidate `
  --regression-threshold 10 `
  --fail-on-regression --fail-on-hash-change

The comparison tool matches backend, scene, dimensions, and profile; reports median, p95, peak RSS, and hash changes. It refuses a timing verdict when machine, driver, compiler, build, dimensions, or sample settings differ. --allow-incompatible is available only for explicitly exploratory reports. Raw files can be checked independently:

python3 scripts/compare_performance.py --validate build/performance-results

A single run directory can be turned into a reviewable Markdown report without manually selecting or copying numbers:

python3 scripts/compare_performance.py \
  --summary build/performance-results \
  --output build/performance-summary.md

Cross-library comparisons

The executable cross_library_benchmark.py runner and machine-readable contract.json quality-gate external adapters before comparing synchronized complete-frame timing. See the cross-library benchmark contract for the adapter CLI, fixed assets, required JSONL metadata, quality thresholds, and publication rules.

A fair adapter must render the same scene at the same resolution with equivalent anti-aliasing, clipping, blending, text, sampling, and synchronization. It must publish:

  • adapter source and exact dependency revisions;
  • optimized compiler flags and backend/device metadata;
  • warmup and sample counts;
  • raw JSONL, output images, and a description of unsupported operations;
  • both quality evidence and timing, without silently simplifying a scene.

Do not compare raw draw-call counts across libraries unless an operation has the same semantics. Do not compare asynchronous GPU submission against synchronized complete-frame time. When an effect has no equivalent implementation, mark the scene unsupported instead of substituting an easier effect.

The first quality-gated NanoVG GL3 baseline is now checked in. On the Windows i7-8700 / GTX 1060 machine, the median of three independent 1080p Standard process medians was:

Scene WhatsCanvas OpenGL NanoVG GL3 Result
geometry_stress 25.659 ms 4.316 ms NanoVG 5.95x faster
image_grid 0.308 ms 0.372 ms WhatsCanvas 1.21x faster
contract_text_latin 15.911 ms 3.334 ms NanoVG 4.77x faster

All three NanoVG captures passed their scene quality gates. This exposes a real WhatsCanvas weakness in per-frame path construction/tessellation and text recording; the image batching path is already competitive. See the raw baseline and methodology.

The first optimization pass has now been remeasured with five independent processes on the same machine. The median of process medians was:

Scene Before After Improvement NanoVG GL3 Remaining gap
geometry_stress 25.659 ms 15.73 ms 38.7% 4.316 ms 3.64x
image_grid 0.308 ms 0.29 ms within noise 0.372 ms none demonstrated
contract_text_latin 15.911 ms 4.78 ms 70.0% 3.334 ms 1.43x

Every process produced the same scene hashes. The text improvement comes from resolved glyph-layout caching, zero-area glyph caching, and four-vertex indexed sprite quads. Geometry improved through move-only command handoff, append-only per-frame path streams, and removal of deterministic AA-cache churn. Geometry still expands simple primitives into generic triangle-soup paths, so its remaining gap requires the semantic primitive and indexed-AA work tracked in the performance optimization backlog.

The second geometry pass retained the same quality contract and again used the median of five independent process medians:

Scene Original Pass 1 Pass 2 NanoVG GL3 Pass 2 gap
geometry_stress 25.659 ms 15.73 ms 8.68 ms 4.316 ms 2.01x
image_grid 0.308 ms 0.29 ms 0.31 ms 0.372 ms within noise
contract_text_latin 15.911 ms 4.78 ms 4.42 ms 3.334 ms 1.33x

Indexed AA reduced the geometry stream from 269,598 duplicated vertices to 62,984 vertices plus indices. Immutable shared cache geometry removed repeated per-command vector copies, and pre-sized affine batch assembly reduced submit work. The scene now uses one draw and uploads 2,841,944 path bytes instead of 7,548,744. Captured hashes remain 44121eb5a074425f, 432ad28b33a51375, and 737cad1b0d1169f2 for geometry, image, and text respectively.

The third pass added 16-bit merged index packets and a guarded simple solid-fill path. Five-process geometry measurement and a nine-process text check produced:

Scene Original Pass 2 Pass 3 NanoVG GL3 Pass 3 gap
geometry_stress 25.659 ms 8.68 ms 6.45 ms 4.316 ms 1.49x
image_grid 0.308 ms 0.31 ms 0.27 ms 0.372 ms within noise
contract_text_latin 15.911 ms 4.42 ms 4.81 ms 3.334 ms 1.44x

The geometry scene records in 3.07 ms and submits in 3.40 ms. Its 269,598 indices occupy 539,196 bytes, confirming a 16-bit stream; total path upload is 2,302,748 bytes, down 69.5% from the original. Complex geometry semantics keep the generic path pipeline, and all captures retain their prior hashes.

The fourth geometry pass introduced parameterized local-space primitive meshes, normalized RGBA8/coverage8 attributes for merged solid packets, a bounded thread-local path-command pool, and a Release trusted-index fast path. Seven alternating WhatsCanvas/NanoVG processes produced:

Scene Original Pass 3 Pass 4 Paired NanoVG GL3 Pass 4 gap
geometry_stress 25.659 ms 6.45 ms 4.682 ms 3.965 ms 1.18x

The current record/submit medians are 1.767/2.956 ms. The scene still uses one draw, 62,984 vertices, and 269,598 16-bit indices, but total path upload is now 1,357,988 bytes: 41.0% below pass 3 and 82.0% below the original. All seven processes produced 5e7e67fb8b9ca579.

The parameterized curve cache changes only 171 of 2,073,600 pixels relative to pass 3 (0.0082%). Every changed channel is one 8-bit level, with RMSE 0.0069. The complete Release build and all 66 Release tests pass.

The fifth geometry pass retained the merged path packet across frames, reused its stable shared-geometry index topology and packed coverage stream, and removed full Paint copies and general 4 x 4 matrix multiplication from the simple solid-fill recording path. Eight WhatsCanvas and eight NanoVG processes run in ABBA order produced:

Scene Original Pass 4 Pass 5 Paired NanoVG GL3 Pass 5 result
geometry_stress 25.659 ms 4.682 ms 2.690 ms 3.947 ms WhatsCanvas 31.8% faster

Pass 5 records in 1.486 ms and submits in 1.199 ms, compared with NanoVG's 1.611/2.372 ms on the same machine and run sequence. Upload volume and draw structure are unchanged at 1,357,988 bytes, four uploads, and one draw: the gain comes from eliminating CPU allocation and reconstruction work before the same GPU packet is uploaded. All eight WhatsCanvas processes retained 5e7e67fb8b9ca579; image and text control hashes remain 432ad28b33a51375 and 737cad1b0d1169f2. The complete Release build and all 66 Release tests pass.

The cross-library runner now automates that methodology instead of requiring a manual process script. Four ABBA blocks launch eight fresh processes per renderer, retain every measured frame sample and quality capture, calculate within-block geometric-mean ratios, and publish deterministic bootstrap 95% confidence intervals. The contract also fixes full-frame draw clear semantics, the Roboto SHA-256, text size/baseline/shaping/raster modes, and parameterized workload fields. The NanoVG adapter implements the same scale/seed/data/ structure options.

The latest 1080p Standard run on the same reference machine produced:

Scene WhatsCanvas OpenGL median (95% CI) NanoVG GL3 median (95% CI) Paired NanoVG / WhatsCanvas (95% CI)
geometry_stress 2.617 ms (2.572-2.764) 3.705 ms (3.605-3.807) 1.407x (1.273-1.441)
image_grid 0.272 ms (0.262-0.304) 0.383 ms (0.380-0.388) 1.369x (1.347-1.417)
contract_text_latin 2.878 ms (2.670-3.021) 3.292 ms (3.255-3.337) 1.153x (1.105-1.169)

All 48 process runs passed their quality gate. These intervals quantify this machine and contract; they are not a cross-hardware ranking.

CI policy

Shared hosted runners execute the quick Software subset to validate build wiring, JSON schema, pixel readback, and hashes. CI intentionally does not gate elapsed time: noisy virtual machines are unsuitable performance laboratories. Timing thresholds belong on stable, dedicated hardware or in controlled local baseline/candidate runs.

Existing microbenchmarks

WhatsCanvasCoreBenchmarks continues to cover text layout, text cache hits, font glyph metrics and rasterization, portable glyph-atlas text, path metrics, pixel hashing, command recording, image upload, lightweight frame flush, and a Software backdrop-blur workload. Set WHATSCANVAS_BENCHMARK_ITERATIONS to control its iteration count.

WhatsCanvasImageFilterBenchmarks continues to emit focused FILTER_BENCHMARK records for overlapping frosted glass and inner-shadow controls. Its timings include frame completion and exclude readback/hash work.