(fc0a5b62d) Option to generate font atlases dynamically (rendering new symbols to the atlas as needed). WIP

This commit is contained in:
Joonas Rikkonen
2019-05-07 18:05:50 +03:00
parent b83302408f
commit e0a3e8f8fb
2 changed files with 157 additions and 22 deletions
@@ -21,6 +21,15 @@ namespace Barotrauma
private List<Texture2D> textures; private List<Texture2D> textures;
private GraphicsDevice graphicsDevice; private GraphicsDevice graphicsDevice;
private Vector2 currentDynamicAtlasCoords;
private int currentDynamicAtlasNextY;
public bool DynamicLoading
{
get;
private set;
}
public uint Size public uint Size
{ {
get get
@@ -47,11 +56,11 @@ namespace Barotrauma
} }
public ScalableFont(XElement element, GraphicsDevice gd = null) 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(); if (Lib == null) Lib = new Library();
this.filename = filename; this.filename = filename;
@@ -69,12 +78,15 @@ namespace Barotrauma
this.face = new Face(Lib, filename); this.face = new Face(Lib, filename);
} }
this.size = size; this.size = size;
this.textures = new List<Texture2D>(); this.textures = new List<Texture2D>();
this.texCoords = new Dictionary<uint, GlyphData>(); this.texCoords = new Dictionary<uint, GlyphData>();
this.DynamicLoading = dynamicLoading;
this.graphicsDevice = gd;
if (gd != null) RenderAtlas(gd); if (gd != null && !dynamicLoading)
{
RenderAtlas(gd);
}
FontList.Add(this); FontList.Add(this);
} }
@@ -87,8 +99,10 @@ namespace Barotrauma
/// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param> /// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param>
/// <param name="texDims">Texture dimensions. Default is 512x512.</param> /// <param name="texDims">Texture dimensions. Default is 512x512.</param>
/// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param> /// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param>
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) if (charRanges == null)
{ {
charRanges = new uint[] { 0x20, 0xFFFF }; charRanges = new uint[] { 0x20, 0xFFFF };
@@ -172,11 +186,13 @@ namespace Barotrauma
} }
} }
GlyphData newData = new GlyphData(); GlyphData newData = new GlyphData
newData.advance = (float)face.Glyph.Metrics.HorizontalAdvance; {
newData.texIndex = texIndex; advance = (float)face.Glyph.Metrics.HorizontalAdvance,
newData.texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight); texIndex = texIndex,
newData.drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight*14/10 - face.Glyph.BitmapTop); 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); texCoords.Add(j, newData);
for (int y = 0; y < glyphHeight; y++) for (int y = 0; y < glyphHeight; y++)
@@ -192,14 +208,99 @@ namespace Barotrauma
} }
textures[texIndex].SetData<uint>(pixelBuffer); textures[texIndex].SetData<uint>(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<uint>(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<uint>(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) 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; int lineNum = 0;
Vector2 currentPos = position; Vector2 currentPos = position;
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)); 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; currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y;
continue; 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 (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
{ {
if (gd.texIndex >= 0) 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.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; 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); sb.Draw(tex, currentPos + drawOffset, gd.texCoords, color, rotation, origin, scale, se, layerDepth);
} }
currentPos += gd.advance * advanceUnit * scale.X; currentPos += gd.advance * advanceUnit * scale.X;
@@ -238,7 +344,7 @@ namespace Barotrauma
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color) 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; Vector2 currentPos = position;
for (int i = 0; i < text.Length; i++) for (int i = 0; i < text.Length; i++)
@@ -249,7 +355,13 @@ namespace Barotrauma
currentPos.Y += baseHeight * 1.8f; currentPos.Y += baseHeight * 1.8f;
continue; 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 (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
{ {
if (gd.texIndex >= 0) if (gd.texIndex >= 0)
@@ -281,6 +393,10 @@ namespace Barotrauma
continue; continue;
} }
uint charIndex = text[i]; uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd)) if (texCoords.TryGetValue(charIndex, out GlyphData gd))
{ {
currentLineX += gd.advance; currentLineX += gd.advance;
@@ -294,6 +410,10 @@ namespace Barotrauma
{ {
Vector2 retVal = Vector2.Zero; Vector2 retVal = Vector2.Zero;
retVal.Y = baseHeight * 1.8f; retVal.Y = baseHeight * 1.8f;
if (DynamicLoading && !texCoords.ContainsKey(c))
{
DynamicRenderAtlas(graphicsDevice, c);
}
if (texCoords.TryGetValue(c, out GlyphData gd)) if (texCoords.TryGetValue(c, out GlyphData gd))
{ {
retVal.X = gd.advance; retVal.X = gd.advance;
@@ -137,9 +137,10 @@ namespace Barotrauma
private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice) private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice)
{ {
string file = GetFontFilePath(element); string file = GetFontFilePath(element);
uint size = GetFontSize(element); uint size = GetFontSize(element);
return new ScalableFont(file, size, graphicsDevice); bool dynamicLoading = GetFontDynamicLoading(element);
return new ScalableFont(file, size, graphicsDevice, dynamicLoading);
} }
private uint GetFontSize(XElement element) private uint GetFontSize(XElement element)
@@ -170,6 +171,20 @@ namespace Barotrauma
return element.GetAttributeString("file", ""); 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) public GUIComponentStyle GetComponentStyle(string name)
{ {
componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style); componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style);