I want to draw a flat surface of 500x500 squares which are built out of four vertices by two triangles.
There are 1,000,000 vertices and 1,500,000 indices.
The Reach profile of xna, limits primitives to 65535 per draw call.
My thought was to split whole data twice, first for indices using the same baseVertex, and second to stay beyond 65535 (ushort.MaxValue).
static class Painter
{
public struct IndexedPrimitives
{
public PrimitiveType Type;
public int baseVertex;
public int minVertexIndex;
public int numVertices;
public int startIndex;
public int primitiveCount;
}
public struct IndexedPrimitivesList
{
public VertexBuffer vb;
public IndexBuffer ib;
public IndexedPrimitives[] primitives;
}
public static IndexedPrimitivesList GenerateIndexedTirangleList(GraphicsDevice GraphicsDevice, VertexBuffer vb, int[] indices)
{
IndexedPrimitivesList ret;
List<int> breaks = new List<int>();
breaks.Add(0);
List<int> levels = new List<int>();
levels.Add(0);
ushort[] inds = new ushort[indices.Length];
for (int i = 0; i < indices.Length; i++)
{
if (indices[i] / (ushort.MaxValue + 1) != levels[levels.Count - 1])
{
levels.Add(indices[i] / (ushort.MaxValue + 1));
breaks.Add(i);
}
inds[i] = (ushort)indices[i];
}
indices = null;
ret.ib = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, inds.Length, BufferUsage.None);
ret.ib.SetData<ushort>(inds);
inds = null;
for (int i = 1; i < breaks.Count; i++)
{
if ((breaks[i] - breaks[i - 1]) % 3 != 0) throw new Exception("Triangles are using to far away vertices, or bad placed indices, foreach index of a triangle it needs index/65536 to be equal");
}
List<IndexedPrimitives> prims = new List<IndexedPrimitives>();
for (int i = 1; i < breaks.Count; i++)
{
int r = breaks[i] - breaks[i - 1];
while ((r / 3) / (ushort.MaxValue + 1) > 0)
{
IndexedPrimitives ip = new IndexedPrimitives();
ip.Type = PrimitiveType.TriangleList;
ip.baseVertex = levels[i - 1] * (ushort.MaxValue + 1);
ip.numVertices = ushort.MaxValue + 1;
ip.startIndex = breaks[i] - r;
ip.primitiveCount = ushort.MaxValue;
prims.Add(ip);
r -= (ushort.MaxValue + 1) * 3;
}
if (r != 0)
{
IndexedPrimitives ip = new IndexedPrimitives();
ip.Type = PrimitiveType.TriangleList;
ip.baseVertex = levels[i - 1] * (ushort.MaxValue + 1);
ip.numVertices = ushort.MaxValue + 1;
ip.startIndex = breaks[i] - r;
ip.primitiveCount = r / 3;
prims.Add(ip);
r -= r;
}
}
int rr = ret.ib.IndexCount - breaks[breaks.Count - 1];
while ((rr / 3) / (ushort.MaxValue + 1) > 0)
{
IndexedPrimitives ip = new IndexedPrimitives();
ip.Type = PrimitiveType.TriangleList;
ip.baseVertex = levels[levels.Count - 1] * (ushort.MaxValue + 1);
ip.numVertices = ushort.MaxValue + 1;
ip.startIndex = ret.ib.IndexCount - rr;
ip.primitiveCount = ushort.MaxValue;
prims.Add(ip);
rr -= (ushort.MaxValue + 1) * 3;
}
if (rr != 0)
{
IndexedPrimitives ip = new IndexedPrimitives();
ip.Type = PrimitiveType.TriangleList;
ip.baseVertex = levels[levels.Count - 1] * (ushort.MaxValue + 1);
ip.numVertices = vb.VertexCount-ip.baseVertex-1;
ip.startIndex = ret.ib.IndexCount - rr;
ip.primitiveCount = rr / 3;
prims.Add(ip);
rr -= rr;
}
ret.vb = vb;
ret.primitives = prims.ToArray();
return ret;
}
public static void DrawIndexedPrimitivesList(GraphicsDevice GraphicsDevice, IndexedPrimitivesList ipl)
{
GraphicsDevice.SetVertexBuffer(ipl.vb);
GraphicsDevice.Indices = ipl.ib;
foreach (IndexedPrimitives ip in ipl.primitives)
{
GraphicsDevice.DrawIndexedPrimitives(ip.Type, ip.baseVertex, ip.minVertexIndex, ip.numVertices, ip.startIndex, ip.primitiveCount);
}
}
public struct SplittedBuffers
{
public IndexBuffer[] ibs;
public VertexBuffer[] vbs;
}
public static SplittedBuffers GenerateSplittedBuffers(IndexedPrimitivesList ipl)
{
SplittedBuffers sb=new SplittedBuffers();
List<List<byte>> vbs = new List<List<byte>>();
List<List<byte>> ibs = new List<List<byte>>();
List<int> baseVertices = new List<int>();
byte[] vb = new byte[ipl.vb.VertexCount * ipl.vb.VertexDeclaration.VertexStride];
ipl.vb.GetData<byte>(vb);
byte[] ib = new byte[ipl.ib.IndexCount * ((ipl.ib.IndexElementSize == IndexElementSize.SixteenBits) ? 2 : 4)];
ipl.ib.GetData<byte>(ib);
foreach (IndexedPrimitives ip in ipl.primitives)
{
int i = baseVertices.IndexOf(ip.baseVertex);
if (i < 0)
{
vbs.Add(new List<byte>());
ibs.Add(new List<byte>());
baseVertices.Add(ip.baseVertex);
i = vbs.Count-1;
}
byte[] tmp = new byte[ip.numVertices * ipl.vb.VertexDeclaration.VertexStride];
Array.Copy(vb, ip.baseVertex * ipl.vb.VertexDeclaration.VertexStride, tmp, 0, tmp.Length);
vbs[i].AddRange(tmp);
tmp = new byte[ip.primitiveCount*((ip.Type==PrimitiveType.TriangleList)?3:1) * ((ipl.ib.IndexElementSize == IndexElementSize.SixteenBits) ? 2 : 4)];
Array.Copy(ib, ip.startIndex * ((ipl.ib.IndexElementSize == IndexElementSize.SixteenBits) ? 2 : 4), tmp, 0, tmp.Length);
ibs[i].AddRange(tmp);
}
sb.ibs = new IndexBuffer[ibs.Count];
sb.vbs = new VertexBuffer[vbs.Count];
for (int i = 0; i < vbs.Count; i++)
{
sb.ibs[i] = new IndexBuffer(ipl.ib.GraphicsDevice, ipl.ib.IndexElementSize, ibs[i].Count / ((ipl.ib.IndexElementSize == IndexElementSize.SixteenBits) ? 2 : 4), ipl.ib.BufferUsage);
sb.ibs[i].SetData<byte>(ibs[i].ToArray());
sb.vbs[i] = new VertexBuffer(ipl.vb.GraphicsDevice, ipl.vb.VertexDeclaration, vbs[i].Count / ipl.vb.VertexDeclaration.VertexStride, ipl.vb.BufferUsage);
sb.vbs[i].SetData<byte>(vbs[i].ToArray());
}
return sb;
}
public static void DrawSplittedBuffers(GraphicsDevice GraphicsDevice,SplittedBuffers sb)
{
for (int i = 0; i < sb.vbs.Length; i++)
{
GraphicsDevice.SetVertexBuffer(sb.vbs[i]);
GraphicsDevice.Indices = sb.ibs[i];
GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, sb.vbs[i].VertexCount, 0, sb.ibs[i].IndexCount/3);
}
}
}
Edits:
I thought up to now vertices and indices are limited to 65535, but the primitives are.
There's other strange behavior of the parameter numVertices, it can be set to a bigger Value then the actual number of vertices in the buffer without throwing an exception or aborting.
The code above now works for me. But it is a bit too slow.
Further edits:
I improved my code to prepare all the values only once. During draw call only the values are inserted, now I'd like to redefine my Question as follow: Is switching Buffers faster then the drawing method above?
I suppose it's not, because the same number of draw calls are needed by both methods, as both methods can't draw in one call triangles with different baseVertex or more then 65535 primitives. May switching the buffers will also slow down a little bit and I think using the offsets baseVertext and startIndex should not slow down the drawing method. What are your thoughts and experiences?
Tested
I tested both drawing methods. For the same data both need the same number of draw calls (in my tests 22 per draw). Drawing the SplittedBuffers is up 3 times slower then using the IndexedPrimitivesList. I will now do some changes in the generation to allow overlapping vertex ranges.