From 7f8379ea6c0e5d05c3735fe3cf45f9e54f212aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Wed, 23 Jun 2021 19:17:17 +0200 Subject: [PATCH] Implemented FontRenderer --- .gitmodules | 3 + BeefSpace.toml | 2 +- GlitchyEngine/BeefProj.toml | 2 +- GlitchyEngine/src/Renderer/Text/Font.bf | 334 ++++++++++++++++++ .../src/Renderer/Text/FontRenderer.bf | 124 +++++++ GlitchyEngine/vendor/directx | 2 +- Sandbox/content/Fonts/GutenbergTextura.ttf | Bin 0 -> 81136 bytes Sandbox/src/SandboxApp2D.bf | 72 +++- 8 files changed, 533 insertions(+), 6 deletions(-) create mode 100644 GlitchyEngine/src/Renderer/Text/Font.bf create mode 100644 GlitchyEngine/src/Renderer/Text/FontRenderer.bf create mode 100644 Sandbox/content/Fonts/GutenbergTextura.ttf diff --git a/.gitmodules b/.gitmodules index f326578..a9bee96 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "vendor/lodepng-beef"] path = vendor/lodepng-beef url = https://github.com/aharabada/lodepng-beef.git +[submodule "GlitchyEngine\\vendor\\freetype"] + path = GlitchyEngine\\vendor\\freetype + url = https://github.com/aharabada/FreeType-beef.git diff --git a/BeefSpace.toml b/BeefSpace.toml index c5c761e..da02317 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -1,5 +1,5 @@ FileVersion = 1 -Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, ImGui = {Path = "GlitchyEngine/vendor/imgui-beef/ImGui"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplWin32"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplDX11"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}} +Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, ImGui = {Path = "GlitchyEngine/vendor/imgui-beef/ImGui"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplWin32"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplDX11"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}} [Workspace] StartupProject = "Sandbox" diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index debb06d..c79e15f 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -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" diff --git a/GlitchyEngine/src/Renderer/Text/Font.bf b/GlitchyEngine/src/Renderer/Text/Font.bf new file mode 100644 index 0000000..471d779 --- /dev/null +++ b/GlitchyEngine/src/Renderer/Text/Font.bf @@ -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 _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((.)bitmap.buffer, (.)desc.MapCoord.X, (.)desc.MapCoord.Y, + (.)desc.SizeX, (.)desc.SizeY, (.)desc.MapCoord.Z); + + desc.IsBitmap = true; + } + } + else + { + Runtime.NotImplemented(); + } + + desc.IsRendered = true; + } + } + } +} diff --git a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf new file mode 100644 index 0000000..48717b8 --- /dev/null +++ b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf @@ -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 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(); + } + } + } +} diff --git a/GlitchyEngine/vendor/directx b/GlitchyEngine/vendor/directx index 7fc4733..0399d40 160000 --- a/GlitchyEngine/vendor/directx +++ b/GlitchyEngine/vendor/directx @@ -1 +1 @@ -Subproject commit 7fc473343221fbd90624542ca31974203e0b9ea6 +Subproject commit 0399d40bf807ea6c0dddf2d5274cfbf37cc7b37e diff --git a/Sandbox/content/Fonts/GutenbergTextura.ttf b/Sandbox/content/Fonts/GutenbergTextura.ttf new file mode 100644 index 0000000000000000000000000000000000000000..88c4e1effedd66d4a7c7f1381b47ed9588897be6 GIT binary patch literal 81136 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 GO8bA(P{)k` literal 0 HcmV?d00001 diff --git a/Sandbox/src/SandboxApp2D.bf b/Sandbox/src/SandboxApp2D.bf index c7363e8..3aed0cd 100644 --- a/Sandbox/src/SandboxApp2D.bf +++ b/Sandbox/src/SandboxApp2D.bf @@ -8,6 +8,7 @@ using ImGui; using GlitchyEngine.Renderer; using GlitchyEngine.Math; using GlitchyEngine.World; +using GlitchyEngine.Renderer.Text; namespace Sandbox { @@ -57,6 +58,8 @@ namespace Sandbox Renderer2D Renderer2D ~ delete _; + Texture2D _testTexture ~ _?.ReleaseRef(); + [AllowAppend] public this() : base("Example") { @@ -97,8 +100,36 @@ namespace Sandbox Renderer2D = new Renderer2D(_context, _effectLibrary); //Init2D(); + + Texture2DDesc desc = .(); + desc.Format = .R8G8B8A8_UNorm; + desc.Width = 2; + desc.Height = 2; + desc.ArraySize = 1; + desc.CpuAccess = .None; + desc.MipLevels = 1; + desc.Usage = .Default; + + _testTexture = new Texture2D(_context, desc); + + Color[4] colors = .( + .Red, .Green, + .Blue, .White); + + _testTexture.SetData(&colors, 0, 1, 1, 1); + //_testTexture.SetData(&colors, 0, 0, 2, 2); + + fonty = new Font(_context, "C:\\Windows\\Fonts\\arial.ttf", 64, true, 'A', 16);//C:\\Windows\\Fonts\\seguiemj.ttf + emojies = new Font(_context, "C:\\Windows\\Fonts\\seguiemj.ttf", 64, true, '😂' - 10, 1); + fonty.[Friend]_fallback = emojies; + + fontRenderer = new FontRenderer(_context); } + Font fonty ~ delete _; + Font emojies ~ delete _; + FontRenderer fontRenderer ~ delete _; + VertexLayout layout ~ delete _; GeometryBinding quadBinding ~ _?.ReleaseRef(); @@ -225,7 +256,8 @@ namespace Sandbox _context.SetViewport(_context.SwapChain.BackbufferViewport); - Renderer2D.Begin(.SortByTexture, .(80, 80)); + /* + Renderer2D.Begin(.FrontToBack, .(80, 80)); //_opaqueBlendState.Bind(); _alphaBlendState.Bind(); @@ -237,7 +269,7 @@ namespace Sandbox //_texture.Bind(0); //_ge_logo.Bind(1); - + for(int x < 40) for(int y < 40) { @@ -253,7 +285,7 @@ namespace Sandbox } } - Renderer2D.Draw(_texture, 0, 0, 20, 10, .White, 20); + Renderer2D.Draw(_ge_logo, 0, 0, 180, 40, .White, 20); /* Renderer2D.Draw(_texture, 10, 50, 100, 100, .White); @@ -268,6 +300,13 @@ namespace Sandbox //Renderer2D.Draw(_ge_logo, 80, 50, 100, 100, .White); + Renderer2D.End(); + */ + _alphaBlendState.Bind(); + Renderer2D.Begin(.FrontToBack, .(_context.SwapChain.Width, _context.SwapChain.Height)); + + fontRenderer.DrawText(Renderer2D, fonty, "Hallo Welt, wie geht es dir? 😂😂😂\nMir geht es gut, danke der Nachfrage, wie geht es dir?\nMir geht es hervorragend: Meine Engine kann endlich Text rendern\n und es scheint ganz in ordnung zu sein.", 0, 0); + Renderer2D.End(); } @@ -285,17 +324,44 @@ namespace Sandbox private bool OnImGuiRender(ImGuiRenderEvent e) { + return true; + ImGui.Begin("Test"); ImGui.ColorEdit3("Square Color", ref _squareColor0); _squareColor1 = ColorRGBA.White - _squareColor0; + //ImGui.Begin + + //ImGui.Image(fonty.[Friend]_atlas.[Friend]nativeView, .(100, 200)); + //ImGui.Scrollbar(.X); + //ImGui.Image(fonty.[Friend]_atlas.[Friend]nativeView, .(fonty.[Friend]_atlas.Width, fonty.[Friend]_atlas.Height), .(0.0f, 0.0f), .(1.0f, 1.0f), .(1.0f, 1.0f, 1.0f, 1.0f), .(1.0f, 0, 0, 1)); + ImGui.End(); + TextureViewer(); + return false; } + float zoom = 1.0f; + + private void TextureViewer() + { + ImGui.Begin("Texture Viewer"); + + ImGui.SliderFloat("Zoom", &zoom, 0.01f, 100.0f); + + ImGui.BeginChild("imageChild", default, true, .HorizontalScrollbar); + + ImGui.Image(fonty.[Friend]_atlas.[Friend]nativeView, .(fonty.[Friend]_atlas.Width * zoom, fonty.[Friend]_atlas.Height * zoom), .(0.0f, 0.0f), .(1.0f, 1.0f), .(1.0f, 1.0f, 1.0f, 1.0f), .(1.0f, 0, 0, 1)); + + ImGui.EndChild(); + + ImGui.End(); + } + private bool OnWindowResize(WindowResizeEvent e) { _depthTarget.ReleaseRef();