diff --git a/CHANGELOG.md b/CHANGELOG.md index a42aa18..f13e47f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,7 @@ Additions Improvements - Allow comparing Keybind and Combination based on the amount of modifiers they have - -Fixes -- Fixed StaticSpriteBatch not resetting its texture when all items are removed +- Allow using multiple textures in a StaticSpriteBatch ### MLEM.Ui Additions diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index 933d71f..76da56a 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using MLEM.Extensions; namespace MLEM.Graphics { /// @@ -18,17 +17,31 @@ namespace MLEM.Graphics { private static readonly VertexPositionColorTexture[] Data = new VertexPositionColorTexture[MaxBatchItems * 4]; /// - /// The amount of vertices that are currently batched + /// The amount of vertices that are currently batched. /// public int Vertices => this.items.Count * 4; + /// + /// The amount of vertex buffers that this static sprite batch has. + /// To see the amount of buffers that are actually in use, see . + /// + public int Buffers => this.vertexBuffers.Count; + /// + /// The amount of textures that this static sprite batch is currently using. + /// + public int Textures => this.textures.Distinct().Count(); + /// + /// The amount of vertex buffers that are currently filled in this static sprite batch. + /// To see the amount of buffers that are available, see . + /// + public int FilledBuffers { get; private set; } private readonly GraphicsDevice graphicsDevice; private readonly SpriteEffect spriteEffect; private readonly List vertexBuffers = new List(); + private readonly List textures = new List(); private readonly ISet items = new HashSet(); private IndexBuffer indices; - private Texture2D texture; private bool batching; private bool batchChanged; @@ -56,49 +69,55 @@ namespace MLEM.Graphics { /// Ends batching. /// Call this method after calling Add or any of its overloads the desired number of times to add batched items. /// - /// The drawing order for sprite drawing. by default. Note that and are not supported. - /// Thrown if this method is called before was called - /// Thrown if the is or , which are not supported - public void EndBatch(SpriteSortMode sortMode = SpriteSortMode.Deferred) { + /// The drawing order for sprite drawing. by default, since it is the best in terms of rendering performance. Note that is not supported. + /// Thrown if this method is called before was called. + /// Thrown if the is , which is not supported. + public void EndBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) { if (!this.batching) throw new InvalidOperationException("Not batching"); - if (sortMode == SpriteSortMode.Immediate || sortMode == SpriteSortMode.Texture) - throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort modes Immediate or Texture for static batching"); + if (sortMode == SpriteSortMode.Immediate) + throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching"); this.batching = false; // if we didn't add or remove any batch items, we don't have to recalculate anything if (!this.batchChanged) return; this.batchChanged = false; - - // ensure we have enough vertex buffers - var requiredBuffers = (this.items.Count / (float) MaxBatchItems).Ceil(); - while (this.vertexBuffers.Count < requiredBuffers) - this.vertexBuffers.Add(new VertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, MaxBatchItems * 4, BufferUsage.WriteOnly)); + this.FilledBuffers = 0; + this.textures.Clear(); // order items according to the sort mode IEnumerable ordered = this.items; - if (sortMode == SpriteSortMode.BackToFront) { - ordered = ordered.OrderBy(i => -i.Depth); - } else if (sortMode == SpriteSortMode.FrontToBack) { - ordered = ordered.OrderBy(i => i.Depth); + switch (sortMode) { + case SpriteSortMode.Texture: + // SortingKey is internal, but this will do for batching the same texture together + ordered = ordered.OrderBy(i => i.Texture.GetHashCode()); + break; + case SpriteSortMode.BackToFront: + ordered = ordered.OrderBy(i => -i.Depth); + break; + case SpriteSortMode.FrontToBack: + ordered = ordered.OrderBy(i => i.Depth); + break; } // fill vertex buffers var dataIndex = 0; - var arrayIndex = 0; + Texture2D texture = null; foreach (var item in ordered) { + // if the texture changes, we also have to start a new buffer! + if (dataIndex > 0 && (item.Texture != texture || dataIndex >= Data.Length)) { + this.FillBuffer(this.FilledBuffers++, texture, Data); + dataIndex = 0; + } Data[dataIndex++] = item.TopLeft; Data[dataIndex++] = item.TopRight; Data[dataIndex++] = item.BottomLeft; Data[dataIndex++] = item.BottomRight; - if (dataIndex >= Data.Length) { - this.vertexBuffers[arrayIndex++].SetData(Data); - dataIndex = 0; - } + texture = item.Texture; } if (dataIndex > 0) - this.vertexBuffers[arrayIndex].SetData(Data); + this.FillBuffer(this.FilledBuffers++, texture, Data); // ensure we have enough indices var maxItems = Math.Min(this.items.Count, MaxBatchItems); @@ -152,21 +171,23 @@ namespace MLEM.Graphics { this.spriteEffect.CurrentTechnique.Passes[0].Apply(); var totalIndex = 0; - foreach (var buffer in this.vertexBuffers) { + for (var i = 0; i < this.FilledBuffers; i++) { + var buffer = this.vertexBuffers[i]; + var texture = this.textures[i]; var tris = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount) / 4 * 2; - if (tris <= 0) - break; + this.graphicsDevice.SetVertexBuffer(buffer); if (effect != null) { foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); - this.graphicsDevice.Textures[0] = this.texture; + this.graphicsDevice.Textures[0] = texture; this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris); } } else { - this.graphicsDevice.Textures[0] = this.texture; + this.graphicsDevice.Textures[0] = texture; this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris); } + totalIndex += buffer.VertexCount; } } @@ -337,8 +358,6 @@ namespace MLEM.Graphics { if (!this.batching) throw new InvalidOperationException("Not batching"); if (this.items.Remove(item)) { - if (this.items.Count <= 0) - this.texture = null; this.batchChanged = true; return true; } @@ -354,7 +373,8 @@ namespace MLEM.Graphics { if (!this.batching) throw new InvalidOperationException("Not batching"); this.items.Clear(); - this.texture = null; + this.textures.Clear(); + this.FilledBuffers = 0; this.batchChanged = true; } @@ -406,33 +426,39 @@ 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"); - if (this.texture != null && this.texture != texture) - throw new ArgumentException("Cannot use multiple textures in one batch", nameof(texture)); - var item = new Item(tl, tr, bl, br, depth); + var item = new Item(texture, depth, tl, tr, bl, br); this.items.Add(item); - this.texture = texture; this.batchChanged = true; return item; } + private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) { + if (this.vertexBuffers.Count <= index) + this.vertexBuffers.Add(new VertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, MaxBatchItems * 4, BufferUsage.WriteOnly)); + this.vertexBuffers[index].SetData(data); + this.textures.Insert(index, texture); + } + /// /// A struct that represents an item added to a using Add or any of its overloads. /// An item returned after adding can be removed using . /// public class Item { + internal readonly Texture2D Texture; + internal readonly float Depth; internal readonly VertexPositionColorTexture TopLeft; internal readonly VertexPositionColorTexture TopRight; internal readonly VertexPositionColorTexture BottomLeft; internal readonly VertexPositionColorTexture BottomRight; - internal readonly float Depth; - internal Item(VertexPositionColorTexture topLeft, VertexPositionColorTexture topRight, VertexPositionColorTexture bottomLeft, VertexPositionColorTexture bottomRight, float depth) { + internal Item(Texture2D texture, float depth, VertexPositionColorTexture topLeft, VertexPositionColorTexture topRight, VertexPositionColorTexture bottomLeft, VertexPositionColorTexture bottomRight) { + this.Texture = texture; + this.Depth = depth; this.TopLeft = topLeft; this.TopRight = topRight; this.BottomLeft = bottomLeft; this.BottomRight = bottomRight; - this.Depth = depth; } }