UI Perf Budget
Estimate draw calls, batches and rebuild cost before you build the screen.
Screen composition
Many similar cells plus a scroll rect — where batching usually falls apart.
Estimate — Mid mobile (60 fps)
Dynamic content on its own canvas, layout groups mostly gone, atlases down to three, RectMask2D, raycast targets trimmed to what is clickable, ASTC 6×6. Toggle the button above to inspect the fixed screen in full.
These are estimates from the shape of UGUI's batching, rebuild and fill-rate behaviour, not measurements. They are good for ranking two layouts and catching structural mistakes; for a real number use the Frame Debugger and the Canvas.BuildBatch profiler marker.
Findings (5)
- high
Split the dynamic content onto its own canvas
A single dirty element re-batches its whole canvas. With ~90 elements per canvas, every one of the 12 per-frame changes pays for all of them. Move the changing elements to a small canvas of their own — usually the single biggest win available.
- high
Replace layout groups with anchored positions
60 elements sit under a layout group. Layout rebuilds walk the whole subtree and run before the graphic rebuild, so they multiply the cost of every change. For grids and lists whose cell size is known, anchor the cells directly and drop the group.
- medium
120 of 180 elements are raycast targets
Image and Text default to Raycast Target = on, and almost none of them need it. The raycaster walks every one on each pointer event, and every parent up the tree. Turn it off on labels, icons and backgrounds — keep it only where a click is actually handled.
- medium
Reorder the hierarchy so same-atlas elements are adjacent
Batching follows hierarchy order. Alternating between atlases — icon, label, icon, label — starts a new batch each time. Grouping all icons then all labels within a row costs nothing and can halve the batch count.
- medium
60 text elements is a lot of mesh
Every TMP label is a mesh whose vertex count scales with character count. For numbers that change often, prefer a fixed-width format so the mesh size stays constant, and never call SetText every frame with an unchanged value — guard it.
UI performance estimate — Mid mobile (60 fps)
============================================================
Screen
Canvases 2 (1 dynamic)
UI elements 180 (~90 per canvas)
Sprite atlases 4 @ 2048² ASTC 6x6
Font assets 2
Masks 2 (RectMask2D)
Raycast targets 120
Full-screen layers 1
Elements dirty / frame 12
Under layout groups 60
Text elements 60
Estimated batches 38 (budget 60)
material groups 12
interleaving penalty 26
mask stencil 0
Estimated rebuild cost 1.92 ms (budget 1.5 ms of a 16.7 ms frame)
canvas re-batch 0.54 ms
layout rebuild 0.72 ms
graphic mesh rebuild 0.24 ms
text mesh rebuild 0.42 ms
raycast per pointer 0.30 ms
Texture memory 9.1 MB (budget 48 MB)
sprite atlases 7.1 MB
font atlases 2.0 MB
Full-screen overdraw 2x (budget 3x)
With every recommendation applied
batches 38 -> 22 (42% lower)
rebuild cost 1.92 -> 1.16 ms (39% lower)
texture memory 9.1 -> 7.3 MB (20% lower)
Findings
1. [HIGH] Split the dynamic content onto its own canvas
A single dirty element re-batches its whole canvas. With ~90 elements per canvas, every one of the 12 per-frame changes pays for all of them. Move the changing elements to a small canvas of their own — usually the single biggest win available.
2. [HIGH] Replace layout groups with anchored positions
60 elements sit under a layout group. Layout rebuilds walk the whole subtree and run before the graphic rebuild, so they multiply the cost of every change. For grids and lists whose cell size is known, anchor the cells directly and drop the group.
3. [MEDIUM] 120 of 180 elements are raycast targets
Image and Text default to Raycast Target = on, and almost none of them need it. The raycaster walks every one on each pointer event, and every parent up the tree. Turn it off on labels, icons and backgrounds — keep it only where a click is actually handled.
4. [MEDIUM] Reorder the hierarchy so same-atlas elements are adjacent
Batching follows hierarchy order. Alternating between atlases — icon, label, icon, label — starts a new batch each time. Grouping all icons then all labels within a row costs nothing and can halve the batch count.
5. [MEDIUM] 60 text elements is a lot of mesh
Every TMP label is a mesh whose vertex count scales with character count. For numbers that change often, prefer a fixed-width format so the mesh size stays constant, and never call SetText every frame with an unchanged value — guard it.
These are estimates derived from how UGUI batches and rebuilds, not measurements.
Use them to rank options and catch structural mistakes; confirm with the Frame
Debugger and the Profiler before acting on any specific number.Paste into a ticket when arguing for a UI refactor — the findings are the argument.