SpriteBatch

Microsoft::Xna::Framework::Graphics — 2D sprite batching, text rendering, and blend control

ⓘ

Implementation status: SpriteBatch is complete at the API level — every Begin(), Draw() and DrawString() overload described in XNA 4.0 is present (8 Begin signatures, 7 XNA Draw plus 3 CNAEXT ones, and 6 DrawString). 2D drawing is the one thing every one of CNA’s 14 selectable renderers is built to do, including the 2D-only SDL_RENDERER. Renderer maturity still varies: CNA declares six identities Production (SDL_RENDERER, OPENGLES3, OPENGL33, VULKAN, DIRECTX9 and DIRECTX11), five Supported (WEBGL2, SDL_GPU, METAL, HEADLESS and STUB) and three Experimental (WEBGPU, SOFTWARE and FNA3D), while HEADLESS and STUB deliberately render nothing. See Renderers before choosing one.

Overview

SpriteBatch is the primary 2D drawing class in the Microsoft::Xna::Framework::Graphics namespace. It collects 2D draw calls issued between a Begin()/End() pair and hands them to the renderer when the batch flushes, one renderer call per sprite (flushBatch). The GPU renderer families then issue one native draw per run of consecutive sprites that share a texture, which is what reduces API overhead compared to drawing each sprite individually; SDL_RENDERER still issues one call per sprite.

The fundamental usage pattern is:

  1. Call Begin() once to configure blend state, sort mode, and any optional transform.
  2. Call Draw() or DrawString() any number of times to queue sprites and text.
  3. Call End() to flush the batch and issue the GPU draw calls.

SpriteBatch is renderer-agnostic. On SDL_RENDERER it maps draw calls directly to SDL3 renderer commands. On the GPU renderers with a programmable pipeline — EasyGL, Vulkan, SDL_GPU, DIRECTX9, DIRECTX11 and the other GPU identities — it generates textured quads and draws them through the renderer's own built-in sprite path (not the public SpriteEffect class, which SpriteBatch never creates or uses) with an orthographic projection covering the back-buffer dimensions. The Software renderer rasterises the same quads on the CPU.

Renderer selection happens at build time by default, so your game does not pick a SpriteBatch path itself — it is fixed by the -DCNA_GRAPHICS_RENDERER value the library was configured with. An opt-in multi-renderer build can select among several compiled-in renderers before the first device is created. State pointers passed to Begin() are const, and a SpriteBatch is constructed from a GraphicsDevice& (SpriteBatch(getGraphicsDeviceProperty())).

Begin() overloads

All overloads open a batch. Parameters not supplied take their XNA 4.0 defaults. A second call to Begin() without a preceding End() is an error. There are eight signatures: the no-argument form, the (sortMode, blendState) form, and the five-, six- and seven-argument forms, each of the last three in a BlendState and a const BlendState* variant (source-compatible; the pointer variants are additive). There is no Begin(sortMode) overload with a single argument — pass a blend state as well, as in the table.

Overload Parameters
Begin() No arguments — defaults to SpriteSortMode::Deferred, BlendState::AlphaBlend
Begin(sortMode, blendState) SpriteSortMode, BlendState (a single-argument Begin(sortMode) has never existed)
Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState) Full render state — adds const SamplerState*, const DepthStencilState*, const RasterizerState* (each may be nullptr for the default)
Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect) Full render state plus a custom Effect* (replaces the built-in SpriteEffect)
Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix) Full render state, custom effect, and a Matrix applied to every sprite position (camera / zoom transform)

SpriteSortMode

Controls the order in which batched sprites are drawn relative to each other. Choose a mode that balances visual correctness against throughput for your use case.

Value Behaviour
Deferred Default. Sprites are queued and drawn in submission order when End() is called. Best throughput.
Immediate Each Draw() call is handed to the renderer immediately and the render state is applied once, in Begin(). EasyGL, FNA3D, SOFTWARE and the per-sprite 2D renderers then submit the sprite before Draw() returns; the Direct3D families, WEBGPU, VULKAN and SDL_GPU do not act on the mode for stock sprites and still batch one run per texture until a texture change or End() (see Immediate mode below the seam). It is the mode for interleaving your own GraphicsDevice work between sprites only on the families that submit per Draw().
Texture Sprites are sorted by texture before draw, minimising texture-switch overhead. Submission order is not preserved.
BackToFront Sprites are sorted by descending layerDepth (furthest first). Use with alpha-blended sprites to avoid transparency artefacts.
FrontToBack Sprites are sorted by ascending layerDepth (nearest first). Useful for depth-buffer early-out on opaque sprites.

