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 316c5d9790..6a69824bc1 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -25,14 +25,16 @@ 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[] GetData() + { + CreateBuffer(); + 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) @@ -61,29 +63,27 @@ 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() { - if (texture == null) + lock (textureLock) { - texture = Game.Renderer.Device.CreateTexture(); - dirty = true; - } - - if (Buffered) - { - lock (textureLock) + if (texture == null) { + texture = Game.Renderer.Device.CreateTexture(); + dirty = true; + } + + if (data != null) + { + if (dirty) { texture.SetData(data, Size.Width, Size.Height); @@ -97,7 +97,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); @@ -112,7 +112,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; @@ -139,22 +139,44 @@ namespace OpenRA.Graphics return bitmap; } + public void CreateBuffer() + { + 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() { - 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/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/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/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/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] diff --git a/OpenRA.Game/Widgets/VqaPlayerWidget.cs b/OpenRA.Game/Widgets/VqaPlayerWidget.cs index 8831a9ddad..1ea729de04 100644 --- a/OpenRA.Game/Widgets/VqaPlayerWidget.cs +++ b/OpenRA.Game/Widgets/VqaPlayerWidget.cs @@ -61,10 +61,10 @@ 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); + videoSheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear; + videoSheet.GetTexture().SetData(video.FrameData); videoSprite = new Sprite(videoSheet, new Rectangle( @@ -89,8 +89,8 @@ 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); - overlaySheet.Texture.SetData(overlay); + var overlaySheet = new Sheet(new Size(1, Exts.NextPowerOf2(scaledHeight))); + 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 43717ce3e1..3e90ec4465 100644 --- a/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ColorMixerWidget.cs @@ -51,8 +51,8 @@ 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); - mixerSheet.Texture.SetData(front, 256, 256); + var mixerSheet = new Sheet(new Size(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 0e31f6c446..2681d50c78 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(), @@ -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 6091f50e95..9bca1134c3 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); - terrainSheet.Texture.SetData(terrainBitmap); + var terrainSheet = new Sheet(s); + terrainSheet.GetTexture().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); } } @@ -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();