mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
335 lines
7.8 KiB
Beef
335 lines
7.8 KiB
Beef
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;
|
|
}
|
|
}
|
|
}
|
|
}
|