Skip to content

feat(webgpu): add instanced rect 2D rendering and raise texture limit - #55

Open
jimwei wants to merge 1 commit into
moonbit-community:mainfrom
jimwei:codex/rect-inst-2d-max-texture
Open

feat(webgpu): add instanced rect 2D rendering and raise texture limit#55
jimwei wants to merge 1 commit into
moonbit-community:mainfrom
jimwei:codex/rect-inst-2d-max-texture

Conversation

@jimwei

@jimwei jimwei commented Aug 13, 2026

Copy link
Copy Markdown

Adds a compact instanced-rectangle 2D path to the WebGPU backend, plus a requestDevice texture-limit fix.

What changed

  1. src/canvas.mbt - new runtime API rt.pushRectInst2d(rects, colors, palette, pipelineCode), a rectInst render command, two storage buffers (rect vec4<f32> + palette index), a dedicated bind group, pipelines for pipeline code 12/13, and a single pass.draw(6, rectCount) submission.
  2. src/shader_sources.mbt - new instance-rect WGSL shader (index 10) that expands the 6 triangle corners in the vertex shader and samples the palette.
  3. src/canvas.mbt - request maxTextureDimension2D from the adapter when calling requestDevice, so large glyph atlases (for example 12,288 px) are not rejected by the default device limit.

Motivation

The spreadsheet PoC renders >100k visible cells. The previous path expanded every rectangle into 6 vertices in JS and uploaded that vertex buffer every frame. The new path uploads 20 bytes per instance (rect + palette index) and submits all rectangles in one draw call.

Benchmarks (Chrome headless + WebGPU, 118,604-cell grid):

Scenario pushColor2d baseline pushRectInst2d
Large grid ~69 ms/frame, 1.72 M cells/s ~31.9 ms/frame, 3.72 M cells/s
Text pressure, 100 frames 925.9 ms 368.5 ms

Compatibility

The consuming app checks for rt.pushRectInst2d and falls back to the existing pushColor2d vertex-expansion path, so unpatched versions of the runtime remain supported.

Verification

The patched package is consumed and verified in the spreadmb PoC (verify_selene.py, verify_selene_unicode_edge.py, verify_cjk_selene.py). A standalone moon check --target js in selene-webgpu currently hits unrelated dependency parse errors in moonbitlang/x@0.4.45 under the latest MoonBit toolchain, so the runtime validation was done through the consuming project.

@Milky2018

Milky2018 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the contribution and for including the benchmark results. The instanced-rectangle direction is useful, and requesting the adapter-reported maxTextureDimension2D is reasonable.

There are several implementation issues that need to be addressed before this can be merged:

  1. Multiple rectInst commands do not bind their respective rectangle/color regions. The uploads advance rectOffset and rectColorOffset, but bindings 1 and 2 always expose the buffers from offset zero. Since the shader indexes from instance_index == 0, every command after the first reads the data belonging to the first command.

  2. All commands in a pass share dyn2dRectPalette:${bufferScope} and overwrite it at offset zero before the command buffer is submitted. If commands use different palettes, earlier draws will also observe the final palette contents.

  3. pushRectInst2d is currently only attached to the internal globalThis.__selene_webgpu_runtime. It is not reachable through the Selene MoonBit render path, and draw_rect still expands every rectangle into six vertices. Please make MoonBit own the batching/API and keep the JS FFI as a thin submission layer rather than requiring consumers to access the internal global runtime.

  4. The command should validate that rects.length % 4 == 0 and that rects.length / 4 == colors.length. The draw currently treats colors.length as the rectangle count without enforcing that invariant.

The preferred implementation is to pack the pass into contiguous rectangle/color/palette data and submit one batch where compatible. If clipping or pipeline changes require separate draws, each draw needs explicit aligned buffer ranges or base indices, plus non-overlapping palette storage.

Please also add a browser-level regression covering at least two rectInst commands with different rectangles and palettes, ideally separated by a clip or blend boundary. That test would catch both buffer-aliasing problems above.

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.

2 participants