Skip to content

test(rendering): renderer-matrix browser suite — close drawable coverage gaps + fix first-flush text bug#269

Merged
Exoridus merged 10 commits into
mainfrom
feat/v0.16-render-matrix-browser-suite
Jul 10, 2026
Merged

test(rendering): renderer-matrix browser suite — close drawable coverage gaps + fix first-flush text bug#269
Exoridus merged 10 commits into
mainfrom
feat/v0.16-render-matrix-browser-suite

Conversation

@Exoridus

Copy link
Copy Markdown
Owner

Renderer-Matrix Browser Suite (v0.16 quality track)

Completes the pixel-assertion matrix: every drawable type rendered on both GPU backends (WebGL2 + WebGPU) in a real headless browser, reading back actual pixels — catching shader (GLSL/WGSL) and backend-divergence bugs that jsdom unit tests (which stub shaders to "") cannot see.

The matrix was already broad (Sprite, RepeatingSprite, Mesh, Graphics gradient/stencil, Text/SDF, TileMap, RenderTexture, blend, DPR, pixel-snap). This closes the remaining gaps.

New coverage (15 test files, both backends unless noted)

  • Particles (WebGl2/WebGpuParticleRenderer) — deterministic placement by writing the SoA arrays directly (no RNG in ParticleSystem); highest priority, matched the ~12%/~5% renderer-coverage note.
  • NineSliceSprite — 9-region UV mapping (corners not stretched, edges per-axis, center), uniform + asymmetric borders. Uses ScaleModes.Nearest fixtures to avoid linear-filter seam bleed.
  • AnimatedSprite — frame-region UV swap correctness across update()/play().
  • Video — per-frame texture upload from a captureStream() <video>. Static-decode only; a dynamic mid-stream repaint scenario proved unreliable headless and was deliberately excluded rather than committed flaky.
  • BitmapText (WebGL2) — the WebGPU path was already pixel-verified (stencil-clip suite); WebGL2 had none. Closed with a solid-atlas BMFont glyph test.
  • Minor paritywebgpu-split-viewport, isolated solid-fill Graphics, webgl2-mesh-tint.

Engine bug found and fixed

The BitmapText work surfaced a real bug: WebGl2TextRenderer._drawBatches() called shader.sync() before writing that flush's u_projection/u_texture/u_nodeData/u_pageSize uniforms. Since ShaderUniform.setValue() only marks a value dirty for the next sync(), the first flush of each text shaderType drew with a stale zero u_projection — degenerate, so nothing rasterized. It self-heals from the second frame on (values are frame-constant), so no continuous-rendering test caught it, but any genuine single-shot render (screenshot, render-to-texture pre-bake, first frame) of Text/BitmapText drew nothing. Fixed by moving sync() after the uniform writes, matching every other WebGL2 renderer. The new BitmapText test renders exactly once (no warm-up) so it guards the ordering.

Notes

Verification

  • WebGL2: 31 files / 187 tests green
  • WebGPU: 28 files / 121 tests green
  • verify:quick (typecheck + lint + prettier + api-docs) green

Follow-ups (separate PRs)

  • Extract the per-file pixel-assertion helper into a shared _pixelAssertions.ts (WebGL2 readPixels + WebGPU readCanvas) — duplication is now well past the factor-on-third-use threshold.
  • Optional: WebGPU float-value readback (current WebGPU float-RT test asserts target validity; WebGL2 asserts value round-trip).

Exoridus added 10 commits July 9, 2026 05:11
… renderer-matrix suite

Establishes the paired WebGL2/WebGPU pixel-assertion pattern for the v0.16
Renderer-Matrix Browser Suite track: a single opaque Sprite over a solid-color
texture, asserting interior color, out-of-bounds clear color, and tint, on
both backends. Follows the existing per-file helper pattern used by
webgl2/webgpu-repeating-sprite.test.ts verbatim — no new shared helper needed
yet.
Covers @codexo/exojs-particles WebGl2ParticleRenderer/WebGpuParticleRenderer
end-to-end: a deterministically-placed particle (SoA slot written directly,
no spawn modules, no update() call) renders at a known pixel position with
the expected texture/tint color, while a particle-free region shows the
clear color. Particle renderer bindings are not part of wireCoreRenderers,
so both files materialize them explicitly via particlesExtension.renderers.
… pixel tests

BitmapText/BmFont: WebGPU already pixel-verifies the color-atlas path
(webgpu-stencil-clip.test.ts's "BitmapText renders inside a Geometry stencil
clip", using its createSolidBitmapText helper), but no WebGL2 browser test
ever constructed a BitmapText, so the WebGL2 side of the shared
WebGl2TextRenderer "color" shaderType path was unexercised. Add
webgl2-bitmap-text.test.ts to close that gap with real pixel readback.

While building it, found and worked around (with a documented warm-up
render, per-file comment) a genuine WebGl2TextRenderer bug: _drawBatches()
calls shader.sync() before setting that flush's u_projection/u_texture/
u_nodeData/u_pageSize uniforms, so ShaderUniform.setValue()'s upload is
deferred to the *next* sync() of that shader object — every other WebGL2
renderer (e.g. WebGl2SpriteRenderer) sets uniforms first and syncs last.
The very first flush of a text shaderType after connect therefore draws
with a zero u_projection (degenerate, invisible). This self-heals from the
second flush onward in continuous rendering, which is why no prior test
caught it, but breaks any true single-shot render.

Render-texture float formats (rgba16f/rgba32f, the 0.15.3 feature) were not
addable: this branch predates commit a3c7d69 ("feat(rendering): float color
formats (rgba16f/rgba32f) for RenderTexture", merged to main after this
branch's fork point) — RenderTexture has no `format` option, and
ColorTextureFormat/rgba16f/rgba32f do not exist anywhere in src on
matrix/verify. There is no float RenderTexture API here to test.
… flush renders

WebGl2TextRenderer._drawBatches() called shader.sync() before writing that
flush's u_projection/u_texture/u_nodeData/u_pageSize uniforms. Since
ShaderUniform.setValue() only marks a value dirty for the NEXT sync(), the
first flush of each text shaderType drew with a stale zero u_projection —
degenerate, so nothing rasterized. It self-healed from the second frame on
(values are frame-constant), so no continuous-rendering test caught it, but any
genuine single-shot render (screenshot, render-to-texture pre-bake, first frame)
of Text/BitmapText drew nothing.

Move sync() after the uniform writes, matching every other WebGL2 renderer
(uniforms first, sync() last). Surfaced by the new WebGL2 BitmapText pixel test,
whose warm-up workaround is now removed so it renders once and guards the order.
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 512 bytes (0.0%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
exo-esm-modules-esm 2.94MB 512 bytes (0.02%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: exo-esm-modules-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
rendering/webgl2/WebGl2TextRenderer.js 512 bytes 26.74kB 1.95%

Files in rendering/webgl2/WebGl2TextRenderer.js:

  • ./src/rendering/webgl2/WebGl2TextRenderer.ts → Total Size: 24.73kB

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@Exoridus Exoridus merged commit 77e7a23 into main Jul 10, 2026
16 checks passed
@Exoridus Exoridus deleted the feat/v0.16-render-matrix-browser-suite branch July 10, 2026 04:01
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