1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-25 14:08:34 +01:00

Allow using multiple textures in a StaticSpriteBatch

This commit is contained in:
Ell 2022-06-08 11:05:18 +02:00
parent 7d9633d989
commit d03116a49a
2 changed files with 66 additions and 42 deletions

View file

@ -20,9 +20,7 @@ Additions
Improvements Improvements
- Allow comparing Keybind and Combination based on the amount of modifiers they have - Allow comparing Keybind and Combination based on the amount of modifiers they have
- Allow using multiple textures in a StaticSpriteBatch
Fixes
- Fixed StaticSpriteBatch not resetting its texture when all items are removed
### MLEM.Ui ### MLEM.Ui
Additions Additions

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
namespace MLEM.Graphics { namespace MLEM.Graphics {
/// <summary> /// <summary>
@ -18,17 +17,31 @@ namespace MLEM.Graphics {
private static readonly VertexPositionColorTexture[] Data = new VertexPositionColorTexture[MaxBatchItems * 4]; private static readonly VertexPositionColorTexture[] Data = new VertexPositionColorTexture[MaxBatchItems * 4];
/// <summary> /// <summary>
/// The amount of vertices that are currently batched /// The amount of vertices that are currently batched.
/// </summary> /// </summary>
public int Vertices => this.items.Count * 4; public int Vertices => this.items.Count * 4;
/// <summary>
/// The amount of vertex buffers that this static sprite batch has.
/// To see the amount of buffers that are actually in use, see <see cref="FilledBuffers"/>.
/// </summary>
public int Buffers => this.vertexBuffers.Count;
/// <summary>
/// The amount of textures that this static sprite batch is currently using.
/// </summary>
public int Textures => this.textures.Distinct().Count();
/// <summary>
/// The amount of vertex buffers that are currently filled in this static sprite batch.
/// To see the amount of buffers that are available, see <see cref="Buffers"/>.
/// </summary>
public int FilledBuffers { get; private set; }
private readonly GraphicsDevice graphicsDevice; private readonly GraphicsDevice graphicsDevice;
private readonly SpriteEffect spriteEffect; private readonly SpriteEffect spriteEffect;
private readonly List<VertexBuffer> vertexBuffers = new List<VertexBuffer>(); private readonly List<VertexBuffer> vertexBuffers = new List<VertexBuffer>();
private readonly List<Texture2D> textures = new List<Texture2D>();
private readonly ISet<Item> items = new HashSet<Item>(); private readonly ISet<Item> items = new HashSet<Item>();
private IndexBuffer indices; private IndexBuffer indices;
private Texture2D texture;
private bool batching; private bool batching;
private bool batchChanged; private bool batchChanged;
@ -56,49 +69,55 @@ namespace MLEM.Graphics {
/// Ends batching. /// Ends batching.
/// Call this method after calling <c>Add</c> or any of its overloads the desired number of times to add batched items. /// Call this method after calling <c>Add</c> or any of its overloads the desired number of times to add batched items.
/// </summary> /// </summary>
/// <param name="sortMode">The drawing order for sprite drawing. <see cref="SpriteSortMode.Deferred" /> by default. Note that <see cref="SpriteSortMode.Immediate"/> and <see cref="SpriteSortMode.Texture"/> are not supported.</param> /// <param name="sortMode">The drawing order for sprite drawing. <see cref="SpriteSortMode.Texture" /> by default, since it is the best in terms of rendering performance. Note that <see cref="SpriteSortMode.Immediate"/> is not supported.</param>
/// <exception cref="InvalidOperationException">Thrown if this method is called before <see cref="BeginBatch"/> was called</exception> /// <exception cref="InvalidOperationException">Thrown if this method is called before <see cref="BeginBatch"/> was called.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/> or <see cref="SpriteSortMode.Texture"/>, which are not supported</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/>, which is not supported.</exception>
public void EndBatch(SpriteSortMode sortMode = SpriteSortMode.Deferred) { public void EndBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) {
if (!this.batching) if (!this.batching)
throw new InvalidOperationException("Not batching"); throw new InvalidOperationException("Not batching");
if (sortMode == SpriteSortMode.Immediate || sortMode == SpriteSortMode.Texture) if (sortMode == SpriteSortMode.Immediate)
throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort modes Immediate or Texture for static batching"); throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching");
this.batching = false; this.batching = false;
// if we didn't add or remove any batch items, we don't have to recalculate anything // if we didn't add or remove any batch items, we don't have to recalculate anything
if (!this.batchChanged) if (!this.batchChanged)
return; return;
this.batchChanged = false; this.batchChanged = false;
this.FilledBuffers = 0;
// ensure we have enough vertex buffers this.textures.Clear();
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));
// order items according to the sort mode // order items according to the sort mode
IEnumerable<Item> ordered = this.items; IEnumerable<Item> ordered = this.items;
if (sortMode == SpriteSortMode.BackToFront) { 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); ordered = ordered.OrderBy(i => -i.Depth);
} else if (sortMode == SpriteSortMode.FrontToBack) { break;
case SpriteSortMode.FrontToBack:
ordered = ordered.OrderBy(i => i.Depth); ordered = ordered.OrderBy(i => i.Depth);
break;
} }
// fill vertex buffers // fill vertex buffers
var dataIndex = 0; var dataIndex = 0;
var arrayIndex = 0; Texture2D texture = null;
foreach (var item in ordered) { 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.TopLeft;
Data[dataIndex++] = item.TopRight; Data[dataIndex++] = item.TopRight;
Data[dataIndex++] = item.BottomLeft; Data[dataIndex++] = item.BottomLeft;
Data[dataIndex++] = item.BottomRight; Data[dataIndex++] = item.BottomRight;
if (dataIndex >= Data.Length) { texture = item.Texture;
this.vertexBuffers[arrayIndex++].SetData(Data);
dataIndex = 0;
}
} }
if (dataIndex > 0) if (dataIndex > 0)
this.vertexBuffers[arrayIndex].SetData(Data); this.FillBuffer(this.FilledBuffers++, texture, Data);
// ensure we have enough indices // ensure we have enough indices
var maxItems = Math.Min(this.items.Count, MaxBatchItems); var maxItems = Math.Min(this.items.Count, MaxBatchItems);
@ -152,21 +171,23 @@ namespace MLEM.Graphics {
this.spriteEffect.CurrentTechnique.Passes[0].Apply(); this.spriteEffect.CurrentTechnique.Passes[0].Apply();
var totalIndex = 0; 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; var tris = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount) / 4 * 2;
if (tris <= 0)
break;
this.graphicsDevice.SetVertexBuffer(buffer); this.graphicsDevice.SetVertexBuffer(buffer);
if (effect != null) { if (effect != null) {
foreach (var pass in effect.CurrentTechnique.Passes) { foreach (var pass in effect.CurrentTechnique.Passes) {
pass.Apply(); pass.Apply();
this.graphicsDevice.Textures[0] = this.texture; this.graphicsDevice.Textures[0] = texture;
this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris); this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris);
} }
} else { } else {
this.graphicsDevice.Textures[0] = this.texture; this.graphicsDevice.Textures[0] = texture;
this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris); this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris);
} }
totalIndex += buffer.VertexCount; totalIndex += buffer.VertexCount;
} }
} }
@ -337,8 +358,6 @@ namespace MLEM.Graphics {
if (!this.batching) if (!this.batching)
throw new InvalidOperationException("Not batching"); throw new InvalidOperationException("Not batching");
if (this.items.Remove(item)) { if (this.items.Remove(item)) {
if (this.items.Count <= 0)
this.texture = null;
this.batchChanged = true; this.batchChanged = true;
return true; return true;
} }
@ -354,7 +373,8 @@ namespace MLEM.Graphics {
if (!this.batching) if (!this.batching)
throw new InvalidOperationException("Not batching"); throw new InvalidOperationException("Not batching");
this.items.Clear(); this.items.Clear();
this.texture = null; this.textures.Clear();
this.FilledBuffers = 0;
this.batchChanged = true; 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) { private Item Add(Texture2D texture, float depth, VertexPositionColorTexture tl, VertexPositionColorTexture tr, VertexPositionColorTexture bl, VertexPositionColorTexture br) {
if (!this.batching) if (!this.batching)
throw new InvalidOperationException("Not batching"); throw new InvalidOperationException("Not batching");
if (this.texture != null && this.texture != texture) var item = new Item(texture, depth, tl, tr, bl, br);
throw new ArgumentException("Cannot use multiple textures in one batch", nameof(texture));
var item = new Item(tl, tr, bl, br, depth);
this.items.Add(item); this.items.Add(item);
this.texture = texture;
this.batchChanged = true; this.batchChanged = true;
return item; 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);
}
/// <summary> /// <summary>
/// A struct that represents an item added to a <see cref="StaticSpriteBatch"/> using <c>Add</c> or any of its overloads. /// A struct that represents an item added to a <see cref="StaticSpriteBatch"/> using <c>Add</c> or any of its overloads.
/// An item returned after adding can be removed using <see cref="Remove"/>. /// An item returned after adding can be removed using <see cref="Remove"/>.
/// </summary> /// </summary>
public class Item { public class Item {
internal readonly Texture2D Texture;
internal readonly float Depth;
internal readonly VertexPositionColorTexture TopLeft; internal readonly VertexPositionColorTexture TopLeft;
internal readonly VertexPositionColorTexture TopRight; internal readonly VertexPositionColorTexture TopRight;
internal readonly VertexPositionColorTexture BottomLeft; internal readonly VertexPositionColorTexture BottomLeft;
internal readonly VertexPositionColorTexture BottomRight; 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.TopLeft = topLeft;
this.TopRight = topRight; this.TopRight = topRight;
this.BottomLeft = bottomLeft; this.BottomLeft = bottomLeft;
this.BottomRight = bottomRight; this.BottomRight = bottomRight;
this.Depth = depth;
} }
} }