Skip to content

fix(font): disable mipmaps on MSDF font atlases#8996

Closed
willeastcott wants to merge 2 commits into
mainfrom
fix/msdf-no-mipmaps
Closed

fix(font): disable mipmaps on MSDF font atlases#8996
willeastcott wants to merge 2 commits into
mainfrom
fix/msdf-no-mipmaps

Conversation

@willeastcott

Copy link
Copy Markdown
Contributor

Follow-up to #8990 (same issue, #8984).

Problem

After the small-text shimmer fix, a faint horizontal line remained just below thin, closely-spaced stems (e.g. the ll in "Hello") on minified text, flickering as the text moves. Most visible at DPR 1 / non-HiDPI.

Cause

The MSDF atlas texture is loaded with mipmaps (mipmaps: true, FILTER_LINEAR_MIPMAP_LINEAR). Mip levels box-average the R/G/B channels, but median(average) != average(median), so minified text samples a corrupted median — spurious faint coverage at thin features/junctions. This has always been the case, but 2.19's inward erosion clipped those sub-0.5 values; the true-edge threshold from #8990 renders them, so it became visible.

Fix

Load MSDF font atlases with mipmaps: false + minFilter: FILTER_LINEAR (base level only). Bitmap fonts are unaffected and keep their mipmaps. Distance fields are meant to be reconstructed from the base level, and the screen-space screenPxRange AA (#8990) already handles minification.

Testing

  • Repro project (Roboto, fontSize 6) at DPR 1 (worst-case minification, ~5 atlas-texels/px): the flickering line is gone, small text is not visibly worse, and there's no shimmer regression (ink CV ~1.4%). Verified against WebGL2 engine source.
  • Text still renders correctly (no incomplete-texture black); text-element suite (90) and lint pass.

Further addresses #8984.

Mipmapping an MSDF atlas averages the R/G/B distance channels, and
median(average) != average(median), so minified text samples a corrupted
median - faint ghost features (e.g. a thin flickering line below the close
stems of "ll") that surface now the true-edge threshold no longer erodes
them (#8990). Sample the base level only (mipmaps off, FILTER_LINEAR) for
MSDF fonts; bitmap fonts are unaffected and keep their mipmaps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2270.4 KB (+0.4 KB, +0.02%) 584.4 KB (+0.2 KB, +0.03%) 454.4 KB (+0.3 KB, +0.06%)
playcanvas.min.mjs 2267.8 KB (+0.4 KB, +0.02%) 583.6 KB (+0.2 KB, +0.03%) 453.6 KB (−0.1 KB, −0.01%)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts font atlas texture sampling for MSDF fonts to prevent minification artifacts caused by mipmapping MSDF channel data. It implements the fix in the font resource loading path in the framework.

Changes:

  • Detect MSDF fonts during font texture loading.
  • For MSDF font atlas textures, force minFilter to FILTER_LINEAR and disable mipmaps before uploading.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/framework/handlers/font.js Outdated
Comment thread src/framework/handlers/font.js Outdated

@mvaligursky mvaligursky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.
Were we setting mipmaps to false elsewhere originally that should be deleted now?

…U-safe)

Mutating texture.mipmaps after load is a no-op on WebGPU: the setter warns
and keeps the mip chain, and since the sampler has no LOD clamp it still
minifies into those levels, so the artifact persisted on that backend.

Request a non-mipmapped atlas up-front instead - thread the loader's existing
`options` arg through to ResourceHandler.open, have the texture handler merge
it into the texture creation options, and the font handler pass
{ mipmaps: false, minFilter: FILTER_LINEAR } for MSDF fonts. Adds a FontHandler
regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Public API report

This PR changes the public API surface (+3 / −3), per the docs' rules (@ignore / @Private / undocumented are excluded).

Show API diff
-AudioHandler.open(url: string, data: any, asset?: Asset): any
+AudioHandler.open(url: string, data: any, asset?: Asset, options?: any): any
-ResourceHandler.open(url: string, data: any, asset?: Asset): any
+ResourceHandler.open(url: string, data: any, asset?: Asset, options?: any): any
-TextureHandler.open(url: any, data: any, asset: any): any
+TextureHandler.open(url: any, data: any, asset: any, options: any): any

Informational only — this never fails the build.

@willeastcott

Copy link
Copy Markdown
Contributor Author

I'm not a fan of how options are being passed around here. Feels like too much of a hack. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui UI related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants