From 16053d9d043dcee4d460417e8c33d65723c6161e Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sat, 28 May 2022 21:21:25 +0200 Subject: [PATCH] further improve runtime texture packer performance by caching the first possible position for a request of a given size --- CHANGELOG.md | 2 +- MLEM.Data/RuntimeTexturePacker.cs | 20 +++++++++++++------- Sandbox/GameImpl.cs | 4 +++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcce781..faa46f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,7 @@ Improvements - Premultiply textures when using RawContentManager - Allow enumerating all region names of a DataTextureAtlas - Cache RuntimeTexturePacker texture data while packing to improve performance -- Improve RuntimeTexturePacker performance by checking against packed textures in the same order as packing +- Greatly improved RuntimeTexturePacker performance Fixes - Fixed SoundEffectReader incorrectly claiming it could read ogg and mp3 files diff --git a/MLEM.Data/RuntimeTexturePacker.cs b/MLEM.Data/RuntimeTexturePacker.cs index e149c99..630e74c 100644 --- a/MLEM.Data/RuntimeTexturePacker.cs +++ b/MLEM.Data/RuntimeTexturePacker.cs @@ -36,6 +36,7 @@ namespace MLEM.Data { private readonly List texturesToPack = new List(); private readonly List alreadyPackedTextures = new List(); + private readonly Dictionary firstPossiblePosForSizeCache = new Dictionary(); private readonly Dictionary dataCache = new Dictionary(); private readonly bool autoIncreaseMaxWidth; private readonly bool forcePowerOfTwo; @@ -168,6 +169,8 @@ namespace MLEM.Data { var stopwatch = Stopwatch.StartNew(); foreach (var request in this.texturesToPack.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) { request.PackedArea = this.FindFreeArea(request); + // if this is the first position that this request fit in, no other requests of the same size will find a position before it + this.firstPossiblePosForSizeCache[request.PackedArea.Size] = request.PackedArea.Location; this.alreadyPackedTextures.Add(request); } stopwatch.Stop(); @@ -201,9 +204,7 @@ namespace MLEM.Data { request.Texture.Texture.Dispose(); } - this.texturesToPack.Clear(); - this.alreadyPackedTextures.Clear(); - this.dataCache.Clear(); + this.ClearTempCollections(); } /// @@ -212,11 +213,9 @@ namespace MLEM.Data { public void Reset() { this.PackedTexture?.Dispose(); this.PackedTexture = null; - this.texturesToPack.Clear(); - this.alreadyPackedTextures.Clear(); - this.dataCache.Clear(); this.LastCalculationTime = TimeSpan.Zero; this.LastPackTime = TimeSpan.Zero; + this.ClearTempCollections(); } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. @@ -229,7 +228,7 @@ namespace MLEM.Data { size.X += request.Padding * 2; size.Y += request.Padding * 2; - var pos = new Point(0, 0); + var pos = this.firstPossiblePosForSizeCache.TryGetValue(size, out var first) ? first : Point.Zero; var lowestY = int.MaxValue; while (true) { var intersected = false; @@ -294,6 +293,13 @@ namespace MLEM.Data { return true; } + private void ClearTempCollections() { + this.texturesToPack.Clear(); + this.alreadyPackedTextures.Clear(); + this.firstPossiblePosForSizeCache.Clear(); + this.dataCache.Clear(); + } + private static int ToPowerOfTwo(int value) { var ret = 1; while (ret < value) diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index bc14fb6..70fd655 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -329,7 +329,9 @@ namespace Sandbox { Console.WriteLine($"Returned {r.Count} regions: {string.Join(", ", r.Select(kv => kv.Key + ": " + kv.Value.Area))}"); }, 1, true); packer.Pack(this.GraphicsDevice); - packer.PackedTexture.SaveAsPng(File.Create("_Packed.png"), packer.PackedTexture.Width, packer.PackedTexture.Height); + + using (var stream = File.Create("_Packed.png")) + packer.PackedTexture.SaveAsPng(stream, packer.PackedTexture.Width, packer.PackedTexture.Height); this.OnDraw += (g, t) => { this.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);