Implemented FontRenderer

This commit is contained in:
Simon Lübeß
2021-06-23 19:17:17 +02:00
parent 8c957f9d9f
commit 7f8379ea6c
8 changed files with 533 additions and 6 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
FileVersion = 1
Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", ImGui = "*", ImGuiImplWin32 = "*", ImGuiImplDX11 = "*", DirectXTK = "*"}
Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", ImGui = "*", ImGuiImplWin32 = "*", ImGuiImplDX11 = "*", DirectXTK = "*", FreeType = "*"}
[Project]
Name = "GlitchyEngine"
+334
View File
@@ -0,0 +1,334 @@
using System;
using FreeType;
using GlitchyEngine.Math;
using System.Collections;
using internal GlitchyEngine.Renderer.Text;
namespace GlitchyEngine.Renderer.Text
{
public class Font
{
public class GlyphDescriptor
{
//public FT_Face Face;
public Font Font;
public FT_UInt GlyphIndex;
public Int32_3 MapCoord;
public int32 SizeX, SizeY;
public FT_Glyph_Metrics Metrics;
public bool IsBitmap;
public bool IsCalculated = false;
public bool IsRendered = false;
}
private GraphicsContext _context ~ _.ReleaseRef();
internal FT_Face _face ~ FreeType.Done_Face(_face);
private Font _fallback;
private uint32 _fontSize;
private int32 _faceIndex;
private bool _hasColor;
private Int32_3 _penPos;
private int32 _lastRowHeight;
private Texture2D _atlas ~ _?.ReleaseRef();
private Int32_3 _atlasSize;
private Dictionary<char32, GlyphDescriptor> _glyphs = new .() ~ DeleteDictionaryAndValues!(_);
GlyphDescriptor _missingGlyph;
private SamplerState _sampler ~ _.ReleaseRef();
/**
* Gets or sets the Sampler that will be used to sample the spritefont.
* Setting to null will result in the use of LinearClamp
*/
public SamplerState Sampler
{
get => _sampler;
set
{
// when value and _sampler are null we don't shortcut because we set _sampler to LinearClamp
if(_sampler == value && value != null)
return;
_sampler?.ReleaseRef();
_sampler = (value ?? SamplerStateManager.LinearClamp)..AddRef();
_atlas?.SamplerState = _sampler;
}
}
public this(GraphicsContext context, String fontPath, uint32 fontSize, bool hasColor = true, char32 firstChar = '\0', uint32 charCount = 128, int32 faceIndex = 0)
{
_context = context..AddRef();
// Set default sampler
Sampler = null;
FontRenderer.InitLibrary();
_glyphs.Add('\0', new GlyphDescriptor(){Font = this});
_fontSize = fontSize;
_faceIndex = faceIndex;
_hasColor = hasColor;
var res = FreeType.New_Face(FontRenderer.Library, fontPath, faceIndex, &_face);
Log.EngineLogger.Assert(res.Success, scope $"New_Face failed({(int)res}): {res}");
res = FreeType.Set_Pixel_Sizes(_face, 0, _fontSize);
Log.EngineLogger.Assert(res.Success, scope $"Set_Pixel_Sizes failed({(int)res}): {res}");
LoadGlyphs(firstChar, charCount);
}
public void LoadGlyphs(char32 firstChar, uint32 charCount)
{
ExtendRange(firstChar, firstChar + charCount);
UpdateAtlas();
}
internal GlyphDescriptor GetGlyph(char32 char, bool allowDynamicLoading = true)
{
GlyphDescriptor desc;
if(_glyphs.TryGetValue(char, out desc))
{
// if the char is not defined in the current font try to load it from the fallback
if(desc == null && _fallback != null)
{
desc = _fallback.GetGlyph(char, allowDynamicLoading);
}
}
else if(allowDynamicLoading)
{
// TODO: make reloading optional
char32 start = (char >> 4) << 4;
LoadGlyphs(start, 64);
desc = GetGlyph(char, false);
}
return desc ?? _missingGlyph;
}
void ExtendRange(char32 firstChar, char32 rangeEnd)
{
// TODO: refactor
for(char32 char = firstChar; char < rangeEnd; char++)
{
GlyphDescriptor desc = new GlyphDescriptor();
if(_glyphs.TryAdd(char, desc))
{
//desc.Face = _face;
desc.Font = this;
desc.GlyphIndex = FreeType.Get_Char_Index(_face, char);
// if the char is not contained by the font we set the desc to null
if(desc.GlyphIndex == 0)
{
delete desc;
_glyphs[char] = null;
}
}
else
{
delete desc;
}
}
}
void UpdateAtlas()
{
DrawAtlas();
}
Int32_3 PrepareAtlas()
{
const uint32 maxRes = 16384; // D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION
const uint32 maxArray = 2048; // D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION
ref Int32_3 pen = ref _penPos;
ref int32 penLeft = ref _penPos.X;
ref int32 penTop = ref _penPos.Y;
ref int32 penArray = ref _penPos.Z;
ref int32 rowHeight = ref _lastRowHeight;
int32 atlasWidth = _atlasSize.X;
int32 atlasHeight = _atlasSize.Y;
int32 atlasArray = _atlasSize.Z;
for(let (char, desc) in _glyphs)
{
if(desc == null || desc.IsCalculated)
{
continue;
}
let loadResult = FreeType.Load_Glyph(_face, desc.GlyphIndex, .Color | .TargetNormal);
if(loadResult.Error)
{
Log.EngineLogger.Error($"Failed to load glyph for '{char}'(Char Code:{(uint32)char} | Error {(int)loadResult}): {loadResult}");
}
desc.Metrics = _face.glyph.metrics;
int32 glyphWidth = (desc.Metrics.width / 64) + 1;
int32 glyphHeight = (desc.Metrics.height / 64) + 1;
// when size is 0 (1 because we added 1) the glyph has no image
if(glyphWidth != 1 || glyphHeight != 1)
{
int32 glyphRight = penLeft + glyphWidth;
if(glyphRight > maxRes)
{
penLeft = 0;
penTop += rowHeight;
rowHeight = 0;
}
int32 glyphBottom = penTop + glyphHeight;
if(glyphBottom > maxRes)
{
penLeft = 0;
penTop = 0;
rowHeight = 0;
penArray++;
}
if(penArray >= maxArray)
{
Log.EngineLogger.Error("Spritefont exceeds maximum texture size.");
}
}
desc.MapCoord = pen;
desc.SizeX = glyphWidth - 1;
desc.SizeY = glyphHeight - 1;
if(desc.GlyphIndex == 0 && _missingGlyph == null)
{
_missingGlyph = desc;
}
penLeft += glyphWidth;
if (penLeft > atlasWidth)
atlasWidth = penLeft;
if (glyphHeight > rowHeight)
rowHeight = glyphHeight;
if (penTop + glyphHeight > atlasHeight)
atlasHeight = penTop + glyphHeight;
if (penArray == atlasArray)
atlasArray++;
desc.IsCalculated = true;
}
return .(atlasWidth, atlasHeight, atlasArray);
}
void DrawAtlas()
{
Int32_3 oldAtlasSize = _atlasSize;
_atlasSize = PrepareAtlas();
if(_atlasSize != oldAtlasSize)
{
var oldAtlas = _atlas;
Texture2DDesc desc;
desc.Format = .B8G8R8A8_UNorm;
desc.MipLevels = 1;
desc.Width = (.)_atlasSize.X;
desc.Height = (.)_atlasSize.Y;
desc.ArraySize = (.)_atlasSize.Z;
desc.CpuAccess = .None;
desc.Usage = .Default;
_atlas = new Texture2D(_context, desc);
_atlas.SamplerState = _sampler;
if(oldAtlas != null)
{
oldAtlas.CopyTo(_atlas);
oldAtlas.ReleaseRef();
}
}
for(let (char, desc) in _glyphs)
{
if(desc == null)
continue;
var glyphIndex = desc.GlyphIndex;
if(desc.IsRendered || glyphIndex == 0 || desc.SizeX == 0 || desc.SizeY == 0)
{
continue;
}
var loadResult = FreeType.Load_Glyph(_face, glyphIndex, .Color | .Render | .TargetNormal);
if(loadResult.Error)
{
Log.EngineLogger.Error($"Failed to render glyph for '{char}'(Char Code:{(uint32)char} | Error {(int)loadResult}): {loadResult}");
}
FT_GlyphSlot glyphSlot = _face.glyph;
FT_Bitmap bitmap = glyphSlot.bitmap;
//ResourceBox glyphBox = .((.)desc.MapCoord.X, (.)desc.MapCoord.X, 0,
// (.)desc.MapCoord.X + (.)desc.SizeX, (.)desc.MapCoord.Y + (.)desc.SizeY, 1);
if(_face.glyph.format == .Bitmap)
{
if(bitmap.pixel_mode == .Gray)
{
Color[] pixels = new Color[bitmap.width * bitmap.rows];
defer delete pixels;
for(int i < pixels.Count)
{
uint8 gray = bitmap.buffer[i];
pixels[i] = .(gray, gray, gray, gray);
}
_atlas.SetData(pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y,
(.)desc.SizeX, (.)desc.SizeY, (.)desc.MapCoord.Z);
}
else if(bitmap.pixel_mode == .Bgra)
{
_atlas.SetData<Color>((.)bitmap.buffer, (.)desc.MapCoord.X, (.)desc.MapCoord.Y,
(.)desc.SizeX, (.)desc.SizeY, (.)desc.MapCoord.Z);
desc.IsBitmap = true;
}
}
else
{
Runtime.NotImplemented();
}
desc.IsRendered = true;
}
}
}
}
@@ -0,0 +1,124 @@
using System;
using FreeType;
using System.Diagnostics;
using GlitchyEngine.Math;
using System.Collections;
using internal GlitchyEngine.Renderer.Text;
namespace GlitchyEngine.Renderer.Text
{
public class FontRenderer
{
internal static FT_Library Library ~ FreeType.Done_FreeType(_);
internal static void InitLibrary()
{
if(Library == null)
{
var res = FreeType.Init_FreeType(&Library);
Log.EngineLogger.Assert(res.Success, scope $"Init_FreeType failed({(int)res}): {res}");
}
}
private GraphicsContext _context ~ _.ReleaseRef();
public this(GraphicsContext context)
{
FontRenderer.InitLibrary();
_context = context..AddRef();
}
public void DrawText(Renderer2D renderer, Font font, String text, float x, float y, Color fontColor = .White, Color bitmapColor = .White)
{
if(text.IsWhiteSpace)
return;
// Todo:
float scale = 1.0f;
// Space between two baselines
float linespace = ((font._face.size.metrics.ascender - font._face.size.metrics.descender) >> 6);// + lineGap;
// The line we are writing on
float baseline = y + linespace;
// Where we are writing the next glyph on the line
float penPosition = x;
int32 lastLine = 0;
int32 line = 0;
List<Texture2D> atlasses = scope .();
//GlyphDescriptor missingGlyph = font.CharMap.GetValue('\0');
String.UTF8Enumerator textEnumerator = String.UTF8Enumerator(text, 0, text.Length);
for(char32 char in textEnumerator)
{
if(char == '\n')
{
line++;
continue;
}
// number of lines that the cursor moved up or down
int32 lineDiff = line - lastLine;
if(lineDiff != 0)
{
baseline += linespace * lineDiff;
// TODO: make carriage return optional?
penPosition = x;
lastLine = line;
}
// Get glyph from Font
var glyphDesc = font.GetGlyph(char);
// This never happens
if(glyphDesc == null)
{
Debug.Break();
continue;
}
// Rectangle on the screen
Vector4 viewportRect = .(penPosition + (glyphDesc.Metrics.horiBearingX / 64) * scale, baseline - (glyphDesc.Metrics.horiBearingY / 64) * scale, glyphDesc.SizeX * scale, glyphDesc.SizeY * scale);
// Rectangle on the font atlas
Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.SizeX, glyphDesc.SizeY);
Color glyphColor = glyphDesc.IsBitmap ? bitmapColor : fontColor;
/*
if(QueueQuad!(viewportRect, texRect, glyphDesc.MapZ, glyphColor))
{
DrawQueue(context);
}
*/
Texture2D atlas = glyphDesc.Font.[Friend]_atlas;
if(!atlasses.Contains(atlas))
{
atlasses.Add(atlas..AddRef());
}
Vector2 atlasSize = .(atlas.Width, atlas.Height);
texRect /= Vector4(atlasSize, atlasSize);
renderer.Draw(atlas, viewportRect.X, viewportRect.Y, viewportRect.Z, viewportRect.W, glyphColor, 0.0f, texRect);
penPosition += (glyphDesc.Metrics.horiAdvance / 64) * scale;
}
renderer.End();
for(int i < atlasses.Count)
{
atlasses[i].ReleaseRef();
}
}
}
}