Skip to content

Draw the editor's dot grid with one shader on the web - #253

Merged
SunkenInTime merged 4 commits into
mainfrom
t3code/web-dot-grid
Oct 6, 2026
Merged

SunkenInTime merged 4 commits into
mainfrom
t3code/web-dot-grid

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

On the web, dragging anything in the editor dropped frames, even after #251 made the cone math cheap. The cause is the dotted background grid. Flutter's web renderer (CanvasKit) keeps no pixels between frames, so it redraws every recorded point every frame. The editor's grid is about 14,700 points, and while dragging it was most of the frame's raster time. Desktop isn't affected: its engine keeps the grid's pixels.

What changed

  • Shader on the web: the editor's DotGrid is now drawn with one rectangle and a small shader, shaders/dot_lattice.frag. It keeps the same lattice as before: spacing stretched so the outer dots sit on the edges, same dot size and colour.
  • Crisp at any zoom: antialiasing is one device pixel wide at the editor's current zoom. The editor's grid reads screenZoomProvider, and the shader repaints when the zoom changes.
  • Unchanged paths: desktop, screenshots, and the first frames while the shader loads still draw the points as before.

Measured

This is the real signed-in web app: a local build against production with a test account, a Lotus strategy, dragging a view cone along the same path in headless Edge (RTX 4070), recording requestAnimationFrame gaps. Two runs each.

build fps frame gap p90 / p99 frames over 16.8 ms
main before #251 83–84 27.8 / 41.6 ms ~830 of ~4,400
main with #251 (earlier run, same drag) 96–100 20.8 / 27.9 ms ~480–660
this PR (main with #251, plus the shader grid) 139 7.0–7.1 / 13.9–14.0 ms 9–12 of ~4,200

Astra ran 24 experiments isolating each element of the scene: the grid, the map art, the side panel, and the cone's fill and clip. Hiding the grid cut sampled raster time from 11.75 s to 1.94 s. Caching the map or the panel did nothing measurable. The engine has no picture raster cache on the web (CkSurface.rasterizeToCanvas replays every picture each frame), so wrapping things in more RepaintBoundarys wouldn't help.

The drag script waits for the browser to process each mouse move before sending the next, so a slower build also gets a slower drag. Read the table as per-frame responsiveness under the same path, not frames per second of identical input. A rerun after the shader-lifetime fix gave the same numbers: 137–138 fps, 13–15 frames over 16.8 ms.

Look: screenshots of the editor at 1:1 and zoomed in about 8x match main to within 3/255 per pixel, with the dots in the same places.

Testing

  • Widget tests touching the editor canvas and grid: 26 pass.
  • On desktop and in tests, kIsWeb is false, so the points path is unchanged there, and the grid doesn't watch the editor's zoom.
  • Each grid's state owns one shader and disposes it when the grid is removed, so zooming doesn't leak a shader per step.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • On web, the editor’s dot grid now renders with a shader and scales with editor zoom. Screenshots and other platforms continue to use point rendering.

RetriggerConfidence Score: 5/5

No outstanding findings block merging.

Summary

The PR replaces point-by-point rendering of the web editor’s dot grid with a shader while retaining point rendering for desktop, screenshots, and shader loading. The latest changes allow a failed shader load to be retried, adjust dot-edge rendering, and add a shader-versus-points rendering test. No outstanding findings remain.

Reviews (2) · Last reviewed commit: "Retry a failed grid shader load, and tes..."

The web engine redraws every recorded point on every frame: it keeps no
pixels between frames the way desktop does. The editor's background grid
is about 14,700 points, and while anything was dragged it was most of the
frame's raster time. On the web the grid is now one rectangle drawn by a
small lattice shader, with the same stretched spacing, dot size and colour
and antialiasing one device pixel wide at the editor's zoom. Desktop, and
screenshots, keep the points.

Dragging a view cone in the signed-in web app went from 96-100 fps with
#251 (83-84 before it) to 139 fps, with frames over 16.8 ms falling from
hundreds per drag to about ten. At 1:1 and zoomed in, the grid matches
the points to within 3/255 per pixel.

Found by Astra's raster investigation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 33 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 564ab30f-be14-4f5d-83a0-485eab6a3c3e
📥 Commits

Reviewing files that changed from the base of the PR and between 5421bbe and 4ab2816.

📒 Files selected for processing (3)
  • lib/widgets/dot_painter.dart
  • shaders/dot_lattice.frag
  • test/dot_lattice_test.dart

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0815e75f-bc7a-496d-aa30-9ad68f02765e
📥 Commits

Reviewing files that changed from the base of the PR and between 98320be and 5421bbe.

📒 Files selected for processing (4)
  • lib/interactive_map.dart
  • lib/widgets/dot_painter.dart
  • pubspec.yaml
  • shaders/dot_lattice.frag

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The dot grid now uses a fragment shader for eligible web rendering, with point rendering as a fallback. InteractiveMap enables editor-zoom following, which adjusts the shader’s dot scale.

Changes

Dot Grid Rendering

Layer / File(s) Summary
Shader-backed dot rendering
lib/widgets/dot_painter.dart, shaders/dot_lattice.frag, pubspec.yaml
DotGrid now manages a web shader and uses it for eligible non-screenshot rendering. The painter uses points when shader rendering is unavailable. The shader asset is configured in Flutter.
Editor zoom integration
lib/interactive_map.dart
InteractiveMap passes followsEditorZoom: true to DotGrid, so the shader’s dot scale accounts for editor zoom.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 5421b

The web grid can use the shader while retaining point rendering when the asset fails to load. No actionable issue remains before merge.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 5421b

This changes how the editor draws its background, not who can access or modify data. No introduced security issue was established. Loading failures retain the existing rendering path; remaining uncertainty concerns browser resource lifetimes and recovery behavior.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The shared loader's observed failure scope is all eligible DotGrid instances in the same running client. Mutable shader uniforms remain per-widget; the inspected path establishes no propagation into tenant authorization, persistent state, or privileged services.

Trust Boundaries and Controls

  • observed — The new resource-loading operation targets a fixed packaged shader and is gated to web, non-screenshot rendering. The public widget options do not flow into resource selection, and the shader consumes numeric rendering uniforms rather than executable input.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: drawing the editor’s dot grid with a shader on the web.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

SunkenInTime and others added 2 commits October 5, 2026 22:00
A shader was made for every painter and never disposed, and its uniforms
are only freed by dispose: zooming replaced the painter each step, so each
step leaked one. The grid's state now owns a single shader and disposes it
when the grid goes. Only the web reads the editor's zoom, so desktop
records its points once again rather than on every zoom step.

Found in review by Astra.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
So a grid that becomes eligible after it mounts still gets the shader,
and note why one shader per grid is safe to reuse across its pictures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread lib/widgets/dot_painter.dart Outdated
Comment thread lib/widgets/dot_painter.dart
Comment thread lib/widgets/dot_painter.dart
A failed load left the loading flag set, so the grid drew points until
the page reloaded; the next build now tries again. The shader's dot edge
is now a linear ramp of about one device pixel, closer to the points'
coverage than smoothstep was. dot_lattice_test paints the grid both ways
in the test runner and holds the shader to the same dots in the same
places: no pixel more than 48/255 apart away from the edges, total ink
within 8% (both found by Greptile).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SunkenInTime
SunkenInTime merged commit 0fca2d1 into main Oct 6, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant