Created
April 18, 2011 11:35
-
-
Save boki/925164 to your computer and use it in GitHub Desktop.
A simple SpriteBatch for XNA in Silverlight 5
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Botomata.Xna | |
{ | |
using System; | |
using System.Globalization; | |
using System.Runtime.InteropServices; | |
using Microsoft.Xna.Framework; | |
using Microsoft.Xna.Framework.Graphics; | |
/// <summary> | |
/// The glyph batch class provides methods and properties to render batches | |
/// of glyphs. | |
/// </summary> | |
public class SpriteBatch : IDisposable | |
{ | |
/// <summary> | |
/// Describes a custom vertex format structure that contains position, | |
/// color and one set of texture coordinates. | |
/// </summary> | |
[StructLayout(LayoutKind.Sequential)] | |
struct SpriteVertex | |
{ | |
/// <summary> | |
/// An array of three vertex elements describing the position, | |
/// texture coordinate and color of this vertex. | |
/// </summary> | |
public static readonly VertexElement[] VertexElements = new VertexElement[] | |
{ | |
new VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.Position, 0), | |
new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), | |
new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 0) | |
}; | |
/// <summary> | |
/// The vertex position. | |
/// </summary> | |
public Vector4 Position; | |
/// <summary> | |
/// The vertex texture coordinates. | |
/// </summary> | |
public Vector2 TextureCoordinate; | |
/// <summary> | |
/// The vertex color. | |
/// </summary> | |
public Color Color; | |
/// <summary> | |
/// Initializes a new instance of the GlyphVertex structure. | |
/// </summary> | |
/// <param name="position">Position of the vertex.</param> | |
/// <param name="textureCoordinate">Texture coordinate of the vertex.</param> | |
/// <param name="color">Color of the vertex.</param> | |
public SpriteVertex(Vector4 position, Vector2 textureCoordinate, Color color) | |
{ | |
Position = position; | |
TextureCoordinate = textureCoordinate; | |
Color = color; | |
} | |
/// <summary> | |
/// Gets the size of the GlyphVertex structure. | |
/// </summary> | |
public static int SizeInBytes | |
{ | |
get { return 7 * 4; } | |
} | |
/// <summary> | |
/// Compares two objects to determine whether they are the same. | |
/// </summary> | |
/// <param name="vertex1">Object to the left of the equality operator.</param> | |
/// <param name="vertex2">Object to the right of the equality operator.</param> | |
/// <returns>true if the objects are the same; false otherwise.</returns> | |
public static bool operator ==(SpriteVertex vertex1, SpriteVertex vertex2) | |
{ | |
return vertex1.Position == vertex2.Position && | |
vertex1.TextureCoordinate == vertex2.TextureCoordinate && | |
vertex1.Color == vertex2.Color; | |
} | |
/// <summary> | |
/// Compares two objects to determine whether they are different. | |
/// </summary> | |
/// <param name="vertex1">Object to the left of the inequality operator.</param> | |
/// <param name="vertex2">Object to the right of the inequality operator.</param> | |
/// <returns>true if the objects are different; false otherwise.</returns> | |
public static bool operator !=(SpriteVertex vertex1, SpriteVertex vertex2) | |
{ | |
return vertex1.Position != vertex2.Position || | |
vertex1.TextureCoordinate != vertex2.TextureCoordinate || | |
vertex1.Color != vertex2.Color; | |
} | |
/// <summary> | |
/// Returns a value that indicates whether the current instance is | |
/// equal to a specified object. | |
/// </summary> | |
/// <param name="obj">The Object to compare with the current GlyphVertex.</param> | |
/// <returns>true if the objects are the same; false otherwise.</returns> | |
public override bool Equals(Object obj) | |
{ | |
return obj != null && obj.GetType() == typeof(SpriteVertex) && this == (SpriteVertex)obj; | |
} | |
/// <summary> | |
/// Gets the hash code for this instance. | |
/// </summary> | |
/// <returns>Hash code for this object.</returns> | |
public override int GetHashCode() | |
{ | |
return base.GetHashCode(); | |
} | |
/// <summary> | |
/// Retrieves a string representation of this object. | |
/// </summary> | |
/// <returns>String representation of this object.</returns> | |
public override String ToString() | |
{ | |
Object[] args = new object[] { Position, Color, TextureCoordinate }; | |
return String.Format(CultureInfo.CurrentCulture, "{{Position:{0} Color:{1} TextureCoordinate:{3}}}", args); | |
} | |
} | |
/// <summary> | |
/// The size of the vertex buffer. | |
/// </summary> | |
const int VertexBufferSize = 2048; | |
/// <summary> | |
/// The size of the index buffer. | |
/// </summary> | |
const int IndexBufferSize = 6 * VertexBufferSize; | |
/// <summary> | |
/// The graphics device where glyphs will be drawn. | |
/// </summary> | |
GraphicsDevice graphicsDevice; | |
/// <summary> | |
/// The size of the drawing surface. | |
/// </summary> | |
Vector4 viewportSize; | |
/// <summary> | |
/// A value indicating whether Begin was called. | |
/// </summary> | |
bool inBeginEnd; | |
/// <summary> | |
/// The currently queued glyph vertices. | |
/// </summary> | |
SpriteVertex[] spriteQueue; | |
/// <summary> | |
/// The number of vertices in the glyphQueue. | |
/// </summary> | |
int spriteQueueCount; | |
/// <summary> | |
/// The fonts glyph texture used to render the glyphs. | |
/// </summary> | |
Texture2D spriteTexture; | |
/// <summary> | |
/// The vertex buffer used to render the glyphs. | |
/// </summary> | |
DynamicVertexBuffer vertexBuffer; | |
/// <summary> | |
/// The vertex declaration used to render the glyphs. | |
/// </summary> | |
VertexDeclaration vertexDeclaration; | |
/// <summary> | |
/// The index buffer used to render the glyphs. | |
/// </summary> | |
DynamicIndexBuffer indexBuffer; | |
/// <summary> | |
/// The vertex shader used to render the glyphs. | |
/// </summary> | |
VertexShader vertexShader; | |
/// <summary> | |
/// The pixel shader used to render the glyphs. | |
/// </summary> | |
PixelShader pixelShader; | |
/// <summary> | |
/// The index into the vertex buffer at which to render from next. | |
/// </summary> | |
int vertexBufferPosition; | |
/// <summary> | |
/// A value that indicates whether the object is disposed. | |
/// </summary> | |
bool isDisposed; | |
/// <summary> | |
/// Initializes a new instance of the GlyphBatch class. | |
/// </summary> | |
/// <param name="graphicsDevice">The graphics device where glyphs will be drawn.</param> | |
/// <param name="width">The width of the drawing surface.</param> | |
/// <param name="height">The height of the drawing surface.</param> | |
public SpriteBatch(GraphicsDevice graphicsDevice, int width, int height) | |
{ | |
if (graphicsDevice == null) | |
{ | |
throw new ArgumentNullException("graphicsDevice", "Graphics device cannot be null"); | |
} | |
this.graphicsDevice = graphicsDevice; | |
this.viewportSize = new Vector4(width, height, 0, 0); | |
this.spriteQueue = new SpriteVertex[VertexBufferSize]; | |
ConstructPlatformData(); | |
} | |
/// <summary> | |
/// Occurs when Dispose is called or when this object is finalized and | |
/// collected by the garbage collector of the Microsoft .NET common | |
/// language runtime. | |
/// </summary> | |
public event EventHandler Disposing; | |
/// <summary> | |
/// Gets a value indicating whether the object is disposed. | |
/// </summary> | |
public bool IsDisposed | |
{ | |
get { return isDisposed; } | |
} | |
/// <summary> | |
/// Prepares the graphics device for drawing sprites. | |
/// </summary> | |
/// <exception cref="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. | |
/// </exception> | |
public void Begin(GraphicsDevice graphicsDevice, Texture2D texture) | |
{ | |
if (inBeginEnd == true) | |
{ | |
throw new InvalidOperationException("End must be called before Begin"); | |
} | |
inBeginEnd = true; | |
this.graphicsDevice = graphicsDevice; | |
spriteTexture = texture; | |
} | |
/// <summary> | |
/// Flushes the sprite batch. | |
/// </summary> | |
/// <exception cref="InvalidOperationException"> | |
/// End was called, but Begin has not yet been called. You must call | |
/// Begin successfully before you can call End. | |
/// </exception> | |
public void End() | |
{ | |
if (inBeginEnd == false) | |
{ | |
throw new InvalidOperationException("Begin must be called before End"); | |
} | |
if (spriteQueueCount > 0) | |
{ | |
RenderBatch(); | |
spriteTexture = null; | |
} | |
inBeginEnd = false; | |
} | |
/// <summary> | |
/// Adds a glyph to the batch of glyphs to be rendered, specifying the | |
/// screen position, glyph source rectangle and color. | |
/// </summary> | |
/// <param name="position">The location, in screen coordinates, where the sprite will be drawn.</param> | |
/// <param name="source">The glyph source rectangle in the fonts texture.</param> | |
/// <param name="color">The color to render the glyph.</param> | |
/// <exception cref="InvalidOperationException"> | |
/// Draw was called, but Begin has not yet been called. You | |
/// must call Begin successfully before you can call Draw. | |
/// </exception> | |
public void Draw(Vector2 position, Rectangle source, Color color) | |
{ | |
if (inBeginEnd == false) | |
{ | |
throw new InvalidOperationException("Begin must be called before Draw"); | |
} | |
Vector2 pos = position; | |
AppendGlyphVertex(pos, new Vector2(source.Left, source.Top), color); | |
pos.X += source.Width; | |
AppendGlyphVertex(pos, new Vector2(source.Right, source.Top), color); | |
pos.Y += source.Height; | |
AppendGlyphVertex(pos, new Vector2(source.Right, source.Bottom), color); | |
pos.X -= source.Width; | |
AppendGlyphVertex(pos, new Vector2(source.Left, source.Bottom), color); | |
} | |
public void Draw(Rectangle position, Rectangle source, Color color) | |
{ | |
if (inBeginEnd == false) | |
{ | |
throw new InvalidOperationException("Begin must be called before Draw"); | |
} | |
Vector2 pos = new Vector2(position.X, position.Y); | |
AppendGlyphVertex(pos, new Vector2(source.Left, source.Top), color); | |
pos.X += position.Width; | |
AppendGlyphVertex(pos, new Vector2(source.Right, source.Top), color); | |
pos.Y += position.Height; | |
AppendGlyphVertex(pos, new Vector2(source.Right, source.Bottom), color); | |
pos.X -= position.Width; | |
AppendGlyphVertex(pos, new Vector2(source.Left, source.Bottom), color); | |
} | |
/// <summary> | |
/// Performs application-defined tasks associated with freeing, | |
/// releasing, or resetting unmanaged resources. | |
/// </summary> | |
/// <filterpriority>2</filterpriority> | |
public void Dispose() | |
{ | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
/// <summary> | |
/// Performs application-defined tasks associated with freeing, | |
/// releasing, or resetting unmanaged resources. | |
/// </summary> | |
/// <param name="disposing">A value indicating whether this method was called from Dispose.</param> | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (disposing == true && isDisposed == false) | |
{ | |
if (Disposing != null) | |
{ | |
Disposing(this, EventArgs.Empty); | |
} | |
vertexDeclaration = null; | |
vertexShader = null; | |
pixelShader = null; | |
vertexBuffer = null; | |
indexBuffer = null; | |
isDisposed = true; | |
} | |
} | |
/// <summary> | |
/// Creates the triangle list indices for the internal buffers. | |
/// </summary> | |
/// <returns>The generated indices.</returns> | |
static short[] CreateIndexData() | |
{ | |
short[] indeces = new short[IndexBufferSize]; | |
for (int i = 0; i < VertexBufferSize; i++) | |
{ | |
indeces[(i * 6) + 0] = (short)((i * 4) + 0); | |
indeces[(i * 6) + 1] = (short)((i * 4) + 1); | |
indeces[(i * 6) + 2] = (short)((i * 4) + 2); | |
indeces[(i * 6) + 3] = (short)((i * 4) + 0); | |
indeces[(i * 6) + 4] = (short)((i * 4) + 2); | |
indeces[(i * 6) + 5] = (short)((i * 4) + 3); | |
} | |
return indeces; | |
} | |
/// <summary> | |
/// Renders the glyphs queued in this batch. | |
/// </summary> | |
void RenderBatch() | |
{ | |
graphicsDevice.SetVertexShader(vertexShader); | |
graphicsDevice.SetPixelShader(pixelShader); | |
graphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; | |
graphicsDevice.DepthStencilState = DepthStencilState.None; | |
graphicsDevice.BlendState = BlendState.AlphaBlend; | |
graphicsDevice.Textures[0] = spriteTexture; | |
graphicsDevice.SamplerStates[0] = SamplerState.PointClamp; | |
graphicsDevice.SetVertexBuffer(vertexBuffer); | |
graphicsDevice.Indices = indexBuffer; | |
graphicsDevice.SetVertexShaderConstantFloat4(0, ref viewportSize); | |
var textureSize = new Vector4() | |
{ | |
X = spriteTexture.Width, | |
Y = spriteTexture.Height | |
}; | |
graphicsDevice.SetVertexShaderConstantFloat4(1, ref textureSize); | |
int count = spriteQueueCount; | |
int offset = 0; | |
while (count > 0) | |
{ | |
SetDataOptions noOverwrite = SetDataOptions.NoOverwrite; | |
int drawCount = count; | |
if (drawCount > (VertexBufferSize - vertexBufferPosition)) | |
{ | |
drawCount = VertexBufferSize - vertexBufferPosition; | |
if (drawCount < VertexBufferSize / 8) | |
{ | |
vertexBufferPosition = 0; | |
noOverwrite = SetDataOptions.Discard; | |
drawCount = count; | |
if (drawCount > VertexBufferSize) | |
{ | |
drawCount = VertexBufferSize; | |
} | |
} | |
} | |
int vertexStride = SpriteVertex.SizeInBytes; | |
int offsetInBytes = (vertexBufferPosition * vertexStride); | |
vertexBuffer.SetData<SpriteVertex>(offsetInBytes, spriteQueue, offset, drawCount, vertexStride, noOverwrite); | |
// 2 triangles = 4 vertices, 6 indeces | |
int minVertexIndex = vertexBufferPosition; | |
int numVertices = drawCount; | |
int startIndex = vertexBufferPosition / 2 * 3; | |
int primitiveCount = drawCount / 2; | |
graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, minVertexIndex, numVertices, startIndex, primitiveCount); | |
vertexBufferPosition += drawCount; | |
offset += drawCount; | |
count -= drawCount; | |
} | |
spriteQueueCount = 0; | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
void ConstructPlatformData() | |
{ | |
var streamResourceInfo = ContentManager.GetStreamResourceInfo("Content/Shaders/SpriteBatch.vs"); | |
vertexShader = VertexShader.FromStream(graphicsDevice, streamResourceInfo.Stream); | |
streamResourceInfo = ContentManager.GetStreamResourceInfo("Content/Shaders/SpriteBatch.ps"); | |
pixelShader = PixelShader.FromStream(graphicsDevice, streamResourceInfo.Stream); | |
vertexDeclaration = new VertexDeclaration(SpriteVertex.VertexElements); | |
AllocateBuffers(); | |
} | |
/// <summary> | |
/// Allocates the internal vertex and index buffers. | |
/// </summary> | |
void AllocateBuffers() | |
{ | |
if (vertexBuffer == null) | |
{ | |
vertexBuffer = new DynamicVertexBuffer(graphicsDevice, vertexDeclaration, VertexBufferSize, BufferUsage.WriteOnly); | |
vertexBufferPosition = 0; | |
} | |
if (indexBuffer == null) | |
{ | |
indexBuffer = new DynamicIndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, IndexBufferSize, BufferUsage.WriteOnly); | |
indexBuffer.SetData<short>(0, CreateIndexData(), 0, IndexBufferSize); | |
} | |
} | |
/// <summary> | |
/// Appends a glyph vertex onto the current batch. | |
/// </summary> | |
/// <param name="position">The location, in screen coordinates, of the vertex to append.</param> | |
/// <param name="textureCoordinate">The texture coordinates of the vertex to append.</param> | |
/// <param name="color">The color of the vertex to append.</param> | |
void AppendGlyphVertex(Vector2 position, Vector2 textureCoordinate, Color color) | |
{ | |
if (spriteQueueCount >= spriteQueue.Length) | |
{ | |
Array.Resize<SpriteVertex>(ref spriteQueue, spriteQueue.Length * 2); | |
} | |
spriteQueue[spriteQueueCount].Position.X = position.X; | |
spriteQueue[spriteQueueCount].Position.Y = position.Y; | |
spriteQueue[spriteQueueCount].Position.Z = 0; | |
spriteQueue[spriteQueueCount].Position.W = 1; | |
spriteQueue[spriteQueueCount].TextureCoordinate = textureCoordinate; | |
spriteQueue[spriteQueueCount].Color = color; | |
spriteQueueCount++; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2011, Bjoern Graf <bjoern.graf@gmx.net> | |
// All rights reserved. | |
// | |
// This software is licensed as described in the file license.txt, which | |
// you should have received as part of this distribution. The terms | |
// are also available at http://www.codeplex.com/Bnoerj/Project/License.aspx. | |
float4 ViewportSize; | |
float4 TextureSize; | |
texture SpriteTexture : register(t0); | |
sampler spriteSampler : register(s0); | |
struct VS_INPUT | |
{ | |
float4 Position : POSITION; | |
float2 TexCoord : TEXCOORD0; | |
float4 Color : COLOR0; | |
}; | |
struct VS_OUTPUT | |
{ | |
float4 Position : POSITION; | |
float2 TexCoord : TEXCOORD0; | |
float4 Color : COLOR0; | |
}; | |
VS_OUTPUT SpriteVS(VS_INPUT In) | |
{ | |
VS_OUTPUT Out; | |
Out.Position.xy = In.Position.xy - 0.5; | |
Out.Position.xy = Out.Position.xy / ViewportSize.xy; | |
Out.Position.xy = Out.Position.xy * float2(2, -2) + float2(-1, 1); | |
Out.Position.zw = In.Position.zw; | |
Out.TexCoord.xy = In.TexCoord.xy / TextureSize.xy; | |
Out.Color = In.Color; | |
return Out; | |
} | |
float4 SpritePS(VS_OUTPUT In) : COLOR0 | |
{ | |
float4 color = tex2D(spriteSampler, In.TexCoord); | |
color = color * In.Color; | |
return color; | |
} | |
technique SpriteBatch | |
{ | |
pass SinglePass | |
{ | |
VertexShader = compile vs_2_0 SpriteVS(); | |
PixelShader = compile ps_2_0 SpritePS(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment