using System;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.Graphics.Geometry;
namespace MonoGame.Extended.Graphics
{
///
/// Enables groups of draw calls to be batched together by enqueueing the geometry in single vertex buffer and index buffer every frame.
///
/// The type of the vertex type.
/// The type of the index type.
/// The type of the draw call data.
///
public class DynamicBatchRenderer : BatchRenderer where TVertexType : struct, IVertexType where TIndexType : struct where TDrawCallData : struct, IDrawCallData
{
private readonly TIndexType[] _sortedIndices;
///
/// Initializes a new instance of the class.
///
/// The graphics device.
/// The default effect.
/// The geometry data buffer.
///
/// The maximum number of draw calls that can be deferred. The default value is 2024.
///
/// , , or is null.
///
/// is less than or equal
/// 0.
///
public DynamicBatchRenderer(GraphicsDevice graphicsDevice, Effect defaultEffect, GraphicsGeometryBuffer geometryBuffer, int maximumDrawCallsCount = DefaultMaximumDrawCallsCount)
: base(graphicsDevice, defaultEffect, geometryBuffer, maximumDrawCallsCount)
{
_sortedIndices = new TIndexType[geometryBuffer.MaximumIndicesCount];
}
///
/// Submits the draw calls to the .
///
protected override void SubmitDrawCalls()
{
int drawCallsCount, indicesCount;
SortIndices(out drawCallsCount, out indicesCount);
EnqueuedDrawCallsCount = drawCallsCount;
GeometryBuffer.VertexBuffer.SetData(GeometryBuffer.Vertices, 0, GeometryBuffer.VerticesCount);
GeometryBuffer.VerticesCount = 0;
GeometryBuffer.IndexBuffer.SetData(_sortedIndices, 0, indicesCount);
GeometryBuffer.IndicesCount = 0;
base.SubmitDrawCalls();
}
private void SortIndices(out int drawCallsCount, out int sortedIndicesCount)
{
var currentDrawCall = DrawCalls[0];
var previousDrawCall = DrawCalls[0];
var newDrawCallsCount = 1;
sortedIndicesCount = 0;
// change the indices used by the draw call to use the sorted indices array so the indices are sequential for the graphics API
DrawCalls[0].StartIndex = 0;
DrawCalls[0].BaseVertex = 0;
// get the number of vertices used for the current draw call, each index represents a vertex
var drawCallIndicesCount = currentDrawCall.PrimitiveType.GetVerticesCount(currentDrawCall.PrimitiveCount);
// copy the indices of the current draw call into our sorted indices array so the indices are sequential for the graphics API
GeometryBuffer.CopyIndicesTo(_sortedIndices, currentDrawCall.StartIndex, 0, drawCallIndicesCount, currentDrawCall.BaseVertex);
sortedIndicesCount += drawCallIndicesCount;
// iterate through sorted draw calls checking if any can now be merged to reduce expensive draw calls to the graphics API
// this might need to be changed for next-gen graphics API (Vulkan, Metal, DirectX 11) where the draw calls are not so expensive
for (var index = 1; index < EnqueuedDrawCallsCount; index++)
{
currentDrawCall = DrawCalls[index];
// get the number of vertices used for the draw call, each index represents a vertex
drawCallIndicesCount = currentDrawCall.PrimitiveType.GetVerticesCount(currentDrawCall.PrimitiveCount);
if (sortedIndicesCount + drawCallIndicesCount > _sortedIndices.Length)
{
base.SubmitDrawCalls();
sortedIndicesCount = 0;
}
if (TryMergeDrawCall(ref sortedIndicesCount, currentDrawCall, ref previousDrawCall, newDrawCallsCount,
drawCallIndicesCount))
continue;
// could not merge draw call
// change the indices used by the current draw call to use the sorted indices array so the indices are sequential for the graphics API
currentDrawCall.StartIndex = sortedIndicesCount;
DrawCalls[newDrawCallsCount++] = currentDrawCall;
previousDrawCall = currentDrawCall;
// copy the indices of the command into our sorted indices array so the merged indices are sequential for the graphics API
GeometryBuffer.CopyIndicesTo(_sortedIndices, currentDrawCall.StartIndex, sortedIndicesCount, drawCallIndicesCount, currentDrawCall.BaseVertex);
sortedIndicesCount += drawCallIndicesCount;
}
drawCallsCount = newDrawCallsCount;
}
private bool TryMergeDrawCall(ref int sortedIndicesCount, DrawCall currentDrawCall, ref DrawCall previousDrawCall, int newDrawCallsCount, int drawCallIndicesCount)
{
if (currentDrawCall.Key != previousDrawCall.Key ||
currentDrawCall.PrimitiveType != previousDrawCall.PrimitiveType)
return false;
if (currentDrawCall.PrimitiveType == PrimitiveType.TriangleStrip || currentDrawCall.PrimitiveType == PrimitiveType.LineStrip)
{
return false;
}
// copy the indices of the draw call into our sorted indices array so the merged indices are sequential for the graphics API
GeometryBuffer.CopyIndicesTo(_sortedIndices, currentDrawCall.StartIndex, sortedIndicesCount, drawCallIndicesCount, currentDrawCall.BaseVertex);
sortedIndicesCount += drawCallIndicesCount;
// increase the number of primitives for the previous draw call by the amount of the current draw call
DrawCalls[newDrawCallsCount - 1].PrimitiveCount =
previousDrawCall.PrimitiveCount += currentDrawCall.PrimitiveCount;
return true;
}
}
}