fix(webgl): premultiply straight bitmaps when device ignores createImageBitmap option#52
Merged
Merged
Conversation
…ageBitmap option
Older Safari/WebKit accepts the createImageBitmap `premultiplyAlpha: 'premultiply'`
option but silently ignores it, returning straight (non-premultiplied) alpha. The
WebGL upload path assumed ImageBitmaps were always premultiplied (hardcoding
UNPACK_PREMULTIPLY_ALPHA_WEBGL = false), so straight pixels reached the GPU and
produced edge ghosting on transparent images.
Fix: unify TextureData.premultiplyAlpha to mean "WebGL should premultiply this
source on upload." When a device is known not to honor the option, create the
bitmap straight ('none') and let WebGL premultiply during texImage2D instead.
- Add a startup probe (detectPremultiplyAlphaHonored) that uploads a known
semi-transparent pixel and reads it back via a framebuffer to verify the
option is actually honored, not just accepted.
- Add `premultiplyAlphaHonored` config: undefined -> true (assume honored, no
probe), null -> run the probe, boolean -> force the value.
- Carry the per-source GL-premultiply intent from each loader (main-thread
bitmap, ImageWorker, SVG ImageData fallback) through getTextureSource to the
GL upload, which now honors it uniformly for bitmaps.
- Add premultiply-alpha example for visual regression.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…efault Collapse the three-state config (undefined=assume-honored, null=probe, boolean=force) into two states on a plain optional boolean: premultiplyAlphaHonored?: boolean // omitted = probe; set = force This removes the confusing undefined-vs-null distinction and the double-normalization that was spread across Renderer and Stage. The probe now runs by default (when the option is omitted) instead of being opt-in. It is cheap — one 1x1 texture upload + readback at startup — and fails safe: any error resolves to null, which leaves the original behavior unchanged. On honored devices it resolves true (no change); on ignored devices it enables the GL-side premultiply fallback. So the fix now self-heals affected devices out of the box rather than requiring deployers to know their fleet. Also adds the previously-missing unit test for detectPremultiplyAlphaHonored (honored/ignored/null branches + GL-premultiply-off assertion). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…robe
Change the probe from default-on to opt-in via an explicit 'auto' sentinel,
restoring the conservative default (assume honored, no probe) so existing
behavior is unchanged out of the box:
premultiplyAlphaHonored?: boolean | 'auto'
undefined -> true (default: assume honored, no probe)
'auto' -> run the startup probe
boolean -> force the value
'auto' is self-documenting where the previous null-vs-undefined sentinel was
not. Renderer normalizes undefined -> true; Stage coerces the same default so
it is safe when constructed directly.
Co-Authored-By: Claude Opus 4.8 (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
Fixes edge ghosting on transparent images on older Safari/WebKit devices that silently ignore the
createImageBitmappremultiplyAlpha: 'premultiply'option. Those devices return straight (non-premultiplied) bitmaps, but the WebGL upload hardcodedUNPACK_PREMULTIPLY_ALPHA_WEBGL = false, so the straight pixels were treated as premultiplied — producing the characteristic halo/ghosting around transparent edges.What changed
TextureData.premultiplyAlphanow consistently means "WebGL should premultiply this source on upload." Each loader path (main-thread bitmap, worker bitmap, basic path, HTMLImageElement fallback, SVG bitmap vs SVG ImageData fallback) sets this intent correctly. The WebGL upload trusts that single flag instead of the oldisImageBitmap ? falsespecial-case.'none') bitmap and let WebGL premultiply on upload instead.detectPremultiplyAlphaHonored): builds a known straightImageData, runs it throughcreateImageBitmap({premultiplyAlpha:'premultiply'}), uploads withUNPACK_PREMULTIPLY_ALPHA_WEBGL=false, and reads back via a framebuffer to detect whether the option was actually honored. Honored → red reads back ~128; ignored → ~255.premultiplyAlphaHonoredconfig onRendererMainSettings—boolean | 'auto':undefined) →true: assume honored, no probe (the default; preserves existing behavior with zero overhead)'auto'→ run the startup probe (auto-detect)boolean→ force the value and skip the probe (useful for known fleet devices)examples/tests/premultiply-alpha.tsrenders transparentrocko.pngover white/black panels in a 2×2 grid to visualize correct vs ghosted output, with an on-screen readout of the detectedpremultiplyHonored.Reviewer notes
premultiplyAlphaHonored: 'auto'(detect) or a forcedfalse.ImageDataunsupported, incomplete framebuffer) resolves tonull, leaving the original behavior unchanged. Rendering only differs when the probe (or a forced config value) reportspremultiplyHonored === false.detectPremultiplyAlphaHonored(honored / ignored / null branches + GL-premultiply-off during the probe). Note CI's visual snapshot runs on chromium, which honors the option — the non-honored upload path is exercised by unit logic, not the visual suite.🤖 Generated with Claude Code