mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 22:18:34 +01:00
Compare commits
5 commits
e60d3591ff
...
b4e1b00c88
Author | SHA1 | Date | |
---|---|---|---|
b4e1b00c88 | |||
eadabf3919 | |||
856d67b6cf | |||
742bc52437 | |||
d6e7c1086d |
3 changed files with 98 additions and 42 deletions
|
@ -18,6 +18,8 @@ Additions
|
|||
Improvements
|
||||
- Improved EnumHelper.GetValues signature to return an array
|
||||
- Allow using external gesture handling alongside InputHandler through ExternalGestureHandling
|
||||
- Discard old data when updating a StaticSpriteBatch
|
||||
- **Drastically improved StaticSpriteBatch batching performance**
|
||||
|
||||
Fixes
|
||||
- Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text
|
||||
|
|
|
@ -10,7 +10,7 @@ using System.IO;
|
|||
|
||||
namespace MLEM.Graphics {
|
||||
/// <summary>
|
||||
/// A static sprite batch is a variation of <see cref="SpriteBatch"/> that keeps all batched items in a <see cref="VertexBuffer"/>, allowing for them to be drawn multiple times.
|
||||
/// A static sprite batch is a highly optimized variation of <see cref="SpriteBatch"/> that keeps all batched items in a <see cref="VertexBuffer"/>, allowing for them to be drawn multiple times.
|
||||
/// To add items to a static sprite batch, use <see cref="BeginBatch"/> to begin batching, <see cref="ClearBatch"/> to clear currently batched items, <c>Add</c> and its various overloads to add batch items, <see cref="Remove"/> to remove them again, and <see cref="EndBatch"/> to end batching.
|
||||
/// To draw the batched items, call <see cref="Draw"/>.
|
||||
/// </summary>
|
||||
|
@ -23,7 +23,7 @@ namespace MLEM.Graphics {
|
|||
/// <summary>
|
||||
/// The amount of vertices that are currently batched.
|
||||
/// </summary>
|
||||
public int Vertices => this.items.Count * 4;
|
||||
public int Vertices => this.itemAmount * 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"/>.
|
||||
|
@ -41,13 +41,16 @@ namespace MLEM.Graphics {
|
|||
|
||||
private readonly GraphicsDevice graphicsDevice;
|
||||
private readonly SpriteEffect spriteEffect;
|
||||
|
||||
private readonly List<VertexBuffer> vertexBuffers = new List<VertexBuffer>();
|
||||
private readonly List<DynamicVertexBuffer> vertexBuffers = new List<DynamicVertexBuffer>();
|
||||
private readonly List<Texture2D> textures = new List<Texture2D>();
|
||||
private readonly ISet<Item> items = new HashSet<Item>();
|
||||
// TODO this can still be optimized by not giving items with a unique depth a single-entry set immediately
|
||||
private readonly SortedDictionary<float, ISet<Item>> items = new SortedDictionary<float, ISet<Item>>();
|
||||
|
||||
private IndexBuffer indices;
|
||||
private bool batching;
|
||||
private bool batchChanged;
|
||||
private SpriteSortMode sortMode;
|
||||
private int itemAmount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new static sprite batch with the given <see cref="GraphicsDevice"/>
|
||||
|
@ -62,10 +65,27 @@ namespace MLEM.Graphics {
|
|||
/// Begins batching.
|
||||
/// Call this method before calling <c>Add</c> or any of its overloads.
|
||||
/// </summary>
|
||||
/// <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 batch is currently batching already</exception>
|
||||
public void BeginBatch() {
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/>, which is not supported.</exception>
|
||||
public void BeginBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) {
|
||||
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 (this.items.Count > 0) {
|
||||
var tempItems = this.items.Values.SelectMany(s => s).ToArray();
|
||||
this.items.Clear();
|
||||
foreach (var item in tempItems)
|
||||
this.AddItemToSet(item);
|
||||
this.batchChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.batching = true;
|
||||
}
|
||||
|
||||
|
@ -73,14 +93,10 @@ namespace MLEM.Graphics {
|
|||
/// Ends batching.
|
||||
/// Call this method after calling <c>Add</c> or any of its overloads the desired number of times to add batched items.
|
||||
/// </summary>
|
||||
/// <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="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/>, which is not supported.</exception>
|
||||
public void EndBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) {
|
||||
public void EndBatch() {
|
||||
if (!this.batching)
|
||||
throw new InvalidOperationException("Not 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
|
||||
|
@ -90,41 +106,28 @@ namespace MLEM.Graphics {
|
|||
this.FilledBuffers = 0;
|
||||
this.textures.Clear();
|
||||
|
||||
// order items according to the sort mode
|
||||
IEnumerable<Item> ordered = this.items;
|
||||
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;
|
||||
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 >= StaticSpriteBatch.Data.Length)) {
|
||||
this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data);
|
||||
dataIndex = 0;
|
||||
foreach (var itemSet in this.items.Values) {
|
||||
foreach (var item in itemSet) {
|
||||
// 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);
|
||||
dataIndex = 0;
|
||||
}
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.TopLeft;
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.TopRight;
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.BottomLeft;
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.BottomRight;
|
||||
texture = item.Texture;
|
||||
}
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.TopLeft;
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.TopRight;
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.BottomLeft;
|
||||
StaticSpriteBatch.Data[dataIndex++] = item.BottomRight;
|
||||
texture = item.Texture;
|
||||
}
|
||||
if (dataIndex > 0)
|
||||
this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data);
|
||||
|
||||
// ensure we have enough indices
|
||||
var maxItems = Math.Min(this.items.Count, StaticSpriteBatch.MaxBatchItems);
|
||||
var maxItems = Math.Min(this.itemAmount, StaticSpriteBatch.MaxBatchItems);
|
||||
// each item has 2 triangles which each have 3 indices
|
||||
if (this.indices == null || this.indices.IndexCount < 6 * maxItems) {
|
||||
var newIndices = new short[6 * maxItems];
|
||||
|
@ -178,7 +181,7 @@ namespace MLEM.Graphics {
|
|||
for (var i = 0; i < this.FilledBuffers; i++) {
|
||||
var buffer = this.vertexBuffers[i];
|
||||
var texture = this.textures[i];
|
||||
var verts = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount);
|
||||
var verts = Math.Min(this.itemAmount * 4 - totalIndex, buffer.VertexCount);
|
||||
|
||||
this.graphicsDevice.SetVertexBuffer(buffer);
|
||||
if (effect != null) {
|
||||
|
@ -362,7 +365,11 @@ namespace MLEM.Graphics {
|
|||
public bool Remove(Item item) {
|
||||
if (!this.batching)
|
||||
throw new InvalidOperationException("Not batching");
|
||||
if (this.items.Remove(item)) {
|
||||
var key = item.GetSortKey(this.sortMode);
|
||||
if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) {
|
||||
if (itemSet.Count <= 0)
|
||||
this.items.Remove(key);
|
||||
this.itemAmount--;
|
||||
this.batchChanged = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -380,6 +387,7 @@ namespace MLEM.Graphics {
|
|||
this.items.Clear();
|
||||
this.textures.Clear();
|
||||
this.FilledBuffers = 0;
|
||||
this.itemAmount = 0;
|
||||
this.batchChanged = true;
|
||||
}
|
||||
|
||||
|
@ -432,15 +440,16 @@ namespace MLEM.Graphics {
|
|||
if (!this.batching)
|
||||
throw new InvalidOperationException("Not batching");
|
||||
var item = new Item(texture, depth, tl, tr, bl, br);
|
||||
this.items.Add(item);
|
||||
this.AddItemToSet(item);
|
||||
this.itemAmount++;
|
||||
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, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly));
|
||||
this.vertexBuffers[index].SetData(data);
|
||||
this.vertexBuffers.Add(new DynamicVertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly));
|
||||
this.vertexBuffers[index].SetData(data, 0, data.Length, SetDataOptions.Discard);
|
||||
this.textures.Insert(index, texture);
|
||||
}
|
||||
|
||||
|
@ -452,6 +461,15 @@ namespace MLEM.Graphics {
|
|||
#endif
|
||||
}
|
||||
|
||||
private void AddItemToSet(Item item) {
|
||||
var sortKey = item.GetSortKey(this.sortMode);
|
||||
if (!this.items.TryGetValue(sortKey, out var itemSet)) {
|
||||
itemSet = new HashSet<Item>();
|
||||
this.items.Add(sortKey, itemSet);
|
||||
}
|
||||
itemSet.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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"/>.
|
||||
|
@ -474,6 +492,19 @@ namespace MLEM.Graphics {
|
|||
this.BottomRight = bottomRight;
|
||||
}
|
||||
|
||||
internal float GetSortKey(SpriteSortMode sortMode) {
|
||||
switch (sortMode) {
|
||||
case SpriteSortMode.Texture:
|
||||
return this.Texture.GetHashCode();
|
||||
case SpriteSortMode.BackToFront:
|
||||
return -this.Depth;
|
||||
case SpriteSortMode.FrontToBack:
|
||||
return this.Depth;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if FNA
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using FontStashSharp;
|
||||
|
@ -13,6 +14,7 @@ using MLEM.Extensions;
|
|||
using MLEM.Font;
|
||||
using MLEM.Formatting;
|
||||
using MLEM.Formatting.Codes;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Input;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Startup;
|
||||
|
@ -351,6 +353,27 @@ public class GameImpl : MlemGame {
|
|||
});
|
||||
}
|
||||
this.UiSystem.Add("WidthTest", widthPanel);
|
||||
|
||||
var batch = new StaticSpriteBatch(this.GraphicsDevice);
|
||||
batch.BeginBatch();
|
||||
var depth = 0F;
|
||||
var items = new List<StaticSpriteBatch.Item>();
|
||||
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 += (_, _) => {
|
||||
if (MlemGame.Input.IsPressed(Keys.S)) {
|
||||
do {
|
||||
sortMode = (SpriteSortMode) (((int) sortMode + 1) % 5);
|
||||
} while (sortMode == SpriteSortMode.Immediate);
|
||||
Console.WriteLine(sortMode);
|
||||
batch.BeginBatch(sortMode);
|
||||
batch.EndBatch();
|
||||
}
|
||||
};
|
||||
this.OnDraw += (_, _) => batch.Draw(null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(3));
|
||||
}
|
||||
|
||||
protected override void DoUpdate(GameTime gameTime) {
|
||||
|
|
Loading…
Reference in a new issue