Drawer and batching¶
Tick UI records draw commands through the Drawer during
DoUI and replays them into a Unity CommandBuffer when the
render-pipeline integration calls RecordInto. This page explains
the command-list model, how instanced batching works, and the
planned draw-layer pass.
The command list¶
Drawer.m_commands is a List<DrawCommand> that fills during
DoUI and drains in RecordInto. Each DrawCommand is a pooled
object carrying the per-call parameters (rect, colour, transform,
mesh reference, and so on).
User code does not interact with the command list directly. Calls
into ui.FillRounded, ui.Text, and friends translate through
the active panel's DrawCommands list, which EndFrame then
flushes into Drawer.m_commands in panel-layer order. For the
flush ordering, refer to
Frame lifecycle.
Per-shape command types¶
DrawCommand.DrawType enumerates every shape Tick UI can
draw:
Rectangle,Shadow,Frosted: shape primitives drawn over the unit quad.Polygon,Line,Segment: vertex-driven primitives.Text,Texture,Texture9Slice,TextureByName: textured draws.Shape,CustomShape: SDF shapes (the built-in catalogue and user shaders).
The GPU scissor is not a DrawType. Each command carries an
optional ClipRect, and the batch loop applies it through
CommandBuffer.EnableScissorRect / DisableScissorRect when the
clip rect changes between commands.
Each type has a corresponding Handle* method in Drawer.cs that
configures a MaterialPropertyBlock and issues a
target.DrawMesh call, except the batched types (see below) which
draw through their batchers.
Materials¶
Drawer builds one Material per shape type in its constructor.
Each shape's material wraps a shader fetched through the resource
provider chain, so user-supplied providers can override pipeline
specific shader variants. For the chain, refer to
Resource providers.
MaterialPropertyBlock per command¶
Each passthrough Handle* method configures a per-call
MaterialPropertyBlock with the rect's _Scale, _Offset,
_Color, _Radius, and similar values, then issues
target.DrawMesh. Passthrough shapes produce one draw call each.
The batched shape types (rectangle, segment, and SDF shape) do not
go through a per-call Handle* method. They accumulate into their
batchers and draw as instanced batches — see
Instancing below.
Frosted glass and the grab-and-blit¶
HandleDrawFrosted is the most expensive shape type. The pipeline:
- Allocate a temporary
RenderTexturesized to the configured render-target size (ResolveTargetSize, which falls back toScreen.width×Screen.heightwhen no explicit size is set). - Blit the current target into the temp RT.
- Restore the original target (the
RenderTargetIdentifierset throughDrawer.SetRenderTarget). - Draw the frosted shape, sampling the temp RT in the shader.
- Release the temp RT.
The blit-per-call lets each frosted shape see everything drawn before it.
The temp RT is sized to the render target the drawer was given, so
projects that render to an off-screen target get a correctly sized
grab as long as that size is passed through SetRenderTarget. Refer
to Render pipelines.
Recording cost¶
CPU cost on the recording side is the limiting factor on typical
hardware. Instancing collapses runs of rectangles, segments, and
SDF shapes into a handful of draw calls, but passthrough shapes
(text, textures, frosted, line, polygon) still record one
DrawMesh each. Profiler markers attribute the cost. Refer to
Profiler markers for the full
list.
Common hot spots include:
- Text: cached after first use, but the first frame of new text pays the mesh-build cost.
- Frosted glass: per-call temp RT allocation, blit, and shader sample.
- Repeated layout pushes: rect-stack push and pop overhead in schemas with hundreds of elements.
Instancing¶
BuildBatchCommands runs before recording and groups consecutive
same-type commands into batches. The batched types draw through
CommandBuffer.DrawMeshInstancedProcedural from a per-batcher
structured buffer of instance data:
- Batches: rectangle (
RectangleBatcher), segment (SegmentBatcher), and SDF shape (ShapeBatcher). - Does not batch (passthrough, one
DrawMesheach): shadow, frosted (per-call grab-and-blit), texture and 9-slice (per-call sampler), text (per-call mesh), line, polygon, and custom shape.
A batch ends when the command type changes or the active clip rect
changes. At record time, RecordBatchCommands walks the batch list,
toggling the GPU scissor as the clip rect changes and dispatching
each batch through its batcher or each passthrough command through
its Handle* method.
Draw layers (planned)¶
The planned layer-bucket system lets opted-in widgets push commands into per-layer buckets within a panel:
ui.PushLayer(Layer.Background);
ui.FillRounded(...);
ui.PopLayer();
ui.PushLayer(Layer.Content);
ui.Text(...);
ui.PopLayer();
At flush, layers drain in fixed order (background, border, content, overlay). Within a layer, commands stay in submission order, so the instancing batcher receives long runs of the same shape type.
Pitfalls¶
Per-frame draw-call count is high. : This is expected. Profile under realistic load before optimising. Many panel-heavy UIs run fine on modern hardware. Refer to the Performance guide.
Frosted glass is expensive. : Each frosted shape costs one full-target blit plus one multi-tap shader sample. Use sparingly; group frosted regions where possible.
Custom shaders pink-render after a provider change. : The custom provider has shader entries that miss some shapes. Refer to Resource providers.
Additional resources¶
- Render pipelines: how recorded commands reach the GPU.
- Frame lifecycle: when the draw-command
flush runs (
EndFrame). - Resource providers: where the shape shaders come from.
- Profiler markers: the full list of markers wrapping the drawer's hot paths.
- Performance guide: the hands-on side of profiling and optimising.