mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-22 12:58:33 +01:00
further improved StaticSpriteBatch performance
This commit is contained in:
parent
b4e1b00c88
commit
df2d102d8e
2 changed files with 81 additions and 19 deletions
|
@ -43,13 +43,12 @@ namespace MLEM.Graphics {
|
||||||
private readonly SpriteEffect spriteEffect;
|
private readonly SpriteEffect spriteEffect;
|
||||||
private readonly List<DynamicVertexBuffer> vertexBuffers = new List<DynamicVertexBuffer>();
|
private readonly List<DynamicVertexBuffer> vertexBuffers = new List<DynamicVertexBuffer>();
|
||||||
private readonly List<Texture2D> textures = new List<Texture2D>();
|
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, ItemSet> items = new SortedDictionary<float, ItemSet>();
|
||||||
private readonly SortedDictionary<float, ISet<Item>> items = new SortedDictionary<float, ISet<Item>>();
|
|
||||||
|
|
||||||
|
private SpriteSortMode sortMode = SpriteSortMode.Texture;
|
||||||
private IndexBuffer indices;
|
private IndexBuffer indices;
|
||||||
private bool batching;
|
private bool batching;
|
||||||
private bool batchChanged;
|
private bool batchChanged;
|
||||||
private SpriteSortMode sortMode;
|
|
||||||
private int itemAmount;
|
private int itemAmount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -65,20 +64,20 @@ 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>
|
/// <param name="sortMode">The drawing order for sprite drawing. When <see langword="null"/> is passed, the last used sort mode will be used again. The initial sort mode is <see cref="SpriteSortMode.Texture"/>. 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>
|
/// <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) {
|
public void BeginBatch(SpriteSortMode? sortMode = null) {
|
||||||
if (this.batching)
|
if (this.batching)
|
||||||
throw new InvalidOperationException("Already batching");
|
throw new InvalidOperationException("Already batching");
|
||||||
if (sortMode == SpriteSortMode.Immediate)
|
if (sortMode == SpriteSortMode.Immediate)
|
||||||
throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching");
|
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 the sort mode changed (which should be very rare in practice), we have to re-sort our list
|
||||||
if (this.sortMode != sortMode) {
|
if (sortMode != null && this.sortMode != sortMode) {
|
||||||
this.sortMode = sortMode;
|
this.sortMode = sortMode.Value;
|
||||||
if (this.items.Count > 0) {
|
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();
|
this.items.Clear();
|
||||||
foreach (var item in tempItems)
|
foreach (var item in tempItems)
|
||||||
this.AddItemToSet(item);
|
this.AddItemToSet(item);
|
||||||
|
@ -110,7 +109,7 @@ namespace MLEM.Graphics {
|
||||||
var dataIndex = 0;
|
var dataIndex = 0;
|
||||||
Texture2D texture = null;
|
Texture2D texture = null;
|
||||||
foreach (var itemSet in this.items.Values) {
|
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 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);
|
||||||
|
@ -355,6 +354,21 @@ namespace MLEM.Graphics {
|
||||||
return this.Add(texture, destinationRectangle, null, color);
|
return this.Add(texture, destinationRectangle, null, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an item to this batch.
|
||||||
|
/// Note that this batch needs to currently be batching, meaning <see cref="BeginBatch"/> has to have been called previously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add.</param>
|
||||||
|
/// <returns>The added <paramref name="item"/>, for chaining.</returns>
|
||||||
|
public Item Add(Item item) {
|
||||||
|
if (!this.batching)
|
||||||
|
throw new InvalidOperationException("Not batching");
|
||||||
|
this.AddItemToSet(item);
|
||||||
|
this.itemAmount++;
|
||||||
|
this.batchChanged = true;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the given item from this batch.
|
/// Removes the given item from this batch.
|
||||||
/// Note that this batch needs to currently be batching, meaning <see cref="BeginBatch"/> has to have been called previously.
|
/// Note that this batch needs to currently be batching, meaning <see cref="BeginBatch"/> has to have been called previously.
|
||||||
|
@ -367,7 +381,7 @@ namespace MLEM.Graphics {
|
||||||
throw new InvalidOperationException("Not batching");
|
throw new InvalidOperationException("Not batching");
|
||||||
var key = item.GetSortKey(this.sortMode);
|
var key = item.GetSortKey(this.sortMode);
|
||||||
if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) {
|
if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) {
|
||||||
if (itemSet.Count <= 0)
|
if (itemSet.IsEmpty)
|
||||||
this.items.Remove(key);
|
this.items.Remove(key);
|
||||||
this.itemAmount--;
|
this.itemAmount--;
|
||||||
this.batchChanged = true;
|
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) {
|
private Item Add(Texture2D texture, float depth, VertexPositionColorTexture tl, VertexPositionColorTexture tr, VertexPositionColorTexture bl, VertexPositionColorTexture br) {
|
||||||
if (!this.batching)
|
return this.Add(new Item(texture, depth, tl, tr, bl, br));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) {
|
private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) {
|
||||||
|
@ -464,7 +472,7 @@ namespace MLEM.Graphics {
|
||||||
private void AddItemToSet(Item item) {
|
private void AddItemToSet(Item item) {
|
||||||
var sortKey = item.GetSortKey(this.sortMode);
|
var sortKey = item.GetSortKey(this.sortMode);
|
||||||
if (!this.items.TryGetValue(sortKey, out var itemSet)) {
|
if (!this.items.TryGetValue(sortKey, out var itemSet)) {
|
||||||
itemSet = new HashSet<Item>();
|
itemSet = new ItemSet();
|
||||||
this.items.Add(sortKey, itemSet);
|
this.items.Add(sortKey, itemSet);
|
||||||
}
|
}
|
||||||
itemSet.Add(item);
|
itemSet.Add(item);
|
||||||
|
@ -507,6 +515,52 @@ namespace MLEM.Graphics {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ItemSet {
|
||||||
|
|
||||||
|
public IEnumerable<Item> Items {
|
||||||
|
get {
|
||||||
|
if (this.items != null)
|
||||||
|
return this.items;
|
||||||
|
if (this.single != null)
|
||||||
|
return Enumerable.Repeat(this.single, 1);
|
||||||
|
return Enumerable.Empty<Item>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool IsEmpty => this.items == null && this.single == null;
|
||||||
|
|
||||||
|
private HashSet<Item> 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<Item>();
|
||||||
|
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
|
#if FNA
|
||||||
private class SpriteEffect : Effect {
|
private class SpriteEffect : Effect {
|
||||||
|
|
||||||
|
|
|
@ -360,7 +360,6 @@ public class GameImpl : MlemGame {
|
||||||
var items = new List<StaticSpriteBatch.Item>();
|
var items = new List<StaticSpriteBatch.Item>();
|
||||||
foreach (var r in atlas.Regions)
|
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));
|
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();
|
batch.EndBatch();
|
||||||
var sortMode = SpriteSortMode.Deferred;
|
var sortMode = SpriteSortMode.Deferred;
|
||||||
this.OnUpdate += (_, _) => {
|
this.OnUpdate += (_, _) => {
|
||||||
|
@ -371,6 +370,15 @@ public class GameImpl : MlemGame {
|
||||||
Console.WriteLine(sortMode);
|
Console.WriteLine(sortMode);
|
||||||
batch.BeginBatch(sortMode);
|
batch.BeginBatch(sortMode);
|
||||||
batch.EndBatch();
|
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));
|
this.OnDraw += (_, _) => batch.Draw(null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(3));
|
||||||
|
|
Loading…
Reference in a new issue