Skip to content

feat(video): per-sprite depth in GPU vertex stream (19.7.0 PR A)#1463

Merged
obiot merged 2 commits into
masterfrom
feat/vertex-depth-attribute
May 24, 2026
Merged

feat(video): per-sprite depth in GPU vertex stream (19.7.0 PR A)#1463
obiot merged 2 commits into
masterfrom
feat/vertex-depth-attribute

Conversation

@obiot
Copy link
Copy Markdown
Member

@obiot obiot commented May 24, 2026

Summary

Foundational vertex-pipeline work toward the 19.7 Camera3d release. Widens aVertex from vec2 to vec3 across every batched shader, adds renderer.setDepth(depth) + renderer.currentDepth state plumbed through RenderState's save/restore stack, and Renderable.preDraw forwards this.depth automatically. Batchers (QuadBatcher, LitQuadBatcher, PrimitiveBatcher, GPU TMX path, ShaderEffect's quad.vert template) emit the depth as the z component of each vertex.

  • Unblocks Camera3d — under perspective projection, sprites at different depths now scale and parallax correctly. Under the default ortho projection, no visible change.
  • Aligns the whole batched pipeline with the WebGPU vertex modelMesh was the lone vec3 aVertex outlier; now all batchers match.
  • Opens depth-aware custom ShaderEffectsgl_FragCoord.z is now meaningful per sprite (visually verified by a temp validation example that's not part of this PR).

Backward compatibility

✅ Fully compatible for all documented public APIs:

API Compat
ShaderEffect (fragment-only effects, all versions)
GLShader with attribute vec2 aVertex (pre-19.6 style) ✓ — WebGL silently drops z, attribute offsets shift but bind-by-name is transparent
Renderable.depth getter/setter ✓ unchanged
Renderable.preDraw() ✓ same signature, internally gains one setDepth call
Renderer.drawImage / fillRect / strokeRect / setTint / setColor / all public renderer methods ✓ unchanged signatures
Mesh, MTL loader, Camera2d, Stage, Container, Application ✓ untouched

⚠️ One exotic incompatibility (flagged in CHANGELOG): subclasses of QuadBatcher / PrimitiveBatcher that reimplement addQuad / drawVertices from scratch and push to vertexData directly need to update the 6-arg push to the 7-arg form (insert z after y, default 0 works under ortho). Subclasses that delegate to super.addQuad() are unaffected.

Validated end-to-end: tested every example (sprite, lights, normalMap, spriteIlluminator, ShaderEffects, platformer coin shine, GPU TMX, mesh3d, plinko, pool-matter, …) — all render correctly with no GL errors.

What's in the diff

Engine

  • package.json → 19.7.0, CHANGELOG.md (new [19.7.0] _unreleased_ section)
  • Shaders: quad-multi.vert, quad-multi-lit.vert, primitive.vert, orthogonal-tmxlayer.vert, quad.vert
  • Batchers: quad_batcher.js, lit_quad_batcher.js, primitive_batcher.js
  • vertex.jspush() signature gains z between y and u (critical fix — the original PR had this hardcoded to the old positional layout, surfaced as black-screen regression during local testing)
  • renderstate.js — new currentDepth field + save/restore stack support
  • renderer.js — new setDepth(depth) + currentDepth getter/setter
  • renderable.js — preDraw forwards depth (one new line)

Tests (3557 total pass / 12 skipped, was 3540 / 12)

  • 16 new in depth.spec.js — state stack, preDraw forwarding, batcher strides (28 / 32 / 24), z-in-buffer readback, tint-slot regression guard (catches the vertex.js push() positional-layout bug)
  • 13 new in depth_adversarial.spec.js — cross-batcher persistence, mid-batch per-vertex emission, NineSliceSprite multi-quad inheritance, nested preDraw/postDraw, blitTexture z=0 invariant, Mesh batcher independence, vec2 aVertex GLShader compat, RenderState stack growth past 32 slots, extreme values + NaN/Infinity, flush boundary preservation, 500-op deterministic fuzz
  • vertexBuffer.spec.js + drawVertices.spec.js updated to the new 6/7/8-float layouts
  • sprite.spec.js stub renderer gets setDepth: noop

Drive-by examples fix

  • packages/examples/src/index.css#screen > * selector so non-canvas children (dropdowns, custom HUDs) receive pointer events. The mesh3d / mesh3dMaterial example dropdowns were unclickable under the previous #screen canvas selector. Pre-existing bug, surfaced during local testing.

Test plan

  • pnpm vitest run — 3557 tests pass, 12 skipped (was 3540 / 12)
  • pnpm lint — 0 errors
  • pnpm build — succeeds
  • CodeRabbit review — 0 findings
  • Visual validation on every example — sprite, lights, normalMap, spriteIlluminator, ShaderEffects, platformer, mesh3d, mesh3dMaterial, GPU TMX (tiledMapLoader incl. non-orthogonal maps), plinko-planck, pool-matter, svgShapes, gradients, lineOfSight
  • Cross-version backward-compat verified visually with a custom GLShader declaring attribute vec2 aVertex; rendering correctly

Next steps

  • PR BCamera3d class (perspective projection mode + view matrix + pitch/yaw/follow-offset, slots into Stage.cameras as a Camera2d subclass).
  • PR C — After Burner-style behind-the-plane shoot-em-up showcase (in-tree example, the headline visual for the 19.7 release).

🤖 Generated with Claude Code

Widens `aVertex` from `vec2` to `vec3` across every batched shader
(`quad-multi`, `quad-multi-lit`, `primitive`, `orthogonal-tmxlayer`,
`quad` template used by ShaderEffect). Adds `renderer.setDepth(depth)`
+ `renderer.currentDepth` state on the base Renderer, plumbed through
`RenderState`'s save/restore stack and forwarded automatically by
`Renderable.preDraw(renderer)`. Batcher emit paths (`QuadBatcher.addQuad`,
`LitQuadBatcher.addQuad`, `PrimitiveBatcher.drawVertices` +
`#expandLinesToTriangles`) read `currentDepth` and write it as the z
component of each vertex.

Unblocks Camera3d (PR B): with a perspective projection, sprites at
different depths now scale and parallax correctly. Under the default
ortho projection there is no visible effect — every existing 2D path
renders identically (verified end-to-end on every example).

Backward-compatible for all documented public APIs:
- `ShaderEffect` (fragment-only effects) — fragment template unchanged
- `GLShader` declaring `attribute vec2 aVertex` — WebGL silently drops
  the z component; downstream attribute offsets shift but GLShader binds
  by name via `getAttribLocation`, so the shift is transparent
- `drawImage` / `fillRect` / `strokeRect` / `setTint` / all renderer
  methods — signatures unchanged
- `Mesh` — its batcher (already `vec3 aVertex`) is unchanged
- `Renderable.preDraw` — same signature, gains one `setDepth` call

The one exotic incompatibility (flagged in the CHANGELOG): subclasses
of `QuadBatcher` / `PrimitiveBatcher` that reimplement `addQuad` /
`drawVertices` from scratch and push to `vertexData` directly need to
update the 6-arg push to the 7-arg form (insert `z` after `y`,
default `0` works under ortho). Subclasses delegating to `super.addQuad`
are unaffected.

Tests
-----
- 16 new in `depth.spec.js` covering state stack, preDraw forwarding,
  batcher strides (28 / 32 / 24), z-in-buffer readback, tint-slot
  regression guard (catches the original push() positional-layout bug
  by checking the UINT32 tint at offset 5), default depth = 0, and
  end-to-end perspective draw without GL error
- 13 new in `depth_adversarial.spec.js` covering cross-batcher
  persistence, mid-batch per-vertex emission, NineSliceSprite multi-quad
  inheritance, nested preDraw/postDraw stack, blitTexture z=0 invariant
  (post-fx blits always screen-space), Mesh batcher independence,
  vec2 aVertex custom GLShader compat, RenderState stack growth past
  32 slots, extreme values + NaN/Infinity, flush boundary preservation,
  and a 500-op deterministic fuzz
- `vertexBuffer.spec.js` + `drawVertices.spec.js` updated to the new
  6/7/8-float layouts (z slot at offset 2)
- `sprite.spec.js` stub renderer gains a `setDepth: noop`

Total: 3557 tests pass, 12 skipped, 0 GL errors across the full WebGL
pipeline adversarial suite, CodeRabbit clean review.

Drive-by fix
------------
`packages/examples/src/index.css` — `#screen` had `pointer-events: none`
to keep the empty fixed overlay from blocking the index page after
unmount, but the rule re-enabled clicks only on `canvas`. Examples that
add HTML controls (the mesh3d / mesh3dMaterial dropdowns) ended up
unclickable. Broadened to `#screen > *` so any direct child opts back
in. Pre-existing bug, surfaced during local testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 24, 2026 07:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces per-renderable depth as a first-class value in the WebGL batched vertex stream by widening aVertex from vec2 to vec3 across the batched shader pipeline and plumbing a renderer.currentDepth state through RenderState + Renderer.setDepth(). It lays foundational work for upcoming perspective/Camera3d rendering while keeping existing 2D behavior intended to remain visually unchanged under orthographic projection.

Changes:

  • Widen batched vertex formats (aVertex: vec3) and update shaders/batchers/VertexArrayBuffer.push() to emit per-vertex z.
  • Add RenderState.currentDepth with save/restore support and expose it via Renderer.currentDepth + Renderer.setDepth(depth).
  • Forward Renderable.depth automatically in Renderable.preDraw, and add extensive WebGL-focused regression/integration tests.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/melonjs/src/video/buffer/vertex.js Updates internal vertex push layout to include z and shifts downstream slots.
packages/melonjs/src/video/renderstate.js Adds currentDepth plus stack save/restore support.
packages/melonjs/src/video/renderer.js Exposes currentDepth and introduces setDepth(depth) API.
packages/melonjs/src/renderable/renderable.js Forwards Renderable.depth into renderer state during preDraw.
packages/melonjs/src/video/webgl/batchers/quad_batcher.js Emits z into quad vertex stream and updates attribute offsets.
packages/melonjs/src/video/webgl/batchers/lit_quad_batcher.js Emits z into lit-quad vertex stream and updates attribute offsets.
packages/melonjs/src/video/webgl/batchers/primitive_batcher.js Emits z into primitive vertex stream and updates attribute offsets.
packages/melonjs/src/video/webgl/shaders/quad.vert Changes aVertex to vec3 and uses z in clip-space transform.
packages/melonjs/src/video/webgl/shaders/quad-multi.vert Same vec3 aVertex change for multi-texture quad path.
packages/melonjs/src/video/webgl/shaders/quad-multi-lit.vert Same vec3 aVertex change for lit multi-texture path; fixes vWorldPos.
packages/melonjs/src/video/webgl/shaders/primitive.vert Same vec3 aVertex change for primitives; keeps line expansion 2D.
packages/melonjs/src/video/webgl/shaders/orthogonal-tmxlayer.vert Updates GPU TMX layer vertex shader to vec3 aVertex.
packages/melonjs/tests/depth.spec.js Adds tests for depth state plumbing, batcher stride, and vertex buffer contents.
packages/melonjs/tests/depth_adversarial.spec.js Adds adversarial integration tests (batcher switching, flush boundaries, fuzz, compat).
packages/melonjs/tests/vertexBuffer.spec.js Updates vertex buffer unit tests for new 6/7/8-float layouts.
packages/melonjs/tests/drawVertices.spec.js Updates drawVertices-related regression tests for new vertex layouts.
packages/melonjs/tests/sprite.spec.js Updates stub renderer used by tests to include setDepth.
packages/melonjs/tests/batcher_attribute_leak.spec.js Updates documentation/comments for new strides.
packages/melonjs/package.json Bumps engine package version to 19.7.0.
packages/melonjs/CHANGELOG.md Adds 19.7.0 unreleased notes describing the vertex layout and API changes.
packages/examples/src/index.css Fixes pointer-events behavior for non-canvas children under #screen.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/melonjs/src/video/webgl/shaders/quad.vert
Address Copilot review on PR #1463: with `aVertex.z` now participating
in clip-space (PR A), any `Renderable.depth` outside the camera's
`[near, far]` range maps outside clip space and the GPU silently culls
the fragment. Pre-19.7 the default ±1000 didn't matter because
clipspace.z was hardcoded to 0 in the vertex shader.

The defaults are easy to exceed in practice:
- `Container.autoDepth = true` (default) assigns `pos.z = childCount` —
  any container with >1000 children clip-culls
- Y-sort patterns (`sprite.depth = sprite.pos.y`) on tall maps easily
  exceed 1000

Widens to ±1e6 to cover every realistic 2D depth value while staying
well within float32 precision. Override per-camera for tighter z
clipping (e.g. Camera3d will use a much smaller range for meaningful
perspective z resolution).

Adds a regression test in `depth.spec.js` that pins the new defaults
and proves a depth=10000 sprite's clipspace.z lands inside [-1, 1]
under the new ortho matrix.

3558 tests pass (was 3557 + 1 new).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@obiot obiot merged commit 12d3d0a into master May 24, 2026
6 checks passed
@obiot obiot deleted the feat/vertex-depth-attribute branch May 24, 2026 07:31
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