From ff16690b86fd2b261c2c49e9bed05bcb1e0ba368 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Mon, 13 Oct 2014 21:09:28 +0100 Subject: [PATCH 1/4] Lazily generate buffer in Sheet. The managed byte buffer is created on demand, meaning a newly allocated sheet will not waste memory holding onto the buffer until some changes are actually required to be written. This avoids a newly allocated sheet wasting memory on buffers that do not differ from their backing texture. --- OpenRA.Game/Graphics/Sheet.cs | 49 +++++++++++++------ OpenRA.Game/Graphics/SheetBuilder.cs | 2 +- OpenRA.Game/Graphics/Theater.cs | 2 +- OpenRA.Game/Widgets/VqaPlayerWidget.cs | 4 +- .../Widgets/ColorMixerWidget.cs | 2 +- OpenRA.Mods.Common/Widgets/HueSliderWidget.cs | 2 +- OpenRA.Mods.Common/Widgets/RadarWidget.cs | 8 +-- 7 files changed, 44 insertions(+), 25 deletions(-) diff --git a/OpenRA.Game/Graphics/Sheet.cs b/OpenRA.Game/Graphics/Sheet.cs index 316c5d9790..116bf7f671 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -25,14 +25,25 @@ namespace OpenRA.Graphics byte[] data; public readonly Size Size; - public byte[] Data { get { return data ?? texture.GetData(); } } - public bool Buffered { get { return data != null; } } + public byte[] Data + { + get + { + if (data != null) + return data; + if (texture == null) + data = new byte[4 * Size.Width * Size.Height]; + else + data = texture.GetData(); + releaseBufferOnCommit = false; + return data; + } + } + public bool Buffered { get { return data != null || texture == null; } } - public Sheet(Size size, bool buffered) + public Sheet(Size size) { Size = size; - if (buffered) - data = new byte[4 * Size.Width * Size.Height]; } public Sheet(ITexture texture) @@ -80,7 +91,7 @@ namespace OpenRA.Graphics dirty = true; } - if (Buffered) + if (data != null) { lock (textureLock) { @@ -141,20 +152,28 @@ namespace OpenRA.Graphics public void CommitData() { - 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 (textureLock) - dirty = true; + CommitData(false); } public void ReleaseBuffer() + { + CommitData(true); + } + + void CommitData(bool releaseBuffer) { lock (textureLock) - releaseBufferOnCommit = true; + { + 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."); + + dirty = true; + if (releaseBuffer) + releaseBufferOnCommit = true; + } } } } diff --git a/OpenRA.Game/Graphics/SheetBuilder.cs b/OpenRA.Game/Graphics/SheetBuilder.cs index 45d9148acd..eb1049f65f 100644 --- a/OpenRA.Game/Graphics/SheetBuilder.cs +++ b/OpenRA.Game/Graphics/SheetBuilder.cs @@ -38,7 +38,7 @@ namespace OpenRA.Graphics public static Sheet AllocateSheet() { - return new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize), true); + return new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize)); } public SheetBuilder(SheetType t) diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Game/Graphics/Theater.cs index dc7c9f3b57..f3e20a95cb 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Game/Graphics/Theater.cs @@ -33,7 +33,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), true); + return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize)); }; sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate); diff --git a/OpenRA.Game/Widgets/VqaPlayerWidget.cs b/OpenRA.Game/Widgets/VqaPlayerWidget.cs index 8831a9ddad..d7eca7ce55 100644 --- a/OpenRA.Game/Widgets/VqaPlayerWidget.cs +++ b/OpenRA.Game/Widgets/VqaPlayerWidget.cs @@ -61,7 +61,7 @@ namespace OpenRA.Widgets var size = Math.Max(video.Width, video.Height); var textureSize = Exts.NextPowerOf2(size); - var videoSheet = new Sheet(new Size(textureSize, textureSize), false); + var videoSheet = new Sheet(new Size(textureSize, textureSize)); videoSheet.Texture.ScaleFilter = TextureScaleFilter.Linear; videoSheet.Texture.SetData(video.FrameData); @@ -89,7 +89,7 @@ namespace OpenRA.Widgets for (var y = 0; y < scaledHeight; y += 2) overlay[y, 0] = black; - var overlaySheet = new Sheet(new Size(1, Exts.NextPowerOf2(scaledHeight)), false); + var overlaySheet = new Sheet(new Size(1, Exts.NextPowerOf2(scaledHeight))); overlaySheet.Texture.SetData(overlay); overlaySprite = new Sprite(overlaySheet, new Rectangle(0, 0, 1, scaledHeight), TextureChannel.Alpha); } diff --git a/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs b/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs index 43717ce3e1..38130c9b5f 100644 --- a/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.Common.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); - var mixerSheet = new Sheet(new Size(256, 256), false); + var mixerSheet = new Sheet(new Size(256, 256)); mixerSheet.Texture.SetData(front, 256, 256); mixerSprite = new Sprite(mixerSheet, rect, TextureChannel.Alpha); } diff --git a/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs b/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs index 0e31f6c446..988244c731 100644 --- a/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs +++ b/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs @@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.Widgets using (var hueBitmap = new Bitmap(256, 256)) { - var hueSheet = new Sheet(new Size(256, 256), false); + var hueSheet = new Sheet(new Size(256, 256)); hueSprite = new Sprite(hueSheet, new Rectangle(0, 0, 256, 1), TextureChannel.Alpha); var bitmapData = hueBitmap.LockBits(hueBitmap.Bounds(), diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 6091f50e95..088d2aecff 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -73,14 +73,14 @@ namespace OpenRA.Mods.Common.Widgets { var r = new Rectangle(0, 0, width, height); var s = new Size(terrainBitmap.Width, terrainBitmap.Height); - var terrainSheet = new Sheet(s, false); + var terrainSheet = new Sheet(s); terrainSheet.Texture.SetData(terrainBitmap); terrainSprite = new Sprite(terrainSheet, r, TextureChannel.Alpha); // Data is set in Tick() - 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); + 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); } } From a6f5a21ed4ee66a85b5c48511024f9063bfde89e Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Tue, 14 Oct 2014 17:02:48 +0100 Subject: [PATCH 2/4] Made Sheet.Texture into Sheet.GetTexture() since we will want to call it for side-effects. Do the same for Sheet.Data since it has the side effect of generating a buffer. --- OpenRA.Game/Graphics/Sheet.cs | 20 +++++++------------ OpenRA.Game/Graphics/SpriteFont.cs | 2 +- OpenRA.Game/Graphics/SpriteRenderer.cs | 4 ++-- OpenRA.Game/Graphics/Util.cs | 4 ++-- OpenRA.Game/Graphics/VoxelRenderer.cs | 2 +- OpenRA.Game/Widgets/VqaPlayerWidget.cs | 10 +++++----- .../Widgets/ColorMixerWidget.cs | 4 ++-- OpenRA.Mods.Common/Widgets/HueSliderWidget.cs | 2 +- OpenRA.Mods.Common/Widgets/RadarWidget.cs | 8 ++++---- 9 files changed, 25 insertions(+), 31 deletions(-) diff --git a/OpenRA.Game/Graphics/Sheet.cs b/OpenRA.Game/Graphics/Sheet.cs index 116bf7f671..e3b4c7ee62 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -25,10 +25,8 @@ namespace OpenRA.Graphics byte[] data; public readonly Size Size; - public byte[] Data + public byte[] GetData() { - get - { if (data != null) return data; if (texture == null) @@ -36,8 +34,7 @@ namespace OpenRA.Graphics else data = texture.GetData(); releaseBufferOnCommit = false; - return data; - } + return data; } public bool Buffered { get { return data != null || texture == null; } } @@ -72,15 +69,12 @@ namespace OpenRA.Graphics ReleaseBuffer(); } - public ITexture Texture + public ITexture GetTexture() { // This is only called from the main thread but 'dirty' // is set from other threads too via CommitData(). - get - { - GenerateTexture(); - return texture; - } + GenerateTexture(); + return texture; } void GenerateTexture() @@ -108,7 +102,7 @@ namespace OpenRA.Graphics public Bitmap AsBitmap() { - var d = Data; + var d = GetData(); var dataStride = 4 * Size.Width; var bitmap = new Bitmap(Size.Width, Size.Height); @@ -123,7 +117,7 @@ namespace OpenRA.Graphics public Bitmap AsBitmap(TextureChannel channel, IPalette pal) { - var d = Data; + var d = GetData(); var dataStride = 4 * Size.Width; var bitmap = new Bitmap(Size.Width, Size.Height); var channelOffset = (int)channel; diff --git a/OpenRA.Game/Graphics/SpriteFont.cs b/OpenRA.Game/Graphics/SpriteFont.cs index f246fdae64..562b302855 100644 --- a/OpenRA.Game/Graphics/SpriteFont.cs +++ b/OpenRA.Game/Graphics/SpriteFont.cs @@ -118,7 +118,7 @@ namespace OpenRA.Graphics unsafe { var p = (byte*)bitmap.Buffer; - var dest = s.sheet.Data; + var dest = s.sheet.GetData(); var destStride = s.sheet.Size.Width * 4; for (var j = 0; j < s.size.Y; j++) diff --git a/OpenRA.Game/Graphics/SpriteRenderer.cs b/OpenRA.Game/Graphics/SpriteRenderer.cs index 8c076e7bc5..0ac6b2e82e 100644 --- a/OpenRA.Game/Graphics/SpriteRenderer.cs +++ b/OpenRA.Game/Graphics/SpriteRenderer.cs @@ -32,7 +32,7 @@ namespace OpenRA.Graphics { if (nv > 0) { - shader.SetTexture("DiffuseTexture", currentSheet.Texture); + shader.SetTexture("DiffuseTexture", currentSheet.GetTexture()); renderer.Device.SetBlendMode(currentBlend); shader.Render(() => @@ -109,7 +109,7 @@ namespace OpenRA.Graphics public void DrawVertexBuffer(IVertexBuffer buffer, int start, int length, PrimitiveType type, Sheet sheet) { - shader.SetTexture("DiffuseTexture", sheet.Texture); + shader.SetTexture("DiffuseTexture", sheet.GetTexture()); renderer.Device.SetBlendMode(BlendMode.Alpha); shader.Render(() => renderer.DrawBatch(buffer, start, length, type)); renderer.Device.SetBlendMode(BlendMode.None); diff --git a/OpenRA.Game/Graphics/Util.cs b/OpenRA.Game/Graphics/Util.cs index 1ebb4b3c3e..7dcf6cfc73 100644 --- a/OpenRA.Game/Graphics/Util.cs +++ b/OpenRA.Game/Graphics/Util.cs @@ -43,7 +43,7 @@ namespace OpenRA.Graphics public static void FastCopyIntoChannel(Sprite dest, byte[] src) { FastCopyIntoChannel(dest, 0, src); } public static void FastCopyIntoChannel(Sprite dest, int channelOffset, byte[] src) { - var data = dest.sheet.Data; + var data = dest.sheet.GetData(); var srcStride = dest.bounds.Width; var destStride = dest.sheet.Size.Width * 4; var destOffset = destStride * dest.bounds.Top + dest.bounds.Left * 4 + channelMasks[(int)dest.channel + channelOffset]; @@ -64,7 +64,7 @@ namespace OpenRA.Graphics public static void FastCopyIntoSprite(Sprite dest, Bitmap src) { - var data = dest.sheet.Data; + var data = dest.sheet.GetData(); var dataStride = dest.sheet.Size.Width * 4; var x = dest.bounds.Left * 4; var width = dest.bounds.Width * 4; diff --git a/OpenRA.Game/Graphics/VoxelRenderer.cs b/OpenRA.Game/Graphics/VoxelRenderer.cs index 8ffd3bbb06..3afa4c611b 100644 --- a/OpenRA.Game/Graphics/VoxelRenderer.cs +++ b/OpenRA.Game/Graphics/VoxelRenderer.cs @@ -257,7 +257,7 @@ namespace OpenRA.Graphics float[] ambientLight, float[] diffuseLight, int colorPalette, int normalsPalette) { - shader.SetTexture("DiffuseTexture", renderData.Sheet.Texture); + shader.SetTexture("DiffuseTexture", renderData.Sheet.GetTexture()); shader.SetVec("PaletteRows", (colorPalette + 0.5f) / HardwarePalette.MaxPalettes, (normalsPalette + 0.5f) / HardwarePalette.MaxPalettes); shader.SetMatrix("TransformMatrix", t); diff --git a/OpenRA.Game/Widgets/VqaPlayerWidget.cs b/OpenRA.Game/Widgets/VqaPlayerWidget.cs index d7eca7ce55..1ea729de04 100644 --- a/OpenRA.Game/Widgets/VqaPlayerWidget.cs +++ b/OpenRA.Game/Widgets/VqaPlayerWidget.cs @@ -63,8 +63,8 @@ namespace OpenRA.Widgets var textureSize = Exts.NextPowerOf2(size); var videoSheet = new Sheet(new Size(textureSize, textureSize)); - videoSheet.Texture.ScaleFilter = TextureScaleFilter.Linear; - videoSheet.Texture.SetData(video.FrameData); + videoSheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear; + videoSheet.GetTexture().SetData(video.FrameData); videoSprite = new Sprite(videoSheet, new Rectangle( @@ -90,7 +90,7 @@ namespace OpenRA.Widgets overlay[y, 0] = black; var overlaySheet = new Sheet(new Size(1, Exts.NextPowerOf2(scaledHeight))); - overlaySheet.Texture.SetData(overlay); + overlaySheet.GetTexture().SetData(overlay); overlaySprite = new Sprite(overlaySheet, new Rectangle(0, 0, 1, scaledHeight), TextureChannel.Alpha); } @@ -117,7 +117,7 @@ namespace OpenRA.Widgets while (nextFrame > video.CurrentFrame) { video.AdvanceFrame(); - videoSprite.sheet.Texture.SetData(video.FrameData); + videoSprite.sheet.GetTexture().SetData(video.FrameData); skippedFrames++; } @@ -185,7 +185,7 @@ namespace OpenRA.Widgets paused = true; Sound.StopVideo(); video.Reset(); - videoSprite.sheet.Texture.SetData(video.FrameData); + videoSprite.sheet.GetTexture().SetData(video.FrameData); world.AddFrameEndTask(_ => onComplete()); } } diff --git a/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs b/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs index 38130c9b5f..3e90ec4465 100644 --- a/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs @@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Widgets 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); var mixerSheet = new Sheet(new Size(256, 256)); - mixerSheet.Texture.SetData(front, 256, 256); + mixerSheet.GetTexture().SetData(front, 256, 256); mixerSprite = new Sprite(mixerSheet, rect, TextureChannel.Alpha); } @@ -123,7 +123,7 @@ namespace OpenRA.Mods.Common.Widgets { try { - mixerSprite.sheet.Texture.SetData(front, 256, 256); + mixerSprite.sheet.GetTexture().SetData(front, 256, 256); } finally { diff --git a/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs b/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs index 988244c731..2681d50c78 100644 --- a/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs +++ b/OpenRA.Mods.Common/Widgets/HueSliderWidget.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.Common.Widgets *(c + h) = HSLColor.FromHSV(h / 255f, 1, 1).RGB.ToArgb(); } hueBitmap.UnlockBits(bitmapData); - hueSheet.Texture.SetData(hueBitmap); + hueSheet.GetTexture().SetData(hueBitmap); } } diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 088d2aecff..9bca1134c3 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -74,7 +74,7 @@ namespace OpenRA.Mods.Common.Widgets var r = new Rectangle(0, 0, width, height); var s = new Size(terrainBitmap.Width, terrainBitmap.Height); var terrainSheet = new Sheet(s); - terrainSheet.Texture.SetData(terrainBitmap); + terrainSheet.GetTexture().SetData(terrainBitmap); terrainSprite = new Sprite(terrainSheet, r, TextureChannel.Alpha); // Data is set in Tick() @@ -203,16 +203,16 @@ namespace OpenRA.Mods.Common.Widgets { updateTicks = 12; using (var bitmap = Minimap.CustomTerrainBitmap(world)) - customTerrainSprite.sheet.Texture.SetData(bitmap); + customTerrainSprite.sheet.GetTexture().SetData(bitmap); } if (updateTicks == 8) using (var bitmap = Minimap.ActorsBitmap(world)) - actorSprite.sheet.Texture.SetData(bitmap); + actorSprite.sheet.GetTexture().SetData(bitmap); if (updateTicks == 4) using (var bitmap = Minimap.ShroudBitmap(world)) - shroudSprite.sheet.Texture.SetData(bitmap); + shroudSprite.sheet.GetTexture().SetData(bitmap); // Enable/Disable the radar var enabled = IsEnabled(); From c2b7d9ca5bb7a9fecd96e1e7da9917cf6e71b5e4 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Mon, 13 Oct 2014 21:16:14 +0100 Subject: [PATCH 3/4] Release sheet buffers in SequenceProvider and MapCache. The buffers in SequenceProvider can be freed if Preload is called, since we know everything is loaded. A SequenceProvider is created for each TileSet is use so this saves memory for however many tilesets had been used in the game. This will be at least one for the shellmap, and often more. The MapCache loading thread is kept alive for 5 seconds after it last generated a map (in anticipation of more requests). Once this time expires the thread is allowed to die, as it is unlikely there will be more requests in the short term. At this time it is ideal to force the changes to be committed to the texture so we can release the buffer. As well as marking the buffer for release, we must access the texture to force the changes stored in the buffer to be written to the texture, after which the buffer can be reclaimed. Additionally, when starting the MapCache loading thread we must ensure the buffer is created from the main thread since it may query the texture object which has thread affinity. After that the buffer may be modified freely on the loading thread until released. --- OpenRA.Game/Graphics/SequenceProvider.cs | 2 ++ OpenRA.Game/Graphics/Sheet.cs | 19 ++++++----- OpenRA.Game/Map/MapCache.cs | 40 +++++++++++++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index c2a69b679b..dd250c5fa3 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -62,8 +62,10 @@ namespace OpenRA.Graphics public void Preload() { + SpriteCache.SheetBuilder.Current.CreateBuffer(); foreach (var unitSeq in sequences.Value.Values) foreach (var seq in unitSeq.Value.Values) { } + SpriteCache.SheetBuilder.Current.ReleaseBuffer(); } } diff --git a/OpenRA.Game/Graphics/Sheet.cs b/OpenRA.Game/Graphics/Sheet.cs index e3b4c7ee62..660e4d2ccc 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -27,13 +27,7 @@ namespace OpenRA.Graphics public readonly Size Size; public byte[] GetData() { - if (data != null) - return data; - if (texture == null) - data = new byte[4 * Size.Width * Size.Height]; - else - data = texture.GetData(); - releaseBufferOnCommit = false; + CreateBuffer(); return data; } public bool Buffered { get { return data != null || texture == null; } } @@ -144,6 +138,17 @@ namespace OpenRA.Graphics return bitmap; } + public void CreateBuffer() + { + if (data != null) + return; + if (texture == null) + data = new byte[4 * Size.Width * Size.Height]; + else + data = texture.GetData(); + releaseBufferOnCommit = false; + } + public void CommitData() { CommitData(false); diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index 1792eb5115..cf528f1a99 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -28,6 +28,7 @@ namespace OpenRA readonly ModData modData; readonly SheetBuilder sheetBuilder; Thread previewLoaderThread; + bool previewLoaderThreadShutDown = true; object syncRoot = new object(); Queue generateMinimap = new Queue(); @@ -135,13 +136,20 @@ namespace OpenRA var maxKeepAlive = 5000 / emptyDelay; var keepAlive = maxKeepAlive; - while (keepAlive-- > 0) + for (;;) { List todo; lock (syncRoot) { todo = generateMinimap.Where(p => p.GetMinimap() == null).ToList(); generateMinimap.Clear(); + if (keepAlive > 0) + keepAlive--; + if (keepAlive == 0 && todo.Count == 0) + { + previewLoaderThreadShutDown = true; + break; + } } if (todo.Count == 0) { @@ -170,20 +178,38 @@ namespace OpenRA Thread.Sleep(Environment.ProcessorCount == 1 ? 25 : 5); } } + sheetBuilder.Current.ReleaseBuffer(); + // The buffer is not fully reclaimed until changes are written out to the texture. + // We will access the texture in order to force changes to be written out, allowing the buffer to be freed. + Game.RunAfterTick(() => sheetBuilder.Current.GetTexture()); Log.Write("debug", "MapCache.LoadAsyncInternal ended"); } public void CacheMinimap(MapPreview preview) { + bool launchPreviewLoaderThread; lock (syncRoot) - generateMinimap.Enqueue(preview); - - if (previewLoaderThread == null || !previewLoaderThread.IsAlive) { - previewLoaderThread = new Thread(LoadAsyncInternal); - previewLoaderThread.IsBackground = true; - previewLoaderThread.Start(); + generateMinimap.Enqueue(preview); + launchPreviewLoaderThread = previewLoaderThreadShutDown; + previewLoaderThreadShutDown = false; } + + if (launchPreviewLoaderThread) + Game.RunAfterTick(() => + { + // Wait for any existing thread to exit before starting a new one. + if (previewLoaderThread != null) + previewLoaderThread.Join(); + + sheetBuilder.Current.CreateBuffer(); + previewLoaderThread = new Thread(LoadAsyncInternal) + { + Name = "Map Preview Loader", + IsBackground = true + }; + previewLoaderThread.Start(); + }); } public MapPreview this[string key] From c2d86557f14f98814f89195248bba6c91152563c Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 22 Nov 2014 17:56:19 +0000 Subject: [PATCH 4/4] Use more locking to improve thread-safety in Sheet. --- OpenRA.Game/Graphics/Sheet.cs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/OpenRA.Game/Graphics/Sheet.cs b/OpenRA.Game/Graphics/Sheet.cs index 660e4d2ccc..6a69824bc1 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -73,16 +73,17 @@ namespace OpenRA.Graphics void GenerateTexture() { - if (texture == null) + lock (textureLock) { - texture = Game.Renderer.Device.CreateTexture(); - dirty = true; - } - - if (data != null) - { - lock (textureLock) + if (texture == null) { + texture = Game.Renderer.Device.CreateTexture(); + dirty = true; + } + + if (data != null) + { + if (dirty) { texture.SetData(data, Size.Width, Size.Height); @@ -140,13 +141,16 @@ namespace OpenRA.Graphics public void CreateBuffer() { - if (data != null) - return; - if (texture == null) - data = new byte[4 * Size.Width * Size.Height]; - else - data = texture.GetData(); - releaseBufferOnCommit = false; + lock (textureLock) + { + if (data != null) + return; + if (texture == null) + data = new byte[4 * Size.Width * Size.Height]; + else + data = texture.GetData(); + releaseBufferOnCommit = false; + } } public void CommitData()