#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