diff --git a/GlitchyEngine/src/Renderer/Renderer2D.bf b/GlitchyEngine/src/Renderer/Renderer2D.bf index 2a83e3e..e19829d 100644 --- a/GlitchyEngine/src/Renderer/Renderer2D.bf +++ b/GlitchyEngine/src/Renderer/Renderer2D.bf @@ -9,6 +9,27 @@ namespace GlitchyEngine.Renderer public class Renderer2D { + enum DrawOrder + { + /// Immediately draw the sprites. + Immediate, + /** + * Deferres rendering until the call of End(). + * Sorts the sprites by their texture. + */ + SortByTexture, + /** + * Deferres rendering until the call of End(). + * Sorts the sprites so that the backmost will be drawn first and the frontmost last. + */ + BackToFront, + /** + * Deferres rendering until the call of End(). + * Sorts the sprites so that the frontmost will be drawn first and the backmost last. + */ + FrontToBack + } + struct RenderVertex : IVertexData { public Vector2 Position; @@ -66,6 +87,10 @@ namespace GlitchyEngine.Renderer private BatchVertex[] _rawInstances = new BatchVertex[1024] ~ delete _; private uint32 _setInstances = 0; + + Matrix _projection; + + DrawOrder _drawOrder; /// The effect that is currently being used to draw the sprites. private Effect _currentEffect ~ _?.ReleaseRef(); @@ -160,31 +185,6 @@ namespace GlitchyEngine.Renderer instancingBinding.SetIndexBuffer(quadBinding.GetIndexBuffer(), 0); } - Matrix _projection; - - DrawOrder _drawOrder; - - enum DrawOrder - { - /// Immediately draw the sprites. - Immediate, - /** - * Deferres rendering until the call of End(). - * Sorts the sprites by their texture. - */ - SortByTexture, - /** - * Deferres rendering until the call of End(). - * Sorts the sprites so that the backmost will be drawn first and the frontmost last. - */ - BackToFront, - /** - * Deferres rendering until the call of End(). - * Sorts the sprites so that the frontmost will be drawn first and the backmost last. - */ - FrontToBack - } - /** * Initializes the renderer. * @param drawOrder Determines if and how the sprites will be ordered before rendering. diff --git a/GlitchyEngine/src/Renderer/Text/Font.bf b/GlitchyEngine/src/Renderer/Text/Font.bf index 0f8a803..2e09bef 100644 --- a/GlitchyEngine/src/Renderer/Text/Font.bf +++ b/GlitchyEngine/src/Renderer/Text/Font.bf @@ -16,11 +16,20 @@ namespace GlitchyEngine.Renderer.Text public FT_UInt GlyphIndex; + // TODO: Consider using floats public Int32_3 MapCoord; - public int32 SizeX, SizeY; + public int32 Width, Height; - public FT_Glyph_Metrics Metrics; + public double TranslationX, TranslationY; + /// How many pixels we have to move the pen after drawing this glyph. + public float Advance; + + // Aligns the image of the glyph with the baseline + public float AdjustToBaseLine; + // Aligns the image of the glyph with the pen + public float AdjustToPen; + public bool IsBitmap; public bool IsCalculated = false; @@ -49,7 +58,7 @@ namespace GlitchyEngine.Renderer.Text /// How much we have to scale the geometry to fit it into our desired pixels private double _geometryScaler; - private double _range = 4.0; + internal double _range = 4.0; /** * Gets or sets the fallback Font for this Font. @@ -189,7 +198,7 @@ namespace GlitchyEngine.Renderer.Text } /// Debug only - public void RedrawAtlas() + private void RedrawAtlas() { for(let (char, desc) in _glyphs) { @@ -200,6 +209,10 @@ namespace GlitchyEngine.Renderer.Text _penPos = .Zero; _lastRowHeight = 0; + int8[] data = new int8[_atlas.Width * _atlas.Height * 4]; + + _atlas.SetData((Color*)data.Ptr); + UpdateAtlas(); } @@ -209,92 +222,71 @@ namespace GlitchyEngine.Renderer.Text 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) + for(var (char, desc) in _glyphs) { if(desc == null || desc.IsCalculated) { continue; } - let loadResult = FreeType.Load_Glyph(_face, desc.GlyphIndex, .Color | .TargetNormal); - if(loadResult.Error) + if(!Calculate(ref desc)) + continue; + + const int32 border = 1; + + if(desc.Width != 0 && desc.Height != 0) { - Log.EngineLogger.Error($"Failed to load glyph for '{char}'(Char Code:{(uint32)char} | Error {(int)loadResult}): {loadResult}"); - } - - desc.Metrics = _face.glyph.metrics; - - int32 border = 2; - - // TODO: adjust size to glyph - int32 glyphWidth = (.)_fontSize + border; - //int32 glyphWidth = (desc.Metrics.width / 64) + 1; - int32 glyphHeight = (.)_fontSize + border; - //int32 glyphHeight = (desc.Metrics.height / 64) + 1; - - // when size is 0 (1 because we added 1) the glyph has no image - if(glyphWidth != border || glyphHeight != border) - { - //int32 glyphRight = penLeft + glyphWidth; - - int32 glyphRight = penLeft + glyphWidth + border; + int32 glyphRight = pen.X + desc.Width + border; if(glyphRight > maxRes) { - penLeft = 0; - penTop += rowHeight; + pen.X = 0; + pen.Y += rowHeight + border; rowHeight = 0; } - int32 glyphBottom = penTop + glyphHeight + border; + int32 glyphBottom = pen.Y + desc.Height + border; if(glyphBottom > maxRes) { - penLeft = 0; - penTop = 0; + pen.X = 0; + pen.Y = 0; rowHeight = 0; - penArray++; - } - - if(penArray >= maxArray) - { - Log.EngineLogger.Error("Spritefont exceeds maximum texture size."); + pen.Z++; + + if(pen.Z >= maxArray) + { + Log.EngineLogger.Error("Sprite font exceeded maximum texture size."); + } } } desc.MapCoord = pen; - //desc.SizeX = glyphWidth - 1; - //desc.SizeY = glyphHeight - 1; - desc.SizeX = (.)_fontSize; - desc.SizeY = (.)_fontSize; - + if(desc.GlyphIndex == 0 && _missingGlyph == null) { _missingGlyph = desc; } - - penLeft += glyphWidth; - if (penLeft > atlasWidth) - atlasWidth = penLeft; + pen.X += desc.Width + border; - if (glyphHeight > rowHeight) - rowHeight = glyphHeight; + if (pen.X > atlasWidth) + atlasWidth = pen.X; - if (penTop + glyphHeight > atlasHeight) - atlasHeight = penTop + glyphHeight; + if (desc.Height > rowHeight) + rowHeight = desc.Height; - if (penArray == atlasArray) + if (pen.Y + desc.Height > atlasHeight) + atlasHeight = pen.Y + desc.Height; + + if (pen.Z == atlasArray) atlasArray++; desc.IsCalculated = true; @@ -340,37 +332,40 @@ namespace GlitchyEngine.Renderer.Text var glyphIndex = desc.GlyphIndex; - if(desc.IsRendered || glyphIndex == 0 || desc.SizeX == 0 || desc.SizeY == 0) + if(desc.IsRendered || glyphIndex == 0 || desc.Width == 0 || desc.Height == 0) { continue; } - DoTheThing(desc); + GenerateMSDF(desc); desc.IsRendered = true; } } - void DoTheThing(GlyphDescriptor desc) + bool Calculate(ref GlyphDescriptor desc) { // prepare shape - + double advance = 0; Shape shape; - msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance); + if(!msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance) || !shape.Validate()) + { + return false; + } + + desc.Advance = (float)(advance * _geometryScaler); shape.Normalize(); var bounds = shape.GetBounds(); - - msdfgen.EdgeColoringSimple(shape, 3.0); // prepare projection - int width; - int height; - + double width; + double height; + double translationX; double translationY; @@ -385,14 +380,16 @@ namespace GlitchyEngine.Renderer.Text b -= 0.5 * _range; r += 0.5 * _range; t += 0.5 * _range; - - // TODO: miter?! + + // TODO: miter? + //if (miterLimit > 0) + // shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); double w = _geometryScaler * (r - l); double h = _geometryScaler * (t - b); - width = (int)Math.Ceiling(w) + 1; - height = (int)Math.Ceiling(h) + 1; + width = Math.Ceiling(w) + 1; + height = Math.Ceiling(h) + 1; translationX = -l + 0.5 * (width - w) / _geometryScaler; translationY = -b + 0.5 * (height - h) / _geometryScaler; @@ -405,59 +402,66 @@ namespace GlitchyEngine.Renderer.Text translationY = 0; } + desc.Width = (.)width; + desc.Height = (.)height; + + desc.TranslationX = translationX; + desc.TranslationY = translationY; + + //desc.AdjustToBaseLine = (float)(- height + _range + (_face.glyph.metrics.height / 64 - _face.glyph.metrics.horiBearingY / 64) * _geometryScaler); + //(float)(- height + translationY * _geometryScaler + (_face.glyph.metrics.height / 64 - _face.glyph.metrics.horiBearingY / 64) * _geometryScaler); + desc.AdjustToBaseLine = (float)(-height + translationY * _geometryScaler); + + //desc.AdjustToPen = (float)(- _range + (_face.glyph.metrics.horiBearingX / 64) * _geometryScaler); + desc.AdjustToPen = (float)(-translationX); + + return true; + } + + void GenerateMSDF(GlyphDescriptor desc) + { + // prepare shape + + double advance = 0; + + Shape shape; + msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance); + + shape.Normalize(); + + var bounds = shape.GetBounds(); + + shape.ReverseIfNeeded(bounds); + + msdfgen.EdgeColoringSimple(shape, 3.0); + + // prepare projection msdfgen.Projection projection = .(); projection.ScaleX = _geometryScaler; projection.ScaleY = _geometryScaler; - projection.TranslationX = translationX; - projection.TranslationY = translationY; + projection.TranslationX = desc.TranslationX; + projection.TranslationY = desc.TranslationY; - /* + int bufferX = desc.Width; + int bufferY = desc.Height; - void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) { - scale *= geometryScale; - range /= geometryScale; - box.range = range; - box.scale = scale; - if (bounds.l < bounds.r && bounds.b < bounds.t) { - double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; - l -= .5*range, b -= .5*range; - r += .5*range, t += .5*range; - if (miterLimit > 0) - shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); - double w = scale*(r-l); - double h = scale*(t-b); - box.rect.w = (int) ceil(w)+1; - box.rect.h = (int) ceil(h)+1; - box.translate.x = -l+.5*(box.rect.w-w)/scale; - box.translate.y = -b+.5*(box.rect.h-h)/scale; - } else { - box.rect.w = 0, box.rect.h = 0; - box.translate = msdfgen::Vector2(); - } - } - - */ - - int x = _fontSize; - int y = _fontSize; - - Bitmap bitmap = .((.)x, (.)y); + Bitmap bitmap = .((.)bufferX, (.)bufferY); MSDFGeneratorConfig config = .(); msdfgen.GenerateMSDF(*(Bitmap*)&bitmap, shape, projection, _range, config); - int8[] pixels = new int8[x * y * 4]; + int8[] pixels = new int8[desc.Width * desc.Height * 4]; int8 ToInt8(float f) => (.)Math.Clamp(127f * f, int8.MinValue, int8.MaxValue); - for(int iy = 0; iy < y; iy++) - for(int ix = 0; ix < x; ix++) + for(int y = 0; y < desc.Height; y++) + for(int x = 0; x < desc.Width; x++) { - ColorRGB pixel = bitmap.Pixels[iy * x + ix]; + ColorRGB pixel = bitmap.Pixels[(y) * bufferX + x]; - int index = ((y - iy - 1) * x + ix) * 4; + int index = ((desc.Height - y - 1) * desc.Width + x) * 4; pixels[index + 0] = ToInt8(pixel.Red); pixels[index + 1] = ToInt8(pixel.Green); @@ -467,7 +471,7 @@ namespace GlitchyEngine.Renderer.Text } _atlas.SetData((Color*)pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y, - (.)desc.SizeX, (.)desc.SizeY, (.)desc.MapCoord.Z); + (.)desc.Width, (.)desc.Height, (.)desc.MapCoord.Z); } } } diff --git a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf index b4f90a1..e4f4a78 100644 --- a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf +++ b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf @@ -21,38 +21,58 @@ namespace GlitchyEngine.Renderer.Text } } + public static Effect _msdfEffect; + static this() { FontRenderer.InitLibrary(); } + public static void Init(EffectLibrary effectLibrary) + { + if(effectLibrary.Exists("msdfShader")) + _msdfEffect = effectLibrary.Get("msdfShader"); + else + _msdfEffect = effectLibrary.Load("content\\Shaders\\msdfShader.hlsl"); + } + + public static void DeInit() + { + _msdfEffect?.ReleaseRef(); + _msdfEffect = null; + } + /** @brief Draws a given text using a specified font stack and renderer. * @param renderer The 2D renderer that will be used to draw the text. * @param font the Fontstack that will be used to draw the text. * @param x The horizontal position of the text (i.e. distance between left side of viewport and the left side of the left-most character, well not really but close enough). * @param y The vertical position of the text (i.e. distance between top side of viewport and the top side of the top-most character, well not really but close enough). + * @param fontSize The font size in pixels. Note: due to technical reasons the actual size might deviate from the specified size. * @param fontColor The (default) color used for glyphs that don't have color (e.g. letters or emojies if the font doesn't use bitmaps for them). * @param bitmapColor The (default) color used for glyphs that are bitmaps (e.g. emojies, if the font provies them as bitmap). - * @param fontSize The font size in pixels. If set to 0 the default size of the font will be used. Note: due to technical reasons the actual size might deviate from the specified size. * @param lineGapOffset Can be used to manually increase or decrease the gap between lines. */ - public static void DrawText(Renderer2D renderer, Font font, String text, float x, float y, Color fontColor = .White, Color bitmapColor = .White, float fontSize = 0, float lineGapOffset = 0) + public static void DrawText(Renderer2D renderer, Font font, String text, float x, float y, float fontSize, Color fontColor = .White, Color bitmapColor = .White, float lineGapOffset = 0) { if(text.IsWhiteSpace) return; - - float scale = 1.0f; - - if(fontSize != 0) - { - scale = (float)fontSize / (float)font._fontSize; - } + + renderer.End(); + var lastEffect = renderer.[Friend]_currentEffect; + renderer.[Friend]_currentEffect = _msdfEffect..AddRef(); + + float scale = (float)fontSize / (float)font._fontSize; + + _msdfEffect.Variables["screenPixelRange"].SetData(scale * 4.0f); // Space between two baselines float linespace = (((font._face.size.metrics.ascender - font._face.size.metrics.descender) / 64) + lineGapOffset) * scale; // The line we are writing on float baseline = y + linespace; + + //renderer.Draw(null, x, baseline, 10000, 1, .Blue); + // Position of the next character on the line float penPosition = x; @@ -68,7 +88,8 @@ namespace GlitchyEngine.Renderer.Text // smallest possible increase a float can represent. // Note: This might do funky stuff when objects are very close to each other. But realistically whe have to do // about 8 million (2^23) increments to reach a depth of 1 so I think it's safe enough. - int32 depthInt = 0; + float f = 1.0f; + int32 depthInt = *(int32*)&f; // enumerate through the unicode codepoints for(char32 char in text.DecodedChars) @@ -91,6 +112,8 @@ namespace GlitchyEngine.Renderer.Text movedLines = 0; } + + //renderer.Draw(null, penPosition, baseline - fontSize, 1, fontSize, .Lime); // Get glyph from Font var glyphDesc = font.GetGlyph(char); @@ -98,7 +121,11 @@ namespace GlitchyEngine.Renderer.Text // This never happens Debug.Assert(glyphDesc != null); - Texture2D atlas = glyphDesc.Font._atlas; + // The glyph might belong to a fallback font + var glyphFont = glyphDesc.Font; + float glyphFontScale = (float)fontSize / glyphFont._fontSize; + + Texture2D atlas = glyphFont._atlas; // Add reference to atlas in case it is recreated during rendering if(!atlasses.Contains(atlas)) @@ -108,26 +135,21 @@ namespace GlitchyEngine.Renderer.Text Vector2 atlasSize = .(atlas.Width, atlas.Height); - float geoToTex = (float)glyphDesc.Font.[Friend]_geometryScaler; + float adjustToPenX = glyphDesc.AdjustToPen; + adjustToPenX *= glyphFontScale; - float adjustToPenX = - 4.0f / geoToTex + (glyphDesc.Metrics.horiBearingX / 64); - adjustToPenX *= (float)fontSize / glyphDesc.Font._fontSize; - - float adjustToBaseline = glyphDesc.Font._fontSize - 4.0f / geoToTex - - (glyphDesc.Metrics.height / 64) * geoToTex + - (glyphDesc.Metrics.horiBearingY / 64) * geoToTex; - - adjustToBaseline *= (float)fontSize / glyphDesc.Font._fontSize; + float adjustToBaseline = glyphDesc.AdjustToBaseLine; + adjustToBaseline *= glyphFontScale; // Rectangle on the screen Vector4 viewportRect = .( penPosition + adjustToPenX, - baseline - adjustToBaseline, - glyphDesc.SizeX * scale, - glyphDesc.SizeY * scale); + baseline + adjustToBaseline, + glyphDesc.Width * glyphFontScale, + glyphDesc.Height * glyphFontScale); // Rectangle on the font atlas - Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.SizeX, glyphDesc.SizeY); + Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.Width, glyphDesc.Height); Color glyphColor = glyphDesc.IsBitmap ? bitmapColor : fontColor; @@ -135,13 +157,16 @@ namespace GlitchyEngine.Renderer.Text renderer.Draw(atlas, viewportRect.X, viewportRect.Y, viewportRect.Z, viewportRect.W, glyphColor, *(float*)(&depthInt), texRect); - penPosition += (glyphDesc.Metrics.horiAdvance / 64) * scale; + penPosition += glyphDesc.Advance * glyphFontScale; // Increase depth for the next glyph - depthInt++; + depthInt--; } renderer.End(); + + _msdfEffect.ReleaseRef(); + renderer.[Friend]_currentEffect = lastEffect; // release all atlas textures for(int i < atlasses.Count) diff --git a/GlitchyEngine/vendor/msdfgen b/GlitchyEngine/vendor/msdfgen index 87cdeae..634a8ee 160000 --- a/GlitchyEngine/vendor/msdfgen +++ b/GlitchyEngine/vendor/msdfgen @@ -1 +1 @@ -Subproject commit 87cdeae2bceab94e592f14ae0ef1335452e513f7 +Subproject commit 634a8ee221ada9f92df8ef00ced07e2bd9a31a67 diff --git a/Sandbox/content/Shaders/msdfShader.hlsl b/Sandbox/content/Shaders/msdfShader.hlsl index 140cdb0..635de11 100644 --- a/Sandbox/content/Shaders/msdfShader.hlsl +++ b/Sandbox/content/Shaders/msdfShader.hlsl @@ -42,8 +42,7 @@ float4 PS(PS_Input input) : SV_Target0 float sd = median(msd.r, msd.g, msd.b); float screenPxDistance = screenPixelRange * (sd - 0.5); float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); - //color = mix(bgColor, fgColor, opacity); - //float4 color = lerp(float4(0, 0, 0, 0), input.Color, opacity); + //float4 color = lerp(float4(1, 0, 0, 1), input.Color, opacity); //return color; return float4(input.Color.rgb, opacity * input.Color.a); diff --git a/Sandbox/src/SandboxApp2D.bf b/Sandbox/src/SandboxApp2D.bf index d381ab2..ac80c09 100644 --- a/Sandbox/src/SandboxApp2D.bf +++ b/Sandbox/src/SandboxApp2D.bf @@ -64,8 +64,6 @@ namespace Sandbox String testText ~ delete _; - Effect _msdfEffect ~ _.ReleaseRef(); - TextureViewer _textureViewer ~ delete _; [AllowAppend] @@ -137,9 +135,14 @@ namespace Sandbox // Load test text File.ReadAllText("test.txt", testText = new String(), true); - _msdfEffect = _effectLibrary.Load("content\\Shaders\\msdfShader.hlsl"); - _textureViewer = new TextureViewer(_context, Renderer2D, _effectLibrary); + + FontRenderer.Init(_effectLibrary); + } + + public ~this() + { + FontRenderer.DeInit(); } Font fonty ~ _.ReleaseRef(); @@ -222,11 +225,6 @@ namespace Sandbox public override void Update(GameTime gameTime) { - if(Input.IsKeyPressing(Key.P)) - { - fonty.RedrawAtlas(); - } - var cameraTransform = _world.GetComponent(_cameraEntity); if(Application.Get().Window.IsActive) @@ -328,19 +326,12 @@ namespace Sandbox _alphaBlendState.Bind(); Renderer2D.Begin(.FrontToBack, .(_context.SwapChain.Width, _context.SwapChain.Height)); - //Renderer2D.Draw(null, 0, 0, fonty.[Friend]_atlas.Width * 10, fonty.[Friend]_atlas.Height * 10, .Red, 5); - - // Hallo Welt, wie geht es dir? 😂😂😂\nMir geht es gut, danke der Nachfrage. Wie geht es dir?\nMir geht es hervorragend und besser noch: meine Engine kann endlich Text rendern!💕❤\n und es scheint ganz in ordnung zu sein. - //FontRenderer.DrawText(Renderer2D, fonty, testText, 0, 0, .White, .White, 32); - Renderer2D.End(); - _msdfEffect.Variables["screenPixelRange"].SetData(size / 64f * 4.0f); - _alphaBlendState.Bind(); - Renderer2D.Begin(.FrontToBack, .(_context.SwapChain.Width, _context.SwapChain.Height), 100, _msdfEffect); + Renderer2D.Begin(.FrontToBack, .(_context.SwapChain.Width, _context.SwapChain.Height), 100); - FontRenderer.DrawText(Renderer2D, fonty, "Hallo", 0, 0, .White, .White, 256); + FontRenderer.DrawText(Renderer2D, fonty, "Hallo! gjy", 0, 0, 512, .White, .White); Renderer2D.End(); }