Draw() overloads

All Draw() overloads accept a const Texture2D& as the first argument (pass *texture when you hold a pointer or an optional). There are seven XNA overloads (listed below) and three CNAEXT convenience overloads taking float x, y coordinates. Omitted optional parameters take XNA 4.0 defaults (rotation = 0, origin = Vector2::Zero, scale = 1, effects = SpriteEffects::None, layerDepth = 0).

Signature Notes
Draw(texture, position, color) position is a Vector2. Draws the full texture at native size.
Draw(texture, position, sourceRect, color) Clips the source to sourceRect (std::optional<Rectangle>). Draws at native sub-region size.
Draw(texture, position, sourceRect, color, rotation, origin, scale, effects, layerDepth) Full Vector2-position overload. sourceRect is a std::optional<Rectangle> (std::nullopt for the whole texture). scale may be a float (uniform) or Vector2 (non-uniform): two separate overloads.
Draw(texture, destinationRect, color) destinationRect is a Rectangle. Stretches the full texture to fill the rectangle.
Draw(texture, destinationRect, sourceRect, color) Source and destination rectangles — stretches the sub-region into the destination.
Draw(texture, destinationRect, sourceRect, color, rotation, origin, effects, layerDepth) Full Rectangle-destination overload with rotation and flip.

DrawString() overloads

DrawString() renders text using a SpriteFont loaded through the ContentManager (getContentProperty().Load<SpriteFont>(…); a SpriteFont has no default constructor, so hold it in a std::optional or unique_ptr). The text parameter is a std::string (UTF-8) or a System::Text::StringBuilder. Glyph destinations stay fractional (sub-pixel), the first glyph's left bearing follows XNA's Max(kerning.X, 0) rule, and non-finite float arguments are accepted as XNA accepts them.

Signature Notes
DrawString(spriteFont, text, position, color) Draws text at position (Vector2) with the given tint color. No rotation or scaling.
DrawString(spriteFont, text, position, color, rotation, origin, scale, effects, layerDepth) Full overload matching the XNA 4.0 signature. scale may be a uniform float or a Vector2 (two overloads). Each of the three forms also exists for a System::Text::StringBuilder text, giving six overloads in all.

SpriteEffects

The SpriteEffects enum controls axis-aligned flipping of a sprite around its origin. Values can be combined with the bitwise OR operator.

Value Effect
SpriteEffects::None No flipping. Default.
SpriteEffects::FlipHorizontally Mirrors the sprite left-to-right.
SpriteEffects::FlipVertically Mirrors the sprite top-to-bottom.

BlendState presets

CNA ships the same four BlendState static presets as XNA 4.0. Pass one to Begin() to control how each sprite's colour is composited over existing framebuffer contents. Following XNA's binding rule, the four presets are pre-bound and immutable, and mutating any BlendState, DepthStencilState, RasterizerState or SamplerState after it has been bound to a device throws InvalidOperationException; make a fresh copy with the copy constructor (a CNAEXT addition) and change the copy instead. The SpriteEffects values above combine with |: there is no FlipBoth enumerator, so write SpriteEffects::FlipHorizontally | SpriteEffects::FlipVertically. SpriteBatch::DrawMeshEXT, an alpha.1 extension, was removed in this snapshot.

Preset Blend equation Typical use
BlendState::AlphaBlend Pre-multiplied alpha: src + dst × (1 − src.a) Default for most 2D UIs and sprites
BlendState::Additive src + dst (ignores alpha) Particle effects, glows, laser beams
BlendState::NonPremultiplied Straight alpha: src × src.a + dst × (1 − src.a) Textures stored with un-premultiplied alpha
BlendState::Opaque No blending — replaces destination entirely Fully opaque backgrounds, render targets

What a batch leaves on the GraphicsDevice

SpriteBatch applies the states it resolved in Begin() through the ordinary GraphicsDevice properties — for SpriteSortMode::Immediate inside Begin(), for every other mode inside End(), just before the queued sprites are drawn — and End() restores nothing. After a batch the device therefore still holds the batch's BlendState (AlphaBlend by default), its DepthStencilState (None by default: depth testing off), its RasterizerState (CullCounterClockwise by default) and its sampler in SamplerStates[0] (LinearClamp by default). This is XNA's and FNA's behaviour. A 3D pass drawn after a sprite pass must assign its own states, typically BlendState::Opaque and DepthStencilState::Default, or it inherits the sprite states.

