| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…und on the way (#1318) The six CSS blend modes that fixed-function blending cannot express — overlay, hard-light, color-dodge, color-burn, soft-light, difference — worked only on the Canvas renderer and silently fell back to "normal" on WebGL 2 and WebGPU. Setting `sprite.blendMode = "overlay"` gave no error and no effect. All thirteen modes the engine names now work on all three renderers, so the Canvas fallback is no longer the most capable backend for blending. Neither GPU backend can read the destination in a fragment shader (verified: no WEBGL_blend_equation_advanced_coherent, no framebuffer fetch, no pixel local storage; gpuweb#394 still open), so each such draw captures the destination, renders to an offscreen target, and composites through a dual-language BlendEffect carrying both a GLSL and a WGSL body. Hooked at `setBatcher`, the one point every draw entry point passes through, so it covers sprites, text, image layers, particles, Tiled layers and shape fills identically — per draw, against the live framebuffer, the way Canvas and the fixed-function modes already behave. `darken` and `lighten` join them. Fixed-function MIN/MAX compute `min(src, dst)` and nothing else, leaving nowhere for the `(1 - srcAlpha) * dst` term source-over contributes after the blend: at 60% opacity darken was 84/255 off the W3C result, and a white `lighten` glow over a light backdrop rendered completely invisible. Measurement also showed multiply, screen and exclusion to be exact at any alpha, so the "approximate for a translucent source" comments they carried were simply wrong and are gone. One shared registry, `video/blendmodes.js`, now defines every mode once. The two GPU backends each carried their own copy — a switch of blendEquation/blendFunc calls on one side, a GPUBlendState table on the other — so a mode corrected on one could silently disagree with the other and `video.AUTO` would render the same scene differently depending on what it picked. Verified pixel-identical before and after across all thirteen modes. THREE PRE-EXISTING BUGS, none of them about blend modes: - Writing a shader uniform corrupted any batch still pending. `GLShader.setUniform` binds its program to write the value, and linking leaves one bound, but GLShader is constructed with a bare `gl` and cannot reach the renderer's program cache — so the cache named the batcher's program while GL had the effect's. Batchers check that cache before re-issuing useProgram, skipped the rebind as redundant, and drew queued geometry through the wrong program: sprites turned black or vanished, silently, because the attribute layouts overlap. Reachable from ordinary code: `effect.setUniform()` between two draws is enough. The cache now lives on the GL context, the one object both sides hold. - `CanvasRenderTarget.invalidate()` re-entered the batcher dispatch, so refreshing a texture looked like a scene draw and bracketed the invalidation itself. - `Batcher.bind()` used the program cache to decide whether to adopt its own shader, two different questions that only coincided while the cache could lie. The first two were found by looking at a screenshot, not by 6000 tests, because `getWebGLRenderer` caught every construction error and treated it as "this machine has no WebGL" — turning engine breakage into a green run of skips across 47 spec files. It now classifies: a genuinely missing GL stack still skips, anything else fails loudly carrying the original error. Verified against the Canvas renderer, which implements all six natively and shares nothing with this code, as well as against a CPU implementation of the W3C formulas. Every example (48 x 3 backends) compared against master with a master-vs-master control to separate animation noise from real change: the Blend Modes grid is the only thing that differs. 6250 tests / 258 spec files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N
DOC_README.md is what `pnpm doc` passes to typedoc as `--readme`, so it is the
landing page a new user reads first. It had gone unmaintained across eight
releases:
- the Quick Start had no `await app.init()`, mandatory since 20.0, so the
very first snippet anyone copied could not run
- the feature table said "WebGL & Canvas 2D" through the whole of 20.x,
omitting the backend that release was built around
- the shader sample taught `renderable.shader =`, deprecated since 19.2.0 in
favour of `addPostEffect()`
The cause is structural: two READMEs with overlapping content in different
directories. The root one is updated every release; this one, tucked inside
packages/melonjs, was invisible. It now sits beside the README it duplicates.
Placement alone does not enforce anything, so `scripts/check-doc-readme.ts`
runs before typedoc and fails the build when a sample constructs an
`Application` without awaiting `init()`, when a sample uses a member marked
`@deprecated` anywhere in src, or when the page stops naming a renderer the
engine supports. Deprecated members are scanned from the source rather than
listed, so something deprecated later is covered without anyone remembering
this file. Both original bugs were verified to fail it.
Also fixes the ParticleEmitter class doc, which rendered as a broken category
name in the sidebar — "Particles ### Blend modes An emitter draws no pixels of
its own..." — because `@category` is a block tag and the sections added in
#1604 and #1606 sat after it, so they were absorbed into its value. Prose now
comes first with `@category` last, and the two `@example` tags are fenced code
blocks, which cannot swallow what follows. Verified in the built output: both
sections render as h3 headings and no raw markdown leaks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N
…cs (#1613) The changelog had drifted a long way from the style it was written in before, so this writes the convention down in CONTRIBUTING.md and applies it: an entry is `Subsystem: what changed` in one or two sentences, giving the mechanism and the symptom without the discovery narrative, covering only what affects a released version. `### Changed` is for user-facing API changes, not for anything that merely moved. Measurements are quoted no more precisely than they were taken, external contributors get `(thanks @user)`, and examples stay out. Applied to 20.2.0 (16 entries down to 11, including five that documented bugs which never shipped) and to the nine released sections that had drifted. The root README's blend mode list named seven of the thirteen modes, from before 20.2 closed the gap on WebGL 2 and WebGPU, and said nothing about the particle reference space. DOC_README.md is what `pnpm doc` passes to typedoc as `--readme`, making it the landing page a new user reads first. It had gone unmaintained across eight releases: no `await app.init()` in the Quick Start though it became mandatory in 20.0, so the first snippet anyone copied could not run; a feature table still reading "WebGL & Canvas 2D" through the whole of 20.x; and a shader sample teaching `renderable.shader =`, deprecated since 19.2.0. The cause is structural — two READMEs with overlapping content in different directories, only one of which anyone looks at. It now sits beside the README it duplicates, and `scripts/check-doc-readme.ts` runs ahead of typedoc so placement is not the only defence: the build fails when a sample constructs an `Application` without awaiting `init()`, when a sample uses anything marked `@deprecated` anywhere in src, or when the page stops naming a renderer the engine supports. Deprecated members are scanned from the source rather than listed, so a future deprecation is covered without anyone remembering this file. Both original bugs were verified to fail it. Also fixes the ParticleEmitter class doc, which rendered in the sidebar as "Particles ### Blend modes An emitter draws no pixels of its own...": `@category` is a block tag and the sections added in #1604 and #1606 sat after it, so they were absorbed into its value. Prose now comes first with `@category` last.
| Back | FazBrowse Home | New Git URL |
Closes #1318.
The six CSS blend modes fixed-function blending cannot express — overlay, hard-light, color-dodge, color-burn, soft-light, difference — worked only on Canvas and silently fell back to "normal" on WebGL 2 and WebGPU. All thirteen modes the engine names now work on all three renderers.
darken and lighten join them: fixed-function MIN/MAX compute min(src, dst) and nothing else, so there was nowhere to put the (1 - srcAlpha) * dst term source-over contributes after the blend. At 60% opacity darken was 84/255 off the W3C result, and a white lighten glow over a light backdrop rendered completely invisible.
How
Neither GPU backend can read the destination in a fragment shader — verified directly, not assumed: no WEBGL_blend_equation_advanced_coherent, no EXT_shader_framebuffer_fetch, no pixel local storage, and gpuweb#394 still open. So each such draw captures the destination, renders to an offscreen target, and composites through a dual-language BlendEffect (GLSL + WGSL).
Hooked at setBatcher, the one point every draw entry point passes through, so sprites, text, image layers, particles, Tiled layers and shape fills are all covered by the same code — per draw, against the live framebuffer, exactly how Canvas and the fixed-function modes already behave.
video/blendmodes.js is now the single registry. The two GPU backends each carried their own copy of the table, so a mode corrected on one could silently disagree with the other and video.AUTO would render the same scene differently depending on what it picked. Verified pixel-identical across all thirteen modes before and after.
Three pre-existing bugs, none about blend modes
Why the suite didn't catch them
getWebGLRenderer caught every construction error and treated it as "this machine has no WebGL", turning engine breakage into a green run of skips across 47 spec files. webgl_available.spec.js was the backstop but only runs with the full suite, so a subset run lost the signal entirely. It now classifies: a missing GL stack still skips, anything else fails loudly with the original error.
Two of the three bugs above were found by looking at a screenshot rather than by 6000 tests.
Verification
6250 tests / 258 spec files.
Note for reviewers
The ### Changed entry is real: a game already setting one of these modes rendered unblended and will now look different, and code branching on setBlendMode's return value takes a new path. drawMesh is the one remaining exclusion — the offscreen's separate depth buffer would break subsequent depth testing — and falls back with a one-time warning rather than silently.
🤖 Generated with Claude Code
https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N