Guide

Performance

The engine does work only for what changed, and allocates nothing per frame in steady state. This chapter says what a frame does, what each kind of change costs, and how to measure your own pages.

The frame

  1. Input, at the end of PreUpdate: DOM events run before your game's Update.
  2. Your Update and LateUpdate write state and DOM. Changes only mark things dirty.
  3. Flush, after LateUpdate, in this order:
    • animations and transitions advance;
    • reactivity runs: [Tick] values, effects, template updates;
    • style for dirty elements only;
    • layout for dirty boxes only;
    • paint, in place, of what changed;
    • upload of the changed GPU records, and one draw per page.

Reading layout from code (GetBoundingClientRect, ElementFromPoint) brings style and layout up to date first, as in a browser. An idle page costs about a microsecond.

What a change costs

ChangeCost
translate, rotate, scale, opacity through typed setters, style: directives, CSS animations and transitionsCheapest. No style, layout or paint: numbers in a table the shader reads
Text through {expr} or Text.SetDataNo allocation; that line is laid out; changed glyphs are painted
Colors, backgrounds, borders that keep their sizeThe element is restyled; colors are rewritten in place, or the box is repainted
A class on one elementRestyle of it and of what its selectors reach
left, top, width, height, margin, font-sizeLayout of the box and of what depends on it
A theme class on bodyEvery element is restyled: fine once, not every frame
An element inserted or removed, or display switchedMost expensive. The view's layout is rebuilt and everything is painted
String setters (SetProperty, SetAttribute("style"))CSS is parsed and memory allocated: not for per-frame values
{@html}, string concatenation in templates, LINQ in [Derived]Allocate: use interpolation and loops
Animated font-sizeNew glyph sizes are rasterized: animate scale, or use -devcore-text-rendering: sdf

Habits that keep a UI fast

Numbers

Milliseconds of UI time per frame in a Windows player (Mono), with UI Toolkit showing the same content beside it. Zero bytes are allocated per frame in every DevCore scenario.

ScenarioDevCoreUI Toolkit
Empty page0.190.19
Idle shop page, 400 cards0.210.40
Minimap: 1,000 markers moved1.922.65
Timers: 200 texts changing5.358.27
Health bars: 300 widths (layout)3.665.65
Theme switch, 1,600 elements4.4550.78
Long list, 10,000 virtual rows0.550.78
Transitions: 200 tiles, color and scale0.521.58
Building a page (warm)6275
Managed memory kept (MB)7.2 to 9.80.6 to 3.5

IL2CPP is often about twice as fast as Mono for this code. A table of 1,000 cells with text changing takes about 16 ms per frame in the same player, so keep large tables still.

One view or several

Measured with a game UI of 1,450 elements (HUD, minimap, menu, inventory) split into one, two and six views at 1080p:

Measuring

Measure in a player, or in the editor with Release code optimization: debug code is several times slower. Sub-millisecond numbers vary between runs, so run twice before calling something a regression. The mouse over the window adds hit-testing cost.