-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: Dynamic Runtime Rasterized MSDF Sprite Font #3055
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
@dotnet-policy-service agree |
|
We already have an offline SDF option - but I understand that the reason you implemented this feature is because non-latin character sets tend to be very large and require a high resolution. Offline SDF we currently have wouldn't work because it would have to cover multiple thousands of characters with a fairly high resolution, potentially blowing out build sizes. Definitely cool to see, now, is there a particular reason why this PR specifically sidesteps using most of the logic already setup for offline SDF ? Also, as it stands, the limitations seems a bit like a show stopper. Our engine already has enough quirk as it is, best to fix those before we merge this in |
|
Thanks for the thoughtful review - I agree with the motivation you outlined (CJK + large character sets + resolution/build-size constraints is the core reason for doing this at runtime). Why this approach doesn’t reuse most of the offline SDF pipelineStride’s current offline SDF path is essentially a font compiler wrapper around an external tool ( For runtime MSDF, we need a very different shape of solution:
So there isn’t much logic to reuse without effectively rewriting the offline pipeline into a reusable in-process library anyway, which is what this PR does, but oriented around runtime constraints. This approach lets us keep the pipeline cross-platform and embeddable (no msdfgen.exe / no DirectWrite-only path), and it would cleanly resolve #3021 (because no platform specific font compilation is required, as atlas generation happens at runtime using managed portable C# libraries, rather than an external Windows-specific toolchain). Finally, because of the modular architecture, this approach unlocks future extensions, such as MTSDF alpha map based effects (outlines, glow, rich text effects) which can be done by evolving just the rasterizer/shader instead of reworking the full asset pipeline again. To address notes and limitations I listed:
This is from release version of stride to demonstrate these artifacts on offline SDF fonts: |
|
Thank you for the clarification, the source for msdfgen isn't part of this repository, but we do maintain our own version through https://github.com/stride3d/msdfgen - which could have been used as the base for a c# port or integration, but doesn't matter too much now, even more so with the fixes you've introduced. I'll try to find the time to take a look at it this week end, thanks for the work. |
Eideren
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good, I was able to repro the missing characters when building a game, haven't had time to look into it yet, but we can't quite merge before this is fixed.
Still, I have a fair amount of things to run by you.
| Parameters.FontSource.Style, | ||
| runtimeSdfType.PixelRange, | ||
| runtimeSdfType.Padding, | ||
| useKerning: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why kerning is always off, on this call site and all other usage I could find
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initial basis of this project is an exact copy of the runtime rasterized font that was already in the system.
And kerning was set as off for it, so I just copied.
| // M1 NOTE: | ||
| // This is a scaffolding step. We serialize a functional font so the pipeline works end-to-end. | ||
| // In M3/M4, this will be replaced by a real Runtime MSDF font object. | ||
| commandContext.Logger.Warning("Runtime SDF font is currently scaffolded in M1 (temporary). It will behave like a runtime raster font until the MSDF runtime generator is implemented."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems either unrelated or too loosely documented, what do you mean by M1 and co. ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are personal milestones for me to keep track of project development. Since MVP Is complete, this can and will be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just had the warning to label it as an experimental feature.
| /// Dummy MSDF rasterizer that generates simple test patterns. | ||
| /// Use this to isolate pipeline issues from MSDF generation issues. | ||
| /// </summary> | ||
| public sealed class DummyTestRasterizer : IGlyphMsdfRasterizer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you set it up as a test in Stride.Graphics.Tests ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two debug rasterizers are for visual debug feedbacks. I can set up some unit test to test outline extraction/MSDF generation functionality without having to manually switch out rasterizers.
| { | ||
| if (contour.Edges.Count == 0) continue; | ||
|
|
||
| EdgeColor[] colors = { EdgeColor.CYAN, EdgeColor.MAGENTA, EdgeColor.YELLOW }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a static array, or a stack allocated one
| foreach (var c in colors) | ||
| { | ||
| if (c != prevColor && c != nextColor) | ||
| { | ||
| chosen = c; | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You only need to test the two other colors afaict ?
sources/engine/Stride.Graphics/Font/RuntimeSignedDistanceFieldSpriteFont.cs
Outdated
Show resolved
Hide resolved
sources/engine/Stride.Graphics/Font/RuntimeSignedDistanceFieldSpriteFont.cs
Show resolved
Hide resolved
sources/engine/Stride.Graphics/Font/RuntimeSignedDistanceFieldSpriteFont.cs
Outdated
Show resolved
Hide resolved
sources/engine/Stride.Graphics/Font/RuntimeSignedDistanceFieldSpriteFont.cs
Outdated
Show resolved
Hide resolved
| // Prefer outline-based MSDF generation when available. | ||
| // We still rely on the bitmap path to populate glyph metrics today, but MSDF uses the outline. | ||
| if (FontManager != null && | ||
| FontManager.TryGetGlyphOutline(FontName, Style, Size, key.C, out var outline, out _)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we warn users somewhere that the font they chose does not provide glyph outline, maybe as part of the build steps for a Runtime SDF asset ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was something I was unsure about when coding this, but it really looks like all fonts would always have a glyph outline and the extraction process is fairly direct. I will set up some unit tests for this, but ideally, I would like to see if it is possible to include an open source .ttf font file (I would suggest Noto Sans CJK Regular for coverage) so the unit tests aren't reliant on checking what font does the tester have installed on their computer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, we have a test setup for japanese over here
https://github.com/stride3d/stride/blob/master/sources/engine/Stride.Graphics.Tests/TestDynamicSpriteFontJapanese.cs
Which uses a font bundled with the repo:
https://github.com/stride3d/stride/blob/master/sources/data/tests/Fonts/SourceHanSans-Light.otf
Feel free to look at how that one is setup to build yours, as long as the font is compatible with MIT it'll be fine.
Yeah, my findings on this issue are that the atlas generation/upload for runtime SDF is async, and the existing font thumbnail compiler is not async aware and since it is only called once to generate the thumbnail, and since the fonts aren't ready when it is called, it returns nothing. I'm not sure what the clean way to resolve it would be, and I've experimented a lot on this. I might be a little busy this weekend, so I'll address these gradually over the next week. Thanks for the thorough review. |
…ral cleanup of FontManager.
…mplified some expression for readability with pattern matching.
…nd bug around fallback logic on full channel queue.
…le for efficiency.


PR Details
This PR adds an experimental runtime MSDF font path to Stride’s sprite font system.
Introduction
Stride’s current sprite font rasterization options each have trade-offs. Dynamically generating MSDF glyphs on-demand is the common approach in modern engines (e.g., Unity/Unreal): it combines the benefits of Stride’s runtime rasterized fonts (large character sets like CJK) with offline SDF/MSDF advantages (smooth edges at high resolution, easy scaling) with minimal downsides.
Implementation
Dynamic MSDF generation differs substantially from Stride’s existing static SDF pipeline.
Rasterizer backends
Two managed, cross-platform MSDFGen ports are included (no P/Invoke):
Also included:
Notes / limitations
Testing
Core files:
RuntimeSignedDistanceFieldSpriteFont.csSharpFontOutlineExtractor.csMsdfGenCoreRasterizer.cs(MSDFGen-Sharp backend)RemoraMsdfRasterizer.csThanks. This took a lot longer than I thought it would.
Comparison
Existing Runtime Rasterized Font:


New Runtime MSDF Font:
Related Issue
#2584
Types of changes
Checklist