Font rendering rework

Handle fonts unscaled
This commit is contained in:
Simon Lübeß
2026-08-01 22:55:35 +02:00
parent ab39386b27
commit 2f5baa5f5e
3 changed files with 466 additions and 74 deletions
+435 -55
View File
@@ -24,29 +24,44 @@ namespace GlitchyEngine.Renderer.Text
public int32 Width, Height;
public double TranslationX, TranslationY;
//public double Scale;
/// How many pixels we have to move the pen after drawing this glyph.
/**
* The size of the quad that has to be drawn for this glyph. (In em)
* Multiply with the desired font size (in world units) to get the actual size of the quad.
* @remarks The quad is larger than the visible ink of the glyph, because the box also contains the MSDF-distance range and the padding.
*/
public double2 QuadSizeEm;
public Shape* Shape;
/// How far we have to move the pen after drawing this glyph. (In em)
public float Advance;
// Aligns the image of the glyph with the baseline
/// Aligns the image of the glyph with the baseline: offset from the baseline to the bottom edge of the quad. (In em, Y points up)
public float AdjustToBaseLine;
// Aligns the image of the glyph with the pen
/// Aligns the image of the glyph with the pen: offset from the pen position to the left edge of the quad. (In em)
public float AdjustToPen;
public bool IsBitmap;
public bool IsCalculated = false;
public bool IsRendered = false;
public msdfgen.Range boxRange;
public double boxScale;
public msdfgen.Rectangle boxRect;
public double2 boxTranslate;
public Padding boxOuterPadding;
}
// TODO: I don't understand why freeing the face and hb_font sometimes results in an access violation...
internal FT_Face _face;//TODO!: ~ FreeType.Done_Face(_face);
internal hb_font_t* _harfBuzzFont;//TODO:!! ~ hb_font_destroy(_);
internal FT_Face _face;// ~ FreeType.Done_Face(_face);//TODO!: ~ FreeType.Done_Face(_face);
internal hb_font_t* _harfBuzzFont;// ~ hb_font_destroy(_);//TODO:!! ~ hb_font_destroy(_);
internal hb_face_t* _harfBuzzFace;// ~ hb_face_destroy(_);//TODO:!! ~ hb_font_destroy(_);
private Font _fallback ~ _?.ReleaseRef();
internal uint32 _fontSize;
//internal uint32 _fontSize;
private int32 _faceIndex;
private bool _hasColor;
@@ -62,12 +77,27 @@ namespace GlitchyEngine.Renderer.Text
private SamplerState _sampler ~ _.ReleaseRef();
/// How much we have to scale the geometry to fit it into our desired pixels
private double _geometryScaler;
private double _geometryScale;
internal double _range = 4.0;
/// The scale at which the glyphs are rasterized into the atlas. (In pixels per em)
internal double _atlasPixelsPerEm;
/// The space between two lines.
internal double _linespace;
/// The width of the MSDF-distance range that is baked into the atlas. (In atlas pixels)
/// The MSDF-shader needs this value in order to determine how many pixels on screen the distance range covers.
internal double _atlasPxRange;
///
// Font Units
///
/// The number of units per em. This is the precision that the font has been designed with internally.
internal double _unitsPerEm;
/// The space between two lines when rendering horizontal text. (In Font Units (em))
internal double _linespaceEmHorizontal;
/// The space between two lines when rendering vertical text. (In Font Units (em))
internal double _linespaceEmVertical;
/**
* Gets or sets the fallback Font for this Font.
@@ -114,14 +144,14 @@ namespace GlitchyEngine.Renderer.Text
return (double(value) / 64.0);
}
public this(String fontPath, uint32 fontSize, bool hasColor = true, char32 firstChar = '\0', uint32 charCount = 128, int32 faceIndex = 0)
public this(String fontPath, bool hasColor = true, char32 firstChar = '\0', uint32 charCount = 128, int32 faceIndex = 0)
{
Debug.Profiler.ProfileResourceFunction!();
// Set default sampler
Sampler = null;
_fontSize = fontSize;
//_fontSize = fontSize;
_faceIndex = faceIndex;
_hasColor = hasColor;
@@ -135,7 +165,9 @@ namespace GlitchyEngine.Renderer.Text
{
Debug.Profiler.ProfileResourceScope!("Freetype.Set_Pixel_Sizes");
FT_Error res = FreeType.Set_Pixel_Sizes(_face, 0, _fontSize);
// We must set a size for Harfbuzz to work. But we actually don't care about pixels (because we use MSDFGen)
// So we just use units per EM
FT_Error res = FreeType.Set_Char_Size(_face, 0, _face.units_per_EM, 0, 0);
Log.EngineLogger.Assert(res.Success, scope $"Set_Pixel_Sizes failed({(int)res}): {res}");
}
@@ -144,15 +176,29 @@ namespace GlitchyEngine.Renderer.Text
Debug.Profiler.ProfileResourceScope!("hb_ft_font_create_referenced");
_harfBuzzFont = hb_ft_font_create_referenced(_face);
hb_font_set_scale(_harfBuzzFont, (.)fontSize * 64, (.)fontSize * 64);
_harfBuzzFace = hb_font_get_face(_harfBuzzFont);
_unitsPerEm = hb_face_get_upem(_harfBuzzFace);
hb_font_set_scale(_harfBuzzFont, (.)_unitsPerEm, (.)_unitsPerEm);
/*hb_font_extents_t horizontalExtends = .();
hb_font_get_h_extents(_harfBuzzFont, &horizontalExtends);
hb_font_extents_t verticalExtends = .();
hb_font_get_v_extents(_harfBuzzFont, &verticalExtends);
_linespaceEmHorizontal = horizontalExtends.ascender - horizontalExtends.descender + horizontalExtends.line_gap;
_linespaceEmVertical = verticalExtends.ascender - verticalExtends.descender + verticalExtends.line_gap;*/
}
double unitsPerEm = F26Dot6ToDouble(_face.units_per_EM);
_geometryScaler = _fontSize / unitsPerEm;
// TODO: FontScale != font size
const double fontScale = 1.0;
_range = 4.0 / _geometryScaler;
// geometryScale is an MSDFgen specific value
// MSDFgen works directly with the freetype face thus we use the value form the freetype face there,
// even though it should always be as hb_face_get_upem.
_geometryScale = fontScale / _face.units_per_EM;
_linespace = ((_face.size.metrics.ascender - _face.size.metrics.descender) / 64);
_linespaceEmHorizontal = _face.height * _geometryScale;
GlyphDescriptor nullDesc = new GlyphDescriptor(){Font = this};
nullDesc.GlyphIndex = FreeType.Get_Char_Index(_face, '\0');
@@ -161,7 +207,13 @@ namespace GlitchyEngine.Renderer.Text
LoadGlyphs(firstChar, charCount);
//TestMSDF();
// MSDF-Stuff:
// metrics.emSize = font->face->units_per_EM * _geometryScaler;
// metrics.ascenderY = font->face->ascender * _geometryScaler;
// metrics.descenderY = font->face->descender * _geometryScaler;
// metrics.lineHeight = font->face->height * _geometryScaler;
// metrics.underlineY = font->face->underline_position * _geometryScaler;
// metrics.underlineThickness = font->face->underline_thickness * _geometryScaler;
}
public void LoadGlyphs(char32 firstChar, uint32 charCount)
@@ -333,7 +385,7 @@ namespace GlitchyEngine.Renderer.Text
UpdateAtlas();
}
int3 PrepareAtlas()
int3 PrepareAtlas(GlyphAttributes attribs)
{
Debug.Profiler.ProfileResourceFunction!();
@@ -354,7 +406,7 @@ namespace GlitchyEngine.Renderer.Text
continue;
}
if(!Calculate(ref desc))
if(!CalculateGlyphBox(ref desc, attribs))
continue;
const int32 border = 1;
@@ -420,8 +472,46 @@ namespace GlitchyEngine.Renderer.Text
{
Debug.Profiler.ProfileResourceFunction!();
// TODO: Default values from MSDF-Atlas-Gen
// TODO:
// Via tryPack argument:
double scale = 32.0;
// Via Atlas Packer constructor:
msdfgen.Range unitRange = .(0.0, 0.0);
msdfgen.Range pxRange = .(-1.0, 1.0);
Padding innerUnitPadding = .(0);
Padding outerUnitPadding = .(0);
Padding innerPxPadding = .(0);
Padding outerPxPadding = .(0);
double miterLimit = 1.0;
bool2 pxAlignOrigin = .(false, true);
GlyphAttributes attribs = .();
attribs.Scale = scale;
attribs.Range = unitRange + pxRange / scale;
attribs.InnerPadding = innerUnitPadding + innerPxPadding / scale;
attribs.OuterPadding = outerUnitPadding + outerPxPadding / scale;
attribs.MiterLimit = miterLimit;
attribs.PxAlignOrigin = pxAlignOrigin;
// Derive the atlas metrics that the renderer and the MSDF-shader need from the attributes we just rasterize with,
// so that they stay in sync if scale or range are ever changed.
_atlasPixelsPerEm = attribs.Scale;
_atlasPxRange = (attribs.Range.Upper - attribs.Range.Lower) * attribs.Scale;
// TODO: Ohne die hier geht nichts!
/*attribs.scale = scale;
attribs.range = unitRange+pxRange/scale;
attribs.innerPadding = innerUnitPadding+innerPxPadding/scale;
attribs.outerPadding = outerUnitPadding+outerPxPadding/scale;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;*/
int3 oldAtlasSize = _atlasSize;
_atlasSize = PrepareAtlas();
_atlasSize = PrepareAtlas(attribs);
if(any(_atlasSize != oldAtlasSize))
{
@@ -439,6 +529,8 @@ namespace GlitchyEngine.Renderer.Text
desc.Usage = .Default;
_atlas = new Texture2D(desc);
_atlas.Identifier = scope $"Font Atlas - {StringView(_face.family_name)} {StringView(_face.style_name)}";
_atlas.SamplerState = _sampler;
if(oldAtlas != null)
@@ -466,7 +558,19 @@ namespace GlitchyEngine.Renderer.Text
}
}
bool Calculate(ref GlyphDescriptor desc)
struct GlyphAttributes
{
public double Scale;
public msdfgen.Range Range;
public msdfgen.Padding InnerPadding, OuterPadding;
public double MiterLimit;
public bool2 PxAlignOrigin;
}
/**
* Calculates the bounding box in pixels around the
*/
bool CalculateGlyphBox(ref GlyphDescriptor desc, GlyphAttributes glyphAttributes)
{
Debug.Profiler.ProfileResourceFunction!();
@@ -474,30 +578,190 @@ namespace GlitchyEngine.Renderer.Text
double advance = 0;
Shape shape;
//Shape* shape;
//defer { msdfgen.DestroyShape(shape); }
{
Debug.Profiler.ProfileResourceScope!("msdfgen.LoadGlyph");
if(!msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance) || !shape.Validate())
// TODO: load in LoadGlyphs-Method, not here!
if (desc.Shape == null && !msdfgen.LoadGlyph(out desc.Shape, ref _face, desc.GlyphIndex, .FONT_SCALING_NONE, out advance))
{
return false;
}
if(!Shape.Validate(desc.Shape))
{
return false;
}
}
desc.Advance = (float)(advance * _geometryScaler);
desc.Advance = (float)(advance * _geometryScale);
//shape.OrientContours();
// No Skia, no ResolveShapeGeometry :/
//if (preprocessGeometry)
//{
// Debug.Profiler.ProfileResourceScope!("msdfgen.ResolveShapeGeometry");
// msdfgen.ResolveShapeGeometry(shape);
//}
Shape.Normalize(desc.Shape);
var bounds = Shape.GetBounds(desc.Shape);
// TODO: Save shapes and bounds in desc?
//if (!preprocessGeometry)
{
Debug.Profiler.ProfileResourceScope!("msdfgen.ResolveShapeGeometry");
msdfgen.ResolveShapeGeometry(shape);
// TODO!
// Determine if shape is winded incorrectly and reverse it in that case
//double2 outerPoint = .(bounds.Left - (bounds.Right - bounds.Left) - 1.0, bounds.Bottom - (bounds.Top - bounds.Bottom) - 1.0);
//if (msdfgen::SimpleTrueShapeDistanceFinder::oneShotDistance(shape, outerPoint) > 0) {
// for (msdfgen::Contour &contour : shape.contours)
// contour.reverse();
//}
}
shape.Normalize();
// TODO: Ohne korrekten _geometryScaler läuft hier garnüscht.
double scale = glyphAttributes.Scale * _geometryScale;
msdfgen.Range range = glyphAttributes.Range / _geometryScale;
Padding fullPadding = (glyphAttributes.InnerPadding + glyphAttributes.OuterPadding) / _geometryScale;
var bounds = shape.GetBounds();
desc.boxRange = range;
desc.boxScale = scale;
/*msdfgen.Range boxRange = range;
double boxScale = scale;
msdfgen.Rectangle boxRect;
double2 boxTranslate;
Padding boxOuterPadding;*/
if (bounds.Left < bounds.Right && bounds.Bottom < bounds.Top)
{
double l = bounds.Left, b = bounds.Bottom, r = bounds.Right, t = bounds.Top;
l += range.Lower;
b += range.Lower;
r -= range.Lower;
t -= range.Lower;
if (glyphAttributes.MiterLimit > 0)
{
// TODO: Does this have to be a static function?
Shape.BoundMiters(desc.Shape, ref l, ref b, ref r, ref t, -range.Lower, glyphAttributes.MiterLimit, 1);
}
l -= fullPadding.Left;
b -= fullPadding.Bottom;
r += fullPadding.Right;
t += fullPadding.Top;
if (glyphAttributes.PxAlignOrigin.X)
{
int sl = (int) Math.Floor(scale * l - 0.5);
int sr = (int) Math.Ceiling(scale * r + 0.5);
desc.boxRect.Width = sr - sl;
desc.boxTranslate.X = -sl/scale;
}
else
{
double w = scale*(r-l);
desc.boxRect.Width = (int) Math.Ceiling(w) + 1;
desc.boxTranslate.X = -l + 0.5 * (desc.boxRect.Width - w) / scale;
}
if (glyphAttributes.PxAlignOrigin.Y)
{
int sb = (int) Math.Floor(scale * b - 0.5);
int st = (int) Math.Ceiling(scale * t + 0.5);
desc.boxRect.Height = st-sb;
desc.boxTranslate.Y = -sb/scale;
}
else
{
double h = scale * (t - b);
desc.boxRect.Height = (int) Math.Ceiling(h) + 1;
desc.boxTranslate.Y = -b + 0.5 * (desc.boxRect.Height - h) / scale;
}
desc.boxOuterPadding = glyphAttributes.Scale * glyphAttributes.OuterPadding;
}
else
{
desc.boxRect.Width = 0;
desc.boxRect.Height = 0;
desc.boxTranslate = 0;
}
desc.Width = (int32)desc.boxRect.Width;
desc.Height = (int32)desc.boxRect.Height;
// The glyph is rasterized with glyphAttributes.Scale pixels per em, so dividing the pixel size of the box
// by that factor gives us the size of the quad in em. (Empty shapes have a zero-sized box and thus a zero-sized quad.)
desc.QuadSizeEm = .((double)desc.boxRect.Width / glyphAttributes.Scale,
(double)desc.boxRect.Height / glyphAttributes.Scale);
// The projection maps a coordinate c to (c + boxTranslate) * scale, so pixel 0 of the box corresponds to
// c = -boxTranslate. That is the left/bottom edge of the box in font units, _geometryScale turns it into em.
desc.AdjustToBaseLine = (float)(-desc.boxTranslate.Y * _geometryScale);
desc.AdjustToPen = (float)(-desc.boxTranslate.X * _geometryScale);
return true;
}
/**
* Calculates the bounding box in pixels around the
*/
bool CalculateGlyphBoxOld(ref GlyphDescriptor desc, GlyphAttributes attribs)
{
Debug.Profiler.ProfileResourceFunction!();
// prepare shape
double advance = 0;
Shape* shape;
defer { msdfgen.DestroyShape(shape); }
{
Debug.Profiler.ProfileResourceScope!("msdfgen.LoadGlyph");
if(!msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, .FONT_SCALING_NONE, out advance) || !Shape.Validate(shape))
{
return false;
}
}
desc.Advance = (float)(advance * _geometryScale);
// No Skia, no ResolveShapeGeometry :/
//if (preprocessGeometry)
//{
// Debug.Profiler.ProfileResourceScope!("msdfgen.ResolveShapeGeometry");
// msdfgen.ResolveShapeGeometry(shape);
//}
Shape.Normalize(shape);
var bounds = Shape.GetBounds(shape);
// TODO: Save shapes and bounds in desc?
//if (!preprocessGeometry)
{
// TODO!
// Determine if shape is winded incorrectly and reverse it in that case
//double2 outerPoint = .(bounds.Left - (bounds.Right - bounds.Left) - 1.0, bounds.Bottom - (bounds.Top - bounds.Bottom) - 1.0);
//if (msdfgen::SimpleTrueShapeDistanceFinder::oneShotDistance(shape, outerPoint) > 0) {
// for (msdfgen::Contour &contour : shape.contours)
// contour.reverse();
//}
}
/*
// prepare projection
double width;
@@ -506,6 +770,22 @@ namespace GlitchyEngine.Renderer.Text
double translationX;
double translationY;
/*
#define DEFAULT_SIZE 32.0
#define DEFAULT_ANGLE_THRESHOLD 3.0
#define DEFAULT_MITER_LIMIT 1.0
#define DEFAULT_PIXEL_RANGE 2.0
#define SDF_ERROR_ESTIMATE_PRECISION 19
*/
//double scale = glyphAttributes.scale*geometryScale;
double scale = _geometryScaler;
msdfgen.Range range = Range(_range);
//Padding fullPadding = (glyphAttributes.innerPadding+glyphAttributes.outerPadding)/geometryScale;
//box.range = range;
//box.scale = scale;
if(bounds.Left < bounds.Right && bounds.Bottom < bounds.Top)
{
double l = bounds.Left;
@@ -513,6 +793,65 @@ namespace GlitchyEngine.Renderer.Text
double b = bounds.Bottom;
double t = bounds.Top;
l -= range.Upper;
b -= range.Upper;
r += range.Upper;
t += range.Upper;
// TODO: Miter
//if (glyphAttributes.miterLimit > 0)
// shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1);
// TODO: Padding
//l -= fullPadding.l, b -= fullPadding.b;
//r += fullPadding.r, t += fullPadding.t;
/*if (glyphAttributes.pxAlignOriginX) {
int sl = (int) floor(scale*l-.5);
int sr = (int) ceil(scale*r+.5);
box.rect.w = sr-sl;
box.translate.x = -sl/scale;
} else {
double w = scale*(r-l);
box.rect.w = (int) ceil(w)+1;
box.translate.x = -l+.5*(box.rect.w-w)/scale;
}
if (glyphAttributes.pxAlignOriginY) {
int sb = (int) floor(scale*b-.5);
int st = (int) ceil(scale*t+.5);
box.rect.h = st-sb;
box.translate.y = -sb/scale;
} else {
double h = scale*(t-b);
box.rect.h = (int) ceil(h)+1;
box.translate.y = -b+.5*(box.rect.h-h)/scale;
}*/
double w = scale * (r - l);
width = (int) Math.Ceiling(w) + 1;
translationX = -l + 0.5 * (width - w) / scale;
double h = scale * (t - b);
height = (int) Math.Ceiling(h) + 1;
translationY = -b + 0.5 * (height - h) / scale;
// TODO: Outer padding?
//box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding;
} else {
width = 0;
height = 0;
translationX = 0;
translationY = 0;
}
/*if(bounds.Left < bounds.Right && bounds.Bottom < bounds.Top)
{
double l = bounds.Left;
double r = bounds.Right;
double b = bounds.Bottom;
double t = bounds.Top;
l -= 0.5 * _range;
b -= 0.5 * _range;
r += 0.5 * _range;
@@ -522,14 +861,14 @@ namespace GlitchyEngine.Renderer.Text
//if (miterLimit > 0)
// shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1);
double w = _geometryScaler * (r - l);
double h = _geometryScaler * (t - b);
//double w = _geometryScaler * (r - l);
//double h = _geometryScaler * (t - b);
width = Math.Ceiling(w) + 1;
height = Math.Ceiling(h) + 1;
width = 32 ;//* (bounds.Right - bounds.Left);//_fontSize; //Math.Ceiling(w) + 1;
height = 32;// * (bounds.Top - bounds.Bottom);//_fontSize; //Math.Ceiling(h) + 1;
translationX = -l + 0.5 * (width - w) / _geometryScaler;
translationY = -b + 0.5 * (height - h) / _geometryScaler;
translationX = 0;//-l + 0.5 * (width - width) / _geometryScaler;
translationY = 0;//-b + 0.5 * (height - height) / _geometryScaler;
}
else
{
@@ -537,7 +876,7 @@ namespace GlitchyEngine.Renderer.Text
height = 0;
translationX = 0;
translationY = 0;
}
}*/
desc.Width = (.)width;
desc.Height = (.)height;
@@ -549,7 +888,7 @@ namespace GlitchyEngine.Renderer.Text
desc.AdjustToBaseLine = (float)(-translationY * _geometryScaler);
desc.AdjustToPen = (float)(-translationX);
desc.AdjustToPen = (float)(-translationX);*/
return true;
}
@@ -653,35 +992,76 @@ namespace GlitchyEngine.Renderer.Text
{
Debug.Profiler.ProfileResourceFunction!();
msdfgen.ResolveShapeGeometry(desc.Shape);
Shape.Normalize(desc.Shape);
msdfgen.EdgeColoringSimple(desc.Shape, 3.0);
// prepare projection
//SDFTransformation t = SDFTransformation(Projection(32.0, 32.0, 0.125, 0.125), DistanceMapping(msdfgen.Range(0.125)));
SDFTransformation t = SDFTransformation(Projection(desc.boxScale, desc.boxScale, desc.boxTranslate.X, desc.boxTranslate.Y), DistanceMapping(desc.boxRange));
int bufferX = desc.Width;
int bufferY = desc.Height;
using(Bitmap<ColorRGB, const 1> bitmap = .((.)bufferX, (.)bufferY, .Y_DOWNWARD))
{
Debug.Profiler.ProfileResourceScope!("GenerateMSDF");
// Default config seems to be fine
MSDFGeneratorConfig config = .();
msdfgen.GenerateMSDF(*(Bitmap<float, const 3>*)&bitmap, desc.Shape, t, config);
int8[] pixels = new:ScopedAlloc! int8[desc.Width * desc.Height * 4];
int8 ToInt8(float f) => (.)Math.Clamp(127f * f, int8.MinValue, int8.MaxValue);
for(int y = 0; y < desc.Height; y++)
for(int x = 0; x < desc.Width; x++)
{
ColorRGB pixel = bitmap.Pixels[(y) * bufferX + x];
int index = (y * desc.Width + x) * 4;
pixels[index + 0] = ToInt8(pixel.R);
pixels[index + 1] = ToInt8(pixel.G);
pixels[index + 2] = ToInt8(pixel.B);
pixels[index + 3] = Int8.MaxValue;
}
_atlas.SetData<Color>((Color*)pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y,
(.)desc.Width, (.)desc.Height, (.)desc.MapCoord.Z);
}
}
void GenerateMSDFOld(GlyphDescriptor desc)
{
Debug.Profiler.ProfileResourceFunction!();
// prepare shape
double advance = 0;
Shape shape;
Shape* shape;
defer { msdfgen.DestroyShape(shape); }
{
Debug.Profiler.ProfileResourceScope!("msdfgen.LoadGlyph");
msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance);
msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, .FONT_SCALING_EM_NORMALIZED, out advance);
}
msdfgen.ResolveShapeGeometry(shape);
shape.Normalize();
//var bounds = shape.GetBounds();
//shape.ReverseIfNeeded(bounds);
Shape.Normalize(shape);
msdfgen.EdgeColoringSimple(shape, 3.0);
// prepare projection
msdfgen.Projection projection = .();
projection.ScaleX = _geometryScaler;
projection.ScaleY = _geometryScaler;
projection.TranslationX = desc.TranslationX;
projection.TranslationY = desc.TranslationY;
SDFTransformation t = SDFTransformation(Projection(32.0, 32.0, 0.125, 0.125), DistanceMapping(msdfgen.Range(0.125)));
int bufferX = desc.Width;
int bufferY = desc.Height;
@@ -692,7 +1072,7 @@ namespace GlitchyEngine.Renderer.Text
MSDFGeneratorConfig config = .();
msdfgen.GenerateMSDF(*(Bitmap<float, const 3>*)&bitmap, shape, projection, _range, config);
msdfgen.GenerateMSDF(*(Bitmap<float, const 3>*)&bitmap, shape, t, config);
int8[] pixels = new:ScopedAlloc! int8[desc.Width * desc.Height * 4];
+27 -15
View File
@@ -16,6 +16,8 @@ namespace GlitchyEngine.Renderer.Text
{
public static class FontRenderer
{
public const int HarfBuzzFontScale = 64;
internal static FT_Library s_Library;
public static AssetHandle<Effect> _msdfEffect;
@@ -201,6 +203,12 @@ namespace GlitchyEngine.Renderer.Text
}
}
/// How large one inch is in meters
private static double InchToMeter = 0.0254;
/// How large one typographic point is in meters (one point = 1 / 72 inches).
private static double FontPointToMeter = (1.0 / 72.0) * InchToMeter;
// TODO: for performance reasons we separated text positioning and rendering. Consider calculating with double instead of float?
public static void PrepareText(TextRendererComponent* textRenderer, Font font)
{
Debug.Profiler.ProfileRendererFunction!();
@@ -238,6 +246,7 @@ namespace GlitchyEngine.Renderer.Text
StyleStack<bool> richTextStack = scope .(true);
// TODO: color stack not used?!
StyleStack<ColorRGBA> fontColorStack = scope .(textRenderer.Color);
StyleStack<float> fontSizeStack = scope .(textRenderer.FontSize);
@@ -248,12 +257,14 @@ namespace GlitchyEngine.Renderer.Text
float scale()
{
return fontSizeStack.CurrentValue() / fontStack.CurrentValue()._fontSize;
return fontSizeStack.CurrentValue() * (float)FontPointToMeter;
}
float linespace()
{
return (float)fontStack.CurrentValue()._linespace * lineSpaceStack.CurrentValue() * scale();
let currentFont = fontStack.CurrentValue();
return (float)(currentFont._linespaceEmHorizontal * lineSpaceStack.CurrentValue() * scale());
}
List<int> lineStartIndices = scope .();
@@ -315,12 +326,12 @@ namespace GlitchyEngine.Renderer.Text
hb_position_t y_advance = glyph_pos[i].y_advance;
// TODO Store color in glyph
PreparedGlyph glyph = .(currentFont, glyphid, .(penPosition, baseline), fontScale);//, x_advance / 64, y_advance / 64);
PreparedGlyph glyph = .(currentFont, glyphid, .(penPosition, baseline), fontScale);//, x_advance / HarfBuzzFontScale, y_advance / HarfBuzzFontScale);
preparedText.Glyphs.Add(glyph);
penPosition += (x_advance / 64) * fontScale;
baseline += (y_advance / 64) * fontScale;
penPosition += (float)(x_advance / fontStack.CurrentValue()._unitsPerEm) * fontScale;
baseline += (float)(y_advance / fontStack.CurrentValue()._unitsPerEm) * fontScale;
}
preparedText.AdvanceX = Math.Max(preparedText.AdvanceX, penPosition);
@@ -573,7 +584,7 @@ namespace GlitchyEngine.Renderer.Text
//Renderer2D.Flush();
// TODO: this doesn't really work with fallback fonts unless we use the same settings for all fonts
float2 unitRange = ((float)text.Font._range) / float2(text.Font._atlas.Width, text.Font._atlas.Height);
float2 unitRange = ((float)text.Font._atlasPxRange) / float2(text.Font._atlas.Width, text.Font._atlas.Height);
_msdfMaterial.SetVariable("UnitRange", unitRange);
List<Texture2D> atlasses = scope .();
@@ -606,13 +617,6 @@ namespace GlitchyEngine.Renderer.Text
float adjustToBaseline = glyphDesc.AdjustToBaseLine;
adjustToBaseline *= glyph.Scale;
// Rectangle on the screen
float4 viewportRect = .(
// TODO: merge adjustToPenX and adjustToBaseline into Position
glyph.Position.X + adjustToPenX,
glyph.Position.Y + adjustToBaseline,
glyphDesc.Width * glyph.Scale,
glyphDesc.Height * glyph.Scale);
// Rectangle on the font atlas
float4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.Width, glyphDesc.Height);
@@ -621,6 +625,14 @@ namespace GlitchyEngine.Renderer.Text
texRect /= float4(atlasSize, atlasSize);
// Rectangle on the screen
float4 viewportRect = .(
// TODO: merge adjustToPenX and adjustToBaseline into Position
glyph.Position.X + adjustToPenX,
glyph.Position.Y + adjustToBaseline,
(float)glyphDesc.QuadSizeEm.X * glyph.Scale,
(float)glyphDesc.QuadSizeEm.Y * glyph.Scale);
float3 position = .(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2, 0);
Matrix glyphTransform = transform * Matrix.Translation(position) * Matrix.Scaling(viewportRect.Z, viewportRect.W, 1.0f);
@@ -629,10 +641,10 @@ namespace GlitchyEngine.Renderer.Text
}
// TODO: Get rid of the flush.
// We need to flush, because currently Renderer2D doesn't increase the counter of passed textures.
// We need to flush, because currently Renderer2D doesn't increase the reference counter of passed textures (which is a smell!).
// Once it does that, we can
// 1. Stop manually holding the references in this method
// 2. Stop forcing a flush (which could make having multiple text instances way more efficient)
// 2. Stop forcing a flush, which would allow Renderer2D to render multiple DrawText-calls using single batch
Renderer2D.Flush();
// release all atlas textures
+1 -1
View File
@@ -123,7 +123,7 @@ namespace GlitchyEngine.World
});
if (_font == null)
_font = new Font(@"C:\Windows\Fonts\arial.ttf", 24);
_font = new Font(@"C:\Windows\Fonts\arial.ttf");
else
_font..AddRef();