From e0a3e8f8fbeb77b109697223a26488cb3351a5f8 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Tue, 7 May 2019 18:05:50 +0300 Subject: [PATCH] (fc0a5b62d) Option to generate font atlases dynamically (rendering new symbols to the atlas as needed). WIP --- .../Source/Fonts/ScalableFont.cs | 158 +++++++++++++++--- .../BarotraumaClient/Source/GUI/GUIStyle.cs | 21 ++- 2 files changed, 157 insertions(+), 22 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs b/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs index 0b813c654..fb01d6910 100644 --- a/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs +++ b/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs @@ -21,6 +21,15 @@ namespace Barotrauma private List textures; private GraphicsDevice graphicsDevice; + private Vector2 currentDynamicAtlasCoords; + private int currentDynamicAtlasNextY; + + public bool DynamicLoading + { + get; + private set; + } + public uint Size { get @@ -47,11 +56,11 @@ namespace Barotrauma } public ScalableFont(XElement element, GraphicsDevice gd = null) - : this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd) + : this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd, element.GetAttributeBool("dynamicloading", false)) { } - public ScalableFont(string filename, uint size, GraphicsDevice gd = null) + public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false) { if (Lib == null) Lib = new Library(); this.filename = filename; @@ -69,12 +78,15 @@ namespace Barotrauma this.face = new Face(Lib, filename); } this.size = size; - this.textures = new List(); - this.texCoords = new Dictionary(); + this.DynamicLoading = dynamicLoading; + this.graphicsDevice = gd; - if (gd != null) RenderAtlas(gd); + if (gd != null && !dynamicLoading) + { + RenderAtlas(gd); + } FontList.Add(this); } @@ -87,8 +99,10 @@ namespace Barotrauma /// Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF. /// Texture dimensions. Default is 512x512. /// Base character used to shift all other characters downwards when rendering. Defaults to T. - public void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 512, uint baseChar = 0x54) + public void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 1024, uint baseChar = 0x54) { + if (DynamicLoading) { return; } + if (charRanges == null) { charRanges = new uint[] { 0x20, 0xFFFF }; @@ -172,11 +186,13 @@ namespace Barotrauma } } - GlyphData newData = new GlyphData(); - newData.advance = (float)face.Glyph.Metrics.HorizontalAdvance; - newData.texIndex = texIndex; - newData.texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight); - newData.drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight*14/10 - face.Glyph.BitmapTop); + GlyphData newData = new GlyphData + { + advance = (float)face.Glyph.Metrics.HorizontalAdvance, + texIndex = texIndex, + texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight), + drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop) + }; texCoords.Add(j, newData); for (int y = 0; y < glyphHeight; y++) @@ -192,14 +208,99 @@ namespace Barotrauma } textures[texIndex].SetData(pixelBuffer); } - - graphicsDevice = gd; } - + + public void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54) + { + if (textures.Count == 0) + { + this.texDims = texDims; + this.baseChar = baseChar; + face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal); + baseHeight = face.Glyph.Metrics.Height.ToInt32(); + textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); + } + + uint glyphIndex = face.GetCharIndex(character); + if (glyphIndex == 0) { return; } + + face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal); + if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0) + { + if (face.Glyph.Metrics.HorizontalAdvance > 0) + { + //glyph is empty, but char still applies advance + GlyphData blankData = new GlyphData(); + blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance; + blankData.texIndex = -1; //indicates no texture because the glyph is empty + texCoords.Add(character, blankData); + } + return; + } + + //stacktrace doesn't really work that well when RenderGlyph throws an exception + face.Glyph.RenderGlyph(RenderMode.Normal); + byte[] bitmap = face.Glyph.Bitmap.BufferData; + int glyphWidth = face.Glyph.Bitmap.Width; + int glyphHeight = bitmap.Length / glyphWidth; + + if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1) + { + throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions"); + } + + //no more room in current texture atlas, create a new one + if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1) + { + currentDynamicAtlasCoords.X = 0; + currentDynamicAtlasCoords.Y = 0; + textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); + } + + currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2); + if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1) + { + currentDynamicAtlasCoords.X = 0; + currentDynamicAtlasCoords.Y += currentDynamicAtlasNextY; + currentDynamicAtlasNextY = 0; + } + //no more room in current texture atlas, create a new one + if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1) + { + currentDynamicAtlasCoords.X = 0; + currentDynamicAtlasCoords.Y = 0; + textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); + } + + GlyphData newData = new GlyphData + { + advance = (float)face.Glyph.Metrics.HorizontalAdvance, + texIndex = textures.Count - 1, + texCoords = new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight), + drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop) + }; + texCoords.Add(character, newData); + + uint[] pixelBuffer = new uint[texDims * texDims]; + textures[newData.texIndex].GetData(pixelBuffer, 0, texDims * texDims); + + for (int y = 0; y < glyphHeight; y++) + { + for (int x = 0; x < glyphWidth; x++) + { + byte byteColor = bitmap[x + y * glyphWidth]; + pixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | byteColor << 16 | byteColor << 8 | byteColor); + } + } + textures[newData.texIndex].SetData(pixelBuffer); + + currentDynamicAtlasCoords.X += glyphWidth + 2; + } + public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth) { - if (textures.Count == 0) return; - + if (textures.Count == 0 && !DynamicLoading) { return; } + int lineNum = 0; Vector2 currentPos = position; Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)); @@ -213,7 +314,13 @@ namespace Barotrauma currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y; continue; } + uint charIndex = text[i]; + if (DynamicLoading && !texCoords.ContainsKey(charIndex)) + { + DynamicRenderAtlas(graphicsDevice, charIndex); + } + if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square { if (gd.texIndex >= 0) @@ -223,7 +330,6 @@ namespace Barotrauma drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y; drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X; - sb.Draw(tex, currentPos + drawOffset, gd.texCoords, color, rotation, origin, scale, se, layerDepth); } currentPos += gd.advance * advanceUnit * scale.X; @@ -238,7 +344,7 @@ namespace Barotrauma public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color) { - if (textures.Count == 0) return; + if (textures.Count == 0 && !DynamicLoading) { return; } Vector2 currentPos = position; for (int i = 0; i < text.Length; i++) @@ -249,7 +355,13 @@ namespace Barotrauma currentPos.Y += baseHeight * 1.8f; continue; } - uint charIndex = text[i]; + + uint charIndex = text[i]; + if (DynamicLoading && !texCoords.ContainsKey(charIndex)) + { + DynamicRenderAtlas(graphicsDevice, charIndex); + } + if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square { if (gd.texIndex >= 0) @@ -281,6 +393,10 @@ namespace Barotrauma continue; } uint charIndex = text[i]; + if (DynamicLoading && !texCoords.ContainsKey(charIndex)) + { + DynamicRenderAtlas(graphicsDevice, charIndex); + } if (texCoords.TryGetValue(charIndex, out GlyphData gd)) { currentLineX += gd.advance; @@ -294,6 +410,10 @@ namespace Barotrauma { Vector2 retVal = Vector2.Zero; retVal.Y = baseHeight * 1.8f; + if (DynamicLoading && !texCoords.ContainsKey(c)) + { + DynamicRenderAtlas(graphicsDevice, c); + } if (texCoords.TryGetValue(c, out GlyphData gd)) { retVal.X = gd.advance; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs index cc32e819a..2eddac84f 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs @@ -137,9 +137,10 @@ namespace Barotrauma private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice) { - string file = GetFontFilePath(element); - uint size = GetFontSize(element); - return new ScalableFont(file, size, graphicsDevice); + string file = GetFontFilePath(element); + uint size = GetFontSize(element); + bool dynamicLoading = GetFontDynamicLoading(element); + return new ScalableFont(file, size, graphicsDevice, dynamicLoading); } private uint GetFontSize(XElement element) @@ -170,6 +171,20 @@ namespace Barotrauma return element.GetAttributeString("file", ""); } + private bool GetFontDynamicLoading(XElement element) + { + foreach (XElement subElement in element.Elements()) + { + if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; } + string language = subElement.GetAttributeString("language", "").ToLowerInvariant(); + if (GameMain.Config.Language.ToLowerInvariant() == language) + { + return subElement.GetAttributeBool("dynamicloading", false); + } + } + return element.GetAttributeBool("dynamicloading", false); + } + public GUIComponentStyle GetComponentStyle(string name) { componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style);