fix(font): disable mipmaps on MSDF font atlases#8996
Conversation
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>
Build size reportThis PR changes the size of the minified bundles.
|
There was a problem hiding this comment.
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
minFiltertoFILTER_LINEARand disablemipmapsbefore uploading.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mvaligursky
left a comment
There was a problem hiding this comment.
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>
Public API reportThis 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): anyInformational only — this never fails the build. |
|
I'm not a fan of how options are being passed around here. Feels like too much of a hack. Closing. |
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
llin "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, butmedian(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-spacescreenPxRangeAA (#8990) already handles minification.Testing
fontSize6) 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-elementsuite (90) and lint pass.Further addresses #8984.