diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 54925f95c4..506ad986e6 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -140,7 +140,10 @@ namespace OpenRA orderManager.World = new World(map, orderManager, isShellmap); orderManager.World.Timestep = Timestep; } + if (worldRenderer != null) + worldRenderer.Dispose(); worldRenderer = new WorldRenderer(orderManager.World); + using (new PerfTimer("LoadComplete")) orderManager.World.LoadComplete(worldRenderer); @@ -215,7 +218,7 @@ namespace OpenRA Settings.Graphics.Renderer = r; try { - Renderer.Initialize(Settings.Graphics.Mode); + Renderer = new Renderer(Settings.Graphics, Settings.Server); break; } catch (Exception e) @@ -225,8 +228,6 @@ namespace OpenRA } } - Renderer = new Renderer(); - try { Sound.Create(Settings.Sound.Engine); @@ -257,12 +258,18 @@ namespace OpenRA BeforeGameStart = () => { }; Ui.ResetAll(); + if (worldRenderer != null) + worldRenderer.Dispose(); worldRenderer = null; if (server != null) server.Shutdown(); if (orderManager != null) orderManager.Dispose(); + if (modData != null) + modData.Dispose(); + modData = null; + // Fall back to default if the mod doesn't exist if (!ModMetadata.AllMods.ContainsKey(mod)) mod = new GameSettings().Mod; @@ -274,7 +281,7 @@ namespace OpenRA Sound.StopVideo(); Sound.Initialize(); - modData = new ModData(mod); + modData = new ModData(mod, true); Renderer.InitializeFonts(modData.Manifest); modData.InitializeLoaders(); using (new PerfTimer("LoadMaps")) @@ -632,8 +639,12 @@ namespace OpenRA if (orderManager != null) orderManager.Dispose(); } - - Renderer.Device.Dispose(); + + if (worldRenderer != null) + worldRenderer.Dispose(); + modData.Dispose(); + ChromeProvider.Deinitialize(); + Renderer.Dispose(); OnQuit(); diff --git a/OpenRA.Game/GameRules/RulesetCache.cs b/OpenRA.Game/GameRules/RulesetCache.cs index 8dccd262b3..0ceecde102 100755 --- a/OpenRA.Game/GameRules/RulesetCache.cs +++ b/OpenRA.Game/GameRules/RulesetCache.cs @@ -17,7 +17,7 @@ using OpenRA.Support; namespace OpenRA { - public class RulesetCache + public sealed class RulesetCache : IDisposable { readonly ModData modData; @@ -137,5 +137,12 @@ namespace OpenRA return items; } + + public void Dispose() + { + foreach (var cache in sequenceCaches.Values) + cache.Dispose(); + sequenceCaches.Clear(); + } } } diff --git a/OpenRA.Game/Graphics/ChromeProvider.cs b/OpenRA.Game/Graphics/ChromeProvider.cs index 0d6fbf66db..f3c665386c 100644 --- a/OpenRA.Game/Graphics/ChromeProvider.cs +++ b/OpenRA.Game/Graphics/ChromeProvider.cs @@ -27,6 +27,8 @@ namespace OpenRA.Graphics public static void Initialize(IEnumerable chromeFiles) { + Deinitialize(); + collections = new Dictionary(); cachedSheets = new Dictionary(); cachedSprites = new Dictionary>(); @@ -37,6 +39,17 @@ namespace OpenRA.Graphics LoadCollection(c.Key, c.Value); } + public static void Deinitialize() + { + if (cachedSheets != null) + foreach (var sheet in cachedSheets.Values) + sheet.Dispose(); + + collections = null; + cachedSheets = null; + cachedSprites = null; + } + public static void Save(string file) { var root = new List(); diff --git a/OpenRA.Game/Graphics/CursorProvider.cs b/OpenRA.Game/Graphics/CursorProvider.cs index 7dd832e4e1..15ca48db5c 100644 --- a/OpenRA.Game/Graphics/CursorProvider.cs +++ b/OpenRA.Game/Graphics/CursorProvider.cs @@ -16,11 +16,12 @@ using OpenRA.Primitives; namespace OpenRA.Graphics { - public class CursorProvider + public sealed class CursorProvider : IDisposable { HardwarePalette palette; Dictionary cursors; Cache palettes; + readonly SheetBuilder sheetBuilder; public static bool CursorViewportZoomed { get { return Game.Settings.Graphics.CursorDouble && Game.Settings.Graphics.PixelDouble; } } @@ -45,10 +46,11 @@ namespace OpenRA.Graphics foreach (var p in nodesDict["Palettes"].Nodes) palette.AddPalette(p.Key, new ImmutablePalette(GlobalFileSystem.Open(p.Value.Value), shadowIndex), false); - var spriteCache = new SpriteCache(modData.SpriteLoaders, new string[0], new SheetBuilder(SheetType.Indexed)); + sheetBuilder = new SheetBuilder(SheetType.Indexed); + var spriteCache = new SpriteCache(modData.SpriteLoaders, new string[0], sheetBuilder); foreach (var s in nodesDict["Cursors"].Nodes) LoadSequencesForCursor(spriteCache, s.Key, s.Value); - spriteCache.SheetBuilder.Current.ReleaseBuffer(); + sheetBuilder.Current.ReleaseBuffer(); palette.Initialize(); } @@ -95,5 +97,11 @@ namespace OpenRA.Graphics throw new InvalidOperationException("Cursor does not have a sequence `{0}`".F(cursor)); } } + + public void Dispose() + { + palette.Dispose(); + sheetBuilder.Dispose(); + } } } diff --git a/OpenRA.Game/Graphics/HardwarePalette.cs b/OpenRA.Game/Graphics/HardwarePalette.cs index 4695d62d04..df10a17b24 100644 --- a/OpenRA.Game/Graphics/HardwarePalette.cs +++ b/OpenRA.Game/Graphics/HardwarePalette.cs @@ -14,7 +14,7 @@ using OpenRA.Traits; namespace OpenRA.Graphics { - public class HardwarePalette + public sealed class HardwarePalette : IDisposable { public const int MaxPalettes = 256; @@ -111,5 +111,10 @@ namespace OpenRA.Graphics modifiedPalette.SetFromPalette(originalPalette); } } + + public void Dispose() + { + Texture.Dispose(); + } } } diff --git a/OpenRA.Game/Graphics/IGraphicsDevice.cs b/OpenRA.Game/Graphics/IGraphicsDevice.cs index 07b78c5085..c3ec766649 100755 --- a/OpenRA.Game/Graphics/IGraphicsDevice.cs +++ b/OpenRA.Game/Graphics/IGraphicsDevice.cs @@ -63,7 +63,7 @@ namespace OpenRA void ReleaseWindowMouseFocus(); } - public interface IVertexBuffer + public interface IVertexBuffer : IDisposable { void Bind(); void SetData(T[] vertices, int length); @@ -80,7 +80,8 @@ namespace OpenRA } public enum TextureScaleFilter { Nearest, Linear } - public interface ITexture + + public interface ITexture : IDisposable { void SetData(Bitmap bitmap); void SetData(uint[,] colors); @@ -90,7 +91,7 @@ namespace OpenRA TextureScaleFilter ScaleFilter { get; set; } } - public interface IFrameBuffer + public interface IFrameBuffer : IDisposable { void Bind(); void Unbind(); diff --git a/OpenRA.Game/Graphics/LineRenderer.cs b/OpenRA.Game/Graphics/LineRenderer.cs index 4c7f240942..6bc4b93323 100644 --- a/OpenRA.Game/Graphics/LineRenderer.cs +++ b/OpenRA.Game/Graphics/LineRenderer.cs @@ -20,13 +20,14 @@ namespace OpenRA.Graphics Renderer renderer; IShader shader; - Vertex[] vertices = new Vertex[Renderer.TempBufferSize]; + readonly Vertex[] vertices; int nv = 0; public LineRenderer(Renderer renderer, IShader shader) { this.renderer = renderer; this.shader = shader; + vertices = new Vertex[renderer.TempBufferSize]; } @@ -71,9 +72,9 @@ namespace OpenRA.Graphics public void DrawLine(float2 start, float2 end, Color startColor, Color endColor) { - Renderer.CurrentBatchRenderer = this; + renderer.CurrentBatchRenderer = this; - if (nv + 2 > Renderer.TempBufferSize) + if (nv + 2 > renderer.TempBufferSize) Flush(); vertices[nv++] = new Vertex(start + offset, diff --git a/OpenRA.Game/Graphics/Minimap.cs b/OpenRA.Game/Graphics/Minimap.cs index 62fdcdaacd..d87eef5a2e 100644 --- a/OpenRA.Game/Graphics/Minimap.cs +++ b/OpenRA.Game/Graphics/Minimap.cs @@ -203,8 +203,8 @@ namespace OpenRA.Graphics public static Bitmap RenderMapPreview(TileSet tileset, Map map, Ruleset resourceRules, bool actualSize) { - var terrain = TerrainBitmap(tileset, map, actualSize); - return AddStaticResources(tileset, map, resourceRules, terrain); + using (var terrain = TerrainBitmap(tileset, map, actualSize)) + return AddStaticResources(tileset, map, resourceRules, terrain); } } } diff --git a/OpenRA.Game/Graphics/QuadRenderer.cs b/OpenRA.Game/Graphics/QuadRenderer.cs index 21a48d3dc0..254064c714 100644 --- a/OpenRA.Game/Graphics/QuadRenderer.cs +++ b/OpenRA.Game/Graphics/QuadRenderer.cs @@ -17,13 +17,14 @@ namespace OpenRA.Graphics Renderer renderer; IShader shader; - Vertex[] vertices = new Vertex[Renderer.TempBufferSize]; + readonly Vertex[] vertices; int nv = 0; public QuadRenderer(Renderer renderer, IShader shader) { this.renderer = renderer; this.shader = shader; + vertices = new Vertex[renderer.TempBufferSize]; } public void Flush() @@ -45,9 +46,9 @@ namespace OpenRA.Graphics public void FillRect(RectangleF rect, Color color) { - Renderer.CurrentBatchRenderer = this; + renderer.CurrentBatchRenderer = this; - if (nv + 4 > Renderer.TempBufferSize) + if (nv + 4 > renderer.TempBufferSize) Flush(); var r = color.R / 255.0f; diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs index 8a99aef2fb..d6818475d2 100644 --- a/OpenRA.Game/Graphics/Renderer.cs +++ b/OpenRA.Game/Graphics/Renderer.cs @@ -18,11 +18,11 @@ using OpenRA.Support; namespace OpenRA.Graphics { - public class Renderer + public sealed class Renderer : IDisposable { - internal static int SheetSize; - internal static int TempBufferSize; - internal static int TempBufferCount; + internal int SheetSize { get; private set; } + internal int TempBufferSize { get; private set; } + internal int TempBufferCount { get; private set; } public SpriteRenderer WorldSpriteRenderer { get; private set; } public SpriteRenderer WorldRgbaSpriteRenderer { get; private set; } @@ -38,11 +38,13 @@ namespace OpenRA.Graphics public Dictionary Fonts; Stack scissorState; - public Renderer() + public Renderer(GraphicSettings graphicSettings, ServerSettings serverSettings) { - TempBufferSize = Game.Settings.Graphics.BatchSize; - TempBufferCount = Game.Settings.Graphics.NumTempBuffers; - SheetSize = Game.Settings.Graphics.SheetSize; + Initialize(graphicSettings, serverSettings); + + TempBufferSize = graphicSettings.BatchSize; + TempBufferCount = graphicSettings.NumTempBuffers; + SheetSize = graphicSettings.SheetSize; scissorState = new Stack(); WorldSpriteRenderer = new SpriteRenderer(this, device.CreateShader("shp")); @@ -142,21 +144,21 @@ namespace OpenRA.Graphics public Size Resolution { get { return device.WindowSize; } } - internal static void Initialize(WindowMode windowMode) + void Initialize(GraphicSettings graphicSettings, ServerSettings serverSettings) { - var resolution = GetResolution(windowMode); + var resolution = GetResolution(graphicSettings); - var renderer = Game.Settings.Server.Dedicated ? "Null" : Game.Settings.Graphics.Renderer; - var rendererPath = Platform.ResolvePath(".", "OpenRA.Renderer." + renderer + ".dll"); + var rendererName = serverSettings.Dedicated ? "Null" : graphicSettings.Renderer; + var rendererPath = Platform.ResolvePath(".", "OpenRA.Renderer." + rendererName + ".dll"); - device = CreateDevice(Assembly.LoadFile(rendererPath), resolution.Width, resolution.Height, windowMode); + device = CreateDevice(Assembly.LoadFile(rendererPath), resolution.Width, resolution.Height, graphicSettings.Mode); } - static Size GetResolution(WindowMode windowmode) + static Size GetResolution(GraphicSettings graphicsSettings) { - var size = (windowmode == WindowMode.Windowed) - ? Game.Settings.Graphics.WindowedSize - : Game.Settings.Graphics.FullscreenSize; + var size = (graphicsSettings.Mode == WindowMode.Windowed) + ? graphicsSettings.WindowedSize + : graphicsSettings.FullscreenSize; return new Size(size.X, size.Y); } @@ -180,8 +182,8 @@ namespace OpenRA.Graphics public interface IBatchRenderer { void Flush(); } - static IBatchRenderer currentBatchRenderer; - public static IBatchRenderer CurrentBatchRenderer + IBatchRenderer currentBatchRenderer; + public IBatchRenderer CurrentBatchRenderer { get { return currentBatchRenderer; } set @@ -240,5 +242,14 @@ namespace OpenRA.Graphics { device.ReleaseWindowMouseFocus(); } + + public void Dispose() + { + Device.Dispose(); + WorldVoxelRenderer.Dispose(); + foreach (var buffer in tempBuffers) + buffer.Dispose(); + tempBuffers.Clear(); + } } } diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index dd250c5fa3..b89559ab52 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -69,7 +69,7 @@ namespace OpenRA.Graphics } } - public class SequenceCache + public sealed class SequenceCache : IDisposable { readonly ModData modData; readonly Lazy spriteCache; @@ -141,5 +141,11 @@ namespace OpenRA.Graphics return new ReadOnlyDictionary(unitSequences); } + + public void Dispose() + { + if (spriteCache.IsValueCreated) + spriteCache.Value.SheetBuilder.Dispose(); + } } } diff --git a/OpenRA.Game/Graphics/Sheet.cs b/OpenRA.Game/Graphics/Sheet.cs index 6a69824bc1..a04db289c7 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -16,7 +16,7 @@ using OpenRA.FileSystem; namespace OpenRA.Graphics { - public class Sheet + public sealed class Sheet : IDisposable { readonly object textureLock = new object(); bool dirty; @@ -178,5 +178,11 @@ namespace OpenRA.Graphics releaseBufferOnCommit = true; } } + + public void Dispose() + { + if (texture != null) + texture.Dispose(); + } } } diff --git a/OpenRA.Game/Graphics/SheetBuilder.cs b/OpenRA.Game/Graphics/SheetBuilder.cs index eb1049f65f..4a1ff5dce6 100644 --- a/OpenRA.Game/Graphics/SheetBuilder.cs +++ b/OpenRA.Game/Graphics/SheetBuilder.cs @@ -9,7 +9,9 @@ #endregion using System; +using System.Collections.Generic; using System.Drawing; +using System.Linq; namespace OpenRA.Graphics { @@ -27,8 +29,9 @@ namespace OpenRA.Graphics BGRA = 4, } - public class SheetBuilder + public sealed class SheetBuilder : IDisposable { + readonly List sheets = new List(); Sheet current; TextureChannel channel; SheetType type; @@ -36,19 +39,23 @@ namespace OpenRA.Graphics Point p; Func allocateSheet; - public static Sheet AllocateSheet() + public static Sheet AllocateSheet(int sheetSize) { - return new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize)); + return new Sheet(new Size(sheetSize, sheetSize)); } public SheetBuilder(SheetType t) - : this(t, AllocateSheet) { } + : this(t, Game.Renderer.SheetSize) { } + + public SheetBuilder(SheetType t, int sheetSize) + : this(t, () => AllocateSheet(sheetSize)) { } public SheetBuilder(SheetType t, Func allocateSheet) { channel = TextureChannel.Red; type = t; current = allocateSheet(); + sheets.Add(current); this.allocateSheet = allocateSheet; } @@ -111,6 +118,7 @@ namespace OpenRA.Graphics { current.ReleaseBuffer(); current = allocateSheet(); + sheets.Add(current); channel = TextureChannel.Red; } else @@ -127,5 +135,12 @@ namespace OpenRA.Graphics } public Sheet Current { get { return current; } } + + public void Dispose() + { + foreach (var sheet in sheets) + sheet.Dispose(); + sheets.Clear(); + } } } diff --git a/OpenRA.Game/Graphics/SpriteRenderer.cs b/OpenRA.Game/Graphics/SpriteRenderer.cs index 0ac6b2e82e..d953128f20 100644 --- a/OpenRA.Game/Graphics/SpriteRenderer.cs +++ b/OpenRA.Game/Graphics/SpriteRenderer.cs @@ -17,8 +17,8 @@ namespace OpenRA.Graphics Renderer renderer; IShader shader; - Vertex[] vertices = new Vertex[Renderer.TempBufferSize]; - Sheet currentSheet = null; + readonly Vertex[] vertices; + Sheet currentSheet; BlendMode currentBlend = BlendMode.Alpha; int nv = 0; @@ -26,6 +26,7 @@ namespace OpenRA.Graphics { this.renderer = renderer; this.shader = shader; + vertices = new Vertex[renderer.TempBufferSize]; } public void Flush() @@ -60,7 +61,7 @@ namespace OpenRA.Graphics void DrawSprite(Sprite s, float2 location, int paletteIndex, float2 size) { - Renderer.CurrentBatchRenderer = this; + renderer.CurrentBatchRenderer = this; if (s.sheet != currentSheet) Flush(); @@ -68,7 +69,7 @@ namespace OpenRA.Graphics if (s.blendMode != currentBlend) Flush(); - if (nv + 4 > Renderer.TempBufferSize) + if (nv + 4 > renderer.TempBufferSize) Flush(); currentBlend = s.blendMode; @@ -90,7 +91,7 @@ namespace OpenRA.Graphics public void DrawSprite(Sprite s, float2 a, float2 b, float2 c, float2 d) { - Renderer.CurrentBatchRenderer = this; + renderer.CurrentBatchRenderer = this; if (s.sheet != currentSheet) Flush(); @@ -98,7 +99,7 @@ namespace OpenRA.Graphics if (s.blendMode != currentBlend) Flush(); - if (nv + 4 > Renderer.TempBufferSize) + if (nv + 4 > renderer.TempBufferSize) Flush(); currentSheet = s.sheet; diff --git a/OpenRA.Game/Graphics/TerrainRenderer.cs b/OpenRA.Game/Graphics/TerrainRenderer.cs index 4aeb3519db..bf01017cc5 100644 --- a/OpenRA.Game/Graphics/TerrainRenderer.cs +++ b/OpenRA.Game/Graphics/TerrainRenderer.cs @@ -8,11 +8,12 @@ */ #endregion +using System; using OpenRA.Traits; namespace OpenRA.Graphics { - class TerrainRenderer + sealed class TerrainRenderer : IDisposable { IVertexBuffer vertexBuffer; @@ -58,5 +59,10 @@ namespace OpenRA.Graphics foreach (var r in world.WorldActor.TraitsImplementing()) r.Render(wr); } + + public void Dispose() + { + vertexBuffer.Dispose(); + } } } diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Game/Graphics/Theater.cs index f3e20a95cb..684f2fb976 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Game/Graphics/Theater.cs @@ -16,7 +16,7 @@ using OpenRA.FileSystem; namespace OpenRA.Graphics { - public class Theater + public sealed class Theater : IDisposable { SheetBuilder sheetBuilder; Dictionary templates; @@ -101,5 +101,10 @@ namespace OpenRA.Graphics } public Sheet Sheet { get { return sheetBuilder.Current; } } + + public void Dispose() + { + sheetBuilder.Dispose(); + } } } diff --git a/OpenRA.Game/Graphics/VoxelLoader.cs b/OpenRA.Game/Graphics/VoxelLoader.cs index 2fc2d2fdf8..5f80528131 100644 --- a/OpenRA.Game/Graphics/VoxelLoader.cs +++ b/OpenRA.Game/Graphics/VoxelLoader.cs @@ -32,7 +32,7 @@ namespace OpenRA.Graphics } } - public class VoxelLoader + public sealed class VoxelLoader : IDisposable { SheetBuilder sheetBuilder; @@ -50,7 +50,7 @@ namespace OpenRA.Graphics if (allocated) throw new SheetOverflowException(""); allocated = true; - return SheetBuilder.AllocateSheet(); + return SheetBuilder.AllocateSheet(Game.Renderer.SheetSize); }; return new SheetBuilder(SheetType.DualIndexed, allocate); @@ -195,6 +195,8 @@ namespace OpenRA.Graphics public void RefreshBuffer() { + if (vertexBuffer != null) + vertexBuffer.Dispose(); vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(totalVertexCount); vertexBuffer.SetData(vertices.SelectMany(v => v).ToArray(), totalVertexCount); cachedVertexCount = totalVertexCount; @@ -230,5 +232,12 @@ namespace OpenRA.Graphics { sheetBuilder.Current.ReleaseBuffer(); } + + public void Dispose() + { + if (vertexBuffer != null) + vertexBuffer.Dispose(); + sheetBuilder.Dispose(); + } } } diff --git a/OpenRA.Game/Graphics/VoxelRenderer.cs b/OpenRA.Game/Graphics/VoxelRenderer.cs index 3afa4c611b..8180cdb7ce 100644 --- a/OpenRA.Game/Graphics/VoxelRenderer.cs +++ b/OpenRA.Game/Graphics/VoxelRenderer.cs @@ -32,7 +32,7 @@ namespace OpenRA.Graphics } } - public class VoxelRenderer + public sealed class VoxelRenderer : IDisposable { Renderer renderer; IShader shader; @@ -68,7 +68,7 @@ namespace OpenRA.Graphics public void SetViewportParams(Size screen, float zoom, int2 scroll) { - var a = 2f / Renderer.SheetSize; + var a = 2f / renderer.SheetSize; var view = new float[] { a, 0, 0, 0, @@ -169,8 +169,8 @@ namespace OpenRA.Graphics var spriteCenter = new float2(sb.Left + sb.Width / 2, sb.Top + sb.Height / 2); var shadowCenter = new float2(ssb.Left + ssb.Width / 2, ssb.Top + ssb.Height / 2); - var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, Renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0); - var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, Renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0); + var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0); + var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0); var correctionTransform = Util.MatrixMultiply(translateMtx, flipMtx); var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, shadowScaleFlipMtx); @@ -330,12 +330,24 @@ namespace OpenRA.Graphics return kv.Key; } - var size = new Size(Renderer.SheetSize, Renderer.SheetSize); + var size = new Size(renderer.SheetSize, renderer.SheetSize); var framebuffer = renderer.Device.CreateFrameBuffer(size); var sheet = new Sheet(framebuffer.Texture); mappedBuffers.Add(sheet, framebuffer); return sheet; } + + public void Dispose() + { + foreach (var kvp in mappedBuffers.Concat(unmappedBuffers)) + { + kvp.Key.Dispose(); + kvp.Value.Dispose(); + } + + mappedBuffers.Clear(); + unmappedBuffers.Clear(); + } } } diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index 049d48a859..4a9d410ef6 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -29,7 +29,7 @@ namespace OpenRA.Graphics } } - public class WorldRenderer + public sealed class WorldRenderer : IDisposable { public readonly World world; public readonly Theater Theater; @@ -251,5 +251,12 @@ namespace OpenRA.Graphics var ts = Game.modData.Manifest.TileSize; return new WPos(1024 * screenPx.X / ts.Width, 1024 * screenPx.Y / ts.Height, 0); } + + public void Dispose() + { + palette.Dispose(); + Theater.Dispose(); + terrainRenderer.Dispose(); + } } } diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index cf528f1a99..2621808439 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -21,7 +21,7 @@ using OpenRA.Primitives; namespace OpenRA { - public class MapCache : IEnumerable + public sealed class MapCache : IEnumerable, IDisposable { public static readonly MapPreview UnknownMap = new MapPreview(null, null); readonly Cache previews; @@ -163,7 +163,13 @@ namespace OpenRA foreach (var p in todo) { // The rendering is thread safe because it only reads from the passed instances and writes to a new bitmap - var bitmap = p.CustomPreview ?? Minimap.RenderMapPreview(modData.DefaultRules.TileSets[p.Map.Tileset], p.Map, modData.DefaultRules, true); + var createdPreview = false; + var bitmap = p.CustomPreview; + if (bitmap == null) + { + createdPreview = true; + bitmap = Minimap.RenderMapPreview(modData.DefaultRules.TileSets[p.Map.Tileset], p.Map, modData.DefaultRules, true); + } // Note: this is not generally thread-safe, but it works here because: // (a) This worker is the only thread writing to this sheet // (b) The main thread is the only thread reading this sheet @@ -172,7 +178,15 @@ namespace OpenRA // the next render cycle. // (d) Any partially written bytes from the next minimap is in an // unallocated area, and will be committed in the next cycle. - p.SetMinimap(sheetBuilder.Add(bitmap)); + try + { + p.SetMinimap(sheetBuilder.Add(bitmap)); + } + finally + { + if (createdPreview) + bitmap.Dispose(); + } // Yuck... But this helps the UI Jank when opening the map selector significantly. Thread.Sleep(Environment.ProcessorCount == 1 ? 25 : 5); @@ -226,5 +240,10 @@ namespace OpenRA { return GetEnumerator(); } + + public void Dispose() + { + sheetBuilder.Dispose(); + } } } diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 288d9ac836..3dcd09796f 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -18,7 +18,7 @@ using OpenRA.Widgets; namespace OpenRA { - public class ModData + public sealed class ModData : IDisposable { public readonly Manifest Manifest; public readonly ObjectCreator ObjectCreator; @@ -33,14 +33,17 @@ namespace OpenRA Lazy defaultRules; public Ruleset DefaultRules { get { return defaultRules.Value; } } - public ModData(string mod) + public ModData(string mod, bool useLoadScreen = false) { Languages = new string[0]; Manifest = new Manifest(mod); ObjectCreator = new ObjectCreator(Manifest); - LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); - LoadScreen.Init(Manifest, Manifest.LoadScreen.ToDictionary(my => my.Value)); - LoadScreen.Display(); + if (useLoadScreen) + { + LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); + LoadScreen.Init(Manifest, Manifest.LoadScreen.ToDictionary(my => my.Value)); + LoadScreen.Display(); + } WidgetLoader = new WidgetLoader(this); RulesetCache = new RulesetCache(this); RulesetCache.LoadingProgress += HandleLoadingProgress; @@ -82,8 +85,13 @@ namespace OpenRA // horribly when you use ModData in unexpected ways. ChromeMetrics.Initialize(Manifest.ChromeMetrics); ChromeProvider.Initialize(Manifest.Chrome); + + if (VoxelLoader != null) + VoxelLoader.Dispose(); VoxelLoader = new VoxelLoader(); + if (CursorProvider != null) + CursorProvider.Dispose(); CursorProvider = new CursorProvider(this); } @@ -130,7 +138,8 @@ namespace OpenRA public Map PrepareMap(string uid) { - LoadScreen.Display(); + if (LoadScreen != null) + LoadScreen.Display(); if (MapCache[uid].Status != MapStatus.Available) throw new InvalidDataException("Invalid map uid: {0}".F(uid)); @@ -157,9 +166,21 @@ namespace OpenRA return map; } + + public void Dispose() + { + if (LoadScreen != null) + LoadScreen.Dispose(); + RulesetCache.Dispose(); + MapCache.Dispose(); + if (VoxelLoader != null) + VoxelLoader.Dispose(); + if (CursorProvider != null) + CursorProvider.Dispose(); + } } - public interface ILoadScreen + public interface ILoadScreen : IDisposable { void Init(Manifest m, Dictionary info); void Display(); diff --git a/OpenRA.Lint/YamlChecker.cs b/OpenRA.Lint/YamlChecker.cs index a076c082ed..11e70ae4e8 100644 --- a/OpenRA.Lint/YamlChecker.cs +++ b/OpenRA.Lint/YamlChecker.cs @@ -55,6 +55,9 @@ namespace OpenRA.Lint FieldLoader.UnknownFieldAction = (s, f) => EmitError("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name)); AppDomain.CurrentDomain.AssemblyResolve += GlobalFileSystem.ResolveAssembly; + Game.Renderer = new Graphics.Renderer( + new GraphicSettings() { Renderer = "Null", NumTempBuffers = 0, SheetSize = 0 }, + new ServerSettings()); Game.modData = new ModData(mod); IEnumerable maps; diff --git a/OpenRA.Mods.Cnc/CncLoadScreen.cs b/OpenRA.Mods.Cnc/CncLoadScreen.cs index b1047a0bde..29d1d9cf06 100644 --- a/OpenRA.Mods.Cnc/CncLoadScreen.cs +++ b/OpenRA.Mods.Cnc/CncLoadScreen.cs @@ -18,10 +18,11 @@ using OpenRA.Widgets; namespace OpenRA.Mods.Cnc { - public class CncLoadScreen : ILoadScreen + public sealed class CncLoadScreen : ILoadScreen { Dictionary loadInfo; Stopwatch loadTimer = Stopwatch.StartNew(); + Sheet sheet; Sprite[] ss; int loadTick; float2 nodPos, gdiPos, evaPos; @@ -39,31 +40,31 @@ namespace OpenRA.Mods.Cnc r = Game.Renderer; if (r == null) return; - var s = new Sheet(Platform.ResolvePath(loadInfo["Image"])); + sheet = new Sheet(Platform.ResolvePath(loadInfo["Image"])); var res = r.Resolution; bounds = new Rectangle(0, 0, res.Width, res.Height); ss = new[] { - new Sprite(s, new Rectangle(161, 128, 62, 33), TextureChannel.Alpha), - new Sprite(s, new Rectangle(161, 223, 62, 33), TextureChannel.Alpha), - new Sprite(s, new Rectangle(128, 161, 33, 62), TextureChannel.Alpha), - new Sprite(s, new Rectangle(223, 161, 33, 62), TextureChannel.Alpha), - new Sprite(s, new Rectangle(128, 128, 33, 33), TextureChannel.Alpha), - new Sprite(s, new Rectangle(223, 128, 33, 33), TextureChannel.Alpha), - new Sprite(s, new Rectangle(128, 223, 33, 33), TextureChannel.Alpha), - new Sprite(s, new Rectangle(223, 223, 33, 33), TextureChannel.Alpha) + new Sprite(sheet, new Rectangle(161, 128, 62, 33), TextureChannel.Alpha), + new Sprite(sheet, new Rectangle(161, 223, 62, 33), TextureChannel.Alpha), + new Sprite(sheet, new Rectangle(128, 161, 33, 62), TextureChannel.Alpha), + new Sprite(sheet, new Rectangle(223, 161, 33, 62), TextureChannel.Alpha), + new Sprite(sheet, new Rectangle(128, 128, 33, 33), TextureChannel.Alpha), + new Sprite(sheet, new Rectangle(223, 128, 33, 33), TextureChannel.Alpha), + new Sprite(sheet, new Rectangle(128, 223, 33, 33), TextureChannel.Alpha), + new Sprite(sheet, new Rectangle(223, 223, 33, 33), TextureChannel.Alpha) }; - nodLogo = new Sprite(s, new Rectangle(0, 256, 256, 256), TextureChannel.Alpha); - gdiLogo = new Sprite(s, new Rectangle(256, 256, 256, 256), TextureChannel.Alpha); - evaLogo = new Sprite(s, new Rectangle(256, 64, 128, 64), TextureChannel.Alpha); + nodLogo = new Sprite(sheet, new Rectangle(0, 256, 256, 256), TextureChannel.Alpha); + gdiLogo = new Sprite(sheet, new Rectangle(256, 256, 256, 256), TextureChannel.Alpha); + evaLogo = new Sprite(sheet, new Rectangle(256, 64, 128, 64), TextureChannel.Alpha); nodPos = new float2(bounds.Width / 2 - 384, bounds.Height / 2 - 128); gdiPos = new float2(bounds.Width / 2 + 128, bounds.Height / 2 - 128); evaPos = new float2(bounds.Width - 43 - 128, 43); - brightBlock = new Sprite(s, new Rectangle(320, 0, 16, 35), TextureChannel.Alpha); - dimBlock = new Sprite(s, new Rectangle(336, 0, 16, 35), TextureChannel.Alpha); + brightBlock = new Sprite(sheet, new Rectangle(320, 0, 16, 35), TextureChannel.Alpha); + dimBlock = new Sprite(sheet, new Rectangle(336, 0, 16, 35), TextureChannel.Alpha); versionText = m.Mod.Version; } @@ -123,5 +124,11 @@ namespace OpenRA.Mods.Cnc { Game.TestAndContinue(); } + + public void Dispose() + { + if (sheet != null) + sheet.Dispose(); + } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/LoadScreens/DefaultLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/DefaultLoadScreen.cs index b10262a0e0..b8ad1c579c 100644 --- a/OpenRA.Mods.Common/LoadScreens/DefaultLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/DefaultLoadScreen.cs @@ -18,13 +18,14 @@ using OpenRA.Widgets; namespace OpenRA.Mods.Common.LoadScreens { - public class DefaultLoadScreen : ILoadScreen + public sealed class DefaultLoadScreen : ILoadScreen { Stopwatch lastUpdate = Stopwatch.StartNew(); Renderer r; Rectangle stripeRect; float2 logoPos; + Sheet sheet; Sprite stripe, logo; string[] messages; @@ -37,9 +38,9 @@ namespace OpenRA.Mods.Common.LoadScreens return; messages = info["Text"].Split(','); - var s = new Sheet(Platform.ResolvePath(info["Image"])); - logo = new Sprite(s, new Rectangle(0, 0, 256, 256), TextureChannel.Alpha); - stripe = new Sprite(s, new Rectangle(256, 0, 256, 256), TextureChannel.Alpha); + sheet = new Sheet(Platform.ResolvePath(info["Image"])); + logo = new Sprite(sheet, new Rectangle(0, 0, 256, 256), TextureChannel.Alpha); + stripe = new Sprite(sheet, new Rectangle(256, 0, 256, 256), TextureChannel.Alpha); stripeRect = new Rectangle(0, r.Resolution.Height / 2 - 128, r.Resolution.Width, 256); logoPos = new float2(r.Resolution.Width / 2 - 128, r.Resolution.Height / 2 - 128); } @@ -71,5 +72,11 @@ namespace OpenRA.Mods.Common.LoadScreens { Game.TestAndContinue(); } + + public void Dispose() + { + if (sheet != null) + sheet.Dispose(); + } } } diff --git a/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs index f5ec655a05..ccc18e204e 100644 --- a/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs @@ -15,7 +15,7 @@ using OpenRA.Widgets; namespace OpenRA.Mods.Common.LoadScreens { - public class ModChooserLoadScreen : ILoadScreen + public sealed class ModChooserLoadScreen : ILoadScreen { Sprite sprite; Rectangle bounds; @@ -43,5 +43,11 @@ namespace OpenRA.Mods.Common.LoadScreens { Ui.LoadWidget("MODCHOOSER", Ui.Root, new WidgetArgs()); } + + public void Dispose() + { + if (sprite != null) + sprite.sheet.Dispose(); + } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/LoadScreens/NullLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/NullLoadScreen.cs index 122a2b1dfe..ce0c7b149c 100644 --- a/OpenRA.Mods.Common/LoadScreens/NullLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/NullLoadScreen.cs @@ -13,7 +13,7 @@ using OpenRA.Widgets; namespace OpenRA.Mods.Common.LoadScreens { - public class NullLoadScreen : ILoadScreen + public sealed class NullLoadScreen : ILoadScreen { public void Init(Manifest m, Dictionary info) { } @@ -31,5 +31,9 @@ namespace OpenRA.Mods.Common.LoadScreens { Ui.ResetAll(); } + + public void Dispose() + { + } } -} \ No newline at end of file +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs index 1ea99373f4..d624827991 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs @@ -23,6 +23,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic Widget modList; ButtonWidget modTemplate; ModMetadata[] allMods; + readonly SheetBuilder sheetBuilder; ModMetadata selectedMod; string selectedAuthor; string selectedDescription; @@ -63,7 +64,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic return ret; }; - var sheetBuilder = new SheetBuilder(SheetType.BGRA); + sheetBuilder = new SheetBuilder(SheetType.BGRA); previews = new Dictionary(); logos = new Dictionary(); allMods = ModMetadata.AllMods.Values.Where(m => m.Id != "modchooser") @@ -75,21 +76,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic { try { - var preview = new Bitmap(Platform.ResolvePath(".", "mods", mod.Id, "preview.png")); - if (preview.Width != 296 || preview.Height != 196) - continue; - - previews.Add(mod.Id, sheetBuilder.Add(preview)); + using (var preview = new Bitmap(Platform.ResolvePath(".", "mods", mod.Id, "preview.png"))) + if (preview.Width == 296 && preview.Height == 196) + previews.Add(mod.Id, sheetBuilder.Add(preview)); } catch (Exception) { } try { - var logo = new Bitmap(Platform.ResolvePath(".", "mods", mod.Id, "logo.png")); - if (logo.Width != 96 || logo.Height != 96) - continue; - - logos.Add(mod.Id, sheetBuilder.Add(logo)); + using (var logo = new Bitmap(Platform.ResolvePath(".", "mods", mod.Id, "logo.png"))) + if (logo.Width == 96 && logo.Height == 96) + logos.Add(mod.Id, sheetBuilder.Add(logo)); } catch (Exception) { } } @@ -155,11 +152,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic modOffset = selectedIndex - 4; } - static void LoadMod(ModMetadata mod) + void LoadMod(ModMetadata mod) { Game.RunAfterTick(() => { Ui.CloseWindow(); + sheetBuilder.Dispose(); Game.InitializeMod(mod.Id, null); }); } diff --git a/OpenRA.Renderer.Null/NullGraphicsDevice.cs b/OpenRA.Renderer.Null/NullGraphicsDevice.cs index 5e2da9c886..299fa0ddea 100644 --- a/OpenRA.Renderer.Null/NullGraphicsDevice.cs +++ b/OpenRA.Renderer.Null/NullGraphicsDevice.cs @@ -78,7 +78,7 @@ namespace OpenRA.Renderer.Null public void Render(Action a) { } } - public class NullTexture : ITexture + public sealed class NullTexture : ITexture { public TextureScaleFilter ScaleFilter { get { return TextureScaleFilter.Nearest; } set { } } public void SetData(Bitmap bitmap) { } @@ -86,18 +86,21 @@ namespace OpenRA.Renderer.Null public void SetData(byte[] colors, int width, int height) { } public byte[] GetData() { return new byte[0]; } public Size Size { get { return new Size(0, 0); } } + public void Dispose() { } } - public class NullFrameBuffer : IFrameBuffer + public sealed class NullFrameBuffer : IFrameBuffer { public void Bind() { } public void Unbind() { } public ITexture Texture { get { return new NullTexture(); } } + public void Dispose() { } } - class NullVertexBuffer : IVertexBuffer + sealed class NullVertexBuffer : IVertexBuffer { public void Bind() { } public void SetData(T[] vertices, int length) { } + public void Dispose() { } } } diff --git a/OpenRA.Renderer.Sdl2/FrameBuffer.cs b/OpenRA.Renderer.Sdl2/FrameBuffer.cs index a640229fdf..9ef1f4aeb2 100644 --- a/OpenRA.Renderer.Sdl2/FrameBuffer.cs +++ b/OpenRA.Renderer.Sdl2/FrameBuffer.cs @@ -16,11 +16,12 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Renderer.Sdl2 { - public class FrameBuffer : IFrameBuffer + public sealed class FrameBuffer : IFrameBuffer { Texture texture; Size size; int framebuffer, depth; + bool disposed; public FrameBuffer(Size size) { @@ -81,16 +82,6 @@ namespace OpenRA.Renderer.Sdl2 return v; } - void FinalizeInner() - { - GL.Ext.DeleteFramebuffers(1, ref framebuffer); - ErrorHandler.CheckGlError(); - GL.Ext.DeleteRenderbuffers(1, ref depth); - ErrorHandler.CheckGlError(); - } - - ~FrameBuffer() { Game.RunAfterTick(FinalizeInner); } - int[] cv = new int[4]; public void Bind() { @@ -120,5 +111,29 @@ namespace OpenRA.Renderer.Sdl2 } public ITexture Texture { get { return texture; } } + + ~FrameBuffer() + { + Game.RunAfterTick(() => Dispose(false)); + } + + public void Dispose() + { + Game.RunAfterTick(() => Dispose(true)); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (disposed) + return; + disposed = true; + if (disposing) + texture.Dispose(); + GL.Ext.DeleteFramebuffers(1, ref framebuffer); + ErrorHandler.CheckGlError(); + GL.Ext.DeleteRenderbuffers(1, ref depth); + ErrorHandler.CheckGlError(); + } } } diff --git a/OpenRA.Renderer.Sdl2/Texture.cs b/OpenRA.Renderer.Sdl2/Texture.cs index de82a06e8e..60efb8c433 100644 --- a/OpenRA.Renderer.Sdl2/Texture.cs +++ b/OpenRA.Renderer.Sdl2/Texture.cs @@ -16,7 +16,7 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Renderer.Sdl2 { - public class Texture : ITexture + public sealed class Texture : ITexture { int texture; TextureScaleFilter scaleFilter; @@ -26,6 +26,8 @@ namespace OpenRA.Renderer.Sdl2 public Size Size { get { return size; } } + bool disposed; + public TextureScaleFilter ScaleFilter { get @@ -55,9 +57,6 @@ namespace OpenRA.Renderer.Sdl2 SetData(bitmap); } - void FinalizeInner() { GL.DeleteTextures(1, ref texture); } - ~Texture() { Game.RunAfterTick(FinalizeInner); } - void PrepareTexture() { ErrorHandler.CheckGlError(); @@ -180,5 +179,24 @@ namespace OpenRA.Renderer.Sdl2 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); ErrorHandler.CheckGlError(); } + + ~Texture() + { + Game.RunAfterTick(() => Dispose(false)); + } + + public void Dispose() + { + Game.RunAfterTick(() => Dispose(true)); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (disposed) + return; + disposed = true; + GL.DeleteTextures(1, ref texture); + } } } diff --git a/OpenRA.Renderer.Sdl2/VertexBuffer.cs b/OpenRA.Renderer.Sdl2/VertexBuffer.cs index a5655e4cd1..c35baa0f7d 100644 --- a/OpenRA.Renderer.Sdl2/VertexBuffer.cs +++ b/OpenRA.Renderer.Sdl2/VertexBuffer.cs @@ -14,11 +14,12 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Renderer.Sdl2 { - public class VertexBuffer : IVertexBuffer + public sealed class VertexBuffer : IVertexBuffer where T : struct { static readonly int VertexSize = Marshal.SizeOf(typeof(T)); int buffer; + bool disposed; public VertexBuffer(int size) { @@ -52,7 +53,23 @@ namespace OpenRA.Renderer.Sdl2 ErrorHandler.CheckGlError(); } - void FinalizeInner() { GL.DeleteBuffers(1, ref buffer); } - ~VertexBuffer() { Game.RunAfterTick(FinalizeInner); } + ~VertexBuffer() + { + Game.RunAfterTick(() => Dispose(false)); + } + + public void Dispose() + { + Game.RunAfterTick(() => Dispose(true)); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (disposed) + return; + disposed = true; + GL.DeleteBuffers(1, ref buffer); + } } }