Anti-alias the edges in the present pass #78

Closed
opened 2026-10-20 08:49:24 +00:00 by sermuns · 1 comment
Owner

Nothing in the renderer is multisampled: every target is created with
sample_count: 1 and the pipelines use MultisampleState::default(). Every
roofline, path edge and avatar silhouette is therefore a staircase, and the
browser has --render-scale partly because weak GPUs are expected.

Real MSAA on the HDR target is not portable: Rgba16Float needs
EXT_color_buffer_float for a multisampled attachment too, and that extension is
already the reason WebGL2 falls back to Rgba8Unorm.

Do it in the present pass instead. FXAA is five taps and works on the tonemapped
image, which is where its perceptual edge test belongs. The pass currently reads
with textureLoad; FXAA needs a filtering sampler on the HDR texture, so add one
alongside the existing post bindings (and keep the integer path when it is
off).

Related: #77, #84, and the depth issue #88 if FXAA later
grows into a temporal filter.

Nothing in the renderer is multisampled: every target is created with `sample_count: 1` and the pipelines use `MultisampleState::default()`. Every roofline, path edge and avatar silhouette is therefore a staircase, and the browser has `--render-scale` partly because weak GPUs are expected. Real MSAA on the HDR target is not portable: `Rgba16Float` needs `EXT_color_buffer_float` for a multisampled attachment too, and that extension is already the reason WebGL2 falls back to `Rgba8Unorm`. Do it in the present pass instead. FXAA is five taps and works on the tonemapped image, which is where its perceptual edge test belongs. The pass currently reads with `textureLoad`; FXAA needs a filtering sampler on the HDR texture, so add one alongside the existing `post` bindings (and keep the integer path when it is off). Related: #77, #84, and the depth issue #88 if FXAA later grows into a temporal filter.
sermuns added this to the Rendering milestone 2026-10-20 08:49:24 +00:00
Author
Owner

Landed — as MSAA, not FXAA, and better than the plan assumed.

  • 3cb2e12 put 4x MSAA on the scene pass (multisampled HDR/color target, single-sample resolve the present pass samples) with a pause-menu switch; 8f1290d added 8x.
  • Portability is handled the honest way: max_msaa_samples asks the adapter whether the chosen color format supports multisample resolve and sample_count before committing, so the Rgba8Unorm WebGL2 fallback path degrades to 1-sample rather than lying. MSAA is the default look; ?msaa=0 is the escape hatch for weak GPUs.
  • Decal/path edges additionally get a per-vertex edge fade in the shared fragment, so ribbon edges read as painted lines rather than staircases.

The issue asked for AA in the present pass because MSAA looked unportable; it turned out portable with the adapter query. No FXAA was needed and none was added.

Landed — as MSAA, not FXAA, and better than the plan assumed. - `3cb2e12` put 4x MSAA on the scene pass (multisampled HDR/color target, single-sample resolve the present pass samples) with a pause-menu switch; `8f1290d` added 8x. - Portability is handled the honest way: `max_msaa_samples` asks the adapter whether the chosen color format supports multisample resolve and `sample_count` before committing, so the `Rgba8Unorm` WebGL2 fallback path degrades to 1-sample rather than lying. MSAA is the default look; `?msaa=0` is the escape hatch for weak GPUs. - Decal/path edges additionally get a per-vertex `edge` fade in the shared fragment, so ribbon edges read as painted lines rather than staircases. The issue asked for AA in the present pass because MSAA looked unportable; it turned out portable with the adapter query. No FXAA was needed and none was added.
Sign in to join this conversation.
No description provided.