PrimitiveBatch

Modern GPUs are designed to work massively parallel and process large amounts of vertices at once. If your game has to draw lots of smaller objects, consisting of only a few polygons each, calling DrawUserPrimitives() hundreds of times is detrimental to performance because of the overhead associated with adding a drawing command to the GPU's execution queue.

VertexThroughput.png

The PrimitiveBatch helps you in this regard by providing you with an easy way to collect individual polygons into larger batches before sending them to the GPU. Its concept is very similar to the SpriteBatch from the XNA Framework, but allows batching of any type of vertex. It also outperforms the SpriteBatch by a significant margin ;)

Usage

The PrimitiveBatch is used just like the SpriteBatch. First, you need to decide on the type of vertex you're going to use. In our example, we're going to render vertices of type VertexPositionColor, one of the XNA Framework's built-in vertex types.
  • Add a reference to Nuclex.Graphics to your project
  • Create an instance of the PrimitiveBatch for your vertex type
  • Use the PrimitiveBatch to render your vertices

The last two points require some further elaboration in code. Here you are:

1. Make sure you've imported to the Nuclex.Graphics.Batching namespace
using Nuclex.Graphics.Batching;

2. Add a field to store your PrimitiveBatch in. You will also need a VertexDeclaration, so we'll add another field for that as well.
/// <summary>Primitive batch used to render particles in batches</summary>
private PrimitiveBatch<VertexPositionColor> particleBatch;
/// <summary>Vertex declaration for the particle vertices</summary>
private VertexDeclaration particleDeclaration;

3. In your Initialize() method, initialize the VertexDeclaration and PrimitiveBatch
/// <summary>
///   Allows the game to perform any initialization it needs to before starting to run.
///   This is where it can query for any required services and load any non-graphic
///   related content. Calling base.Initialize will enumerate through any components
///   and initialize them as well.
/// </summary>
protected override void Initialize() {
  this.particleDeclaration = new VertexDeclaration(
    GraphicsDevice, VertexPositionColor.VertexElements
  );

  this.particleBatch = new PrimitiveBatch<VertexPositionColor>(
    GraphicsDevice, this.particleDeclaration, VertexPositionColor.SizeInBytes
  );
}

4. Because you cannot switch to another effect inmidst of a DrawPrimitives() call, the PrimitiveBatch needs to know when its no longer possible to append vertices to its current batch and start a new one. This is controlled by the DrawContext class.

A DrawContext is used by the PrimitiveBatch like an Effect. It has a Begin()/End() pair and renders in one or more passes. A DrawContext can directly delegate to an Effect, but you could also share an Effect between multiple DrawContexts and set different Effect parameters in the DrawContext's Begin() method.

The only requirement is for your own DrawContexts is to implement the Equals() method, so the PrimitiveBatch knows when to start a new batch and when vertices can be appended to the current batch because they're using the same DrawContext. For your convenience, two implementations, EffectDrawContext and BasicEffectDrawContext have been provided
/// <summary>
///   Allows the game to perform any initialization it needs to before starting to run.
///   This is where it can query for any required services and load any non-graphic
///   related content. Calling base.Initialize will enumerate through any components
///   and initialize them as well.
/// </summary>
protected override void Initialize() {
  // ...
  // (this.particleEffect is the effect you're using to draw the particles)
  this.particleContext = new EffectDrawContext(this.particleEffect);
}

/// <summary>Drawing context used to render the particles</summary>
private EffectDrawContext particleContext;

5. In your Draw() method, use the PrimitiveBatch to draw your vertices
/// <summary>This is called when the game should draw itself.</summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime) {
  this.particleBatch.Begin(QueueingStrategy.Deferred);
  try {
    // Draw vertices using the PrimitiveBatch here. If you were implementing
    // a particle system, it might look like this:
    for(int system = 0; system < this.particleSystemCount; ++system) {
      this.particleBatch.Draw(
        this.particleSystems[system].Particles, 0, 100,
        PrimitiveType.PointList, this.particleContext
      );
    }
  }
  finally {
    this.particleBatch.End();
  }
}

Provided you had written some effect for rendering point sprite particles and some particle system code, you would now be looking at a beautiful particle system rendered through the PrimitiveBatch =)

Last edited Sep 12, 2009 at 7:41 PM by Cygon, version 5

Comments

No comments yet.