2021-10-17 23:20:05 +02:00
using System ;
using System.Collections.Generic ;
2021-11-13 16:42:50 +01:00
using System.Linq ;
2021-10-17 23:20:05 +02:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2022-08-20 11:39:28 +02:00
#if FNA
2022-06-24 14:01:26 +02:00
using MLEM.Extensions ;
2022-08-20 11:39:28 +02:00
using System.IO ;
#endif
2021-10-17 23:20:05 +02:00
2021-11-29 21:24:08 +01:00
namespace MLEM.Graphics {
2021-10-17 23:20:05 +02:00
/// <summary>
2022-09-12 22:57:01 +02:00
/// 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.
2021-11-12 18:35:10 +01:00
/// 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.
2021-10-17 23:20:05 +02:00
/// To draw the batched items, call <see cref="Draw"/>.
/// </summary>
public class StaticSpriteBatch : IDisposable {
2021-10-19 23:12:56 +02:00
// this maximum is limited by indices being a short
private const int MaxBatchItems = short . MaxValue / 6 ;
2022-06-15 11:38:11 +02:00
private static readonly VertexPositionColorTexture [ ] Data = new VertexPositionColorTexture [ StaticSpriteBatch . MaxBatchItems * 4 ] ;
2021-10-19 23:12:56 +02:00
2021-10-17 23:20:05 +02:00
/// <summary>
2022-06-08 11:05:18 +02:00
/// The amount of vertices that are currently batched.
2021-10-17 23:20:05 +02:00
/// </summary>
2022-09-12 22:57:01 +02:00
public int Vertices = > this . itemAmount * 4 ;
2022-06-08 11:05:18 +02:00
/// <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 ; }
2021-10-17 23:20:05 +02:00
private readonly GraphicsDevice graphicsDevice ;
private readonly SpriteEffect spriteEffect ;
2022-09-12 21:13:43 +02:00
private readonly List < DynamicVertexBuffer > vertexBuffers = new List < DynamicVertexBuffer > ( ) ;
2022-06-08 11:05:18 +02:00
private readonly List < Texture2D > textures = new List < Texture2D > ( ) ;
2022-09-13 11:57:28 +02:00
private readonly SortedDictionary < float , ItemSet > items = new SortedDictionary < float , ItemSet > ( ) ;
2022-09-12 21:51:21 +02:00
2022-09-13 11:57:28 +02:00
private SpriteSortMode sortMode = SpriteSortMode . Texture ;
2021-10-18 01:22:22 +02:00
private IndexBuffer indices ;
2021-10-17 23:20:05 +02:00
private bool batching ;
private bool batchChanged ;
2022-09-12 22:57:01 +02:00
private int itemAmount ;
2021-10-17 23:20:05 +02:00
/// <summary>
/// Creates a new static sprite batch with the given <see cref="GraphicsDevice"/>
/// </summary>
/// <param name="graphicsDevice">The graphics device to use for rendering</param>
public StaticSpriteBatch ( GraphicsDevice graphicsDevice ) {
this . graphicsDevice = graphicsDevice ;
this . spriteEffect = new SpriteEffect ( graphicsDevice ) ;
}
/// <summary>
/// Begins batching.
/// Call this method before calling <c>Add</c> or any of its overloads.
/// </summary>
2022-09-13 11:57:28 +02:00
/// <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>
2021-10-17 23:20:05 +02:00
/// <exception cref="InvalidOperationException">Thrown if this batch is currently batching already</exception>
2022-09-12 21:51:21 +02:00
/// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/>, which is not supported.</exception>
2022-09-13 11:57:28 +02:00
public void BeginBatch ( SpriteSortMode ? sortMode = null ) {
2021-10-17 23:20:05 +02:00
if ( this . batching )
throw new InvalidOperationException ( "Already batching" ) ;
2022-09-12 21:51:21 +02:00
if ( sortMode = = SpriteSortMode . Immediate )
throw new ArgumentOutOfRangeException ( nameof ( sortMode ) , "Cannot use sprite sort mode Immediate for static batching" ) ;
2022-09-12 22:57:01 +02:00
// if the sort mode changed (which should be very rare in practice), we have to re-sort our list
2022-09-13 11:57:28 +02:00
if ( sortMode ! = null & & this . sortMode ! = sortMode ) {
this . sortMode = sortMode . Value ;
2022-09-12 22:57:01 +02:00
if ( this . items . Count > 0 ) {
2022-09-13 11:57:28 +02:00
var tempItems = this . items . Values . SelectMany ( s = > s . Items ) . ToArray ( ) ;
2022-09-12 22:57:01 +02:00
this . items . Clear ( ) ;
foreach ( var item in tempItems )
this . AddItemToSet ( item ) ;
this . batchChanged = true ;
}
2022-09-12 21:51:21 +02:00
}
2021-10-17 23:20:05 +02:00
this . batching = true ;
}
/// <summary>
/// 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>
2022-06-08 11:05:18 +02:00
/// <exception cref="InvalidOperationException">Thrown if this method is called before <see cref="BeginBatch"/> was called.</exception>
2022-09-12 21:51:21 +02:00
public void EndBatch ( ) {
2021-10-17 23:20:05 +02:00
if ( ! this . batching )
throw new InvalidOperationException ( "Not batching" ) ;
this . batching = false ;
2021-11-12 18:12:57 +01:00
// if we didn't add or remove any batch items, we don't have to recalculate anything
2021-10-17 23:20:05 +02:00
if ( ! this . batchChanged )
return ;
this . batchChanged = false ;
2022-06-08 11:05:18 +02:00
this . FilledBuffers = 0 ;
this . textures . Clear ( ) ;
2021-10-17 23:20:05 +02:00
2021-10-18 01:22:22 +02:00
// fill vertex buffers
2021-11-12 20:21:08 +01:00
var dataIndex = 0 ;
2022-06-08 11:05:18 +02:00
Texture2D texture = null ;
2022-09-12 22:57:01 +02:00
foreach ( var itemSet in this . items . Values ) {
2022-09-13 11:57:28 +02:00
foreach ( var item in itemSet . Items ) {
2022-09-12 22:57:01 +02:00
// 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 ;
2022-06-08 11:05:18 +02:00
}
2022-09-12 22:57:01 +02:00
}
2021-11-12 20:21:08 +01:00
if ( dataIndex > 0 )
2022-06-15 11:38:11 +02:00
this . FillBuffer ( this . FilledBuffers + + , texture , StaticSpriteBatch . Data ) ;
2021-10-17 23:20:05 +02:00
// ensure we have enough indices
2022-09-12 22:57:01 +02:00
var maxItems = Math . Min ( this . itemAmount , StaticSpriteBatch . MaxBatchItems ) ;
2021-10-17 23:20:05 +02:00
// each item has 2 triangles which each have 3 indices
2021-10-18 01:22:22 +02:00
if ( this . indices = = null | | this . indices . IndexCount < 6 * maxItems ) {
var newIndices = new short [ 6 * maxItems ] ;
2021-10-17 23:20:05 +02:00
var index = 0 ;
for ( var item = 0 ; item < maxItems ; item + + ) {
// a square is made up of two triangles
// 0--1
// | /|
// |/ |
// 2--3
// top left triangle (0 -> 1 -> 2)
2021-10-18 01:22:22 +02:00
newIndices [ index + + ] = ( short ) ( item * 4 + 0 ) ;
newIndices [ index + + ] = ( short ) ( item * 4 + 1 ) ;
newIndices [ index + + ] = ( short ) ( item * 4 + 2 ) ;
2021-10-17 23:20:05 +02:00
// bottom right triangle (1 -> 3 -> 2)
2021-10-18 01:22:22 +02:00
newIndices [ index + + ] = ( short ) ( item * 4 + 1 ) ;
newIndices [ index + + ] = ( short ) ( item * 4 + 3 ) ;
newIndices [ index + + ] = ( short ) ( item * 4 + 2 ) ;
2021-10-17 23:20:05 +02:00
}
2021-10-30 13:48:52 +02:00
this . indices ? . Dispose ( ) ;
2021-10-18 01:22:22 +02:00
this . indices = new IndexBuffer ( this . graphicsDevice , IndexElementSize . SixteenBits , newIndices . Length , BufferUsage . WriteOnly ) ;
this . indices . SetData ( newIndices ) ;
2021-10-17 23:20:05 +02:00
}
}
/// <summary>
/// Draws this batch's content onto the <see cref="GraphicsDevice"/>'s current render target (or the back buffer) with the given settings.
/// Note that this method should not be called while a regular <see cref="SpriteBatch"/> is currently active.
/// </summary>
/// <param name="blendState">State of the blending. Uses <see cref="BlendState.AlphaBlend"/> if null.</param>
/// <param name="samplerState">State of the sampler. Uses <see cref="SamplerState.LinearClamp"/> if null.</param>
/// <param name="depthStencilState">State of the depth-stencil buffer. Uses <see cref="DepthStencilState.None"/> if null.</param>
/// <param name="rasterizerState">State of the rasterization. Uses <see cref="RasterizerState.CullCounterClockwise"/> if null.</param>
/// <param name="effect">A custom <see cref="Effect"/> to override the default sprite effect. Uses default sprite effect if null.</param>
/// <param name="transformMatrix">An optional matrix used to transform the sprite geometry. Uses <see cref="Matrix.Identity"/> if null.</param>
/// <exception cref="InvalidOperationException">Thrown if this batch is currently batching</exception>
public void Draw ( BlendState blendState = null , SamplerState samplerState = null , DepthStencilState depthStencilState = null , RasterizerState rasterizerState = null , Effect effect = null , Matrix ? transformMatrix = null ) {
if ( this . batching )
throw new InvalidOperationException ( "Cannot draw the batch while batching" ) ;
this . graphicsDevice . BlendState = blendState ? ? BlendState . AlphaBlend ;
this . graphicsDevice . SamplerStates [ 0 ] = samplerState ? ? SamplerState . LinearClamp ;
this . graphicsDevice . DepthStencilState = depthStencilState ? ? DepthStencilState . None ;
this . graphicsDevice . RasterizerState = rasterizerState ? ? RasterizerState . CullCounterClockwise ;
2021-10-18 01:22:22 +02:00
this . graphicsDevice . Indices = this . indices ;
2021-10-17 23:20:05 +02:00
this . spriteEffect . TransformMatrix = transformMatrix ;
this . spriteEffect . CurrentTechnique . Passes [ 0 ] . Apply ( ) ;
var totalIndex = 0 ;
2022-06-08 11:05:18 +02:00
for ( var i = 0 ; i < this . FilledBuffers ; i + + ) {
var buffer = this . vertexBuffers [ i ] ;
var texture = this . textures [ i ] ;
2022-09-12 22:57:01 +02:00
var verts = Math . Min ( this . itemAmount * 4 - totalIndex , buffer . VertexCount ) ;
2022-06-08 11:05:18 +02:00
2021-10-18 01:22:22 +02:00
this . graphicsDevice . SetVertexBuffer ( buffer ) ;
2021-10-17 23:20:05 +02:00
if ( effect ! = null ) {
foreach ( var pass in effect . CurrentTechnique . Passes ) {
pass . Apply ( ) ;
2022-06-08 11:05:18 +02:00
this . graphicsDevice . Textures [ 0 ] = texture ;
2022-06-24 14:01:26 +02:00
this . DrawPrimitives ( verts ) ;
2021-10-17 23:20:05 +02:00
}
} else {
2022-06-08 11:05:18 +02:00
this . graphicsDevice . Textures [ 0 ] = texture ;
2022-06-24 14:01:26 +02:00
this . DrawPrimitives ( verts ) ;
2021-10-17 23:20:05 +02:00
}
2022-06-08 11:05:18 +02:00
2021-10-18 01:22:22 +02:00
totalIndex + = buffer . VertexCount ;
2021-10-17 23:20:05 +02:00
}
}
/// <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="texture">A texture.</param>
/// <param name="position">The drawing location on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
/// <param name="rotation">A rotation of this sprite.</param>
/// <param name="origin">Center of the rotation. 0,0 by default.</param>
/// <param name="scale">A scaling of this sprite.</param>
/// <param name="effects">Modificators for drawing. Can be combined.</param>
2021-11-13 16:42:50 +01:00
/// <param name="layerDepth">A depth of the layer of this sprite.</param>
2021-11-12 20:21:08 +01:00
/// <returns>The <see cref="Item"/> that was created from the added data</returns>
public Item Add ( Texture2D texture , Vector2 position , Rectangle ? sourceRectangle , Color color , float rotation , Vector2 origin , Vector2 scale , SpriteEffects effects , float layerDepth ) {
2021-10-17 23:20:05 +02:00
origin * = scale ;
Vector2 size , texTl , texBr ;
if ( sourceRectangle . HasValue ) {
var src = sourceRectangle . Value ;
size . X = src . Width * scale . X ;
size . Y = src . Height * scale . Y ;
texTl . X = src . X * ( 1F / texture . Width ) ;
texTl . Y = src . Y * ( 1F / texture . Height ) ;
texBr . X = ( src . X + src . Width ) * ( 1F / texture . Width ) ;
texBr . Y = ( src . Y + src . Height ) * ( 1F / texture . Height ) ;
} else {
size . X = texture . Width * scale . X ;
size . Y = texture . Height * scale . Y ;
texTl = Vector2 . Zero ;
texBr = Vector2 . One ;
}
if ( ( effects & SpriteEffects . FlipVertically ) ! = 0 )
( texBr . Y , texTl . Y ) = ( texTl . Y , texBr . Y ) ;
if ( ( effects & SpriteEffects . FlipHorizontally ) ! = 0 )
( texBr . X , texTl . X ) = ( texTl . X , texBr . X ) ;
if ( rotation = = 0 ) {
2021-11-12 18:12:57 +01:00
return this . Add ( texture , position - origin , size , color , texTl , texBr , layerDepth ) ;
2021-10-17 23:20:05 +02:00
} else {
2021-11-12 18:12:57 +01:00
return this . Add ( texture , position , - origin , size , ( float ) Math . Sin ( rotation ) , ( float ) Math . Cos ( rotation ) , color , texTl , texBr , layerDepth ) ;
2021-10-17 23:20:05 +02:00
}
}
/// <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="texture">A texture.</param>
/// <param name="position">The drawing location on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
/// <param name="rotation">A rotation of this sprite.</param>
/// <param name="origin">Center of the rotation. 0,0 by default.</param>
/// <param name="scale">A scaling of this sprite.</param>
/// <param name="effects">Modificators for drawing. Can be combined.</param>
2021-11-13 16:42:50 +01:00
/// <param name="layerDepth">A depth of the layer of this sprite.</param>
2021-11-12 20:21:08 +01:00
/// <returns>The <see cref="Item"/> that was created from the added data</returns>
public Item Add ( Texture2D texture , Vector2 position , Rectangle ? sourceRectangle , Color color , float rotation , Vector2 origin , float scale , SpriteEffects effects , float layerDepth ) {
2021-11-12 18:12:57 +01:00
return this . Add ( texture , position , sourceRectangle , color , rotation , origin , new Vector2 ( scale ) , effects , layerDepth ) ;
2021-10-17 23:20:05 +02:00
}
/// <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="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
/// <param name="rotation">A rotation of this sprite.</param>
/// <param name="origin">Center of the rotation. 0,0 by default.</param>
/// <param name="effects">Modificators for drawing. Can be combined.</param>
2021-11-13 16:42:50 +01:00
/// <param name="layerDepth">A depth of the layer of this sprite.</param>
2021-11-12 20:21:08 +01:00
/// <returns>The <see cref="Item"/> that was created from the added data</returns>
public Item Add ( Texture2D texture , Rectangle destinationRectangle , Rectangle ? sourceRectangle , Color color , float rotation , Vector2 origin , SpriteEffects effects , float layerDepth ) {
2021-10-17 23:20:05 +02:00
Vector2 texTl , texBr ;
if ( sourceRectangle . HasValue ) {
var src = sourceRectangle . Value ;
texTl . X = src . X * ( 1F / texture . Width ) ;
texTl . Y = src . Y * ( 1F / texture . Height ) ;
texBr . X = ( src . X + src . Width ) * ( 1F / texture . Width ) ;
texBr . Y = ( src . Y + src . Height ) * ( 1F / texture . Height ) ;
origin . X = origin . X * destinationRectangle . Width * ( src . Width ! = 0 ? src . Width : 1F / texture . Width ) ;
origin . Y = origin . Y * destinationRectangle . Height * ( src . Height ! = 0 ? src . Height : 1F / texture . Height ) ;
} else {
texTl = Vector2 . Zero ;
texBr = Vector2 . One ;
origin . X = origin . X * destinationRectangle . Width * ( 1F / texture . Width ) ;
origin . Y = origin . Y * destinationRectangle . Height * ( 1F / texture . Height ) ;
}
if ( ( effects & SpriteEffects . FlipVertically ) ! = 0 )
( texBr . Y , texTl . Y ) = ( texTl . Y , texBr . Y ) ;
if ( ( effects & SpriteEffects . FlipHorizontally ) ! = 0 )
( texBr . X , texTl . X ) = ( texTl . X , texBr . X ) ;
2022-06-24 14:01:26 +02:00
var destSize = new Vector2 ( destinationRectangle . Width , destinationRectangle . Height ) ;
2021-10-17 23:20:05 +02:00
if ( rotation = = 0 ) {
2022-06-24 14:01:26 +02:00
return this . Add ( texture , destinationRectangle . Location . ToVector2 ( ) - origin , destSize , color , texTl , texBr , layerDepth ) ;
2021-10-17 23:20:05 +02:00
} else {
2022-06-24 14:01:26 +02:00
return this . Add ( texture , destinationRectangle . Location . ToVector2 ( ) , - origin , destSize , ( float ) Math . Sin ( rotation ) , ( float ) Math . Cos ( rotation ) , color , texTl , texBr , layerDepth ) ;
2021-10-17 23:20:05 +02:00
}
}
/// <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="texture">A texture.</param>
/// <param name="position">The drawing location on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
2021-11-12 20:21:08 +01:00
/// <returns>The <see cref="Item"/> that was created from the added data</returns>
public Item Add ( Texture2D texture , Vector2 position , Rectangle ? sourceRectangle , Color color ) {
2021-11-12 18:12:57 +01:00
return this . Add ( texture , position , sourceRectangle , color , 0 , Vector2 . Zero , 1 , SpriteEffects . None , 0 ) ;
2021-10-17 23:20:05 +02:00
}
/// <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="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
2021-11-12 20:21:08 +01:00
/// <returns>The <see cref="Item"/> that was created from the added data</returns>
public Item Add ( Texture2D texture , Rectangle destinationRectangle , Rectangle ? sourceRectangle , Color color ) {
2021-11-12 18:12:57 +01:00
return this . Add ( texture , destinationRectangle , sourceRectangle , color , 0 , Vector2 . Zero , SpriteEffects . None , 0 ) ;
2021-10-17 23:20:05 +02:00
}
/// <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="texture">A texture.</param>
/// <param name="position">The drawing location on screen.</param>
/// <param name="color">A color mask.</param>
2021-11-12 20:21:08 +01:00
/// <returns>The <see cref="Item"/> that was created from the added data</returns>
public Item Add ( Texture2D texture , Vector2 position , Color color ) {
2021-11-12 18:12:57 +01:00
return this . Add ( texture , position , null , color ) ;
2021-10-17 23:20:05 +02:00
}
/// <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="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="color">A color mask.</param>
2021-11-12 20:21:08 +01:00
/// <returns>The <see cref="Item"/> that was created from the added data</returns>
public Item Add ( Texture2D texture , Rectangle destinationRectangle , Color color ) {
2021-11-12 18:12:57 +01:00
return this . Add ( texture , destinationRectangle , null , color ) ;
}
2022-09-13 11:57:28 +02:00
/// <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 ;
}
2021-11-12 18:12:57 +01:00
/// <summary>
/// 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.
/// </summary>
/// <param name="item">The item to remove</param>
/// <returns>Whether the item was successfully removed</returns>
/// <exception cref="InvalidOperationException">Thrown if this method is called before <see cref="BeginBatch"/> was called</exception>
2021-11-12 20:21:08 +01:00
public bool Remove ( Item item ) {
2021-11-12 18:12:57 +01:00
if ( ! this . batching )
throw new InvalidOperationException ( "Not batching" ) ;
2022-09-12 22:57:01 +02:00
var key = item . GetSortKey ( this . sortMode ) ;
if ( this . items . TryGetValue ( key , out var itemSet ) & & itemSet . Remove ( item ) ) {
2022-09-13 11:57:28 +02:00
if ( itemSet . IsEmpty )
2022-09-12 22:57:01 +02:00
this . items . Remove ( key ) ;
this . itemAmount - - ;
2021-11-12 18:12:57 +01:00
this . batchChanged = true ;
return true ;
}
return false ;
2021-10-17 23:20:05 +02:00
}
2021-11-12 18:35:10 +01:00
/// <summary>
/// Clears the batch, removing all currently batched vertices.
/// After this operation, <see cref="Vertices"/> will return 0.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this method is called before <see cref="BeginBatch"/> was called</exception>
public void ClearBatch ( ) {
if ( ! this . batching )
throw new InvalidOperationException ( "Not batching" ) ;
2022-09-12 22:57:01 +02:00
this . items . Clear ( ) ;
2022-06-08 11:05:18 +02:00
this . textures . Clear ( ) ;
this . FilledBuffers = 0 ;
2022-09-12 22:57:01 +02:00
this . itemAmount = 0 ;
2021-11-12 18:35:10 +01:00
this . batchChanged = true ;
}
2021-11-22 19:25:18 +01:00
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
2021-10-17 23:20:05 +02:00
public void Dispose ( ) {
this . spriteEffect . Dispose ( ) ;
2021-10-30 13:48:52 +02:00
this . indices ? . Dispose ( ) ;
foreach ( var buffer in this . vertexBuffers )
buffer . Dispose ( ) ;
2021-10-17 23:20:05 +02:00
GC . SuppressFinalize ( this ) ;
}
2021-11-12 20:21:08 +01:00
private Item Add ( Texture2D texture , Vector2 pos , Vector2 offset , Vector2 size , float sin , float cos , Color color , Vector2 texTl , Vector2 texBr , float depth ) {
2021-11-13 16:42:50 +01:00
return this . Add ( texture , depth ,
2022-01-02 22:58:01 +01:00
new VertexPositionColorTexture ( new Vector3 (
pos . X + offset . X * cos - offset . Y * sin ,
pos . Y + offset . X * sin + offset . Y * cos , depth ) ,
color , texTl ) ,
new VertexPositionColorTexture ( new Vector3 (
pos . X + ( offset . X + size . X ) * cos - offset . Y * sin ,
pos . Y + ( offset . X + size . X ) * sin + offset . Y * cos , depth ) ,
color , new Vector2 ( texBr . X , texTl . Y ) ) ,
new VertexPositionColorTexture ( new Vector3 (
pos . X + offset . X * cos - ( offset . Y + size . Y ) * sin ,
pos . Y + offset . X * sin + ( offset . Y + size . Y ) * cos , depth ) ,
color , new Vector2 ( texTl . X , texBr . Y ) ) ,
new VertexPositionColorTexture ( new Vector3 (
pos . X + ( offset . X + size . X ) * cos - ( offset . Y + size . Y ) * sin ,
pos . Y + ( offset . X + size . X ) * sin + ( offset . Y + size . Y ) * cos , depth ) ,
color , texBr ) ) ;
2021-10-17 23:20:05 +02:00
}
2021-11-12 20:21:08 +01:00
private Item Add ( Texture2D texture , Vector2 pos , Vector2 size , Color color , Vector2 texTl , Vector2 texBr , float depth ) {
2021-11-13 16:42:50 +01:00
return this . Add ( texture , depth ,
2022-01-02 22:58:01 +01:00
new VertexPositionColorTexture (
new Vector3 ( pos , depth ) ,
color , texTl ) ,
new VertexPositionColorTexture (
new Vector3 ( pos . X + size . X , pos . Y , depth ) ,
color , new Vector2 ( texBr . X , texTl . Y ) ) ,
new VertexPositionColorTexture (
new Vector3 ( pos . X , pos . Y + size . Y , depth ) ,
color , new Vector2 ( texTl . X , texBr . Y ) ) ,
new VertexPositionColorTexture (
new Vector3 ( pos . X + size . X , pos . Y + size . Y , depth ) ,
color , texBr ) ) ;
2021-10-17 23:20:05 +02:00
}
2021-11-13 16:42:50 +01:00
private Item Add ( Texture2D texture , float depth , VertexPositionColorTexture tl , VertexPositionColorTexture tr , VertexPositionColorTexture bl , VertexPositionColorTexture br ) {
2022-09-13 11:57:28 +02:00
return this . Add ( new Item ( texture , depth , tl , tr , bl , br ) ) ;
2021-11-12 18:12:57 +01:00
}
2022-06-08 11:05:18 +02:00
private void FillBuffer ( int index , Texture2D texture , VertexPositionColorTexture [ ] data ) {
if ( this . vertexBuffers . Count < = index )
2022-09-12 21:13:43 +02:00
this . vertexBuffers . Add ( new DynamicVertexBuffer ( this . graphicsDevice , VertexPositionColorTexture . VertexDeclaration , StaticSpriteBatch . MaxBatchItems * 4 , BufferUsage . WriteOnly ) ) ;
this . vertexBuffers [ index ] . SetData ( data , 0 , data . Length , SetDataOptions . Discard ) ;
2022-06-08 11:05:18 +02:00
this . textures . Insert ( index , texture ) ;
}
2022-06-24 14:01:26 +02:00
private void DrawPrimitives ( int vertices ) {
#if FNA
this . graphicsDevice . DrawIndexedPrimitives ( PrimitiveType . TriangleList , 0 , 0 , vertices , 0 , vertices / 4 * 2 ) ;
#else
this . graphicsDevice . DrawIndexedPrimitives ( PrimitiveType . TriangleList , 0 , 0 , vertices / 4 * 2 ) ;
#endif
}
2022-09-12 22:57:01 +02:00
private void AddItemToSet ( Item item ) {
var sortKey = item . GetSortKey ( this . sortMode ) ;
if ( ! this . items . TryGetValue ( sortKey , out var itemSet ) ) {
2022-09-13 11:57:28 +02:00
itemSet = new ItemSet ( ) ;
2022-09-12 22:57:01 +02:00
this . items . Add ( sortKey , itemSet ) ;
2022-09-12 21:51:21 +02:00
}
2022-09-12 22:57:01 +02:00
itemSet . Add ( item ) ;
2022-09-12 21:51:21 +02:00
}
2021-11-12 18:12:57 +01:00
/// <summary>
/// A struct that represents an item added to a <see cref="StaticSpriteBatch"/> using <c>Add</c> or any of its overloads.
2021-11-12 20:21:08 +01:00
/// An item returned after adding can be removed using <see cref="Remove"/>.
2021-11-12 18:12:57 +01:00
/// </summary>
2021-11-12 20:21:08 +01:00
public class Item {
2022-06-08 11:05:18 +02:00
internal readonly Texture2D Texture ;
internal readonly float Depth ;
2021-11-12 20:21:08 +01:00
internal readonly VertexPositionColorTexture TopLeft ;
internal readonly VertexPositionColorTexture TopRight ;
internal readonly VertexPositionColorTexture BottomLeft ;
internal readonly VertexPositionColorTexture BottomRight ;
2022-06-08 11:05:18 +02:00
internal Item ( Texture2D texture , float depth , VertexPositionColorTexture topLeft , VertexPositionColorTexture topRight , VertexPositionColorTexture bottomLeft , VertexPositionColorTexture bottomRight ) {
this . Texture = texture ;
this . Depth = depth ;
2021-11-12 20:21:08 +01:00
this . TopLeft = topLeft ;
this . TopRight = topRight ;
this . BottomLeft = bottomLeft ;
this . BottomRight = bottomRight ;
2021-11-12 18:12:57 +01:00
}
2022-09-12 22:57:01 +02:00
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 ;
}
}
2021-10-17 23:20:05 +02:00
}
2022-09-13 11:57:28 +02:00
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 ;
}
}
}
2022-06-24 14:01:26 +02:00
#if FNA
private class SpriteEffect : Effect {
private EffectParameter matrixParam ;
private Viewport lastViewport ;
private Matrix projection ;
public Matrix ? TransformMatrix { get ; set ; }
public SpriteEffect ( GraphicsDevice device ) : base ( device , SpriteEffect . LoadEffectCode ( ) ) {
this . CacheEffectParameters ( ) ;
}
private SpriteEffect ( SpriteEffect cloneSource ) : base ( cloneSource ) {
this . CacheEffectParameters ( ) ;
}
public override Effect Clone ( ) {
return new SpriteEffect ( this ) ;
}
private void CacheEffectParameters ( ) {
this . matrixParam = this . Parameters [ "MatrixTransform" ] ;
}
protected override void OnApply ( ) {
var vp = this . GraphicsDevice . Viewport ;
if ( vp . Width ! = this . lastViewport . Width | | vp . Height ! = this . lastViewport . Height ) {
Matrix . CreateOrthographicOffCenter ( 0 , vp . Width , vp . Height , 0 , 0 , - 1 , out this . projection ) ;
this . projection . M41 + = - 0.5f * this . projection . M11 ;
this . projection . M42 + = - 0.5f * this . projection . M22 ;
this . lastViewport = vp ;
}
if ( this . TransformMatrix . HasValue ) {
this . matrixParam . SetValue ( this . TransformMatrix . GetValueOrDefault ( ) * this . projection ) ;
} else {
this . matrixParam . SetValue ( this . projection ) ;
}
}
private static byte [ ] LoadEffectCode ( ) {
using ( var stream = typeof ( Effect ) . Assembly . GetManifestResourceStream ( "Microsoft.Xna.Framework.Graphics.Effect.Resources.SpriteEffect.fxb" ) ) {
using ( var memory = new MemoryStream ( ) ) {
stream . CopyTo ( memory ) ;
return memory . ToArray ( ) ;
}
}
}
}
#endif
2021-10-17 23:20:05 +02:00
}
2022-06-17 18:23:47 +02:00
}