From 3a30748f0572720e96b495bfb0a3dc050c9538d2 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sun, 22 Jun 2014 16:01:55 +0100 Subject: [PATCH] Reduce working set by releasing buffers for sheets. Sheets carry a managed buffer of data that allows updates to be made without having to constantly fetch and set data to the texture memory of the video card. This is useful for things like SheetBuilder which make small progressive changes to sheets. However these buffers are often large and are kept alive because sheets are referenced by the sprites that use them. If this buffer is explicitly null'ed when it is no longer needed then the GC can reclaim it. Sometimes a buffer need not even be created because the object using the sheet only works on the texture directly anyway. In practise, this reduced memory consumed by such buffers from ~165 MiB to ~112 MiB (at the start of a new RA skirmish mission). --- OpenRA.Game/Game.cs | 2 + OpenRA.Game/Graphics/CursorProvider.cs | 1 + OpenRA.Game/Graphics/Sheet.cs | 56 +++++++++++++++------- OpenRA.Game/Graphics/SheetBuilder.cs | 11 +++-- OpenRA.Game/Graphics/Theater.cs | 6 ++- OpenRA.Game/Graphics/VoxelLoader.cs | 6 +++ OpenRA.Game/ModData.cs | 5 +- OpenRA.Game/Widgets/VqaPlayerWidget.cs | 36 +++++++------- OpenRA.Mods.RA/Widgets/ColorMixerWidget.cs | 5 +- OpenRA.Mods.RA/Widgets/HueSliderWidget.cs | 15 +++--- OpenRA.Mods.RA/Widgets/RadarWidget.cs | 11 +++-- 11 files changed, 97 insertions(+), 57 deletions(-) diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index bc82294d46..b3ed7c2915 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -271,6 +271,8 @@ namespace OpenRA orderManager.LastTickTime = Environment.TickCount; orderManager.StartGame(); worldRenderer.RefreshPalette(); + + GC.Collect(); } public static bool IsHost diff --git a/OpenRA.Game/Graphics/CursorProvider.cs b/OpenRA.Game/Graphics/CursorProvider.cs index 05a3f082ec..d84fc07ee7 100644 --- a/OpenRA.Game/Graphics/CursorProvider.cs +++ b/OpenRA.Game/Graphics/CursorProvider.cs @@ -46,6 +46,7 @@ namespace OpenRA.Graphics var spriteLoader = new SpriteLoader(new string[0], new SheetBuilder(SheetType.Indexed)); foreach (var s in nodesDict["Cursors"].Nodes) LoadSequencesForCursor(spriteLoader, s.Key, s.Value); + spriteLoader.SheetBuilder.Current.ReleaseBuffer(); palette.Initialize(); } diff --git a/OpenRA.Game/Graphics/Sheet.cs b/OpenRA.Game/Graphics/Sheet.cs index 893d585300..316c5d9790 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -18,18 +18,21 @@ namespace OpenRA.Graphics { public class Sheet { - ITexture texture; + readonly object textureLock = new object(); bool dirty; - readonly byte[] data; - readonly object dirtyLock = new object(); + bool releaseBufferOnCommit; + ITexture texture; + byte[] data; public readonly Size Size; public byte[] Data { get { return data ?? texture.GetData(); } } + public bool Buffered { get { return data != null; } } - public Sheet(Size size) + public Sheet(Size size, bool buffered) { Size = size; - data = new byte[4 * Size.Width * Size.Height]; + if (buffered) + data = new byte[4 * Size.Width * Size.Height]; } public Sheet(ITexture texture) @@ -54,6 +57,8 @@ namespace OpenRA.Graphics Marshal.Copy(IntPtr.Add(bd.Scan0, y * bd.Stride), data, y * dataStride, dataStride); bitmap.UnlockBits(bd); } + + ReleaseBuffer(); } public ITexture Texture @@ -62,22 +67,31 @@ namespace OpenRA.Graphics // is set from other threads too via CommitData(). get { - if (texture == null) - { - texture = Game.Renderer.Device.CreateTexture(); - dirty = true; - } + GenerateTexture(); + return texture; + } + } - lock (dirtyLock) + void GenerateTexture() + { + if (texture == null) + { + texture = Game.Renderer.Device.CreateTexture(); + dirty = true; + } + + if (Buffered) + { + lock (textureLock) { if (dirty) { texture.SetData(data, Size.Width, Size.Height); dirty = false; + if (releaseBufferOnCommit) + data = null; } } - - return texture; } } @@ -119,6 +133,7 @@ namespace OpenRA.Graphics } } } + bitmap.UnlockBits(bd); return bitmap; @@ -126,11 +141,20 @@ namespace OpenRA.Graphics public void CommitData() { - if (data == null) - throw new InvalidOperationException("Texture-wrappers are read-only"); + if (!Buffered) + throw new InvalidOperationException( + "This sheet is unbuffered. You cannot call CommitData on an unbuffered sheet. " + + "If you need to completely replace the texture data you should set data into the texture directly. " + + "If you need to make only small changes to the texture data consider creating a buffered sheet instead."); - lock (dirtyLock) + lock (textureLock) dirty = true; } + + public void ReleaseBuffer() + { + lock (textureLock) + releaseBufferOnCommit = true; + } } } diff --git a/OpenRA.Game/Graphics/SheetBuilder.cs b/OpenRA.Game/Graphics/SheetBuilder.cs index 46e57b56f9..45d9148acd 100644 --- a/OpenRA.Game/Graphics/SheetBuilder.cs +++ b/OpenRA.Game/Graphics/SheetBuilder.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, @@ -17,7 +17,7 @@ namespace OpenRA.Graphics public class SheetOverflowException : Exception { public SheetOverflowException(string message) - : base(message) {} + : base(message) { } } public enum SheetType @@ -38,11 +38,11 @@ namespace OpenRA.Graphics public static Sheet AllocateSheet() { - return new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize)); + return new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize), true); } public SheetBuilder(SheetType t) - : this(t, AllocateSheet) {} + : this(t, AllocateSheet) { } public SheetBuilder(SheetType t, Func allocateSheet) { @@ -109,6 +109,7 @@ namespace OpenRA.Graphics var next = NextChannel(channel); if (next == null) { + current.ReleaseBuffer(); current = allocateSheet(); channel = TextureChannel.Red; } @@ -116,7 +117,7 @@ namespace OpenRA.Graphics channel = next.Value; rowHeight = imageSize.Height; - p = new Point(0,0); + p = new Point(0, 0); } var rect = new Sprite(current, new Rectangle(p, imageSize), spriteOffset, channel, BlendMode.Alpha); diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Game/Graphics/Theater.cs index 141e3ddd02..e3b962160e 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Game/Graphics/Theater.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, @@ -58,7 +58,7 @@ namespace OpenRA.Graphics throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter."); allocated = true; - return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize)); + return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize), true); }; var sourceCache = new Dictionary(); @@ -69,6 +69,8 @@ namespace OpenRA.Graphics // 1x1px transparent tile missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1)); + + Sheet.ReleaseBuffer(); } public Sprite TileSprite(TerrainTile r) diff --git a/OpenRA.Game/Graphics/VoxelLoader.cs b/OpenRA.Game/Graphics/VoxelLoader.cs index b4f7986a65..7ca9760151 100644 --- a/OpenRA.Game/Graphics/VoxelLoader.cs +++ b/OpenRA.Game/Graphics/VoxelLoader.cs @@ -179,6 +179,7 @@ namespace OpenRA.Graphics { // Sheet overflow - allocate a new sheet and try once more Log.Write("debug", "Voxel sheet overflow! Generating new sheet"); + sheetBuilder.Current.ReleaseBuffer(); sheetBuilder = CreateSheetBuilder(); v = GenerateSlicePlanes(l).SelectMany(x => x).ToArray(); } @@ -223,5 +224,10 @@ namespace OpenRA.Graphics { return voxels[Pair.New(vxl, hva)]; } + + public void Finish() + { + sheetBuilder.Current.ReleaseBuffer(); + } } } diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 4f8754ab7f..c5b5fc3a0c 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -87,7 +87,7 @@ namespace OpenRA FieldLoader.Translations = new Dictionary(); return; } - + var yaml = Manifest.Translations.Select(MiniYaml.FromFile).Aggregate(MiniYaml.MergeLiberal); Languages = yaml.Select(t => t.Key).ToArray(); @@ -100,7 +100,7 @@ namespace OpenRA else if (y.Key == Game.Settings.Graphics.DefaultLanguage) defaultTranslations = y.Value.ToDictionary(my => my.Value ?? ""); } - + var translations = new Dictionary(); foreach (var tkv in defaultTranslations.Concat(selectedTranslations)) { @@ -140,6 +140,7 @@ namespace OpenRA map.SequenceProvider.Preload(); VoxelProvider.Initialize(Manifest.VoxelSequences, map.VoxelSequenceDefinitions); + VoxelLoader.Finish(); return map; } diff --git a/OpenRA.Game/Widgets/VqaPlayerWidget.cs b/OpenRA.Game/Widgets/VqaPlayerWidget.cs index cdc8a37d0b..a3a3c40b04 100644 --- a/OpenRA.Game/Widgets/VqaPlayerWidget.cs +++ b/OpenRA.Game/Widgets/VqaPlayerWidget.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, @@ -27,14 +27,13 @@ namespace OpenRA.Widgets bool stopped; bool paused; - Action OnComplete; + Action onComplete; public bool Paused { get { return paused; } } - readonly World world; [ObjectCreator.UseCtor] - public VqaPlayerWidget( World world ) + public VqaPlayerWidget(World world) { this.world = world; } @@ -48,33 +47,35 @@ namespace OpenRA.Widgets stopped = true; paused = true; Sound.StopVideo(); - OnComplete = () => {}; + onComplete = () => { }; cachedVideo = filename; video = new VqaReader(GlobalFileSystem.Open(filename)); - invLength = video.Framerate*1f/video.Frames; + invLength = video.Framerate * 1f / video.Frames; var size = Math.Max(video.Width, video.Height); var textureSize = Exts.NextPowerOf2(size); - videoSprite = new Sprite(new Sheet(new Size(textureSize,textureSize)), new Rectangle( 0, 0, video.Width, video.Height ), TextureChannel.Alpha); - videoSprite.sheet.Texture.SetData(video.FrameData); + var videoSheet = new Sheet(new Size(textureSize, textureSize), false); + videoSheet.Texture.SetData(video.FrameData); + videoSprite = new Sprite(videoSheet, new Rectangle(0, 0, video.Width, video.Height), TextureChannel.Alpha); var scale = Math.Min(RenderBounds.Width / video.Width, RenderBounds.Height / video.Height); - videoOrigin = new float2(RenderBounds.X + (RenderBounds.Width - scale*video.Width)/2, RenderBounds.Y + (RenderBounds.Height - scale*video.Height)/2); + videoOrigin = new float2(RenderBounds.X + (RenderBounds.Width - scale * video.Width) / 2, RenderBounds.Y + (RenderBounds.Height - scale * video.Height) / 2); videoSize = new float2(video.Width * scale, video.Height * scale); if (!DrawOverlay) return; - overlay = new uint[2*textureSize, 2*textureSize]; + overlay = new uint[2 * textureSize, 2 * textureSize]; var black = (uint)255 << 24; for (var y = 0; y < video.Height; y++) for (var x = 0; x < video.Width; x++) - overlay[2*y,x] = black; + overlay[2 * y, x] = black; - overlaySprite = new Sprite(new Sheet(new Size(2*textureSize,2*textureSize)), new Rectangle( 0, 0, video.Width, 2*video.Height ), TextureChannel.Alpha); - overlaySprite.sheet.Texture.SetData(overlay); + var overlaySheet = new Sheet(new Size(2 * textureSize, 2 * textureSize), false); + overlaySheet.Texture.SetData(overlay); + overlaySprite = new Sprite(overlaySheet, new Rectangle(0, 0, video.Width, 2 * video.Height), TextureChannel.Alpha); } public override void Draw() @@ -84,7 +85,7 @@ namespace OpenRA.Widgets if (!(stopped || paused)) { - var nextFrame = (int)float2.Lerp(0, video.Frames, Sound.VideoSeekPosition*invLength); + var nextFrame = (int)float2.Lerp(0, video.Frames, Sound.VideoSeekPosition * invLength); if (nextFrame > video.Frames) { Stop(); @@ -115,12 +116,13 @@ namespace OpenRA.Widgets return true; } } + return false; } public void Play() { - PlayThen(() => {}); + PlayThen(() => { }); } public void PlayThen(Action after) @@ -128,7 +130,7 @@ namespace OpenRA.Widgets if (video == null) return; - OnComplete = after; + onComplete = after; if (stopped) Sound.PlayVideo(video.AudioData); else @@ -156,7 +158,7 @@ namespace OpenRA.Widgets Sound.StopVideo(); video.Reset(); videoSprite.sheet.Texture.SetData(video.FrameData); - world.AddFrameEndTask(_ => OnComplete()); + world.AddFrameEndTask(_ => onComplete()); } } } diff --git a/OpenRA.Mods.RA/Widgets/ColorMixerWidget.cs b/OpenRA.Mods.RA/Widgets/ColorMixerWidget.cs index a3807a6ac1..8548301935 100755 --- a/OpenRA.Mods.RA/Widgets/ColorMixerWidget.cs +++ b/OpenRA.Mods.RA/Widgets/ColorMixerWidget.cs @@ -51,8 +51,9 @@ namespace OpenRA.Mods.RA.Widgets back = new byte[4*256*256]; var rect = new Rectangle((int)(255*SRange[0]), (int)(255*(1 - VRange[1])), (int)(255*(SRange[1] - SRange[0]))+1, (int)(255*(VRange[1] - VRange[0])) + 1); - mixerSprite = new Sprite(new Sheet(new Size(256, 256)), rect, TextureChannel.Alpha); - mixerSprite.sheet.Texture.SetData(front, 256, 256); + var mixerSheet = new Sheet(new Size(256, 256), false); + mixerSheet.Texture.SetData(front, 256, 256); + mixerSprite = new Sprite(mixerSheet, rect, TextureChannel.Alpha); } void GenerateBitmap() diff --git a/OpenRA.Mods.RA/Widgets/HueSliderWidget.cs b/OpenRA.Mods.RA/Widgets/HueSliderWidget.cs index bd50749b66..c5e0cf7514 100755 --- a/OpenRA.Mods.RA/Widgets/HueSliderWidget.cs +++ b/OpenRA.Mods.RA/Widgets/HueSliderWidget.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, @@ -19,8 +19,8 @@ namespace OpenRA.Mods.RA.Widgets { Sprite hueSprite; - public HueSliderWidget() {} - public HueSliderWidget(HueSliderWidget other) : base(other) {} + public HueSliderWidget() { } + public HueSliderWidget(HueSliderWidget other) : base(other) { } public override void Initialize(WidgetArgs args) { @@ -28,7 +28,8 @@ namespace OpenRA.Mods.RA.Widgets using (var hueBitmap = new Bitmap(256, 256)) { - hueSprite = new Sprite(new Sheet(new Size(256, 256)), new Rectangle(0, 0, 256, 1), TextureChannel.Alpha); + var hueSheet = new Sheet(new Size(256, 256), false); + hueSprite = new Sprite(hueSheet, new Rectangle(0, 0, 256, 1), TextureChannel.Alpha); var bitmapData = hueBitmap.LockBits(hueBitmap.Bounds(), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); @@ -39,8 +40,7 @@ namespace OpenRA.Mods.RA.Widgets *(c + h) = HSLColor.FromHSV(h / 255f, 1, 1).RGB.ToArgb(); } hueBitmap.UnlockBits(bitmapData); - - hueSprite.sheet.Texture.SetData(hueBitmap); + hueSheet.Texture.SetData(hueBitmap); } } @@ -54,9 +54,8 @@ namespace OpenRA.Mods.RA.Widgets Game.Renderer.RgbaSpriteRenderer.DrawSprite(hueSprite, ro, new float2(rb.Size)); var sprite = ChromeProvider.GetImage("lobby-bits", "huepicker"); - var pos = RenderOrigin + new int2(PxFromValue(Value).Clamp(0, rb.Width-1) - sprite.bounds.Width/2, (rb.Height-sprite.bounds.Height)/2); + var pos = RenderOrigin + new int2(PxFromValue(Value).Clamp(0, rb.Width - 1) - sprite.bounds.Width / 2, (rb.Height - sprite.bounds.Height) / 2); Game.Renderer.RgbaSpriteRenderer.DrawSprite(sprite, pos); } } } - diff --git a/OpenRA.Mods.RA/Widgets/RadarWidget.cs b/OpenRA.Mods.RA/Widgets/RadarWidget.cs index e7b4fdaa96..708c1d1556 100755 --- a/OpenRA.Mods.RA/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.RA/Widgets/RadarWidget.cs @@ -72,13 +72,14 @@ namespace OpenRA.Mods.RA.Widgets { var r = new Rectangle(0, 0, width, height); var s = new Size(terrainBitmap.Width, terrainBitmap.Height); - terrainSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - terrainSprite.sheet.Texture.SetData(terrainBitmap); + var terrainSheet = new Sheet(s, false); + terrainSheet.Texture.SetData(terrainBitmap); + terrainSprite = new Sprite(terrainSheet, r, TextureChannel.Alpha); // Data is set in Tick() - customTerrainSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - actorSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - shroudSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); + customTerrainSprite = new Sprite(new Sheet(s, false), r, TextureChannel.Alpha); + actorSprite = new Sprite(new Sheet(s, false), r, TextureChannel.Alpha); + shroudSprite = new Sprite(new Sheet(s, false), r, TextureChannel.Alpha); } }