fix(fonts): msdf atlas settings, and give the font ownership of mirror preload - #2194
Merged
Conversation
An atlas page was created as a plain texture asset with no data of its own, so it inherited the colour-texture defaults: mipmaps on, srgb on, minfilter linear_mip_linear. All three are wrong for a distance field. Mip levels box-average the r/g/b channels and median(average) != average(median), so minified text samples a corrupted median; srgb gamma corrupts it the same way. ReferencedFontHandler already forced the right values on the loaded texture, so output was correct, but it was correcting rather than preventing: the texture was built with an 11-level mip chain and an sRGB format and then changed, and on WebGPU changing mipmaps re-creates the texture because the mip count is baked into the immutable descriptor. The asset data also misreported what the font actually uses, so the inspector showed settings that had no effect. Create the page with mipmaps false, srgb false and minfilter linear instead. The handler keeps forcing them, now as a no-op for generated pages and a safety net for an atlas repointed at the user's own texture. Verified on a local environment, on WebGL2 and WebGPU: the atlas reports mipLevelCount 1 with zero mipmaps transitions and a single recreateImpl (the constructor's own), where before it needed one re-create for mipmaps and another for the format switch. Holds on the legacy-font conversion path too.
A legacy font was a single asset with a single Preload checkbox, and it governed the descriptor and atlas because those were files inside it. Unpacking them into real assets split that into three independent checkboxes, two of which the user has no way to reason about: the font's own load resolves and loads both mirrors, so their flags only ever agreed with the font by luck. Unticking the font while a page stayed ticked left the atlas downloading at boot for a font nobody preloaded, silently. Put the font back in charge. Its preload is written through to the descriptor and every atlas page, on change and on repoint, and their own field is disabled in the inspector while they serve it. Writes only on a real difference, so it is a no-op once they agree and self-heals a repoint or a merge. Ownership is keyed on live references rather than on how the asset was created, so it ends when the link does: clear a picker and the asset gets its field back, and a page also used by something else — a material map, say — keeps it, because that use has its own reason to preload. The field is disabled rather than hidden so the inherited value stays visible. Verified on a local environment: toggling the font drives both mirrors; clearing the json reference re-enables its field and further font toggles no longer reach it; a page shared with a material stays editable. A converted legacy font that was not preloaded produces mirrors that are not preloaded either.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Follow-up to #2190. Two problems with the json/texture assets a client-imported font references.
The atlas was created with colour-texture settings
An atlas page was created as a plain texture asset with no
dataof its own, so it inherited the defaults for colour data:mipmaps: true,srgb: true,minfilter: linear_mip_linear. All three are wrong for a distance field. Mip levels box-average the r/g/b channels andmedian(average) != average(median), so minified text samples a corrupted median; sRGB gamma corrupts it the same way. This is the same reasoning as playcanvas/engine#8997.ReferencedFontHandleralready forced the right values on the loaded texture, so output was correct — but it was correcting rather than preventing. The texture was built with an 11-level mip chain and an sRGB format and then changed, and on WebGPU changingmipmapsre-creates the texture, because the mip count is baked into the immutable descriptor (playcanvas/engine#9003). The asset data also misreported what the font actually uses, so the inspector showed settings that had no effect and a user toggling them changed nothing.Pages are now created with
mipmaps: false, srgb: false, minfilter: linear. The handler keeps forcing them, now as a no-op for generated pages and a safety net for an atlas repointed at a texture of the user's own.The font did not own its mirrors' preload
A legacy font was a single asset with a single Preload checkbox, and it governed the descriptor and atlas because those were files inside it. Unpacking them into real assets split that into three independent checkboxes, two of which a user has no way to reason about: the font's own load resolves and loads both mirrors, so their flags only ever agreed with the font by luck. Unticking the font while a page stayed ticked left the atlas downloading at boot for a font nobody preloaded, with no feedback.
The font's preload is now written through to the descriptor and every atlas page, on change and on repoint, and their own field is disabled in the inspector while they serve it. It writes only on a real difference, so it is a no-op once they agree and self-heals a repoint or a merge.
Ownership is keyed on live references rather than on how the asset was created, so it ends when the link does:
The field is disabled rather than hidden so the inherited value stays visible.
Verification
Driven against a local backend with the editor served locally.
Atlas settings, on WebGL2 and WebGPU: the atlas reports
mipLevelCount: 1with zeromipmapstransitions and a singlerecreateImpl(the constructor's own), where before it needed one re-create for the mip count and another for the format switch. No engine warnings.Legacy conversion: a server-pipeline font set to
preload: falseconverts to mirrors that are alsopreload: false, with the atlas settings applied, 95 glyphs, andnumLevels: 1at runtime.meta.charspreserved.Also confirmed the timing assumption behind all of this: the descriptor and atlas requests start 0.2 ms apart, and the font's own placeholder file is never fetched, so nothing serialises behind it.
A matching backend change, so that a texture uploaded with
noConvertstill gets thumbnails generated, ships separately.