Skip to content

Setting Up MSDF Fonts

Israel Calebe edited this page Sep 21, 2026 · 1 revision

This tutorial assumes you have completed setting up your first project and setting up compiled resource stores, and that you are already familiar with setting up fonts the usual way.

In this tutorial, you will learn how to generate MSDF font atlases and render them with libmsdf:

Unlike the bitmap fonts osu!framework ships with, an MSDF font stores glyph shapes as distance fields rather than pre-rasterised pixels. Coverage is reconstructed in the fragment shader, so the same atlas stays sharp at 12px and at 400px. The trade-off is that you generate the atlas with a different tool and register it in a different store.

Installing the package

Add the package to your game project:

dotnet add package kajarlabs.osu.Framework.MsdfTextRendering

Generating the MSDF atlas

An MSDF font is two files: a PNG atlas and a JSON metadata file. Both are produced by msdf-atlas-gen, which you can download from its releases page.

Run it once per weight and style you intend to use:

msdf-atlas-gen -font MyAwesomeFont-Regular.ttf ^
               -type msdf ^
               -format png ^
               -size 48 ^
               -pxrange 4 ^
               -yorigin top ^
               -imageout MyAwesomeFont-Regular.png ^
               -json MyAwesomeFont-Regular.json

The flags that matter, and what breaks if you get them wrong:

Option Description If you change it
-type msdf Generates a multi-channel signed distance field sdf or psdf produce a field the shader cannot read
-format png Stores the atlas as a PNG texture Other formats are not loaded by the font store
-size 48 Em size used for generation Lower values lose fine detail; higher values grow the atlas
-pxrange 4 Distance range stored around glyph edges Too low rounds off sharp corners at large sizes
-yorigin top Top-left origin, matching osu!framework's coordinates Omitting it renders every glyph upside down

Warning: -pxrange is also written into the JSON as distanceRange, and the shader reads it from there. Do not edit one without the other.

The output is a pair of files:

MyAwesomeFont-Regular.png
MyAwesomeFont-Regular.json

The JSON contains the glyph metrics, atlas bounds, plane bounds, kerning pairs and the distance range. Both files are required; the store will not load an atlas without its metadata.

File naming

File names follow the same pattern osu!framework uses for FontUsage, so the store can resolve a family, a weight and an italic flag to a file:

FontUsage.Family FontUsage.Weight FontUsage.Italics File name
MyAwesomeFont "Light" false MyAwesomeFont-Light
MyAwesomeFont "Bold" true MyAwesomeFont-BoldItalic
MyAwesomeFont null false MyAwesomeFont
MyAwesomeFont null true MyAwesomeFont-Italic

Place the pairs inside your Resources folder, for example in MyAwesomeProject.Game/Resources/Typefaces/:

Resources/
└── Typefaces/
    └──MyAwesomeFont
       ├── MyAwesomeFont-Regular.png
       ├── MyAwesomeFont-Regular.json
       ├── MyAwesomeFont-Bold.png
       └── MyAwesomeFont-Bold.json

Adding the font to the MSDF font store

MSDF fonts do not go into the global Fonts store. They live in an MsdfFontStore, which owns the shader and the glyph cache, and which you cache so that MsdfSpriteText can resolve it through dependency injection.

namespace MyAwesomeProject.Game
{
    public partial class MyAwesomeGame : osu.Framework.Game
    {
        private DependencyContainer dependencies = null!;

        protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
            => dependencies = new DependencyContainer(parent);

        [BackgroundDependencyLoader]
        private void load()
        {
            // Add the DLL resource store of this game project into the global resources store.
            Resources.AddStore(new DllResourceStore(@"MyAwesomeProject.dll"));

            // Register the shaders and resources libmsdf needs.
            MsdfTextRendering.CreateCapabilities(Resources);

            // Create the store that resolves and caches MSDF glyphs.
            var msdfFonts = new MsdfFontStore(Host.Renderer, Shaders);

            // Add one entry per weight/style, by family and weight.
            msdfFonts.AddFont(Resources, @"MyAwesomeFont", @"Regular");
            msdfFonts.AddFont(Resources, @"MyAwesomeFont", @"Bold");

            // Cache it so MsdfSpriteText can find it.
            dependencies.CacheAs(msdfFonts);
        }
    }
}

CreateCapabilities must run before the store is constructed: it is what registers the MSDF shaders with the renderer.

Warning: Certain font names may end up getting mangled by the .NET embedded resource machinery. For instance, numbers may be prefixed with underscores. A safe bet is to stick with letters. See this explanation for more detail.


Using the font in a sprite text

MsdfSpriteText takes the same FontUsage as SpriteText, so the family, weight and italic flag you pass must match the file names from the table above:

new MsdfSpriteText
{
    Text = @"Hello, MSDF!",
    Font = new FontUsage(@"MyAwesomeFont", size: 40f, weight: @"Regular"),
}

Because MSDF glyphs are reconstructed per fragment, the same atlas can be scaled up without blurring. Scaling the drawable produces the same result as increasing size:

new MsdfSpriteText
{
    Text = @"Still sharp at 8x.",
    Font = new FontUsage(@"MyAwesomeFont", size: 40f),
    Scale = new Vector2(8),
}

Layout behaviour is unchanged from SpriteText: Truncate, MaxWidth, AllowMultiline, Shadow and fixed-width all work the same way, because libmsdf plugs into the framework's existing text builder rather than replacing it.


Troubleshooting

Symptom Likely cause
Every glyph is upside down The atlas was generated without -yorigin top
Text renders as blank space Only the PNG or only the JSON was copied into the resource store
Corners look rounded when scaled up -pxrange too low for the size you are rendering at
Glyphs overlap or sit on top of each other Font metrics missing from the JSON; regenerate the atlas
Nothing renders and the log mentions shaders CreateCapabilities was not called, or was called after the store

MSDF is not a free upgrade for every case. At small, static sizes a bitmap font is cheaper and can look crisper, because it was rasterised for exactly that size with hinting. Reach for MSDF when the same typeface has to survive zooming, animated scaling, or a wide range of sizes from one atlas.