Tutorial 25: SpriteSortMode and Layering

Graphics  ·  SpriteSortMode  ·  layerDepth  ·  Batching

SpriteSortMode is the first parameter to SpriteBatch::Begin(). It controls two things: when accumulated sprites are submitted to the GPU and in what order they appear on screen. Choosing the right mode has visual and performance consequences. This tutorial covers all five values and shows practical multi-batch layering patterns.

SpriteSortMode enum — all five values

Deferred (default)

Sprites are queued in CPU memory until End() is called. At End() they are submitted to the GPU in draw-call order with consecutive sprites using the same texture grouped into a single draw call. This is the most efficient mode for most games.

spriteBatch_->Begin(SpriteSortMode::Deferred);
// Draw 500 sprites from 5 textures → 5 draw calls at End()
spriteBatch_->End();

Use when: You are drawing a fixed set of sprites and want the fewest draw calls. The layerDepth parameter is ignored.

Immediate

Each Draw() call immediately flushes one draw call to the GPU. No batching occurs. This mode sets GPU state before each sprite, making it the only mode where you can safely call GraphicsDevice.SetSamplerState() or other state functions between individual Draw() calls.

spriteBatch_->Begin(SpriteSortMode::Immediate);
// Change GPU state here freely between Draw() calls
spriteBatch_->Draw(*texA, posA, Color::White);
getGraphicsDeviceProperty().setSamplerState(0, SamplerState::PointClamp);
spriteBatch_->Draw(*texB, posB, Color::White);
spriteBatch_->End();

Use when: You need per-sprite GPU state changes. Very slow for large sprite counts — use only when necessary.

Texture

Same as Deferred but at End() sprites are additionally sorted by texture pointer before submission. This groups all sprites from the same texture together regardless of draw-call order, further reducing draw calls when sprites from different textures are interleaved in game logic.

spriteBatch_->Begin(SpriteSortMode::Texture);
// Draw player (atlas A), enemy (atlas B), tree (atlas A), coin (atlas B)
// Sorted at End() → atlas A batch, atlas B batch = 2 draw calls
spriteBatch_->End();

Use when: Your game logic draws sprites from multiple textures in interleaved order and profiling shows texture-switch overhead. The layerDepth parameter is ignored; visual order equals draw-call order.

BackToFront

At End(), sprites are sorted by descending layerDepth (1.0 = back, 0.0 = front) then submitted. Sprites at depth 1.0 are drawn first; sprites at 0.0 are drawn last and appear on top. This produces correct alpha-blended layering without manual draw-call ordering:

spriteBatch_->Begin(SpriteSortMode::BackToFront);

// Call Draw() in any order — layer depth controls final paint order
spriteBatch_->Draw(*playerTex_,  playerPos_,  std::nullopt, Color::White, 0,
                   Vector2::Zero, 1.0f, SpriteEffects::None, 0.1f);  // near front
spriteBatch_->Draw(*enemyTex_,   enemyPos_,   std::nullopt, Color::White, 0,
                   Vector2::Zero, 1.0f, SpriteEffects::None, 0.2f);
spriteBatch_->Draw(*backgroundTex_, Vector2::Zero, std::nullopt, Color::White, 0,
                   Vector2::Zero, 1.0f, SpriteEffects::None, 0.9f);  // far back

spriteBatch_->End();
// Render order: background (0.9), enemy (0.2), player (0.1)

Use when: You need pixel-correct depth ordering with alpha blending (e.g. a top-down RPG with trees that overlap characters).

FrontToBack

Same as BackToFront but sorted in ascending order (0.0 = front drawn first). This is useful with depth testing: front sprites write their depth values early, causing later back sprites to fail the depth test and be discarded — saving pixel shader time on fillrate-bound scenes. Less useful for 2D alpha blending.

spriteBatch_->Begin(SpriteSortMode::FrontToBack);
// Layer 0.0 = front, 1.0 = back
// Primarily useful with opaque sprites and depth testing enabled
spriteBatch_->End();

Use when: You have many opaque sprites and want early-depth-rejection. Rarely needed in 2D games; more relevant in 2.5D or isometric scenes with a depth buffer.

Layer depth parameter in Draw()

The layerDepth parameter is the last argument to the full Draw() overload. It is a Single (float) in the range [0.0, 1.0]. It is only meaningful when the sort mode is BackToFront or FrontToBack; otherwise it is ignored:

// Full Draw() signature showing layerDepth
spriteBatch_->Draw(
    texture,          // Texture2D&
    position,         // Vector2
    sourceRect,       // std::optional<Rectangle>
    color,            // Color
    rotation,         // Single (radians)
    origin,           // Vector2
    scale,            // Single or Vector2
    effects,          // SpriteEffects
    layerDepth        // Single — 0.0f to 1.0f
);

Performance implications

ModeCPU sort costGPU draw callsNotes
DeferredNone1 per texture groupBest general performance
ImmediateNone1 per Draw() callWorst GPU performance
TextureO(n log n) by texture1 per textureBetter than Deferred when textures interleave
BackToFrontO(n log n) by depth1 per texture group (post-sort)Required for correct alpha layering
FrontToBackO(n log n) by depth1 per texture group (post-sort)Good for opaque + depth rejection

Combining sort modes with multiple SpriteBatch calls

A professional game typically uses multiple Begin()/End() pairs per frame, each with a different sort mode, to achieve correct layering cheaply:

void Draw(const GameTime&) override {
    auto& gd = getGraphicsDeviceProperty();
    gd.Clear(Color::Black);

    // Layer 1 — Background tiles (opaque, Deferred = fast)
    spriteBatch_->Begin(SpriteSortMode::Deferred, &BlendState::Opaque,
                         nullptr, nullptr, nullptr, nullptr,
                         camera_->getTransform());
    DrawBackgroundTiles();
    spriteBatch_->End();

    // Layer 2 — World entities with depth-sorted alpha
    spriteBatch_->Begin(SpriteSortMode::BackToFront, &BlendState::AlphaBlend,
                         nullptr, nullptr, nullptr, nullptr,
                         camera_->getTransform());
    DrawEntities();    // each entity passes its Y as layerDepth for isometric sort
    spriteBatch_->End();

    // Layer 3 — Particle effects (additive, no sort needed)
    spriteBatch_->Begin(SpriteSortMode::Deferred, &BlendState::Additive,
                         nullptr, nullptr, nullptr, nullptr,
                         camera_->getTransform());
    DrawParticles();
    spriteBatch_->End();

    // Layer 4 — HUD (screen space, no camera transform)
    spriteBatch_->Begin(SpriteSortMode::Deferred);
    DrawHUD();
    spriteBatch_->End();

    gd.Present();
}

Isometric Y-sorting

A common pattern for isometric games: entities further up the screen (higher Y) appear behind those lower on screen. Use BackToFront and derive layerDepth from the entity's Y position:

// Normalise Y to [0,1] range
Single layerDepth = 1.0f - (entity.position.Y / (Single)worldHeight);
spriteBatch_->Draw(*entity.tex, entity.position, std::nullopt, Color::White,
                   0.0f, entity.origin, 1.0f, SpriteEffects::None, layerDepth);