From c313c52e969889ae0fa7ec2adc5464a5eda097b8 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Wed, 14 Jan 2015 22:33:58 +0000 Subject: [PATCH] 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. --- OpenRA.Game/Graphics/Renderer.cs | 11 ++++++++++- OpenRA.Game/Graphics/SheetBuilder.cs | 6 +++--- OpenRA.Game/Graphics/SpriteFont.cs | 24 +++++++++++------------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs index 04133779ab..6880fa30df 100644 --- a/OpenRA.Game/Graphics/Renderer.cs +++ b/OpenRA.Game/Graphics/Renderer.cs @@ -37,6 +37,8 @@ namespace OpenRA.Graphics readonly Queue> tempBuffers = new Queue>(); readonly Stack scissorState = new Stack(); + SheetBuilder fontSheetBuilder; + Size? lastResolution; int2? lastScroll; float? lastZoom; @@ -94,8 +96,13 @@ namespace OpenRA.Graphics public void InitializeFonts(Manifest m) { using (new Support.PerfTimer("SpriteFonts")) + { + if (fontSheetBuilder != null) + fontSheetBuilder.Dispose(); + fontSheetBuilder = new SheetBuilder(SheetType.BGRA); 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) @@ -246,6 +253,8 @@ namespace OpenRA.Graphics foreach (var buffer in tempBuffers) buffer.Dispose(); tempBuffers.Clear(); + if (fontSheetBuilder != null) + fontSheetBuilder.Dispose(); } } } diff --git a/OpenRA.Game/Graphics/SheetBuilder.cs b/OpenRA.Game/Graphics/SheetBuilder.cs index 10644f58fc..5817f91f53 100644 --- a/OpenRA.Game/Graphics/SheetBuilder.cs +++ b/OpenRA.Game/Graphics/SheetBuilder.cs @@ -31,8 +31,8 @@ namespace OpenRA.Graphics public sealed class SheetBuilder : IDisposable { + public readonly SheetType Type; readonly List sheets = new List(); - readonly SheetType type; readonly Func allocateSheet; Sheet current; @@ -54,7 +54,7 @@ namespace OpenRA.Graphics public SheetBuilder(SheetType t, Func allocateSheet) { channel = TextureChannel.Red; - type = t; + Type = t; current = allocateSheet(); sheets.Add(current); this.allocateSheet = allocateSheet; @@ -93,7 +93,7 @@ namespace OpenRA.Graphics TextureChannel? NextChannel(TextureChannel t) { - var nextChannel = (int)t + (int)type; + var nextChannel = (int)t + (int)Type; if (nextChannel > (int)TextureChannel.Alpha) return null; diff --git a/OpenRA.Game/Graphics/SpriteFont.cs b/OpenRA.Game/Graphics/SpriteFont.cs index af409a0910..b2d1905448 100644 --- a/OpenRA.Game/Graphics/SpriteFont.cs +++ b/OpenRA.Game/Graphics/SpriteFont.cs @@ -19,26 +19,27 @@ namespace OpenRA.Graphics { public class SpriteFont { - static Library library = new Library(); - static SheetBuilder builder; + static readonly Library Library = new Library(); readonly int size; - + readonly SheetBuilder builder; readonly Func lineWidth; + readonly Face face; + readonly Cache, 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); glyphs = new Cache, GlyphInfo>(CreateGlyph, Pair.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 characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance; lineWidth = line => line.Sum(characterWidth); @@ -96,9 +97,6 @@ namespace OpenRA.Graphics return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size); } - Cache, GlyphInfo> glyphs; - Face face; - GlyphInfo CreateGlyph(Pair c) { face.LoadChar(c.First, LoadFlags.Default, LoadTarget.Normal);