Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
File last commit:
Show/Diff file:
Action:
FNA/src/Graphics/SpriteBatch.cs
1583 lines | 36.9 KiB | text/x-csharp | CSharpLexer
#region License
/* FNA - XNA4 Reimplementation for Desktop Platforms
* Copyright 2009-2020 Ethan Lee and the MonoGame Team
*
* Released under the Microsoft Public License.
* See LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
#endregion
namespace Microsoft.Xna.Framework.Graphics
{
/* MSDN Docs:
* http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.spritebatch.aspx
* Other References:
* http://directxtk.codeplex.com/SourceControl/changeset/view/17079#Src/SpriteBatch.cpp
* http://gamedev.stackexchange.com/questions/21220/how-exactly-does-xnas-spritebatch-work
*/
public class SpriteBatch : GraphicsResource
{
#region Private Constant Values
// As defined by the HiDef profile spec
private const int MAX_SPRITES = 2048;
private const int MAX_VERTICES = MAX_SPRITES * 4;
private const int MAX_INDICES = MAX_SPRITES * 6;
// Used to quickly flip text for DrawString
private static readonly float[] axisDirectionX = new float[]
{
-1.0f,
1.0f,
-1.0f,
1.0f
};
private static readonly float[] axisDirectionY = new float[]
{
-1.0f,
-1.0f,
1.0f,
1.0f
};
private static readonly float[] axisIsMirroredX = new float[]
{
0.0f,
1.0f,
0.0f,
1.0f
};
private static readonly float[] axisIsMirroredY = new float[]
{
0.0f,
0.0f,
1.0f,
1.0f
};
// Used to calculate texture coordinates
private static readonly float[] CornerOffsetX = new float[]
{
0.0f,
1.0f,
0.0f,
1.0f
};
private static readonly float[] CornerOffsetY = new float[]
{
0.0f,
0.0f,
1.0f,
1.0f
};
#endregion
#region Private Variables
// Buffer objects used for actual drawing
private DynamicVertexBuffer vertexBuffer;
private IndexBuffer indexBuffer;
// Local data stored before buffering to GPU
private SpriteInfo[] spriteInfos;
private IntPtr[] sortedSpriteInfos; // SpriteInfo*[]
private VertexPositionColorTexture4[] vertexInfo;
private Texture2D[] textureInfo;
// Default SpriteBatch Effect
private Effect spriteEffect;
private IntPtr spriteMatrixTransform;
private EffectPass spriteEffectPass;
// Tracks Begin/End calls
private bool beginCalled;
// Current sort mode
private SpriteSortMode sortMode;
// Keep render state for non-Immediate modes.
private BlendState blendState;
private SamplerState samplerState;
private DepthStencilState depthStencilState;
private RasterizerState rasterizerState;
// How many sprites are in the current batch?
private int numSprites;
// Where are we in the vertex buffer ring?
private int bufferOffset;
// Matrix to be used when creating the projection matrix
private Matrix transformMatrix;
// User-provided Effect, if applicable
private Effect customEffect;
#endregion
#region Private Static Variables
/* If you use this file to make your own SpriteBatch, take the
* shader source and binary and load it as a file. Find it in
* src/Graphics/Effect/StockEffects/, the HLSL and FXB folders!
* -flibit
*/
private static readonly byte[] spriteEffectCode = Resources.SpriteEffect;
private static readonly short[] indexData = GenerateIndexArray();
private static readonly TextureComparer TextureCompare = new TextureComparer();
private static readonly BackToFrontComparer BackToFrontCompare = new BackToFrontComparer();
private static readonly FrontToBackComparer FrontToBackCompare = new FrontToBackComparer();
#endregion
#region Public Constructor
public SpriteBatch(GraphicsDevice graphicsDevice)
{
if (graphicsDevice == null)
{
throw new ArgumentNullException("graphicsDevice");
}
GraphicsDevice = graphicsDevice;
vertexInfo = new VertexPositionColorTexture4[MAX_SPRITES];
textureInfo = new Texture2D[MAX_SPRITES];
spriteInfos = new SpriteInfo[MAX_SPRITES];
sortedSpriteInfos = new IntPtr[MAX_SPRITES];
vertexBuffer = new DynamicVertexBuffer(
graphicsDevice,
typeof(VertexPositionColorTexture),
MAX_VERTICES,
BufferUsage.WriteOnly
);
indexBuffer = new IndexBuffer(
graphicsDevice,
IndexElementSize.SixteenBits,
MAX_INDICES,
BufferUsage.WriteOnly
);
indexBuffer.SetData(indexData);
spriteEffect = new Effect(
graphicsDevice,
spriteEffectCode
);
spriteMatrixTransform = spriteEffect.Parameters["MatrixTransform"].values;
spriteEffectPass = spriteEffect.CurrentTechnique.Passes[0];
beginCalled = false;
numSprites = 0;
}
#endregion
#region Public Dispose Method
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
spriteEffect.Dispose();
indexBuffer.Dispose();
vertexBuffer.Dispose();
}
base.Dispose(disposing);
}
#endregion
#region Public Begin Methods
public void Begin()
{
Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.LinearClamp,
DepthStencilState.None,
RasterizerState.CullCounterClockwise,
null,
Matrix.Identity
);
}
public void Begin(
SpriteSortMode sortMode,
BlendState blendState
) {
Begin(
sortMode,
blendState,
SamplerState.LinearClamp,
DepthStencilState.None,
RasterizerState.CullCounterClockwise,
null,
Matrix.Identity
);
}
public void Begin(
SpriteSortMode sortMode,
BlendState blendState,
SamplerState samplerState,
DepthStencilState depthStencilState,
RasterizerState rasterizerState
) {
Begin(
sortMode,
blendState,
samplerState,
depthStencilState,
rasterizerState,
null,
Matrix.Identity
);
}
public void Begin(
SpriteSortMode sortMode,
BlendState blendState,
SamplerState samplerState,
DepthStencilState depthStencilState,
RasterizerState rasterizerState,
Effect effect
) {
Begin(
sortMode,
blendState,
samplerState,
depthStencilState,
rasterizerState,
effect,
Matrix.Identity
);
}
public void Begin(
SpriteSortMode sortMode,
BlendState blendState,
SamplerState samplerState,
DepthStencilState depthStencilState,
RasterizerState rasterizerState,
Effect effect,
Matrix transformationMatrix
) {
if (beginCalled)
{
throw new InvalidOperationException(
"Begin has been called before calling End" +
" after the last call to Begin." +
" Begin cannot be called again until" +
" End has been successfully called."
);
}
beginCalled = true;
this.sortMode = sortMode;
this.blendState = blendState ?? BlendState.AlphaBlend;
this.samplerState = samplerState ?? SamplerState.LinearClamp;
this.depthStencilState = depthStencilState ?? DepthStencilState.None;
this.rasterizerState = rasterizerState ?? RasterizerState.CullCounterClockwise;
customEffect = effect;
transformMatrix = transformationMatrix;
if (sortMode == SpriteSortMode.Immediate)
{
PrepRenderState();
}
}
#endregion
#region Public End Method
public void End()
{
if (!beginCalled)
{
throw new InvalidOperationException(
"End was called, but Begin has not yet" +
" been called. You must call Begin " +
" successfully before you can call End."
);
}
beginCalled = false;
if (sortMode != SpriteSortMode.Immediate)
{
FlushBatch();
}
customEffect = null;
}
#endregion
#region Public Draw Methods
public void Draw(
Texture2D texture,
Vector2 position,
Color color
) {
CheckBegin("Draw");
PushSprite(
texture,
0.0f,
0.0f,
1.0f,
1.0f,
position.X,
position.Y,
texture.Width,
texture.Height,
color,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0
);
}
public void Draw(
Texture2D texture,
Vector2 position,
Rectangle? sourceRectangle,
Color color
) {
float sourceX, sourceY, sourceW, sourceH;
float destW, destH;
if (sourceRectangle.HasValue)
{
sourceX = sourceRectangle.Value.X / (float) texture.Width;
sourceY = sourceRectangle.Value.Y / (float) texture.Height;
sourceW = sourceRectangle.Value.Width / (float) texture.Width;
sourceH = sourceRectangle.Value.Height / (float) texture.Height;
destW = sourceRectangle.Value.Width;
destH = sourceRectangle.Value.Height;
}
else
{
sourceX = 0.0f;
sourceY = 0.0f;
sourceW = 1.0f;
sourceH = 1.0f;
destW = texture.Width;
destH = texture.Height;
}
CheckBegin("Draw");
PushSprite(
texture,
sourceX,
sourceY,
sourceW,
sourceH,
position.X,
position.Y,
destW,
destH,
color,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0
);
}
public void Draw(
Texture2D texture,
Vector2 position,
Rectangle? sourceRectangle,
Color color,
float rotation,
Vector2 origin,
float scale,
SpriteEffects effects,
float layerDepth
) {
CheckBegin("Draw");
float sourceX, sourceY, sourceW, sourceH;
float destW = scale;
float destH = scale;
if (sourceRectangle.HasValue)
{
sourceX = sourceRectangle.Value.X / (float) texture.Width;
sourceY = sourceRectangle.Value.Y / (float) texture.Height;
sourceW = Math.Sign(sourceRectangle.Value.Width) * Math.Max(
Math.Abs(sourceRectangle.Value.Width),
MathHelper.MachineEpsilonFloat
) / (float) texture.Width;
sourceH = Math.Sign(sourceRectangle.Value.Height) * Math.Max(
Math.Abs(sourceRectangle.Value.Height),
MathHelper.MachineEpsilonFloat
) / (float) texture.Height;
destW *= sourceRectangle.Value.Width;
destH *= sourceRectangle.Value.Height;
}
else
{
sourceX = 0.0f;
sourceY = 0.0f;
sourceW = 1.0f;
sourceH = 1.0f;
destW *= texture.Width;
destH *= texture.Height;
}
PushSprite(
texture,
sourceX,
sourceY,
sourceW,
sourceH,
position.X,
position.Y,
destW,
destH,
color,
origin.X / sourceW / (float) texture.Width,
origin.Y / sourceH / (float) texture.Height,
(float) Math.Sin(rotation),
(float) Math.Cos(rotation),
layerDepth,
(byte) (effects & (SpriteEffects) 0x03)
);
}
public void Draw(
Texture2D texture,
Vector2 position,
Rectangle? sourceRectangle,
Color color,
float rotation,
Vector2 origin,
Vector2 scale,
SpriteEffects effects,
float layerDepth
) {
CheckBegin("Draw");
float sourceX, sourceY, sourceW, sourceH;
if (sourceRectangle.HasValue)
{
sourceX = sourceRectangle.Value.X / (float) texture.Width;
sourceY = sourceRectangle.Value.Y / (float) texture.Height;
sourceW = Math.Sign(sourceRectangle.Value.Width) * Math.Max(
Math.Abs(sourceRectangle.Value.Width),
MathHelper.MachineEpsilonFloat
) / (float) texture.Width;
sourceH = Math.Sign(sourceRectangle.Value.Height) * Math.Max(
Math.Abs(sourceRectangle.Value.Height),
MathHelper.MachineEpsilonFloat
) / (float) texture.Height;
scale.X *= sourceRectangle.Value.Width;
scale.Y *= sourceRectangle.Value.Height;
}
else
{
sourceX = 0.0f;
sourceY = 0.0f;
sourceW = 1.0f;
sourceH = 1.0f;
scale.X *= texture.Width;
scale.Y *= texture.Height;
}
PushSprite(
texture,
sourceX,
sourceY,
sourceW,
sourceH,
position.X,
position.Y,
scale.X,
scale.Y,
color,
origin.X / sourceW / (float) texture.Width,
origin.Y / sourceH / (float) texture.Height,
(float) Math.Sin(rotation),
(float) Math.Cos(rotation),
layerDepth,
(byte) (effects & (SpriteEffects) 0x03)
);
}
public void Draw(
Texture2D texture,
Rectangle destinationRectangle,
Color color
) {
CheckBegin("Draw");
PushSprite(
texture,
0.0f,
0.0f,
1.0f,
1.0f,
destinationRectangle.X,
destinationRectangle.Y,
destinationRectangle.Width,
destinationRectangle.Height,
color,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0
);
}
public void Draw(
Texture2D texture,
Rectangle destinationRectangle,
Rectangle? sourceRectangle,
Color color
) {
CheckBegin("Draw");
float sourceX, sourceY, sourceW, sourceH;
if (sourceRectangle.HasValue)
{
sourceX = sourceRectangle.Value.X / (float) texture.Width;
sourceY = sourceRectangle.Value.Y / (float) texture.Height;
sourceW = sourceRectangle.Value.Width / (float) texture.Width;
sourceH = sourceRectangle.Value.Height / (float) texture.Height;
}
else
{
sourceX = 0.0f;
sourceY = 0.0f;
sourceW = 1.0f;
sourceH = 1.0f;
}
PushSprite(
texture,
sourceX,
sourceY,
sourceW,
sourceH,
destinationRectangle.X,
destinationRectangle.Y,
destinationRectangle.Width,
destinationRectangle.Height,
color,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0
);
}
public void Draw(
Texture2D texture,
Rectangle destinationRectangle,
Rectangle? sourceRectangle,
Color color,
float rotation,
Vector2 origin,
SpriteEffects effects,
float layerDepth
) {
CheckBegin("Draw");
float sourceX, sourceY, sourceW, sourceH;
if (sourceRectangle.HasValue)
{
sourceX = sourceRectangle.Value.X / (float) texture.Width;
sourceY = sourceRectangle.Value.Y / (float) texture.Height;
sourceW = Math.Sign(sourceRectangle.Value.Width) * Math.Max(
Math.Abs(sourceRectangle.Value.Width),
MathHelper.MachineEpsilonFloat
) / (float) texture.Width;
sourceH = Math.Sign(sourceRectangle.Value.Height) * Math.Max(
Math.Abs(sourceRectangle.Value.Height),
MathHelper.MachineEpsilonFloat
) / (float) texture.Height;
}
else
{
sourceX = 0.0f;
sourceY = 0.0f;
sourceW = 1.0f;
sourceH = 1.0f;
}
PushSprite(
texture,
sourceX,
sourceY,
sourceW,
sourceH,
destinationRectangle.X,
destinationRectangle.Y,
destinationRectangle.Width,
destinationRectangle.Height,
color,
origin.X / sourceW / (float) texture.Width,
origin.Y / sourceH / (float) texture.Height,
(float) Math.Sin(rotation),
(float) Math.Cos(rotation),
layerDepth,
(byte) (effects & (SpriteEffects) 0x03)
);
}
#endregion
#region Public DrawString Methods
public void DrawString(
SpriteFont spriteFont,
StringBuilder text,
Vector2 position,
Color color
) {
if (text == null)
{
throw new ArgumentNullException("text");
}
DrawString(
spriteFont,
text,
position,
color,
0.0f,
Vector2.Zero,
Vector2.One,
SpriteEffects.None,
0.0f
);
}
public void DrawString(
SpriteFont spriteFont,
StringBuilder text,
Vector2 position,
Color color,
float rotation,
Vector2 origin,
float scale,
SpriteEffects effects,
float layerDepth
) {
if (text == null)
{
throw new ArgumentNullException("text");
}
DrawString(
spriteFont,
text,
position,
color,
rotation,
origin,
new Vector2(scale),
effects,
layerDepth
);
}
public void DrawString(
SpriteFont spriteFont,
StringBuilder text,
Vector2 position,
Color color,
float rotation,
Vector2 origin,
Vector2 scale,
SpriteEffects effects,
float layerDepth
) {
/* FIXME: This method is a duplicate of DrawString(string)!
* The only difference is how we iterate through the StringBuilder.
* We don't use ToString() since it generates garbage.
* -flibit
*/
CheckBegin("DrawString");
if (text == null)
{
throw new ArgumentNullException("text");
}
if (text.Length == 0)
{
return;
}
effects &= (SpriteEffects) 0x03;
/* We pull all these internal variables in at once so
* anyone who wants to use this file to make their own
* SpriteBatch can easily replace these with reflection.
* -flibit
*/
Texture2D textureValue = spriteFont.textureValue;
List<Rectangle> glyphData = spriteFont.glyphData;
List<Rectangle> croppingData = spriteFont.croppingData;
List<Vector3> kerning = spriteFont.kerning;
Dictionary<char, int> characterIndexMap = spriteFont.characterIndexMap;
// FIXME: This needs an accuracy check! -flibit
// Calculate offsets/axes, using the string size for flipped text
Vector2 baseOffset = origin;
float axisDirX = axisDirectionX[(int) effects];
float axisDirY = axisDirectionY[(int) effects];
float axisDirMirrorX = 0.0f;
float axisDirMirrorY = 0.0f;
if (effects != SpriteEffects.None)
{
Vector2 size = spriteFont.MeasureString(text);
baseOffset.X -= size.X * axisIsMirroredX[(int) effects];
baseOffset.Y -= size.Y * axisIsMirroredY[(int) effects];
axisDirMirrorX = axisIsMirroredX[(int) effects];
axisDirMirrorY = axisIsMirroredY[(int) effects];
}
Vector2 curOffset = Vector2.Zero;
bool firstInLine = true;
for (int i = 0; i < text.Length; i += 1)
{
char c = text[i];
// Special characters
if (c == '\r')
{
continue;
}
if (c == '\n')
{
curOffset.X = 0.0f;
curOffset.Y += spriteFont.LineSpacing;
firstInLine = true;
continue;
}
/* Get the List index from the character map, defaulting to the
* DefaultCharacter if it's set.
*/
int index;
if (!characterIndexMap.TryGetValue(c, out index))
{
if (!spriteFont.DefaultCharacter.HasValue)
{
throw new ArgumentException(
"Text contains characters that cannot be" +
" resolved by this SpriteFont.",
"text"
);
}
index = characterIndexMap[spriteFont.DefaultCharacter.Value];
}
/* For the first character in a line, always push the width
* rightward, even if the kerning pushes the character to the
* left.
*/
Vector3 cKern = kerning[index];
if (firstInLine)
{
curOffset.X += Math.Abs(cKern.X);
firstInLine = false;
}
else
{
curOffset.X += spriteFont.Spacing + cKern.X;
}
// Calculate the character origin
Rectangle cCrop = croppingData[index];
Rectangle cGlyph = glyphData[index];
float offsetX = baseOffset.X + (
curOffset.X + cCrop.X
) * axisDirX;
float offsetY = baseOffset.Y + (
curOffset.Y + cCrop.Y
) * axisDirY;
if (effects != SpriteEffects.None)
{
offsetX += cGlyph.Width * axisDirMirrorX;
offsetY += cGlyph.Height * axisDirMirrorY;
}
// Draw!
float sourceW = Math.Sign(cGlyph.Width) * Math.Max(
Math.Abs(cGlyph.Width),
MathHelper.MachineEpsilonFloat
) / (float) textureValue.Width;
float sourceH = Math.Sign(cGlyph.Height) * Math.Max(
Math.Abs(cGlyph.Height),
MathHelper.MachineEpsilonFloat
) / (float) textureValue.Height;
PushSprite(
textureValue,
cGlyph.X / (float) textureValue.Width,
cGlyph.Y / (float) textureValue.Height,
sourceW,
sourceH,
position.X,
position.Y,
cGlyph.Width * scale.X,
cGlyph.Height * scale.Y,
color,
offsetX / sourceW / (float) textureValue.Width,
offsetY / sourceH / (float) textureValue.Height,
(float) Math.Sin(rotation),
(float) Math.Cos(rotation),
layerDepth,
(byte) effects
);
/* Add the character width and right-side
* bearing to the line width.
*/
curOffset.X += cKern.Y + cKern.Z;
}
}
public void DrawString(
SpriteFont spriteFont,
string text,
Vector2 position,
Color color
) {
DrawString(
spriteFont,
text,
position,
color,
0.0f,
Vector2.Zero,
Vector2.One,
SpriteEffects.None,
0.0f
);
}
public void DrawString(
SpriteFont spriteFont,
string text,
Vector2 position,
Color color,
float rotation,
Vector2 origin,
float scale,
SpriteEffects effects,
float layerDepth
) {
DrawString(
spriteFont,
text,
position,
color,
rotation,
origin,
new Vector2(scale),
effects,
layerDepth
);
}
public void DrawString(
SpriteFont spriteFont,
string text,
Vector2 position,
Color color,
float rotation,
Vector2 origin,
Vector2 scale,
SpriteEffects effects,
float layerDepth
) {
/* FIXME: This method is a duplicate of DrawString(StringBuilder)!
* The only difference is how we iterate through the string.
* -flibit
*/
CheckBegin("DrawString");
if (text == null)
{
throw new ArgumentNullException("text");
}
if (text.Length == 0)
{
return;
}
effects &= (SpriteEffects) 0x03;
/* We pull all these internal variables in at once so
* anyone who wants to use this file to make their own
* SpriteBatch can easily replace these with reflection.
* -flibit
*/
Texture2D textureValue = spriteFont.textureValue;
List<Rectangle> glyphData = spriteFont.glyphData;
List<Rectangle> croppingData = spriteFont.croppingData;
List<Vector3> kerning = spriteFont.kerning;
Dictionary<char, int> characterIndexMap = spriteFont.characterIndexMap;
// FIXME: This needs an accuracy check! -flibit
// Calculate offsets/axes, using the string size for flipped text
Vector2 baseOffset = origin;
float axisDirX = axisDirectionX[(int) effects];
float axisDirY = axisDirectionY[(int) effects];
float axisDirMirrorX = 0.0f;
float axisDirMirrorY = 0.0f;
if (effects != SpriteEffects.None)
{
Vector2 size = spriteFont.MeasureString(text);
baseOffset.X -= size.X * axisIsMirroredX[(int) effects];
baseOffset.Y -= size.Y * axisIsMirroredY[(int) effects];
axisDirMirrorX = axisIsMirroredX[(int) effects];
axisDirMirrorY = axisIsMirroredY[(int) effects];
}
Vector2 curOffset = Vector2.Zero;
bool firstInLine = true;
foreach (char c in text)
{
// Special characters
if (c == '\r')
{
continue;
}
if (c == '\n')
{
curOffset.X = 0.0f;
curOffset.Y += spriteFont.LineSpacing;
firstInLine = true;
continue;
}
/* Get the List index from the character map, defaulting to the
* DefaultCharacter if it's set.
*/
int index;
if (!characterIndexMap.TryGetValue(c, out index))
{
if (!spriteFont.DefaultCharacter.HasValue)
{
throw new ArgumentException(
"Text contains characters that cannot be" +
" resolved by this SpriteFont.",
"text"
);
}
index = characterIndexMap[spriteFont.DefaultCharacter.Value];
}
/* For the first character in a line, always push the width
* rightward, even if the kerning pushes the character to the
* left.
*/
Vector3 cKern = kerning[index];
if (firstInLine)
{
curOffset.X += Math.Abs(cKern.X);
firstInLine = false;
}
else
{
curOffset.X += spriteFont.Spacing + cKern.X;
}
// Calculate the character origin
Rectangle cCrop = croppingData[index];
Rectangle cGlyph = glyphData[index];
float offsetX = baseOffset.X + (
curOffset.X + cCrop.X
) * axisDirX;
float offsetY = baseOffset.Y + (
curOffset.Y + cCrop.Y
) * axisDirY;
if (effects != SpriteEffects.None)
{
offsetX += cGlyph.Width * axisDirMirrorX;
offsetY += cGlyph.Height * axisDirMirrorY;
}
// Draw!
float sourceW = Math.Sign(cGlyph.Width) * Math.Max(
Math.Abs(cGlyph.Width),
MathHelper.MachineEpsilonFloat
) / (float) textureValue.Width;
float sourceH = Math.Sign(cGlyph.Height) * Math.Max(
Math.Abs(cGlyph.Height),
MathHelper.MachineEpsilonFloat
) / (float) textureValue.Height;
PushSprite(
textureValue,
cGlyph.X / (float) textureValue.Width,
cGlyph.Y / (float) textureValue.Height,
sourceW,
sourceH,
position.X,
position.Y,
cGlyph.Width * scale.X,
cGlyph.Height * scale.Y,
color,
offsetX / sourceW / (float) textureValue.Width,
offsetY / sourceH / (float) textureValue.Height,
(float) Math.Sin(rotation),
(float) Math.Cos(rotation),
layerDepth,
(byte) effects
);
/* Add the character width and right-side
* bearing to the line width.
*/
curOffset.X += cKern.Y + cKern.Z;
}
}
#endregion
#region Private Methods
private unsafe void PushSprite(
Texture2D texture,
float sourceX,
float sourceY,
float sourceW,
float sourceH,
float destinationX,
float destinationY,
float destinationW,
float destinationH,
Color color,
float originX,
float originY,
float rotationSin,
float rotationCos,
float depth,
byte effects
) {
if (numSprites >= MAX_SPRITES)
{
// Oh crap, we're out of space, flush!
FlushBatch();
}
if (sortMode == SpriteSortMode.Immediate)
{
int offset;
fixed (VertexPositionColorTexture4* sprite = &vertexInfo[0])
{
GenerateVertexInfo(
sprite,
sourceX,
sourceY,
sourceW,
sourceH,
destinationX,
destinationY,
destinationW,
destinationH,
color,
originX,
originY,
rotationSin,
rotationCos,
depth,
effects
);
if (GraphicsDevice.GLDevice.SupportsNoOverwrite)
{
offset = UpdateVertexBuffer(1);
}
else
{
/* We do NOT use Discard here because
* it would be stupid to reallocate the
* whole buffer just for one sprite.
*
* Unless you're using this to blit a
* target, stop using Immediate ya donut
* -flibit
*/
offset = 0;
vertexBuffer.SetDataPointerEXT(
0,
(IntPtr) sprite,
VertexPositionColorTexture4.RealStride,
SetDataOptions.None
);
}
}
DrawPrimitives(texture, offset, 1);
}
else if (sortMode == SpriteSortMode.Deferred)
{
fixed (VertexPositionColorTexture4* sprite = &vertexInfo[numSprites])
{
GenerateVertexInfo(
sprite,
sourceX,
sourceY,
sourceW,
sourceH,
destinationX,
destinationY,
destinationW,
destinationH,
color,
originX,
originY,
rotationSin,
rotationCos,
depth,
effects
);
}
textureInfo[numSprites] = texture;
numSprites += 1;
}
else
{
fixed (SpriteInfo* spriteInfo = &spriteInfos[numSprites])
{
spriteInfo->textureHash = texture.GetHashCode();
spriteInfo->sourceX = sourceX;
spriteInfo->sourceY = sourceY;
spriteInfo->sourceW = sourceW;
spriteInfo->sourceH = sourceH;
spriteInfo->destinationX = destinationX;
spriteInfo->destinationY = destinationY;
spriteInfo->destinationW = destinationW;
spriteInfo->destinationH = destinationH;
spriteInfo->color = color;
spriteInfo->originX = originX;
spriteInfo->originY = originY;
spriteInfo->rotationSin = rotationSin;
spriteInfo->rotationCos = rotationCos;
spriteInfo->depth = depth;
spriteInfo->effects = effects;
}
textureInfo[numSprites] = texture;
numSprites += 1;
}
}
private unsafe void FlushBatch()
{
int offset = 0;
Texture2D curTexture = null;
PrepRenderState();
if (numSprites == 0)
{
// Nothing to do.
return;
}
if (sortMode != SpriteSortMode.Deferred)
{
IComparer<IntPtr> comparer;
if (sortMode == SpriteSortMode.Texture)
{
comparer = TextureCompare;
}
else if (sortMode == SpriteSortMode.BackToFront)
{
comparer = BackToFrontCompare;
}
else
{
comparer = FrontToBackCompare;
}
fixed (SpriteInfo* spriteInfo = &spriteInfos[0]) {
fixed (IntPtr* sortedSpriteInfo = &sortedSpriteInfos[0]) {
fixed (VertexPositionColorTexture4* sprites = &vertexInfo[0])
{
for (int i = 0; i < numSprites; i += 1)
{
sortedSpriteInfo[i] = (IntPtr) (&spriteInfo[i]);
}
Array.Sort(
sortedSpriteInfos,
textureInfo,
0,
numSprites,
comparer
);
for (int i = 0; i < numSprites; i += 1)
{
SpriteInfo* info = (SpriteInfo*) sortedSpriteInfo[i];
GenerateVertexInfo(
&sprites[i],
info->sourceX,
info->sourceY,
info->sourceW,
info->sourceH,
info->destinationX,
info->destinationY,
info->destinationW,
info->destinationH,
info->color,
info->originX,
info->originY,
info->rotationSin,
info->rotationCos,
info->depth,
info->effects
);
}
}}}
}
int baseOff = UpdateVertexBuffer(numSprites);
curTexture = textureInfo[0];
for (int i = 1; i < numSprites; i += 1)
{
if (textureInfo[i] != curTexture)
{
DrawPrimitives(curTexture, baseOff + offset, i - offset);
curTexture = textureInfo[i];
offset = i;
}
}
DrawPrimitives(curTexture, baseOff + offset, numSprites - offset);
numSprites = 0;
}
private unsafe int UpdateVertexBuffer(int count)
{
int offset;
SetDataOptions options;
if ( (bufferOffset + count) > MAX_SPRITES ||
!GraphicsDevice.GLDevice.SupportsNoOverwrite )
{
offset = 0;
options = SetDataOptions.Discard;
}
else
{
offset = bufferOffset;
options = SetDataOptions.NoOverwrite;
}
fixed (VertexPositionColorTexture4* p = &vertexInfo[0])
{
/* We use Discard here because the last batch
* may still be executing, and we can't always
* trust the driver to use a staging buffer for
* buffer uploads that overlap between commands.
*
* If you aren't using the whole vertex buffer,
* that's your own fault. Use the whole buffer!
* -flibit
*/
vertexBuffer.SetDataPointerEXT(
offset * VertexPositionColorTexture4.RealStride,
(IntPtr) p,
count * VertexPositionColorTexture4.RealStride,
options
);
}
bufferOffset = offset + count;
return offset;
}
private static unsafe void GenerateVertexInfo(
VertexPositionColorTexture4* sprite,
float sourceX,
float sourceY,
float sourceW,
float sourceH,
float destinationX,
float destinationY,
float destinationW,
float destinationH,
Color color,
float originX,
float originY,
float rotationSin,
float rotationCos,
float depth,
byte effects
) {
float cornerX = -originX * destinationW;
float cornerY = -originY * destinationH;
sprite->Position0.X = (
(-rotationSin * cornerY) +
(rotationCos * cornerX) +
destinationX
);
sprite->Position0.Y = (
(rotationCos * cornerY) +
(rotationSin * cornerX) +
destinationY
);
cornerX = (1.0f - originX) * destinationW;
cornerY = -originY * destinationH;
sprite->Position1.X = (
(-rotationSin * cornerY) +
(rotationCos * cornerX) +
destinationX
);
sprite->Position1.Y = (
(rotationCos * cornerY) +
(rotationSin * cornerX) +
destinationY
);
cornerX = -originX * destinationW;
cornerY = (1.0f - originY) * destinationH;
sprite->Position2.X = (
(-rotationSin * cornerY) +
(rotationCos * cornerX) +
destinationX
);
sprite->Position2.Y = (
(rotationCos * cornerY) +
(rotationSin * cornerX) +
destinationY
);
cornerX = (1.0f - originX) * destinationW;
cornerY = (1.0f - originY) * destinationH;
sprite->Position3.X = (
(-rotationSin * cornerY) +
(rotationCos * cornerX) +
destinationX
);
sprite->Position3.Y = (
(rotationCos * cornerY) +
(rotationSin * cornerX) +
destinationY
);
fixed (float* flipX = &CornerOffsetX[0]) {
fixed (float* flipY = &CornerOffsetY[0])
{
sprite->TextureCoordinate0.X = (flipX[0 ^ effects] * sourceW) + sourceX;
sprite->TextureCoordinate0.Y = (flipY[0 ^ effects] * sourceH) + sourceY;
sprite->TextureCoordinate1.X = (flipX[1 ^ effects] * sourceW) + sourceX;
sprite->TextureCoordinate1.Y = (flipY[1 ^ effects] * sourceH) + sourceY;
sprite->TextureCoordinate2.X = (flipX[2 ^ effects] * sourceW) + sourceX;
sprite->TextureCoordinate2.Y = (flipY[2 ^ effects] * sourceH) + sourceY;
sprite->TextureCoordinate3.X = (flipX[3 ^ effects] * sourceW) + sourceX;
sprite->TextureCoordinate3.Y = (flipY[3 ^ effects] * sourceH) + sourceY;
}}
sprite->Position0.Z = depth;
sprite->Position1.Z = depth;
sprite->Position2.Z = depth;
sprite->Position3.Z = depth;
sprite->Color0 = color;
sprite->Color1 = color;
sprite->Color2 = color;
sprite->Color3 = color;
}
private void PrepRenderState()
{
GraphicsDevice.BlendState = blendState;
GraphicsDevice.SamplerStates[0] = samplerState;
GraphicsDevice.DepthStencilState = depthStencilState;
GraphicsDevice.RasterizerState = rasterizerState;
GraphicsDevice.SetVertexBuffer(vertexBuffer);
GraphicsDevice.Indices = indexBuffer;
Viewport viewport = GraphicsDevice.Viewport;
// Inlined CreateOrthographicOffCenter * transformMatrix
float tfWidth = (float) (2.0 / (double) viewport.Width);
float tfHeight = (float) (-2.0 / (double) viewport.Height);
unsafe
{
float* dstPtr = (float*) spriteMatrixTransform;
dstPtr[0] = (tfWidth * transformMatrix.M11) - transformMatrix.M14;
dstPtr[1] = (tfWidth * transformMatrix.M21) - transformMatrix.M24;
dstPtr[2] = (tfWidth * transformMatrix.M31) - transformMatrix.M34;
dstPtr[3] = (tfWidth * transformMatrix.M41) - transformMatrix.M44;
dstPtr[4] = (tfHeight * transformMatrix.M12) + transformMatrix.M14;
dstPtr[5] = (tfHeight * transformMatrix.M22) + transformMatrix.M24;
dstPtr[6] = (tfHeight * transformMatrix.M32) + transformMatrix.M34;
dstPtr[7] = (tfHeight * transformMatrix.M42) + transformMatrix.M44;
dstPtr[8] = transformMatrix.M13;
dstPtr[9] = transformMatrix.M23;
dstPtr[10] = transformMatrix.M33;
dstPtr[11] = transformMatrix.M43;
dstPtr[12] = transformMatrix.M14;
dstPtr[13] = transformMatrix.M24;
dstPtr[14] = transformMatrix.M34;
dstPtr[15] = transformMatrix.M44;
}
// FIXME: When is this actually applied? -flibit
spriteEffectPass.Apply();
}
private void DrawPrimitives(Texture texture, int baseSprite, int batchSize)
{
GraphicsDevice.Textures[0] = texture;
if (customEffect != null)
{
foreach (EffectPass pass in customEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
baseSprite * 4,
0,
batchSize * 4,
0,
batchSize * 2
);
}
}
else
{
GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
baseSprite * 4,
0,
batchSize * 4,
0,
batchSize * 2
);
}
}
private void CheckBegin(string method)
{
if (!beginCalled)
{
throw new InvalidOperationException(
method + " was called, but Begin has" +
" not yet been called. Begin must be" +
" called successfully before you can" +
" call " + method + "."
);
}
}
#endregion
#region Private Static Methods
private static short[] GenerateIndexArray()
{
short[] result = new short[MAX_INDICES];
for (int i = 0, j = 0; i < MAX_INDICES; i += 6, j += 4)
{
result[i] = (short) (j);
result[i + 1] = (short) (j + 1);
result[i + 2] = (short) (j + 2);
result[i + 3] = (short) (j + 3);
result[i + 4] = (short) (j + 2);
result[i + 5] = (short) (j + 1);
}
return result;
}
#endregion
#region Private Sprite Data Container Class
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct VertexPositionColorTexture4 : IVertexType
{
public const int RealStride = 96;
VertexDeclaration IVertexType.VertexDeclaration
{
get
{
throw new NotImplementedException();
}
}
public Vector3 Position0;
public Color Color0;
public Vector2 TextureCoordinate0;
public Vector3 Position1;
public Color Color1;
public Vector2 TextureCoordinate1;
public Vector3 Position2;
public Color Color2;
public Vector2 TextureCoordinate2;
public Vector3 Position3;
public Color Color3;
public Vector2 TextureCoordinate3;
}
#endregion
#region Private SpriteInfo Container Type
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct SpriteInfo
{
/* We store the hash instead of the Texture2D because
* it allows this to stay an unmanaged type and prevents
* us from constantly calling GetHashCode during sorts.
*/
public int textureHash;
public float sourceX;
public float sourceY;
public float sourceW;
public float sourceH;
public float destinationX;
public float destinationY;
public float destinationW;
public float destinationH;
public Color color;
public float originX;
public float originY;
public float rotationSin;
public float rotationCos;
public float depth;
public byte effects;
}
#endregion
#region Private Sprite Comparison Classes
private class TextureComparer : IComparer<IntPtr>
{
public unsafe int Compare(IntPtr i1, IntPtr i2)
{
SpriteInfo* p1 = (SpriteInfo*) i1;
SpriteInfo* p2 = (SpriteInfo*) i2;
return p1->textureHash.CompareTo(p2->textureHash);
}
}
private class BackToFrontComparer : IComparer<IntPtr>
{
public unsafe int Compare(IntPtr i1, IntPtr i2)
{
SpriteInfo* p1 = (SpriteInfo*) i1;
SpriteInfo* p2 = (SpriteInfo*) i2;
return p2->depth.CompareTo(p1->depth);
}
}
private class FrontToBackComparer : IComparer<IntPtr>
{
public unsafe int Compare(IntPtr i1, IntPtr i2)
{
SpriteInfo* p1 = (SpriteInfo*) i1;
SpriteInfo* p2 = (SpriteInfo*) i2;
return p1->depth.CompareTo(p2->depth);
}
}
#endregion
}
}