1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-29 15:58:33 +01:00

Compare commits

..

No commits in common. "b4e1b00c88f1549f32cd1b4d874f9269c0152df6" and "e60d3591fff634c7776b2b2f8e7c092163988b56" have entirely different histories.

3 changed files with 42 additions and 98 deletions

View file

@ -18,8 +18,6 @@ Additions
Improvements Improvements
- Improved EnumHelper.GetValues signature to return an array - Improved EnumHelper.GetValues signature to return an array
- Allow using external gesture handling alongside InputHandler through ExternalGestureHandling - Allow using external gesture handling alongside InputHandler through ExternalGestureHandling
- Discard old data when updating a StaticSpriteBatch
- **Drastically improved StaticSpriteBatch batching performance**
Fixes Fixes
- Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text - Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text

View file

@ -10,7 +10,7 @@ using System.IO;
namespace MLEM.Graphics { namespace MLEM.Graphics {
/// <summary> /// <summary>
/// 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. /// 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.
/// 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 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"/>. /// To draw the batched items, call <see cref="Draw"/>.
/// </summary> /// </summary>
@ -23,7 +23,7 @@ namespace MLEM.Graphics {
/// <summary> /// <summary>
/// The amount of vertices that are currently batched. /// The amount of vertices that are currently batched.
/// </summary> /// </summary>
public int Vertices => this.itemAmount * 4; public int Vertices => this.items.Count * 4;
/// <summary> /// <summary>
/// The amount of vertex buffers that this static sprite batch has. /// 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"/>. /// To see the amount of buffers that are actually in use, see <see cref="FilledBuffers"/>.
@ -41,16 +41,13 @@ namespace MLEM.Graphics {
private readonly GraphicsDevice graphicsDevice; private readonly GraphicsDevice graphicsDevice;
private readonly SpriteEffect spriteEffect; private readonly SpriteEffect spriteEffect;
private readonly List<DynamicVertexBuffer> vertexBuffers = new List<DynamicVertexBuffer>();
private readonly List<Texture2D> textures = new List<Texture2D>();
// 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 readonly List<VertexBuffer> vertexBuffers = new List<VertexBuffer>();
private readonly List<Texture2D> textures = new List<Texture2D>();
private readonly ISet<Item> items = new HashSet<Item>();
private IndexBuffer indices; private IndexBuffer indices;
private bool batching; private bool batching;
private bool batchChanged; private bool batchChanged;
private SpriteSortMode sortMode;
private int itemAmount;
/// <summary> /// <summary>
/// Creates a new static sprite batch with the given <see cref="GraphicsDevice"/> /// Creates a new static sprite batch with the given <see cref="GraphicsDevice"/>
@ -65,27 +62,10 @@ namespace MLEM.Graphics {
/// Begins batching. /// Begins batching.
/// Call this method before calling <c>Add</c> or any of its overloads. /// Call this method before calling <c>Add</c> or any of its overloads.
/// </summary> /// </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> /// <exception cref="InvalidOperationException">Thrown if this batch is currently batching already</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/>, which is not supported.</exception> public void BeginBatch() {
public void BeginBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) {
if (this.batching) if (this.batching)
throw new InvalidOperationException("Already 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; this.batching = true;
} }
@ -93,10 +73,14 @@ 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.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>
public void EndBatch() { /// <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) {
if (!this.batching) if (!this.batching)
throw new InvalidOperationException("Not 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; 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
@ -106,11 +90,25 @@ namespace MLEM.Graphics {
this.FilledBuffers = 0; this.FilledBuffers = 0;
this.textures.Clear(); 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 // fill vertex buffers
var dataIndex = 0; var dataIndex = 0;
Texture2D texture = null; Texture2D texture = null;
foreach (var itemSet in this.items.Values) { foreach (var item in ordered) {
foreach (var item in itemSet) {
// if the texture changes, we also have to start a new buffer! // if the texture changes, we also have to start a new buffer!
if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) { if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) {
this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data);
@ -122,12 +120,11 @@ namespace MLEM.Graphics {
StaticSpriteBatch.Data[dataIndex++] = item.BottomRight; StaticSpriteBatch.Data[dataIndex++] = item.BottomRight;
texture = item.Texture; texture = item.Texture;
} }
}
if (dataIndex > 0) if (dataIndex > 0)
this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data);
// ensure we have enough indices // ensure we have enough indices
var maxItems = Math.Min(this.itemAmount, StaticSpriteBatch.MaxBatchItems); var maxItems = Math.Min(this.items.Count, StaticSpriteBatch.MaxBatchItems);
// each item has 2 triangles which each have 3 indices // each item has 2 triangles which each have 3 indices
if (this.indices == null || this.indices.IndexCount < 6 * maxItems) { if (this.indices == null || this.indices.IndexCount < 6 * maxItems) {
var newIndices = new short[6 * maxItems]; var newIndices = new short[6 * maxItems];
@ -181,7 +178,7 @@ namespace MLEM.Graphics {
for (var i = 0; i < this.FilledBuffers; i++) { for (var i = 0; i < this.FilledBuffers; i++) {
var buffer = this.vertexBuffers[i]; var buffer = this.vertexBuffers[i];
var texture = this.textures[i]; var texture = this.textures[i];
var verts = Math.Min(this.itemAmount * 4 - totalIndex, buffer.VertexCount); var verts = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount);
this.graphicsDevice.SetVertexBuffer(buffer); this.graphicsDevice.SetVertexBuffer(buffer);
if (effect != null) { if (effect != null) {
@ -365,11 +362,7 @@ namespace MLEM.Graphics {
public bool Remove(Item item) { public bool Remove(Item item) {
if (!this.batching) if (!this.batching)
throw new InvalidOperationException("Not batching"); throw new InvalidOperationException("Not batching");
var key = item.GetSortKey(this.sortMode); if (this.items.Remove(item)) {
if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) {
if (itemSet.Count <= 0)
this.items.Remove(key);
this.itemAmount--;
this.batchChanged = true; this.batchChanged = true;
return true; return true;
} }
@ -387,7 +380,6 @@ namespace MLEM.Graphics {
this.items.Clear(); this.items.Clear();
this.textures.Clear(); this.textures.Clear();
this.FilledBuffers = 0; this.FilledBuffers = 0;
this.itemAmount = 0;
this.batchChanged = true; this.batchChanged = true;
} }
@ -440,16 +432,15 @@ namespace MLEM.Graphics {
if (!this.batching) if (!this.batching)
throw new InvalidOperationException("Not batching"); throw new InvalidOperationException("Not batching");
var item = new Item(texture, depth, tl, tr, bl, br); var item = new Item(texture, depth, tl, tr, bl, br);
this.AddItemToSet(item); this.items.Add(item);
this.itemAmount++;
this.batchChanged = true; this.batchChanged = true;
return item; return item;
} }
private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) { private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) {
if (this.vertexBuffers.Count <= index) if (this.vertexBuffers.Count <= index)
this.vertexBuffers.Add(new DynamicVertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly)); this.vertexBuffers.Add(new VertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly));
this.vertexBuffers[index].SetData(data, 0, data.Length, SetDataOptions.Discard); this.vertexBuffers[index].SetData(data);
this.textures.Insert(index, texture); this.textures.Insert(index, texture);
} }
@ -461,15 +452,6 @@ namespace MLEM.Graphics {
#endif #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> /// <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"/>.
@ -492,19 +474,6 @@ namespace MLEM.Graphics {
this.BottomRight = bottomRight; 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 #if FNA

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FontStashSharp; using FontStashSharp;
@ -14,7 +13,6 @@ using MLEM.Extensions;
using MLEM.Font; using MLEM.Font;
using MLEM.Formatting; using MLEM.Formatting;
using MLEM.Formatting.Codes; using MLEM.Formatting.Codes;
using MLEM.Graphics;
using MLEM.Input; using MLEM.Input;
using MLEM.Misc; using MLEM.Misc;
using MLEM.Startup; using MLEM.Startup;
@ -353,27 +351,6 @@ public class GameImpl : MlemGame {
}); });
} }
this.UiSystem.Add("WidthTest", widthPanel); 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) { protected override void DoUpdate(GameTime gameTime) {