From df2d102d8e36b4c8e24a61f8074134b2ef8c19dc Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 13 Sep 2022 11:57:28 +0200 Subject: [PATCH] further improved StaticSpriteBatch performance --- MLEM/Graphics/StaticSpriteBatch.cs | 90 ++++++++++++++++++++++++------ Sandbox/GameImpl.cs | 10 +++- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index e251fc6..a707db7 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -43,13 +43,12 @@ namespace MLEM.Graphics { private readonly SpriteEffect spriteEffect; private readonly List vertexBuffers = new List(); private readonly List textures = new List(); - // TODO this can still be optimized by not giving items with a unique depth a single-entry set immediately - private readonly SortedDictionary> items = new SortedDictionary>(); + private readonly SortedDictionary items = new SortedDictionary(); + private SpriteSortMode sortMode = SpriteSortMode.Texture; private IndexBuffer indices; private bool batching; private bool batchChanged; - private SpriteSortMode sortMode; private int itemAmount; /// @@ -65,20 +64,20 @@ namespace MLEM.Graphics { /// Begins batching. /// Call this method before calling Add or any of its overloads. /// - /// The drawing order for sprite drawing. by default, since it is the best in terms of rendering performance. Note that is not supported. + /// The drawing order for sprite drawing. When is passed, the last used sort mode will be used again. The initial sort mode is . Note that is not supported. /// Thrown if this batch is currently batching already /// Thrown if the is , which is not supported. - public void BeginBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) { + public void BeginBatch(SpriteSortMode? sortMode = null) { if (this.batching) throw new InvalidOperationException("Already batching"); if (sortMode == SpriteSortMode.Immediate) throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching"); // if the sort mode changed (which should be very rare in practice), we have to re-sort our list - if (this.sortMode != sortMode) { - this.sortMode = sortMode; + if (sortMode != null && this.sortMode != sortMode) { + this.sortMode = sortMode.Value; if (this.items.Count > 0) { - var tempItems = this.items.Values.SelectMany(s => s).ToArray(); + var tempItems = this.items.Values.SelectMany(s => s.Items).ToArray(); this.items.Clear(); foreach (var item in tempItems) this.AddItemToSet(item); @@ -110,7 +109,7 @@ namespace MLEM.Graphics { var dataIndex = 0; Texture2D texture = null; foreach (var itemSet in this.items.Values) { - foreach (var item in itemSet) { + foreach (var item in itemSet.Items) { // if the texture changes, we also have to start a new buffer! if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) { this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); @@ -355,6 +354,21 @@ namespace MLEM.Graphics { return this.Add(texture, destinationRectangle, null, color); } + /// + /// Adds an item to this batch. + /// Note that this batch needs to currently be batching, meaning has to have been called previously. + /// + /// The item to add. + /// The added , for chaining. + public Item Add(Item item) { + if (!this.batching) + throw new InvalidOperationException("Not batching"); + this.AddItemToSet(item); + this.itemAmount++; + this.batchChanged = true; + return item; + } + /// /// Removes the given item from this batch. /// Note that this batch needs to currently be batching, meaning has to have been called previously. @@ -367,7 +381,7 @@ namespace MLEM.Graphics { throw new InvalidOperationException("Not batching"); var key = item.GetSortKey(this.sortMode); if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) { - if (itemSet.Count <= 0) + if (itemSet.IsEmpty) this.items.Remove(key); this.itemAmount--; this.batchChanged = true; @@ -437,13 +451,7 @@ namespace MLEM.Graphics { } private Item Add(Texture2D texture, float depth, VertexPositionColorTexture tl, VertexPositionColorTexture tr, VertexPositionColorTexture bl, VertexPositionColorTexture br) { - if (!this.batching) - throw new InvalidOperationException("Not batching"); - var item = new Item(texture, depth, tl, tr, bl, br); - this.AddItemToSet(item); - this.itemAmount++; - this.batchChanged = true; - return item; + return this.Add(new Item(texture, depth, tl, tr, bl, br)); } private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) { @@ -464,7 +472,7 @@ namespace MLEM.Graphics { private void AddItemToSet(Item item) { var sortKey = item.GetSortKey(this.sortMode); if (!this.items.TryGetValue(sortKey, out var itemSet)) { - itemSet = new HashSet(); + itemSet = new ItemSet(); this.items.Add(sortKey, itemSet); } itemSet.Add(item); @@ -507,6 +515,52 @@ namespace MLEM.Graphics { } + private class ItemSet { + + public IEnumerable Items { + get { + if (this.items != null) + return this.items; + if (this.single != null) + return Enumerable.Repeat(this.single, 1); + return Enumerable.Empty(); + } + } + public bool IsEmpty => this.items == null && this.single == null; + + private HashSet items; + private Item single; + + public void Add(Item item) { + if (this.items != null) { + this.items.Add(item); + } else if (this.single != null) { + this.items = new HashSet(); + this.items.Add(this.single); + this.items.Add(item); + this.single = null; + } else { + this.single = item; + } + } + + public bool Remove(Item item) { + if (this.items != null && this.items.Remove(item)) { + if (this.items.Count <= 1) { + this.single = this.items.Single(); + this.items = null; + } + return true; + } else if (this.single == item) { + this.single = null; + return true; + } else { + return false; + } + } + + } + #if FNA private class SpriteEffect : Effect { diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index 7b51c96..7314feb 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -360,7 +360,6 @@ public class GameImpl : MlemGame { var items = new List(); foreach (var r in atlas.Regions) items.Add(batch.Add(r, new Vector2(50 + r.GetHashCode() % 200, 50), ColorHelper.FromHexRgb(r.GetHashCode()), 0, Vector2.Zero, 1, SpriteEffects.None, depth += 0.0001F)); - batch.Remove(items[5]); batch.EndBatch(); var sortMode = SpriteSortMode.Deferred; this.OnUpdate += (_, _) => { @@ -371,6 +370,15 @@ public class GameImpl : MlemGame { Console.WriteLine(sortMode); batch.BeginBatch(sortMode); batch.EndBatch(); + } else { + for (var i = 0; i < items.Count; i++) { + if (MlemGame.Input.IsPressed(Keys.D1 + i)) { + batch.BeginBatch(); + if (!batch.Remove(items[i])) + batch.Add(items[i]); + batch.EndBatch(); + } + } } }; this.OnDraw += (_, _) => batch.Draw(null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(3));