Summary
Add color-only multisample antialiasing for offscreen framebuffers, with automatic resolve into the framebuffer's texture attachments.
This supersedes #2702, which identified that Texture.samples is currently ignored by the WebGL backend.
Motivation
Offscreen effects often render into a texture and sample that texture in a later pass. WebGL2 supports MSAA for this workflow, but only by rendering into a multisampled renderbuffer and resolving it into a texture with blitFramebuffer.
Today luma.gl does not expose that path:
WEBGLTexture allocates ordinary texture storage regardless of Texture.samples.
WEBGLFramebuffer only attaches textures.
- Applications must drop down to raw WebGL renderbuffers and blits.
Proposed API
Put the multisampling request on the framebuffer rather than on the resolved texture:
const colorTexture = device.createTexture({
width,
height,
format: 'rgba8unorm',
usage: Texture.RENDER | Texture.SAMPLE
});
const framebuffer = device.createFramebuffer({
width,
height,
samples: 4,
colorAttachments: [colorTexture]
});
The textures in colorAttachments remain ordinary single-sampled textures. After the render pass ends, they contain the resolved result and can be sampled or copied as usual.
WebGL implementation
For samples > 1, WEBGLFramebuffer privately owns:
- the existing texture-backed framebuffer, used as the resolve destination;
- a second framebuffer used for rendering;
- one multisampled renderbuffer per color attachment.
WEBGLRenderPass binds the multisampled framebuffer for clear and draw operations. Its end() resolves every color attachment into the texture-backed framebuffer with:
gl.blitFramebuffer(
0,
0,
width,
height,
0,
0,
width,
height,
gl.COLOR_BUFFER_BIT,
gl.NEAREST
);
The renderbuffers are recreated on resize and deleted with the framebuffer. They remain backend-private; no public Renderbuffer resource is needed.
Initial scope
- WebGL2 only.
- Color attachments only.
- Support one or more color attachments with a common framebuffer sample count.
- Resolve automatically when
RenderPass.end() is called.
- Validate that the requested count is supported and that attachments are color-renderable.
- Reject depth/stencil attachments together with
samples > 1 in the first implementation.
Non-goals
- Multisampled depth or stencil.
- Sampling an unresolved multisampled attachment.
- Exposing renderbuffers as public luma.gl resources.
- Treating
Texture.samples as a WebGL MSAA request.
Why not Texture.samples?
In WebGL, the texture receiving the resolved result is physically single-sampled. Reusing Texture.samples would make the texture's metadata and memory accounting inaccurate, and would give it different semantics from a true multisampled WebGPU texture.
A framebuffer-level option expresses the portable intent: multisample rendering into these resolved color textures.
WebGPU follow-up
The same API can later map to a private multisampled GPUTexture plus the supplied color texture as resolveTarget. That follow-up is not required for the initial WebGL implementation.
Validation
Add tests for:
- allocating the multisampled render framebuffer and color renderbuffers;
- binding the multisampled framebuffer during the pass;
- resolving on
end();
- multiple color attachments;
- resize and destroy cleanup;
- rejection of depth/stencil attachments in the initial scope;
- rejection or clamping of unsupported sample counts.
Open questions
- Should unsupported sample counts throw or be clamped to
MAX_SAMPLES?
- Should the default remain
1, or should canvas/offscreen defaults eventually be aligned?
- Should a later cross-backend API expose explicit resolve targets for advanced cases?
Summary
Add color-only multisample antialiasing for offscreen framebuffers, with automatic resolve into the framebuffer's texture attachments.
This supersedes #2702, which identified that
Texture.samplesis currently ignored by the WebGL backend.Motivation
Offscreen effects often render into a texture and sample that texture in a later pass. WebGL2 supports MSAA for this workflow, but only by rendering into a multisampled renderbuffer and resolving it into a texture with
blitFramebuffer.Today luma.gl does not expose that path:
WEBGLTextureallocates ordinary texture storage regardless ofTexture.samples.WEBGLFramebufferonly attaches textures.Proposed API
Put the multisampling request on the framebuffer rather than on the resolved texture:
The textures in
colorAttachmentsremain ordinary single-sampled textures. After the render pass ends, they contain the resolved result and can be sampled or copied as usual.WebGL implementation
For
samples > 1,WEBGLFramebufferprivately owns:WEBGLRenderPassbinds the multisampled framebuffer for clear and draw operations. Itsend()resolves every color attachment into the texture-backed framebuffer with:The renderbuffers are recreated on resize and deleted with the framebuffer. They remain backend-private; no public
Renderbufferresource is needed.Initial scope
RenderPass.end()is called.samples > 1in the first implementation.Non-goals
Texture.samplesas a WebGL MSAA request.Why not
Texture.samples?In WebGL, the texture receiving the resolved result is physically single-sampled. Reusing
Texture.sampleswould make the texture's metadata and memory accounting inaccurate, and would give it different semantics from a true multisampled WebGPU texture.A framebuffer-level option expresses the portable intent: multisample rendering into these resolved color textures.
WebGPU follow-up
The same API can later map to a private multisampled
GPUTextureplus the supplied color texture asresolveTarget. That follow-up is not required for the initial WebGL implementation.Validation
Add tests for:
end();Open questions
MAX_SAMPLES?1, or should canvas/offscreen defaults eventually be aligned?