> For the complete documentation index, see [llms.txt](https://docs.flatredball.com/gum/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flatredball.com/gum/code/performance-and-optimization/batchkeygroupedorderer.md).

# Batch Key Grouped Orderer

### Introduction

Gum's default renderer walks the visual tree in depth-first order and emits one draw per renderable. When two adjacent renderables go through different batchers — for example, a `SpriteRuntime` (which uses `SpriteBatch`) followed by a Gum.Shapes shape (which uses Apos.Shapes) — the renderer must end one batch and begin another. A scene that alternates between these types ends up with a batch transition on nearly every draw.

`BatchKeyGroupedOrderer` is an opt-in alternative ordering strategy that reorders draws within layer- and clip-bounded windows so that runs of the same `BatchKey` become contiguous. This collapses the per-alternation transitions to roughly one per distinct batch type in the scene.

### When to use it

The default `HierarchicalOrderer` is correct and fast. The grouped orderer helps when:

* Your scene mixes batch types the user can't unify with atlas tricks — most commonly `SpriteBatch` (sprite/text/nineslice/solid rectangle) combined with Gum.Shapes shapes (`CircleRuntime`, `RectangleRuntime`, etc.).
* You're seeing a `SpriteBatch.Begin` count that scales with the number of items in a list or grid rather than with the number of distinct textures.

Within `SpriteBatch` alone, you can usually reduce flushes yourself by packing fonts and sprites into one atlas (see [SinglePixelTexture](/gum/code/performance-and-optimization/singlepixeltexture.md)). Cross-batcher alternation has no such workaround — that's the case this orderer exists to fix.

### Choosing an orderer

`Renderer.SiblingOrdering` is a swap slot: assign it one of the available orderer instances to choose how the main render pass is ordered. The shipped options are:

| Set `Renderer.SiblingOrdering` to          | What it does                                                                                                                                                      |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HierarchicalOrderer.Instance` *(default)* | Depth-first walk, one draw per renderable in tree order. No reordering — this is the order Gum has always used.                                                   |
| `BatchKeyGroupedOrderer.Instance`          | Reorders draws within layer- and clip-bounded windows so same-`BatchKey` runs become contiguous, cutting batch flushes. Output is pixel-identical to the default. |

Note that the default and the opt-in are **different** types — `HierarchicalOrderer` versus `BatchKeyGroupedOrderer`. Assign the grouped one to turn the optimization on:

```csharp
// Initialize
RenderingLibrary.Graphics.Renderer.SiblingOrdering =
    RenderingLibrary.Graphics.BatchKeyGroupedOrderer.Instance;
```

To switch back, assign `HierarchicalOrderer.Instance`. You can flip the property at any time; no teardown is required, and the next frame uses the new orderer.

### What it preserves

The grouped orderer is pixel-correct for any scene the default orderer renders correctly:

* `BeginClip` / `EndClip` scopes are never crossed by reorder.
* `Layer` boundaries are never crossed.
* Same-Y runs on layers with `SecondarySortOnY` stay independent.
* Two renderables whose absolute bounds intersect always keep their original front-to-back order, so the painter's algorithm result is unchanged.

### Trade-offs

* The orderer runs per layer per frame and builds an overlap graph between renderables in each reorder window. The cost is `O(n²)` in the number of renderables in the window. For typical UI scenes this is negligible; for windows with thousands of renderables, profile before assuming it's free.
* The first-pass scope keys only on the existing `BatchKey` string. Finer texture-level grouping inside a single batch type is a possible future optimization.

### Measuring the win

Use [LastFrameDrawStates](/gum/code/performance-and-optimization/lastframedrawstates.md) to count `SpriteBatch.Begin` calls before and after enabling the orderer. The grouped orderer should reduce the count substantially on any scene that mixes `SpriteBatch` and Apos.Shapes work; it should leave a single-batcher scene unchanged.

Before reaching for the orderer, confirm it can actually help: call `Renderer.GetDrawStateSummary` (see [Summarizing by cause](/gum/code/performance-and-optimization/lastframedrawstates.md#summarizing-by-cause)) and look at the `Apos.Shapes ShapeBatch.Begin(s)` row. The orderer only collapses alternation between the `SpriteBatch` and Apos.Shapes batchers, so if that row is near zero, your begins come from clipping or texture sets instead — and the orderer will leave the count unchanged no matter what. In that case, reduce `ClipsChildren` usage or atlas your textures rather than switching orderers.

{% hint style="info" %}
`LastFrameDrawStates` only sees `SpriteBatch.Begin` calls. Apos.Shapes batch starts are not counted, so the total batch count is higher than the reported number. The orderer's effect on `SpriteBatch.Begin` is still a useful proxy for the overall trend.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.flatredball.com/gum/code/performance-and-optimization/batchkeygroupedorderer.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
