#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 System.Diagnostics; using Microsoft.Xna.Framework.Graphics; namespace Nuclex.Graphics.Batching { /// Draws batched vertices using a dynamic vertex buffer internal class DynamicBufferBatchDrawer : IBatchDrawer, IDisposable where VertexType : struct, IVertexType { /// Number of regions the vertex buffer is divided into public const int Divisions = 4; /// /// Equivalent to SetDataOptions.Discard if supported by the target platform /// #if XBOX360 private const SetDataOptions DiscardIfPossible = SetDataOptions.None; #else private const SetDataOptions DiscardIfPossible = SetDataOptions.None; #endif /// Initializes a new dynamic vertex buffer based batch drawer /// /// Graphics device that will be used for rendering /// public DynamicBufferBatchDrawer(GraphicsDevice graphicsDevice) { const int BatchSize = PrimitiveBatch.BatchSize; const int BufferSize = BatchSize * Divisions; Debug.Assert( (BufferSize - 1) <= short.MaxValue, "Selected batch size results in a vertex buffer that's too large" ); this.graphicsDevice = graphicsDevice; // Create a new vertex buffer this.vertexBuffer = new DynamicVertexBuffer( graphicsDevice, typeof(VertexType), BufferSize, BufferUsage.WriteOnly ); this.stride = this.vertexBuffer.VertexDeclaration.VertexStride; // Create a new index buffer this.indexBuffer = new DynamicIndexBuffer( graphicsDevice, typeof(short), BufferSize, BufferUsage.WriteOnly ); } /// Immediately releases all resources owned by the instance public void Dispose() { if(this.vertexBuffer != null) { this.vertexBuffer.Dispose(); this.vertexBuffer = null; } if(this.indexBuffer != null) { this.indexBuffer.Dispose(); this.indexBuffer = null; } } /// /// Maximum number of vertices or indices a single batch is allowed to have /// public int MaximumBatchSize { get { return PrimitiveBatch.BatchSize; } } /// Selects the vertices that will be used for drawing /// Primitive vertices /// Number of vertices to draw /// Indices of the vertices to draw /// Number of vertex indices to draw public void Select( VertexType[] vertices, int vertexCount, short[] indices, int indexCount ) { unselectBuffers(); this.currentDivisionIndex = (this.currentDivisionIndex + 1) % Divisions; if(this.currentDivisionIndex == 0) { this.vertexBuffer.SetData( 0, vertices, 0, vertexCount, this.stride, DiscardIfPossible ); this.indexBuffer.SetData( 0, indices, 0, indexCount, DiscardIfPossible ); } else { int offset = PrimitiveBatch.BatchSize * this.currentDivisionIndex; this.vertexBuffer.SetData( offset * this.stride, vertices, 0, vertexCount, this.stride, SetDataOptions.NoOverwrite ); this.indexBuffer.SetData( offset * 2, indices, 0, indexCount, SetDataOptions.NoOverwrite ); } selectBuffers(this.currentDivisionIndex); } /// Selects the vertices that will be used for drawing /// Primitive vertices /// Number of vertices to draw public void Select( VertexType[] vertices, int vertexCount ) { unselectBuffers(); this.currentDivisionIndex = (this.currentDivisionIndex + 1) % Divisions; if(this.currentDivisionIndex == 0) { this.vertexBuffer.SetData( 0, vertices, 0, vertexCount, this.stride, DiscardIfPossible ); } else { int offset = PrimitiveBatch.BatchSize * this.currentDivisionIndex; this.vertexBuffer.SetData( offset * this.stride, vertices, 0, vertexCount, this.stride, SetDataOptions.NoOverwrite ); } selectBuffers(this.currentDivisionIndex); } /// Draws a batch of indexed primitives /// /// Index of the first vertex in the vertex array. This vertex will become /// the new index 0 for the index buffer. /// /// Number of vertices used in the call /// /// Position at which to begin processing the index array /// /// Number of indices that will be processed /// Type of primitives to draw /// Desired graphics device settings for the primitives public void Draw( int startVertex, int vertexCount, int startIndex, int indexCount, PrimitiveType type, DrawContext context ) { startIndex += PrimitiveBatch.BatchSize * this.currentDivisionIndex; int primitiveCount = VertexHelper.GetPrimitiveCount(indexCount, type); // If the DrawContext requires more than one pass, we have to draw the // primitives multiple times for (int pass = 0; pass < context.Passes; ++pass) { // Enter the pass and send all primitives to the graphics card context.Apply(pass); this.graphicsDevice.DrawIndexedPrimitives( type, // type of primitives to render startVertex, // offset that will be added to all vertex indices 0, // lowest vertex used in the call (relative to previous parameter) vertexCount, // number of vertices used in the call startIndex, // where in the index array to begin processing primitiveCount // number of primitives ); } // for } /// Draws a batch of primitives /// Index of vertex to begin drawing with /// Number of vertices used /// Type of primitives that will be drawn /// Desired graphics device settings for the primitives public void Draw( int startVertex, int vertexCount, PrimitiveType type, DrawContext context ) { int primitiveCount = VertexHelper.GetPrimitiveCount(vertexCount, type); // If the DrawContext requires more than one pass, we have to draw the // primitives multiple times for (int pass = 0; pass < context.Passes; ++pass) { // Enter the pass and send all primitives to the graphics card context.Apply(pass); this.graphicsDevice.DrawPrimitives( type, // type of primitives to render startVertex, // offset that will be added to all vertex indices primitiveCount // number of primitives being drawn ); } // for } /// Deselects the index and vertex buffers private void unselectBuffers() { // Unselect the buffer to allow them to be modified this.graphicsDevice.Indices = null; this.graphicsDevice.SetVertexBuffer(null); } /// Selects the index and vertex buffers /// Index of the division to select private void selectBuffers(int divisionIndex) { const int BatchSize = PrimitiveBatch.BatchSize; // Assign the index and vertex buffers for the DrawPrimitive call this.graphicsDevice.Indices = this.indexBuffer; this.graphicsDevice.SetVertexBuffer( this.vertexBuffer, BatchSize * divisionIndex ); } /// Graphics device being used for rendering private GraphicsDevice graphicsDevice; /// Dynamic VertexBuffer used to render batches private DynamicVertexBuffer vertexBuffer; /// Dynamic IndexBuffer used to render batches private DynamicIndexBuffer indexBuffer; /// Division that will be filled next private int currentDivisionIndex; /// Size, in bytes, of a single vertex private int stride; } } // namespace Nuclex.Graphics.Batching