#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