#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
namespace Nuclex.Graphics.Batching {
/// Queues vertices until the end of the drawing cycle
///
/// This queuer respects the order in which drawing commands were given and
/// tries to merge any consecutively issued primitives of the same type into
/// a single DrawPrimitive() call. This is ideal if you have a large number
/// of small objects that are rendered with the same settings (eg. a particle
/// system or letters in a bitmap/vector font system).
///
internal partial class DeferredQueuer : Queuer
where VertexType : struct, IVertexType {
#region class RenderOperation
/// Set of primitives that can be drawn in a single run
private class RenderOperation {
/// Initializes a new render operation
/// Starting index of the RenderOperation
/// What kind of primitives to draw
/// Controls the graphics device settings
public RenderOperation(
int startIndex, PrimitiveType primitiveType, DrawContext drawContext
) {
this.StartIndex = startIndex;
this.EndIndex = startIndex;
this.PrimitiveType = primitiveType;
this.DrawContext = drawContext;
}
/// First index to draw
public int StartIndex;
/// Index after the last index to draw
public int EndIndex;
/// Type of primitives to draw
public PrimitiveType PrimitiveType;
/// Draw context controlling the graphics device settings
public DrawContext DrawContext;
/// Base vertex index for the vertex buffer in this operation
public int BaseVertexIndex;
/// Number of vertices used in the operation
public int VertexCount;
}
#endregion
/// Initializes a new deferred primitive queuer
///
/// Batch drawer that will be used to render completed vertex batches
///
public DeferredQueuer(IBatchDrawer batchDrawer) :
base(batchDrawer) {
// Create a new list of queued rendering operations
this.operations = new List();
this.currentOperation = new RenderOperation(0, (PrimitiveType)(-1), null);
this.operations.Add(this.currentOperation);
// Set up some arrays to store the vertices and indices in
this.vertices = new VertexType[batchDrawer.MaximumBatchSize];
this.indices = new short[batchDrawer.MaximumBatchSize];
}
/// Informs the queuer that a new drawing cycle is about to start
public override void Begin() {
reset();
}
/// Informs the queuer that the current drawing cycle has ended
public override void End() {
flush();
}
/// Queues a series of primitives
/// Primitive vertices
/// Index of vertex to begin drawing with
/// Number of vertices to draw
/// Type of primitives to draw
/// Desired graphics device settings for the primitives
public override void Queue(
VertexType[] vertices, int startVertex, int vertexCount,
PrimitiveType type, DrawContext context
) {
int remainingSpace = this.BatchDrawer.MaximumBatchSize - Math.Max(
this.usedVertexCount, this.usedIndexCount
);
if (vertexCount > remainingSpace) {
queueVerticesBufferSplit(
vertices, startVertex, vertexCount, type, context, remainingSpace
);
} else {
queueVerticesNoOverflow(
vertices, startVertex, vertexCount, type, context
);
}
}
/// Queues a series of indexed primitives
/// Primitive vertices
///
/// Index in the vertex array of the first vertex. This vertex will become
/// the new index 0 for the index buffer.
///
/// Number of vertices to draw
/// Indices of the vertices to draw
/// Index of the vertex index to begin drawing with
/// Number of vertex indices to draw
/// Type of primitives to draw
/// Desired graphics device settings for the primitives
public override void Queue(
VertexType[] vertices, int startVertex, int vertexCount,
short[] indices, int startIndex, int indexCount,
PrimitiveType type, DrawContext context
) {
int remainingVertices = this.BatchDrawer.MaximumBatchSize - this.usedVertexCount;
int remainingIndices = this.BatchDrawer.MaximumBatchSize - this.usedIndexCount;
bool exceedsBatchSpace =
(vertexCount > remainingVertices) ||
(indexCount > remainingIndices);
if (exceedsBatchSpace) {
queueIndexedVerticesBufferSplit(
vertices, startVertex, vertexCount,
indices, startIndex, indexCount,
type, context, Math.Min(remainingVertices, remainingIndices)
);
} else {
queueIndexedVerticesNoOverflow(
vertices, startVertex, vertexCount,
indices, startIndex, indexCount,
type, context
);
}
}
///
/// Queues the provided vertices for deferred rendering when there is enough
/// space left in the current batch to hold all vertices
///
/// Primitive vertices
///
/// Index in the vertex array of the first vertex. This vertex will become
/// the new index 0 for the index buffer.
///
/// Number of vertices to draw
/// Type of primitives to draw
/// Desired graphics device settings for the primitives
///
/// This is a special optimized method for adding vertices when the amount of
/// vertices to render does not exceed available batch space, which should be
/// the default usage of a vertex batcher.
///
private void queueVerticesNoOverflow(
VertexType[] vertices, int startVertex, int vertexCount,
PrimitiveType type, DrawContext context
) {
createNewOperationIfNecessary(type, context);
// Take over the vertices into our own array
Array.Copy(
vertices, startVertex,
this.vertices, this.usedVertexCount,
vertexCount
);
// Generate indices for the new vertices. This might not be faster than using
// a special non-indexed RenderOperation, but it yields stable speeds between
// indexed and non-indexed vertices and to lessens the impact the worst-case
// has while allowing us to send both types in the same batch.
int endIndex = this.currentOperation.VertexCount + vertexCount;
for (int index = this.currentOperation.VertexCount; index < endIndex; ++index) {
this.indices[this.usedIndexCount] = (short)index;
++this.usedIndexCount;
}
this.usedVertexCount += vertexCount;
// Update the end index for the current operation
this.currentOperation.VertexCount = endIndex;
this.currentOperation.EndIndex = this.usedIndexCount;
}
/// Queues the provided indexed vertices for deferred rendering
/// Primitive vertices
///
/// Index in the vertex array of the first vertex. This vertex will become
/// the new index 0 for the index buffer.
///
/// Number of vertices to draw
/// Indices of the vertices to draw
/// Index of the vertex index to begin drawing with
/// Number of vertex indices to draw
/// Type of primitives to draw
/// Desired graphics device settings for the primitives
///
/// This is a special optimized method for adding vertices when the amount of
/// vertices to render does not exceed available batch space, which should be
/// the default usage of a vertex batcher.
///
private void queueIndexedVerticesNoOverflow(
VertexType[] vertices, int startVertex, int vertexCount,
short[] indices, int startIndex, int indexCount,
PrimitiveType type, DrawContext context
) {
createNewOperationIfNecessary(type, context);
// Take over the vertices into our own array
Array.Copy(
vertices, startVertex,
this.vertices, this.usedVertexCount,
vertexCount
);
// Offset that needs to be added to the indices given the subset of vertices
// we will we cut out and the positioning in our own vertex array
int indexOffset = this.currentOperation.VertexCount;
// Take over the indices and adjust them for the new vertex offset
for (int index = startIndex; index < (startIndex + indexCount); ++index) {
this.indices[this.usedIndexCount] = (short)(indices[index] + indexOffset);
++this.usedIndexCount;
}
// Update the end index for the current operation
this.usedVertexCount += vertexCount;
this.currentOperation.VertexCount += vertexCount;
this.currentOperation.EndIndex = this.usedIndexCount;
}
/// Flushes the queued vertices to the graphics card
private void flush() {
// The XNA classes don't like being fed zero values
if (this.usedVertexCount == 0)
return;
if (this.usedIndexCount == 0) {
this.BatchDrawer.Select(this.vertices, this.usedVertexCount);
} else {
this.BatchDrawer.Select(
this.vertices, this.usedVertexCount,
this.indices, this.usedIndexCount
);
}
// Draw all queued primitives, one after another
for (int index = 1; index < this.operations.Count; ++index) {
// Cache the render operation to keep the following code readable
RenderOperation operation = this.operations[index];
this.BatchDrawer.Draw(
operation.BaseVertexIndex,
operation.VertexCount,
operation.StartIndex,
operation.EndIndex - operation.StartIndex,
operation.PrimitiveType,
operation.DrawContext
);
} // for index
}
/// Resets the internal buffers to the empty state
private void reset() {
// Remove all rendering operations but the first one (which is only a placeholder
// so we can skip some if-empty checks for the sake of performance).
if (this.operations.Count > 1) {
this.operations.RemoveRange(1, this.operations.Count - 1);
this.currentOperation = this.operations[0];
}
// Reset the counters for used vertices and indices
this.usedVertexCount = 0;
this.usedIndexCount = 0;
}
///
/// Creates a new rendering operation if the drawing context or primitive type
/// have changed since the last call
///
/// Primitive type of the upcoming vertices
/// Drawing context used by the upcoming vertices
private void createNewOperationIfNecessary(PrimitiveType type, DrawContext context) {
// If the currently running context is not identical to the one this
// drawing call uses, we need to set up a new RenderOperation
if (
(!context.Equals(this.currentOperation.DrawContext)) ||
(type != this.currentOperation.PrimitiveType) ||
(this.currentOperation.PrimitiveType == PrimitiveType.LineStrip) ||
(this.currentOperation.PrimitiveType == PrimitiveType.TriangleStrip)
) {
this.currentOperation = new RenderOperation(
this.currentOperation.EndIndex, type, context
);
this.currentOperation.BaseVertexIndex = this.usedVertexCount;
this.operations.Add(this.currentOperation);
}
}
/// All vertex batches enqueued for rendering so far
private List operations;
/// Cached reference to the current RenderOperation
private RenderOperation currentOperation;
/// Queued vertices
private VertexType[] vertices;
/// Number of used vertices in the vertex array
private int usedVertexCount;
/// Queued indices
private short[] indices;
/// Number of used indices in the index array
private int usedIndexCount;
}
} // namespace Nuclex.Graphics.Batching