feat(video): per-sprite depth in GPU vertex stream (19.7.0 PR A)#1463
Merged
Conversation
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>
Contributor
There was a problem hiding this comment.
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-vertexz. - Add
RenderState.currentDepthwith save/restore support and expose it viaRenderer.currentDepth+Renderer.setDepth(depth). - Forward
Renderable.depthautomatically inRenderable.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.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Foundational vertex-pipeline work toward the 19.7 Camera3d release. Widens
aVertexfromvec2tovec3across every batched shader, addsrenderer.setDepth(depth)+renderer.currentDepthstate plumbed throughRenderState's save/restore stack, andRenderable.preDrawforwardsthis.depthautomatically. Batchers (QuadBatcher,LitQuadBatcher,PrimitiveBatcher, GPU TMX path,ShaderEffect'squad.verttemplate) emit the depth as the z component of each vertex.Meshwas the lonevec3 aVertexoutlier; now all batchers match.ShaderEffects —gl_FragCoord.zis 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:
ShaderEffect(fragment-only effects, all versions)GLShaderwithattribute vec2 aVertex(pre-19.6 style)Renderable.depthgetter/setterRenderable.preDraw()setDepthcallRenderer.drawImage/fillRect/strokeRect/setTint/setColor/ all public renderer methodsMesh,MTLloader,Camera2d,Stage,Container,ApplicationQuadBatcher/PrimitiveBatcherthat reimplementaddQuad/drawVerticesfrom scratch and push tovertexDatadirectly need to update the 6-arg push to the 7-arg form (insertzaftery, default0works under ortho). Subclasses that delegate tosuper.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)quad-multi.vert,quad-multi-lit.vert,primitive.vert,orthogonal-tmxlayer.vert,quad.vertquad_batcher.js,lit_quad_batcher.js,primitive_batcher.jsvertex.js—push()signature gainszbetweenyandu(critical fix — the original PR had this hardcoded to the old positional layout, surfaced as black-screen regression during local testing)renderstate.js— newcurrentDepthfield + save/restore stack supportrenderer.js— newsetDepth(depth)+currentDepthgetter/setterrenderable.js— preDraw forwards depth (one new line)Tests (3557 total pass / 12 skipped, was 3540 / 12)
depth.spec.js— state stack, preDraw forwarding, batcher strides (28 / 32 / 24), z-in-buffer readback, tint-slot regression guard (catches thevertex.js push()positional-layout bug)depth_adversarial.spec.js— cross-batcher persistence, mid-batch per-vertex emission, NineSliceSprite multi-quad inheritance, nested preDraw/postDraw,blitTexturez=0 invariant, Mesh batcher independence, vec2 aVertex GLShader compat,RenderStatestack growth past 32 slots, extreme values + NaN/Infinity, flush boundary preservation, 500-op deterministic fuzzvertexBuffer.spec.js+drawVertices.spec.jsupdated to the new 6/7/8-float layoutssprite.spec.jsstub renderer getssetDepth: noopDrive-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 canvasselector. Pre-existing bug, surfaced during local testing.Test plan
pnpm vitest run— 3557 tests pass, 12 skipped (was 3540 / 12)pnpm lint— 0 errorspnpm build— succeedsattribute vec2 aVertex;rendering correctlyNext steps
Camera3dclass (perspective projection mode + view matrix + pitch/yaw/follow-offset, slots intoStage.camerasas aCamera2dsubclass).🤖 Generated with Claude Code