Add Camera2d.colorMatrix property for built-in color grading#1409
Merged
Add Camera2d.colorMatrix property for built-in color grading#1409
Conversation
…et architecture (#1396) Replace the single `shader` property on Renderable with a `postEffects` array supporting multiple shader effects applied in sequence via FBO ping-pong. Single-effect renderables use a zero-overhead customShader fast path (no FBO). Introduce an abstract `RenderTarget` base class with `WebGLRenderTarget` and `CanvasRenderTarget` implementations, and a renderer-agnostic `RenderTargetPool` using a factory pattern — ready for future WebGPU support. Extract direct GL state calls from the post-effect pipeline into renderer methods (setViewport, clearRenderTarget, enableScissor, disableScissor, setBlendEnabled), all no-ops on the base Renderer, implemented on WebGLRenderer. Fix WebGL texture unit corruption during FBO resize by explicitly using TEXTURE0. Fix projection matrix not being saved/restored across post-effect passes. Fix Canvas renderer not syncing customShader in save/restore. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clear customShader when no enabled effects or entering FBO path (prevents shader leak to children) - Reset _activeBase/_previousBase in RenderTargetPool.destroy() - Guard against nested sprite post-effect passes in RenderTargetPool.begin() - Handle null blob in toBlob() callbacks (reject instead of resolving null) - Restore toImageBitmap() fast path on CanvasRenderTarget using createImageBitmap(canvas) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a public `colorMatrix` property to Camera2d — a built-in ColorMatrix that is always applied as the final post-processing pass, after any effects added via addPostEffect(). Zero overhead when identity (default). The effect is transient: pushed to postEffects before beginPostEffect and removed after endPostEffect each frame. Camera2d uses only the public ColorMatrixEffect API (reset + multiply), no internal shader knowledge. Also fix FileReader error handling in RenderTarget.toDataURL(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends melonJS’s post-processing system by introducing multi-pass post-effect chaining on all renderables and adding a built-in Camera2d.colorMatrix for convenient color grading, backed by a renderer-agnostic RenderTarget/RenderTargetPool abstraction.
Changes:
- Replace the single
Renderable.shaderwith apostEffectsarray (+ helper APIs) and implement multi-pass ping-pong post-processing inWebGLRenderer. - Add
Camera2d.colorMatrix(built-inColorMatrix) applied as the final transient post-effect pass. - Refactor render targets into an abstract
RenderTargetbase +RenderTargetPool, updating WebGL/Canvas render targets and adding unit tests.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/melonjs/src/renderable/renderable.js | Introduces postEffects + helper methods; deprecates shader; integrates renderer-driven post-effect setup/teardown. |
| packages/melonjs/src/video/webgl/webgl_renderer.js | Implements multi-pass post-effect pipeline (FBO ping-pong), render target pooling, and renderer-agnostic state helpers. |
| packages/melonjs/src/video/rendertarget/render_target_pool.js | Adds renderer-agnostic render target pooling for camera and sprite post effects. |
| packages/melonjs/src/video/rendertarget/rendertarget.ts | Adds abstract RenderTarget base class + shared toBlob/toImageBitmap/toDataURL helpers. |
| packages/melonjs/src/video/rendertarget/webglrendertarget.js | Makes WebGL render target extend RenderTarget; adjusts texture unit handling; removes duplicated export helpers. |
| packages/melonjs/src/video/rendertarget/canvasrendertarget.js | Makes Canvas render target extend RenderTarget; optimizes toDataURL/toBlob/toImageBitmap. |
| packages/melonjs/src/video/renderer.js | Updates base renderer post-effect hooks and adds new renderer-agnostic state method stubs. |
| packages/melonjs/src/video/canvas/canvas_renderer.js | Ensures customShader participates in save/restore to prevent shader state leakage in Canvas mode. |
| packages/melonjs/src/camera/camera2d.ts | Adds colorMatrix + transient internal ColorMatrixEffect appended after user post effects. |
| packages/melonjs/src/index.ts | Exports new RenderTarget base class. |
| packages/melonjs/tests/renderable.spec.js | Adds tests for postEffects API + Canvas fast-path customShader behavior and cleanup. |
| packages/melonjs/tests/camera.spec.js | Adds tests for Camera2d.colorMatrix identity/creation/lifecycle behavior. |
| packages/melonjs/tests/renderTargetPool.spec.js | Adds unit coverage for RenderTargetPool behavior (mock + WebGL-backed). |
| packages/melonjs/tests/renderTarget.spec.js | Adds coverage for RenderTarget + Canvas/WebGL implementations. |
| packages/melonjs/CHANGELOG.md | Documents new post-effect chaining, camera colorMatrix, and render target abstractions. |
| packages/examples/src/examples/platformer/play.ts | Updates platformer to use addPostEffect + viewport.colorMatrix for color grading. |
| packages/examples/src/examples/platformer/entities/minimap.ts | Updates minimap camera to use addPostEffect instead of shader. |
| packages/examples/src/examples/platformer/entities/coin.ts | Updates coin effect hookup to use addPostEffect. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Save/restore active texture unit in WebGLRenderTarget constructor and resize() to keep the batcher's currentTextureUnit cache in sync (TEXTURE0 fix preserved) - Handle null 2d context in RenderTarget.toBlob() fallback paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflicts: keep colorMatrix property, Copilot review fixes (active texture save/restore, null context handling). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
onloadend fires on success, error, and abort — could resolve with null. onload only fires on success. Also handle onabort explicitly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Apr 22, 2026
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
colorMatrixproperty to Camera2d — a built-inColorMatrixfor convenient color grading (brightness, contrast, saturation, etc.)addPostEffect()beginPostEffect, removed afterendPostEffecteach framereset()+multiply()), no internal shader knowledgeapp.viewport.colorMatrix.contrast(1.1).saturate(1.1)instead of manualColorMatrixEffectRenderTarget.toDataURL()Closes #1396.
Test plan
clearPostEffects()doesn't break colorMatrix on next frame🤖 Generated with Claude Code