Avoid a globally shared SheetBuilder in SpriteFont.

A globally shared sheet builder leaks memory and resources between mod switches. Instead, we create and inject the sheet builder during mod startup to ensure we still share the builder across all fonts, but can reclaim it when the mod is unloaded.
This commit is contained in:
RoosterDragon
2015-01-14 22:33:58 +00:00
parent 398af8e513
commit c313c52e96
3 changed files with 24 additions and 17 deletions

View File

@@ -37,6 +37,8 @@ namespace OpenRA.Graphics
readonly Queue<IVertexBuffer<Vertex>> tempBuffers = new Queue<IVertexBuffer<Vertex>>(); readonly Queue<IVertexBuffer<Vertex>> tempBuffers = new Queue<IVertexBuffer<Vertex>>();
readonly Stack<Rectangle> scissorState = new Stack<Rectangle>(); readonly Stack<Rectangle> scissorState = new Stack<Rectangle>();
SheetBuilder fontSheetBuilder;
Size? lastResolution; Size? lastResolution;
int2? lastScroll; int2? lastScroll;
float? lastZoom; float? lastZoom;
@@ -94,8 +96,13 @@ namespace OpenRA.Graphics
public void InitializeFonts(Manifest m) public void InitializeFonts(Manifest m)
{ {
using (new Support.PerfTimer("SpriteFonts")) using (new Support.PerfTimer("SpriteFonts"))
{
if (fontSheetBuilder != null)
fontSheetBuilder.Dispose();
fontSheetBuilder = new SheetBuilder(SheetType.BGRA);
Fonts = m.Fonts.ToDictionary(x => x.Key, Fonts = m.Fonts.ToDictionary(x => x.Key,
x => new SpriteFont(Platform.ResolvePath(x.Value.First), x.Value.Second)).AsReadOnly(); x => new SpriteFont(Platform.ResolvePath(x.Value.First), x.Value.Second, fontSheetBuilder)).AsReadOnly();
}
} }
public void BeginFrame(int2 scroll, float zoom) public void BeginFrame(int2 scroll, float zoom)
@@ -246,6 +253,8 @@ namespace OpenRA.Graphics
foreach (var buffer in tempBuffers) foreach (var buffer in tempBuffers)
buffer.Dispose(); buffer.Dispose();
tempBuffers.Clear(); tempBuffers.Clear();
if (fontSheetBuilder != null)
fontSheetBuilder.Dispose();
} }
} }
} }

View File

@@ -31,8 +31,8 @@ namespace OpenRA.Graphics
public sealed class SheetBuilder : IDisposable public sealed class SheetBuilder : IDisposable
{ {
public readonly SheetType Type;
readonly List<Sheet> sheets = new List<Sheet>(); readonly List<Sheet> sheets = new List<Sheet>();
readonly SheetType type;
readonly Func<Sheet> allocateSheet; readonly Func<Sheet> allocateSheet;
Sheet current; Sheet current;
@@ -54,7 +54,7 @@ namespace OpenRA.Graphics
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet) public SheetBuilder(SheetType t, Func<Sheet> allocateSheet)
{ {
channel = TextureChannel.Red; channel = TextureChannel.Red;
type = t; Type = t;
current = allocateSheet(); current = allocateSheet();
sheets.Add(current); sheets.Add(current);
this.allocateSheet = allocateSheet; this.allocateSheet = allocateSheet;
@@ -93,7 +93,7 @@ namespace OpenRA.Graphics
TextureChannel? NextChannel(TextureChannel t) TextureChannel? NextChannel(TextureChannel t)
{ {
var nextChannel = (int)t + (int)type; var nextChannel = (int)t + (int)Type;
if (nextChannel > (int)TextureChannel.Alpha) if (nextChannel > (int)TextureChannel.Alpha)
return null; return null;

View File

@@ -19,26 +19,27 @@ namespace OpenRA.Graphics
{ {
public class SpriteFont public class SpriteFont
{ {
static Library library = new Library(); static readonly Library Library = new Library();
static SheetBuilder builder;
readonly int size; readonly int size;
readonly SheetBuilder builder;
readonly Func<string, float> lineWidth; readonly Func<string, float> lineWidth;
readonly Face face;
readonly Cache<Pair<char, Color>, GlyphInfo> glyphs;
public SpriteFont(string name, int size) public SpriteFont(string name, int size, SheetBuilder builder)
{ {
this.size = size; if (builder.Type != SheetType.BGRA)
throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
face = new Face(library, name); this.size = size;
this.builder = builder;
face = new Face(Library, name);
face.SetPixelSizes((uint)size, (uint)size); face.SetPixelSizes((uint)size, (uint)size);
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer); glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
// setup a SheetBuilder for our private use
// TODO: SheetBuilder state is leaked between mod switches
if (builder == null)
builder = new SheetBuilder(SheetType.BGRA);
Func<char, float> characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance; Func<char, float> characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance;
lineWidth = line => line.Sum(characterWidth); lineWidth = line => line.Sum(characterWidth);
@@ -96,9 +97,6 @@ namespace OpenRA.Graphics
return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size); return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size);
} }
Cache<Pair<char, Color>, GlyphInfo> glyphs;
Face face;
GlyphInfo CreateGlyph(Pair<char, Color> c) GlyphInfo CreateGlyph(Pair<char, Color> c)
{ {
face.LoadChar(c.First, LoadFlags.Default, LoadTarget.Normal); face.LoadChar(c.First, LoadFlags.Default, LoadTarget.Normal);