Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d49b9ff
M1: Added Runtime SDF placeholder.
yuechen-li-dev Jan 29, 2026
3275c16
M2. MSDF Cache Scaffold.
yuechen-li-dev Jan 29, 2026
5d9185f
M3: Font works end to end, renders ugly circles.
yuechen-li-dev Jan 29, 2026
75f5369
M4 WIP: Buggy implementation with flickers so far.
yuechen-li-dev Jan 29, 2026
1fa1cd4
It works, but each text spins computer like jet engine.
yuechen-li-dev Jan 29, 2026
2f0b165
Async implementation and various optimizations.
yuechen-li-dev Jan 29, 2026
dd4f8e0
Fixed bad copypaste for SpriteFontAssetCompiler.cs
yuechen-li-dev Jan 30, 2026
0dc668c
Less sloppy Async.
yuechen-li-dev Jan 31, 2026
69e2f15
Revert "Less sloppy Async."
yuechen-li-dev Jan 31, 2026
7ad4caa
Remove runtime SDF bake size plumbing
yuechen-li-dev Feb 1, 2026
b04c959
Merge pull request #4 from yuechen-li-dev/codex/refactor-glyph-sizing…
yuechen-li-dev Feb 1, 2026
d5d9576
Adjusted default bakesize to 64 so SDF is high quality.
yuechen-li-dev Feb 1, 2026
18e227d
Merge branch 'stride3d:master' into Runtime-SDF-Font
yuechen-li-dev Feb 2, 2026
217a8fd
Changed to channel based async design.
yuechen-li-dev Feb 2, 2026
e2e9f09
Channel based refactor for async, introduce interface for easier libr…
yuechen-li-dev Feb 2, 2026
ae2e234
See above message. Wrong commit lol.
yuechen-li-dev Feb 2, 2026
a0058c4
Merge branch 'Runtime-SDF-Font' of https://github.com/yuechen-li-dev/…
yuechen-li-dev Feb 2, 2026
914819d
refactor for future MSDFGeneration.
yuechen-li-dev Feb 3, 2026
048b521
Initial wiring. Need to fix generationPipeline.
yuechen-li-dev Feb 3, 2026
d7d8d5b
Ok, it renders but only as blocks or dots. Debug time.
yuechen-li-dev Feb 3, 2026
dbba408
It works! Mostly. Need to squash some font specific bugs.
yuechen-li-dev Feb 4, 2026
a22eb17
Next try with MsdfGen too.
yuechen-li-dev Feb 4, 2026
e455a49
Comment edits.
yuechen-li-dev Feb 4, 2026
cc65561
clean up gitignore of temp file.
yuechen-li-dev Feb 4, 2026
4d9159c
Move offset logic out of ApplyUploadedGlyph for safety. Change glyphk…
yuechen-li-dev Feb 4, 2026
dd6b53a
Minor changes so VS would have less messages.
yuechen-li-dev Feb 4, 2026
738280e
Cleaned up nullable in FontSystem.
yuechen-li-dev Feb 9, 2026
92e5460
removed unsafe keyword and unneed cast for buffer copy.
yuechen-li-dev Feb 9, 2026
739c69e
Spacing consistancy in pregenerated glyph method.
yuechen-li-dev Feb 9, 2026
0bea15f
Changed the font manager to private.
yuechen-li-dev Feb 9, 2026
6a98305
Remove extra colon.
yuechen-li-dev Feb 9, 2026
a457fd6
Remerged MSDF method into FontManager. Remerged unused overload. Gene…
yuechen-li-dev Feb 10, 2026
3463072
Changed warning from scaffolding to indicate experimental feature. Si…
yuechen-li-dev Feb 10, 2026
113d16d
Moved comment to more accurately depict pipeline for upload step.
yuechen-li-dev Feb 10, 2026
f2930a2
Small refactor for EnsureSdfScheduled to address 0 dimension glyphs a…
yuechen-li-dev Feb 10, 2026
3176a76
Removed debug message from FontCacheManagerMSDF and cleanup for reada…
yuechen-li-dev Feb 10, 2026
dcfae4b
Move oversized glyph dimension check from FontCacheManager to Font fi…
yuechen-li-dev Feb 10, 2026
f8aadd1
Re-added accidentally deleted packer logic.
yuechen-li-dev Feb 10, 2026
0753019
remove unused bool isClosed definition from outline extractor.
yuechen-li-dev Feb 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion sources/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
<PackageVersion Include="Microsoft.Management.Infrastructure" Version="3.0.0-preview.4" />
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="MSDF-Sharp.Core" Version="1.0.2" />
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="Remora.MSDFGen" Version="1.0.0" />
<PackageVersion Include="ServiceWire" Version="5.6.0" />
<PackageVersion Include="SharpDX" Version="4.2.0" />
<PackageVersion Include="SharpDX.D3DCompiler" Version="4.2.0" />
Expand Down Expand Up @@ -123,4 +125,4 @@
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using System.ComponentModel;
using Stride.Core;
using Stride.Core.Annotations;
using Stride.Core.Mathematics;

