diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index f73e84a..78b111d 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -1,5 +1,5 @@ FileVersion = 1 -Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", ImGui = "*", ImGuiImplWin32 = "*", ImGuiImplDX11 = "*", DirectXTK = "*", FreeType = "*", cgltf-beef = "*"} +Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", ImGui = "*", ImGuiImplWin32 = "*", ImGuiImplDX11 = "*", DirectXTK = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*"} [Project] Name = "GlitchyEngine" diff --git a/GlitchyEngine/src/Renderer/Text/Font.bf b/GlitchyEngine/src/Renderer/Text/Font.bf index 51c0ff2..0f8a803 100644 --- a/GlitchyEngine/src/Renderer/Text/Font.bf +++ b/GlitchyEngine/src/Renderer/Text/Font.bf @@ -2,6 +2,7 @@ using System; using FreeType; using GlitchyEngine.Math; using System.Collections; +using msdfgen; using internal GlitchyEngine.Renderer.Text; @@ -44,6 +45,11 @@ namespace GlitchyEngine.Renderer.Text GlyphDescriptor _missingGlyph; private SamplerState _sampler ~ _.ReleaseRef(); + + /// How much we have to scale the geometry to fit it into our desired pixels + private double _geometryScaler; + + private double _range = 4.0; /** * Gets or sets the fallback Font for this Font. @@ -84,6 +90,12 @@ namespace GlitchyEngine.Renderer.Text } } + [Inline] + double F26Dot6ToDouble(int32 value) + { + return (double(value) / 64.0); + } + public this(GraphicsContext context, String fontPath, uint32 fontSize, bool hasColor = true, char32 firstChar = '\0', uint32 charCount = 128, int32 faceIndex = 0) { _context = context..AddRef(); @@ -105,6 +117,11 @@ namespace GlitchyEngine.Renderer.Text res = FreeType.Set_Pixel_Sizes(_face, 0, _fontSize); Log.EngineLogger.Assert(res.Success, scope $"Set_Pixel_Sizes failed({(int)res}): {res}"); + double unitsPerEm = F26Dot6ToDouble(_face.units_per_EM); + _geometryScaler = _fontSize / unitsPerEm; + + _range = 4.0 / _geometryScaler; + LoadGlyphs(firstChar, charCount); } @@ -171,6 +188,21 @@ namespace GlitchyEngine.Renderer.Text DrawAtlas(); } + /// Debug only + public void RedrawAtlas() + { + for(let (char, desc) in _glyphs) + { + desc?.IsCalculated = false; + desc?.IsRendered = false; + } + + _penPos = .Zero; + _lastRowHeight = 0; + + UpdateAtlas(); + } + Int32_3 PrepareAtlas() { const uint32 maxRes = 16384; // D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION @@ -201,13 +233,20 @@ namespace GlitchyEngine.Renderer.Text desc.Metrics = _face.glyph.metrics; - int32 glyphWidth = (desc.Metrics.width / 64) + 1; - int32 glyphHeight = (desc.Metrics.height / 64) + 1; + 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 != 1 || glyphHeight != 1) + if(glyphWidth != border || glyphHeight != border) { - int32 glyphRight = penLeft + glyphWidth; + //int32 glyphRight = penLeft + glyphWidth; + + int32 glyphRight = penLeft + glyphWidth + border; if(glyphRight > maxRes) { @@ -216,7 +255,7 @@ namespace GlitchyEngine.Renderer.Text rowHeight = 0; } - int32 glyphBottom = penTop + glyphHeight; + int32 glyphBottom = penTop + glyphHeight + border; if(glyphBottom > maxRes) { @@ -234,8 +273,10 @@ namespace GlitchyEngine.Renderer.Text } desc.MapCoord = pen; - desc.SizeX = glyphWidth - 1; - desc.SizeY = glyphHeight - 1; + //desc.SizeX = glyphWidth - 1; + //desc.SizeY = glyphHeight - 1; + desc.SizeX = (.)_fontSize; + desc.SizeY = (.)_fontSize; if(desc.GlyphIndex == 0 && _missingGlyph == null) { @@ -262,6 +303,8 @@ namespace GlitchyEngine.Renderer.Text return .(atlasWidth, atlasHeight, atlasArray); } + public bool secondTime = false; + void DrawAtlas() { Int32_3 oldAtlasSize = _atlasSize; @@ -272,7 +315,7 @@ namespace GlitchyEngine.Renderer.Text var oldAtlas = _atlas; Texture2DDesc desc; - desc.Format = .B8G8R8A8_UNorm; + desc.Format = .R8G8B8A8_SNorm; desc.MipLevels = 1; desc.Width = (.)_atlasSize.X; desc.Height = (.)_atlasSize.Y; @@ -302,50 +345,129 @@ namespace GlitchyEngine.Renderer.Text 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(); - } + DoTheThing(desc); desc.IsRendered = true; } } + + void DoTheThing(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(); + + msdfgen.EdgeColoringSimple(shape, 3.0); + + // prepare projection + + int width; + int height; + + double translationX; + double translationY; + + 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; + t += 0.5 * _range; + + // TODO: miter?! + + double w = _geometryScaler * (r - l); + double h = _geometryScaler * (t - b); + + width = (int)Math.Ceiling(w) + 1; + height = (int)Math.Ceiling(h) + 1; + + translationX = -l + 0.5 * (width - w) / _geometryScaler; + translationY = -b + 0.5 * (height - h) / _geometryScaler; + } + else + { + width = 0; + height = 0; + translationX = 0; + translationY = 0; + } + + msdfgen.Projection projection = .(); + projection.ScaleX = _geometryScaler; + projection.ScaleY = _geometryScaler; + + projection.TranslationX = translationX; + projection.TranslationY = translationY; + + /* + + 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); + + MSDFGeneratorConfig config = .(); + + msdfgen.GenerateMSDF(*(Bitmap*)&bitmap, shape, projection, _range, config); + + int8[] pixels = new int8[x * y * 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++) + { + ColorRGB pixel = bitmap.Pixels[iy * x + ix]; + + int index = ((y - iy - 1) * x + ix) * 4; + + pixels[index + 0] = ToInt8(pixel.Red); + pixels[index + 1] = ToInt8(pixel.Green); + pixels[index + 2] = ToInt8(pixel.Blue); + + pixels[index + 3] = Int8.MaxValue; + } + + _atlas.SetData((Color*)pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y, + (.)desc.SizeX, (.)desc.SizeY, (.)desc.MapCoord.Z); + } } } diff --git a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf index 8bd2934..b4f90a1 100644 --- a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf +++ b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf @@ -61,6 +61,15 @@ namespace GlitchyEngine.Renderer.Text List atlasses = scope .(); + // We render every char with a slightly greater depth, so that the chars don't cull each other. + // In order to do that we increment an integer for each glyph and use it as the depth for the next one. + // Whenn passing the depth to the renderer we treat the integer as the binary representation of a float. + // Therefore every increment will increment the mantissa of the float and is therefore equivalent to the + // 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; + // enumerate through the unicode codepoints for(char32 char in text.DecodedChars) { @@ -99,10 +108,21 @@ namespace GlitchyEngine.Renderer.Text Vector2 atlasSize = .(atlas.Width, atlas.Height); + float geoToTex = (float)glyphDesc.Font.[Friend]_geometryScaler; + + 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; + // Rectangle on the screen Vector4 viewportRect = .( - penPosition + (glyphDesc.Metrics.horiBearingX / 64) * scale, - baseline - (glyphDesc.Metrics.horiBearingY / 64) * scale, + penPosition + adjustToPenX, + baseline - adjustToBaseline, glyphDesc.SizeX * scale, glyphDesc.SizeY * scale); @@ -113,9 +133,12 @@ namespace GlitchyEngine.Renderer.Text texRect /= Vector4(atlasSize, atlasSize); - renderer.Draw(atlas, viewportRect.X, viewportRect.Y, viewportRect.Z, viewportRect.W, glyphColor, 0.0f, texRect); + renderer.Draw(atlas, viewportRect.X, viewportRect.Y, viewportRect.Z, viewportRect.W, glyphColor, *(float*)(&depthInt), texRect); penPosition += (glyphDesc.Metrics.horiAdvance / 64) * scale; + + // Increase depth for the next glyph + depthInt++; } renderer.End(); diff --git a/Sandbox/content/Shaders/msdfShader.hlsl b/Sandbox/content/Shaders/msdfShader.hlsl index 4f2e1b3..140cdb0 100644 --- a/Sandbox/content/Shaders/msdfShader.hlsl +++ b/Sandbox/content/Shaders/msdfShader.hlsl @@ -43,9 +43,7 @@ float4 PS(PS_Input input) : SV_Target0 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); - //return color; return float4(input.Color.rgb, opacity * input.Color.a); diff --git a/Sandbox/src/SandboxApp2D.bf b/Sandbox/src/SandboxApp2D.bf index 6834cc6..d381ab2 100644 --- a/Sandbox/src/SandboxApp2D.bf +++ b/Sandbox/src/SandboxApp2D.bf @@ -135,186 +135,13 @@ namespace Sandbox emojis.Fallback = mathstuff..ReleaseRefNoDelete(); // Load test text - var result = File.ReadAllText("test.txt", testText = new String(), true); - //Log.EngineLogger.Assert(result) + File.ReadAllText("test.txt", testText = new String(), true); _msdfEffect = _effectLibrary.Load("content\\Shaders\\msdfShader.hlsl"); _textureViewer = new TextureViewer(_context, Renderer2D, _effectLibrary); } - double F26Dot6ToDouble(uint16 value) - { - return (double(value) / 64.0); - } - - void DoTheThing(FreeType.FT_Face fontFace, double pixelSizeY) - { - double unitsPerEm = F26Dot6ToDouble(fontFace.units_per_EM); - - // How much we have to scale the geometry to fit it into our desired pixels - double geometryScaler = pixelSizeY / unitsPerEm; - - // prepare shape - - double advance = 0; - - var glyphIndex = FreeType.FreeType.Get_Char_Index(fonty.[Friend]_face, 'ß'); - - Shape shape; - msdfgen.LoadGlyph(out shape, &fonty.[Friend]_face, glyphIndex, out advance); - - shape.Normalize(); - - var bounds = shape.GetBounds(); - - msdfgen.EdgeColoringSimple(shape, 3.0); - - // prepare projection - - double scale = geometryScaler; - double range = 4.0 / geometryScaler; - - int width; - int height; - - double translationX; - double translationY; - - 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; - t += 0.5 * range; - - // TODO: miter?! - - double w = scale * (r - l); - double h = scale * (t - b); - - width = (int)Math.Ceiling(w) + 1; - height = (int)Math.Ceiling(h) + 1; - - translationX = -l + 0.5 * (width - w) / scale; - translationY = -b + 0.5 * (height - h) / scale; - } - else - { - width = 0; - height = 0; - translationX = 0; - translationY = 0; - } - - msdfgen.Projection projection = .(); - projection.ScaleX = scale; - projection.ScaleY = scale; - - projection.TranslationX = translationX; - projection.TranslationY = translationY; - - /* - - 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 = (int)pixelSizeY; - int y = (int)pixelSizeY; - - Bitmap bitmap = .((.)x, (.)y); - - MSDFGeneratorConfig config = .(); - - msdfgen.GenerateMSDF(*(Bitmap*)&bitmap, shape, projection, range, config); - - // Save png - /* - uint8[] data = new uint8[x * y * 3]; - - for(int i = 0; i < x * y * 3; i++) - { - float f = bitmap.Pixels[i / 3].Red / 2f + 0.5f; - float f2 = bitmap.Pixels[i / 3].Green / 2f + 0.5f; - float f3 = bitmap.Pixels[i / 3].Blue / 2f + 0.5f; - - data[i++] = (uint8)Math.Clamp(256.0f * f, 0, 255); - data[i++] = (uint8)Math.Clamp(256.0f * f2, 0, 255); - data[i] = (uint8)Math.Clamp(256.0f * f3, 0, 255); - } - - // TODO: free shape - - LodePng.LodePng.EncodeFile("test.png", data.Ptr, (.)x, (.)y, .RGB, 8); - */ - Texture2DDesc desc; - desc.Width = (.)x; - desc.Height = (.)y; - desc.Format = .R8G8B8A8_SNorm; - desc.MipLevels = 1; - desc.Usage = .Immutable; - desc.CpuAccess = .None; - desc.ArraySize = 1; - - _dasTestA = new Texture2D(_context, desc); - - int8[] pixels = new int8[x * y * 4]; - - int8 ToInt8(float f) => (.)Math.Clamp(127f * f, int8.MinValue, int8.MaxValue); - - //for(int i = 0; i < x * y; i++) - for(int iy = 0; iy < y; iy++) - for(int ix = 0; ix < x; ix++) - { - ColorRGB pixel = bitmap.Pixels[iy * y + ix]; - - float f = pixel.Red;// / 2f + 0.5f; - float f2 = pixel.Green;// / 2f + 0.5f; - float f3 = pixel.Blue;// / 2f + 0.5f; - - int index = ((y - iy - 1) * y + ix) * 4; - - pixels[index + 0] = ToInt8(f); - pixels[index + 1] = ToInt8(f2); - pixels[index + 2] = ToInt8(f3); - pixels[index + 3] = 0; - - //pixels[(y - iy - 1) * y + ix] = Color(f, f2, f3); - } - - _dasTestA.SetData((.)pixels.Ptr, 0, 0, (.)x, (.)y); - //_dasTestA.SetData(pixelData.Ptr, 0, 0, (.)x, (.)y); - } - - Texture2D _dasTestA ~ _.ReleaseRef(); - Font fonty ~ _.ReleaseRef(); VertexLayout layout ~ _?.ReleaseRef(); @@ -395,6 +222,11 @@ 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) @@ -496,20 +328,19 @@ namespace Sandbox _alphaBlendState.Bind(); Renderer2D.Begin(.FrontToBack, .(_context.SwapChain.Width, _context.SwapChain.Height)); - Renderer2D.Draw(null, 0, 0, 502, 502, .Black, 5); - + //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); - _msdfEffect.Variables["screenPixelRange"].SetData(size / 64f * 4.0f); - - Renderer2D.Draw(_dasTestA, 1, 1, size, size); - - // TODO: SDFs in Font Atlas packen + FontRenderer.DrawText(Renderer2D, fonty, "Hallo", 0, 0, .White, .White, 256); Renderer2D.End(); } @@ -570,23 +401,6 @@ namespace Sandbox 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]nativeResourceView, .(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();