Tutorial 25: SpriteSortMode and Layering
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
| Mode | CPU sort cost | GPU draw calls | Notes |
|---|---|---|---|
| Deferred | None | 1 per texture group | Best general performance |
| Immediate | None | 1 per Draw() call | Worst GPU performance |
| Texture | O(n log n) by texture | 1 per texture | Better than Deferred when textures interleave |
| BackToFront | O(n log n) by depth | 1 per texture group (post-sort) | Required for correct alpha layering |
| FrontToBack | O(n log n) by depth | 1 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);