namespace Stride.Assets.SpriteFont
{
[DataContract("RuntimeSignedDistanceFieldSpriteFontType")]
[Display("Runtime SDF")]
public class RuntimeSignedDistanceFieldSpriteFontType : SpriteFontTypeBase
{
/// <inheritdoc/>
[DataMember(30)]
[DataMemberRange(MathUtil.ZeroTolerance, 2)]
[DefaultValue(20)]
[Display("Default Size")]
public override float Size { get; set; } = 64;

/// <summary>
/// Distance field range/spread (in pixels) used during MSDF generation.
/// </summary>
[DataMember(40)]
[DefaultValue(8)]
[DataMemberRange(1, 64, 1, 4, 0)]
[Display("Pixel Range")]
public int PixelRange { get; set; } = 8;

/// <summary>
/// Extra padding around each glyph inside the atlas (in pixels).
/// </summary>
[DataMember(50)]
[DefaultValue(2)]
[DataMemberRange(0, 16, 1, 2, 0)]
[Display("Padding")]
public int Padding { get; set; } = 2;
}
}
115 changes: 79 additions & 36 deletions sources/engine/Stride.Assets/SpriteFont/SpriteFontAssetCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ protected override void Prepare(AssetCompilerContext context, AssetItem assetIte
UFile assetAbsolutePath = assetItem.FullPath;
var colorSpace = context.GetColorSpace();

var fontTypeSdf = asset.FontType as SignedDistanceFieldSpriteFontType;
if (fontTypeSdf != null)
if (asset.FontType is SignedDistanceFieldSpriteFontType fontTypeSdf)
{
// copy the asset and transform the source and character set file path to absolute paths
var assetClone = AssetCloner.Clone(asset);
Expand All @@ -40,39 +39,54 @@ protected override void Prepare(AssetCompilerContext context, AssetItem assetIte
result.BuildSteps = new AssetBuildStep(assetItem);
result.BuildSteps.Add(new SignedDistanceFieldFontCommand(targetUrlInStorage, assetClone, assetItem.Package));
}
else
if (asset.FontType is RuntimeRasterizedSpriteFontType)
else if (asset.FontType is RuntimeRasterizedSpriteFontType)
{
UFile fontPathOnDisk = asset.FontSource.GetFontPath(result);
if (fontPathOnDisk == null)
{
UFile fontPathOnDisk = asset.FontSource.GetFontPath(result);
if (fontPathOnDisk == null)
{
result.Error($"Runtime rasterized font compilation failed. Font {asset.FontSource.GetFontName()} was not found on this machine.");
result.BuildSteps = new AssetBuildStep(assetItem);
result.BuildSteps.Add(new FailedFontCommand());
return;
}

var fontImportLocation = FontHelper.GetFontPath(asset.FontSource.GetFontName(), asset.FontSource.Style);

result.Error($"Runtime rasterized font compilation failed. Font {asset.FontSource.GetFontName()} was not found on this machine.");
result.BuildSteps = new AssetBuildStep(assetItem);
result.BuildSteps.Add(new ImportStreamCommand { SourcePath = fontPathOnDisk, Location = fontImportLocation });
result.BuildSteps.Add(new RuntimeRasterizedFontCommand(targetUrlInStorage, asset, assetItem.Package));
result.BuildSteps.Add(new FailedFontCommand());
return;
}
else
{
var fontTypeStatic = asset.FontType as OfflineRasterizedSpriteFontType;
if (fontTypeStatic == null)
throw new ArgumentException("Tried to compile a non-offline rasterized sprite font with the compiler for offline resterized fonts!");

// copy the asset and transform the source and character set file path to absolute paths
var assetClone = AssetCloner.Clone(asset);
var assetDirectory = assetAbsolutePath.GetParent();
assetClone.FontSource = asset.FontSource;
fontTypeStatic.CharacterSet = !string.IsNullOrEmpty(fontTypeStatic.CharacterSet) ? UPath.Combine(assetDirectory, fontTypeStatic.CharacterSet): null;
var fontImportLocation = FontHelper.GetFontPath(asset.FontSource.GetFontName(), asset.FontSource.Style);

result.BuildSteps = new AssetBuildStep(assetItem);
result.BuildSteps.Add(new ImportStreamCommand { SourcePath = fontPathOnDisk, Location = fontImportLocation });
result.BuildSteps.Add(new RuntimeRasterizedFontCommand(targetUrlInStorage, asset, assetItem.Package));
}
else if (asset.FontType is RuntimeSignedDistanceFieldSpriteFontType)
{
UFile fontPathOnDisk = asset.FontSource.GetFontPath(result);
if (fontPathOnDisk == null)
{
result.Error($"Runtime SDF font compilation failed. Font {asset.FontSource.GetFontName()} was not found on this machine.");
result.BuildSteps = new AssetBuildStep(assetItem);
result.BuildSteps.Add(new OfflineRasterizedFontCommand(targetUrlInStorage, assetClone, colorSpace, assetItem.Package));
result.BuildSteps.Add(new FailedFontCommand());
return;
}

var fontImportLocation = FontHelper.GetFontPath(asset.FontSource.GetFontName(), asset.FontSource.Style);

result.BuildSteps = new AssetBuildStep(assetItem);
result.BuildSteps.Add(new ImportStreamCommand { SourcePath = fontPathOnDisk, Location = fontImportLocation });
result.BuildSteps.Add(new RuntimeSignedDistanceFieldFontCommand(targetUrlInStorage, asset, assetItem.Package));
}
else
{
if (asset.FontType is not OfflineRasterizedSpriteFontType fontTypeStatic)
throw new ArgumentException("Tried to compile a non-offline rasterized sprite font with the compiler for offline resterized fonts!");

// copy the asset and transform the source and character set file path to absolute paths
var assetClone = AssetCloner.Clone(asset);
var assetDirectory = assetAbsolutePath.GetParent();
assetClone.FontSource = asset.FontSource;
fontTypeStatic.CharacterSet = !string.IsNullOrEmpty(fontTypeStatic.CharacterSet) ? UPath.Combine(assetDirectory, fontTypeStatic.CharacterSet) : null;

result.BuildSteps = new AssetBuildStep(assetItem);
result.BuildSteps.Add(new OfflineRasterizedFontCommand(targetUrlInStorage, assetClone, colorSpace, assetItem.Package));
}
}

internal class OfflineRasterizedFontCommand : AssetCommand<SpriteFontAsset>
Expand All @@ -88,15 +102,13 @@ public OfflineRasterizedFontCommand(string url, SpriteFontAsset description, Col
public override IEnumerable<ObjectUrl> GetInputFiles()
{
var asset = Parameters;
var fontTypeStatic = asset.FontType as OfflineRasterizedSpriteFontType;
if (fontTypeStatic != null)
if (asset.FontType is OfflineRasterizedSpriteFontType fontTypeStatic)
{
if (File.Exists(fontTypeStatic.CharacterSet))
yield return new ObjectUrl(UrlType.File, fontTypeStatic.CharacterSet);
}

var fontTypeSdf = asset.FontType as SignedDistanceFieldSpriteFontType;
if (fontTypeSdf != null)
if (asset.FontType is SignedDistanceFieldSpriteFontType fontTypeSdf)
{
if (File.Exists(fontTypeSdf.CharacterSet))
yield return new ObjectUrl(UrlType.File, fontTypeSdf.CharacterSet);
Expand All @@ -118,7 +130,7 @@ protected override Task<ResultStatus> DoCommandOverride(ICommandContext commandC
{
staticFont = OfflineRasterizedFontCompiler.Compile(FontDataFactory, Parameters, colorspace == ColorSpace.Linear);
}
catch (FontNotFoundException ex)
catch (FontNotFoundException ex)
{
commandContext.Logger.Error($"Font [{ex.FontName}] was not found on this machine.", ex);
return Task.FromResult(ResultStatus.Failed);
Expand Down Expand Up @@ -190,9 +202,9 @@ public RuntimeRasterizedFontCommand(string url, SpriteFontAsset description, IAs
protected override Task<ResultStatus> DoCommandOverride(ICommandContext commandContext)
{
var dynamicFont = FontDataFactory.NewDynamic(
Parameters.FontType.Size, Parameters.FontSource.GetFontName(), Parameters.FontSource.Style,
Parameters.FontType.AntiAlias, useKerning:false, extraSpacing:Parameters.Spacing, extraLineSpacing:Parameters.LineSpacing,
defaultCharacter:Parameters.DefaultCharacter);
Parameters.FontType.Size, Parameters.FontSource.GetFontName(), Parameters.FontSource.Style,
Parameters.FontType.AntiAlias, useKerning: false, extraSpacing: Parameters.Spacing, extraLineSpacing: Parameters.LineSpacing,
defaultCharacter: Parameters.DefaultCharacter);

var assetManager = new ContentManager(MicrothreadLocalDatabases.ProviderService);
assetManager.Save(Url, dynamicFont);
Expand All @@ -201,6 +213,37 @@ protected override Task<ResultStatus> DoCommandOverride(ICommandContext commandC
}
}

internal class RuntimeSignedDistanceFieldFontCommand : AssetCommand<SpriteFontAsset>
{
public RuntimeSignedDistanceFieldFontCommand(string url, SpriteFontAsset description, IAssetFinder assetFinder)
: base(url, description, assetFinder)
{
}

protected override Task<ResultStatus> DoCommandOverride(ICommandContext commandContext)
{
commandContext.Logger.Warning("Runtime SDF font is currently an experimental feature.");

var runtimeSdfType = (RuntimeSignedDistanceFieldSpriteFontType)Parameters.FontType;

var sdfFont = FontDataFactory.NewRuntimeSignedDistanceField(
runtimeSdfType.Size,
Parameters.FontSource.GetFontName(),
Parameters.FontSource.Style,
runtimeSdfType.PixelRange,
runtimeSdfType.Padding,
useKerning: false,
Copy link
Collaborator

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

Copy link
Author

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.

https://github.com/stride3d/stride/blob/master/sources/engine/Stride.Assets/SpriteFont/SpriteFontAssetCompiler.cs

And kerning was set as off for it, so I just copied.

extraSpacing: Parameters.Spacing,
extraLineSpacing: Parameters.LineSpacing,
defaultCharacter: Parameters.DefaultCharacter);

var assetManager = new ContentManager(MicrothreadLocalDatabases.ProviderService);
assetManager.Save(Url, sdfFont);

return Task.FromResult(ResultStatus.Successful);
}
}

/// <summary>
/// Proxy command which always fails, called when font is compiled with the wrong assets
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static SpriteFontAsset Create()
FontSource = new SystemFontProvider(),
FontType = new OfflineRasterizedSpriteFontType()
{
CharacterRegions = { new CharacterRegion(' ', (char)127) }
CharacterRegions = { new CharacterRegion(' ', (char)127) }
},
};
}
Expand Down Expand Up @@ -42,7 +42,7 @@ public override SpriteFontAsset New()
}
}

public class SignedDistanceFieldSpriteFontFactory: AssetFactory<SpriteFontAsset>
public class SignedDistanceFieldSpriteFontFactory : AssetFactory<SpriteFontAsset>
{
public static SpriteFontAsset Create()
{
Expand All @@ -61,4 +61,21 @@ public override SpriteFontAsset New()
return Create();
}
}

public class RuntimeSignedDistanceFieldSpriteFontFactory : AssetFactory<SpriteFontAsset>
{
public static SpriteFontAsset Create()
{
return new SpriteFontAsset
{
FontSource = new SystemFontProvider(),
FontType = new RuntimeSignedDistanceFieldSpriteFontType(),
};
}

public override SpriteFontAsset New()
{
return Create();
}
}
}
Loading