Two further rules follow XNA. A RasterizerState passed to Begin() is honoured, so a state with ScissorTestEnable set clips the batch to GraphicsDevice::ScissorRectangle without a separate device assignment. And an Immediate batch is exclusive on its device: Begin() throws InvalidOperationException if an Immediate batch would overlap any other open batch on the same device, while several non-Immediate batches may be open together. Ordering mistakes (Begin() twice, End() or Draw() without Begin()) also throw InvalidOperationException. The exact timing, the failure rules and the evidence are on SpriteBatch: state, lifecycle and error semantics; how sprites are sorted and turned into draw calls on each renderer is on SpriteBatch sorting, flushing and renderer batching.

Code examples

1. Basic 2D sprite draw

The minimal Begin / Draw / End cycle. spriteBatch is typically stored as a member of your Game subclass and created once in LoadContent().

// In LoadContent():
spriteBatch = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
playerTexture.emplace(getContentProperty().Load<Texture2D>("player"));   // std::optional<Texture2D>

// In Draw():
spriteBatch->Begin();
spriteBatch->Draw(*playerTexture, Vector2(100.0f, 200.0f), Color::White);
spriteBatch->End();

2. Draw with rotation and scale

Use the full Vector2-position overload to rotate a sprite around its centre and scale it uniformly.

float rotation = MathHelper::ToRadians(45.0f);   // 45 degrees
Vector2 origin(playerTexture->getWidthProperty() / 2.0f,
               playerTexture->getHeightProperty() / 2.0f);  // centre pivot
float scale = 2.0f;

spriteBatch->Begin();
spriteBatch->Draw(
    *playerTexture,
    Vector2(400.0f, 300.0f),    // screen position
    std::nullopt,               // sourceRect: full texture
    Color::White,               // tint
    rotation,                   // rotation in radians
    origin,                     // pivot point
    scale,                      // uniform scale
    SpriteEffects::None,
    0.0f);                      // layerDepth
spriteBatch->End();

3. DrawString example

Load a SpriteFont via ContentManager, then call DrawString() to render text.

// In LoadContent():
font.emplace(getContentProperty().Load<SpriteFont>("fonts/Arial20"));   // std::optional<SpriteFont>

// In Draw():
spriteBatch->Begin();
spriteBatch->DrawString(
    *font,
    "Score: " + std::to_string(score),
    Vector2(16.0f, 16.0f),
    Color::Yellow);
spriteBatch->End();

4. Additive blending for particles

Switch to BlendState::Additive so particle sprites brighten the scene rather than covering it.

spriteBatch->Begin(SpriteSortMode::Deferred, BlendState::Additive);

for (auto& particle : particles) {
    spriteBatch->Draw(
        *sparkTexture,
        particle.Position,
        std::nullopt,
        Color(255, 200, 100, particle.Alpha),
        particle.Rotation,
        sparkOrigin,
        particle.Scale,
        SpriteEffects::None,
        0.0f);
}

spriteBatch->End();

5. Transform matrix camera (2D scrolling)

Pass a Matrix to Begin() to apply a global transform to every sprite in the batch. This is the standard XNA pattern for 2D camera scroll and zoom.

// Build a camera matrix: translate by -cameraPosition, scale by zoom
const Viewport vp = getGraphicsDeviceProperty().getViewportProperty();
Matrix cameraTransform =
    Matrix::CreateTranslation(-cameraPosition.X, -cameraPosition.Y, 0.0f) *
    Matrix::CreateScale(zoom, zoom, 1.0f) *
    Matrix::CreateTranslation(
        vp.getWidthProperty()  / 2.0f,
        vp.getHeightProperty() / 2.0f,
        0.0f);

spriteBatch->Begin(
    SpriteSortMode::Deferred,
    BlendState::AlphaBlend,
    nullptr,            // SamplerState  (default)
    nullptr,            // DepthStencilState (default)
    nullptr,            // RasterizerState (default)
    nullptr,            // Effect (built-in SpriteEffect)
    cameraTransform);   // <-- camera matrix applied to all draws

// Draw world tiles, characters, etc. in world-space coordinates
for (auto& tile : world.VisibleTiles(cameraPosition)) {
    spriteBatch->Draw(*tileSheet, tile.WorldPosition, tile.SourceRect, Color::White);
}

spriteBatch->End();
⚠

Never call Draw() or DrawString() outside a Begin() – End() pair. Doing so throws an InvalidOperationException at runtime, matching XNA 4.0 behaviour. Similarly, nested Begin() calls without a matching End() are an error.