Yes, it’s flushed when the texture changes. But by setting SpriteSortMode.Texture in your SpriteBatch.Begin call you can let it sort by texture first.
So if you spritebatch draw texture A then B then A how can it sort with just one buffer efficiently ?
Anyways its just a test to see if i can make a sort of 2d particle system because i’ve never tried. So probably a lot of textures and a lot of quads with my single texture test i was pushing 25,000.
My idea was to have a bunch of buffers of PositionColorTexture. One for each texture as it is used. Then as i send in each. The dictionary would drop the call right into the correct buffer so that if i did have a ton of textures. No sorting all would be needed. The problem with wraping the texture is it accepts parameters just like spritebatch so they wont be coming in wrapped.
In draw it would make a DrawAll call loop the buffers and drawprimitive each. then reset the index pointers for all of them to 0 all in one shot.
Well that was the idea but its sort of something that was a test.
Though my other version actually works on a single texture pretty well i just couldn’t get my head around how to do it fast for bunches of individual textures without some big sorting penalty so its what i came up with.
I made it already but it still needs some work you can have a look at what i got so far im open to suggestions.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MyDirectSpriteBatcher
{
public class TextureDataBufferMap
{
private Dictionary<Texture2D, DrawableTextureQuadDataBuffer> d = new Dictionary<Texture2D, DrawableTextureQuadDataBuffer>();
private List<Texture2D> texturesList = new List<Texture2D>();
private DrawableTextureQuadDataBuffer cb = new DrawableTextureQuadDataBuffer();
public int InitialQuadCapacity = 256;
private const int MaxQuadCapacity = short.MaxValue / 6;
#region Default UV values
private Vector2 uvLT = new Vector2(0f, 0f);
private Vector2 uvLB = new Vector2(0f, 1f);
private Vector2 uvRT = new Vector2(1f, 0f);
private Vector2 uvRB = new Vector2(1f, 1f);
#endregion
private Effect currentEffect;
private Texture2D currentTexture;
private int windowWidth = 0;
private int windowHeight = 0;
private float cw = 150;
private float ch = 150;
public TextureDataBufferMap(Rectangle windowClientBounds)
{
windowWidth = windowClientBounds.Width;
windowHeight = windowClientBounds.Height;
cw = 2f / windowWidth;
ch = 2f / windowHeight;
}
public TextureDataBufferMap(Rectangle windowClientBounds, Effect effectToUse)
{
windowWidth = windowClientBounds.Width;
windowHeight = windowClientBounds.Height;
cw = 2f / windowWidth;
ch = 2f / windowHeight;
InitialQuadCapacity = Math.Min(InitialQuadCapacity, MaxQuadCapacity);
currentEffect = effectToUse;
}
/// <summary>
/// spritebatch like version
/// </summary>
public void SetSpriteToBatch(
Texture2D texture,
Rectangle destinationPositionRectangle,
Rectangle sourceTextureRectangle,
Color color,
float rotation,
Vector2 scale,
float depth
)
{
var _LT = new Vector2(destinationPositionRectangle.Left, destinationPositionRectangle.Top);
var _LB = new Vector2(destinationPositionRectangle.Left, destinationPositionRectangle.Bottom);
var _RT = new Vector2(destinationPositionRectangle.Right, destinationPositionRectangle.Top);
var _RB = new Vector2(destinationPositionRectangle.Right, destinationPositionRectangle.Bottom);
var u = 1f / texture.Width;
var v = 1f / texture.Height;
var uvL = (float)sourceTextureRectangle.Left * u;
var uvR = (float)sourceTextureRectangle.Right * u;
var uvT = (float)sourceTextureRectangle.Top * v;
var uvB = (float)sourceTextureRectangle.Bottom * v;
var uv0 = new Vector2(uvL, uvT);
var uv1 = new Vector2(uvL, uvB);
var uv2 = new Vector2(uvR, uvT);
var uv3 = new Vector2(uvR, uvB);
SetSpriteToBatch(texture, _LT, _LB, _RT, _RB , uv0, uv1, uv2, uv3,color,rotation,scale,depth);
}
/// <summary>
/// primary call
/// </summary>
public void SetSpriteToBatch(
Texture2D texture,
Vector2 _LT, Vector2 _LB, Vector2 _RT, Vector2 _RB,
Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3,
Color color, float rotation, Vector2 scale, float depth
)
{
// Projection to gpu graphing coordinates
// Requires the window width height be updated on a resize or fullscreen change...
// Via hooking onwindowsclientsizechanged.
float cw = 2f / windowWidth;
float ch = 2f / windowHeight;
// If we really want to just transform from top left to bottom right like xna yuck.
// Then we can instead just set the local origin to _LT
Vector2 origin = (_LT + _LB + _RT+ _RB) * .25f;
// translate to the local origin and scale
var lt = (_LT - origin) * scale;
var lb = (_LB - origin) * scale;
var rt = (_RT - origin) * scale;
var rb = (_RB - origin) * scale;
// rotate
if (rotation != 0)
{
Vector2 q = new Vector2((float)Math.Sin(rotation), (float)Math.Cos(rotation));
lt = new Vector2(lt.X * q.Y - lt.Y * q.X, lt.X * q.X + lt.Y * q.Y);
lb = new Vector2(lb.X * q.Y - lb.Y * q.X, lb.X * q.X + lb.Y * q.Y);
rt = new Vector2(rt.X * q.Y - rt.Y * q.X, rt.X * q.X + rt.Y * q.Y);
rb = new Vector2(rb.X * q.Y - rb.Y * q.X, rb.X * q.X + rb.Y * q.Y);
}
// de-originate and project
var LT = new Vector3((lt.X + origin.X) * cw - 1f, (lt.Y + origin.Y) * -ch + 1f, depth);
var LB = new Vector3((lb.X + origin.X) * cw - 1f, (lb.Y + origin.Y) * -ch + 1f, depth);
var RT = new Vector3((rt.X + origin.X) * cw - 1f, (rt.Y + origin.Y) * -ch + 1f, depth);
var RB = new Vector3((rb.X + origin.X) * cw - 1f, (rb.Y + origin.Y) * -ch + 1f, depth);
// switch to texture
if (!System.Object.ReferenceEquals(texture, currentTexture))
{
SetCurrentTextureDrawBufferTo(texture, ref cb);
}
// create the vertice quad
cb.spriteVertices[cb.vi_pointer + 0].Position = LT;
cb.spriteVertices[cb.vi_pointer + 0].Color = color;
cb.spriteVertices[cb.vi_pointer + 0].TextureCoordinate = uv0;
cb.spriteVertices[cb.vi_pointer + 1].Position = LB;
cb.spriteVertices[cb.vi_pointer + 1].Color = color;
cb.spriteVertices[cb.vi_pointer + 1].TextureCoordinate = uv1;
cb.spriteVertices[cb.vi_pointer + 2].Position = RT;
cb.spriteVertices[cb.vi_pointer + 2].Color = color;
cb.spriteVertices[cb.vi_pointer + 2].TextureCoordinate = uv2;
cb.spriteVertices[cb.vi_pointer + 3].Position = RB;
cb.spriteVertices[cb.vi_pointer + 3].Color = color;
cb.spriteVertices[cb.vi_pointer + 3].TextureCoordinate = uv3;
// create the indexs im not sure this indexing is not simply redundant overhead
// p = 3x2 c is 4 tc is 4 = 14 indexs are 6 x 4 =24.
// seems like regular primitives would be woth trying it would cost 4bytes but save the look up per quad
//
// LT 0 2 RT
// | /| Triangle 1 is 0 1 2 ccw
// | / | Triangle 2 is 2 1 3 ccw
// LB 1 3 RB
cb.triangleIndexList[cb.ti_pointer + 0] = 0 + cb.vi_pointer;
cb.triangleIndexList[cb.ti_pointer + 1] = 1 + cb.vi_pointer;
cb.triangleIndexList[cb.ti_pointer + 2] = 2 + cb.vi_pointer;
cb.triangleIndexList[cb.ti_pointer + 3] = 2 + cb.vi_pointer;
cb.triangleIndexList[cb.ti_pointer + 4] = 1 + cb.vi_pointer;
cb.triangleIndexList[cb.ti_pointer + 5] = 3 + cb.vi_pointer;
cb.currentQuads += 1;
cb.vi_pointer += 4;
cb.ti_pointer += 6;
if (cb.currentQuads >= cb.quadCapacity - 1)
{
IncreaseCapacity();
}
}
public void DrawAll(GraphicsDevice gd, bool allbuffers)
{
for (int i = 0; i < texturesList.Count; i++)
{
currentTexture = texturesList[i];
currentEffect.Parameters["Texture"].SetValue(currentTexture);
SetCurrentTextureDrawBufferTo(currentTexture, ref cb);
if (cb.TriangleDrawCount() > 0)
{
foreach (EffectPass pass in currentEffect.CurrentTechnique.Passes)
{
pass.Apply();
gd.DrawUserIndexedPrimitives
(
PrimitiveType.TriangleList,
cb.spriteVertices,
0,
cb.VerticesPerQuad(),
cb.triangleIndexList,
0,
cb.TriangleDrawCount()
);
}
}
}
}
public void ClearAll()
{
for (int i = 0; i < texturesList.Count; i++)
{
SetCurrentTextureDrawBufferTo(texturesList[i], ref cb);
cb.currentQuads = 0;
cb.vi_pointer = 0;
cb.ti_pointer = 0;
}
}
public void SetCurrentEffect(Effect effectToUse)
{
currentEffect = effectToUse;
}
public void OnResizeUpdateWindowWidthHeight(Rectangle windowClientBounds)
{
windowWidth = windowClientBounds.Width;
windowHeight = windowClientBounds.Height;
cw = 2f / windowWidth;
ch = 2f / windowHeight;
}
private void SetCurrentTextureDrawBufferTo(Texture2D t, ref DrawableTextureQuadDataBuffer b)
{
if (d.ContainsKey(t) == false)
{
texturesList.Add(t);
DrawableTextureQuadDataBuffer n = new DrawableTextureQuadDataBuffer();
n.quadCapacity = InitialQuadCapacity;
n.spriteVertices = new VertexPositionColorTexture[InitialQuadCapacity * 4];
n.triangleIndexList = new int[InitialQuadCapacity * 6];
d.Add(t, n);
}
currentTexture = t;
b = d[t];
}
private void IncreaseCapacity()
{
int newVerticeCapacity = cb.spriteVertices.Length + InitialQuadCapacity * 4;
int newIndexCapacity = cb.triangleIndexList.Length + InitialQuadCapacity * 6;
VertexPositionColorTexture[] v = new VertexPositionColorTexture[newVerticeCapacity];
int[] ind = new int[newIndexCapacity];
Array.Copy(cb.spriteVertices, v, cb.spriteVertices.Length);
Array.Copy(cb.triangleIndexList, ind, cb.triangleIndexList.Length);
// i gotta count the bytes ill do it later
//Buffer.BlockCopy(cb.spriteVertices, 0, v, 0, cb.currentQuads * 14);
//Buffer.BlockCopy(cb.triangleIndexList, 0, ind, 0, cb.triangleIndexList.Length * 4);
cb.spriteVertices = v;
cb.triangleIndexList = ind;
cb.quadCapacity += InitialQuadCapacity;
}
}
public class DrawableTextureQuadDataBuffer
{
//public Texture2D texture;
public int quadCapacity;
public int currentQuads;
public int ti_pointer;
public int vi_pointer;
public int[] triangleIndexList; // = new int[6];
public VertexPositionColorTexture[] spriteVertices; // = new VertexPositionColorTexture[4];
// methods
public int TotalQuads()
{
return currentQuads;
}
public int VerticesPerQuad()
{
return 4;
}
public int TriangleDrawCount()
{
return currentQuads * 2;
}
public int TotalVertices() { return spriteVertices.Length; }
public int TotalIndices() { return triangleIndexList.Length; }
}
}