Companion to #81. Traced 2026-06-10 on the live GUI showing the animations demo (http://localhost:8088/animations/, fixture testdata/sites/animations/). The page freezes during hover transitions; the trace shows the UI thread is already saturated before any interaction.
Findings
1. Layer-tree reuse never engages on this page. app.js auto-starts a WAAPI animation, and its rAF loop writes status.textContent every frame. The text mutation bumps LayoutInvalidationVersion, so WebviewPanel.LiveTick (src/Starling.Gui/Controls/WebviewPanel.cs:1591) takes the full relayout + rebuild path every tick and sets _animationTickPresent=false — the cached-tree fast path in NativeViewportRenderer.Present (src/Starling.Gui.Core/Rendering/NativeViewportRenderer.cs:126) is skipped on every frame. Idle trace: UI thread 100% busy, 7.3s of 8.5s inside LayerTreeBuilder.Build.
2. The rebuild cost is dominated by the per-box style-override callback. WebviewPanel.AnimatedStyle runs for every box during Build (via DisplayListBuilder.EffectiveStyle, 5.1s of the 8.5s idle trace), enumerating Animation/Transition ActiveProperties per box per frame.
3. Hover transitions stack ~170ms synchronous present stalls on top. A hover state change is a non-animation present (whole-tree rebuild by design), and the 2048x512 tiles containing the hovered element re-raster. Those tiles hold cards with box-shadow: 0 18px 40px: the shadow blur is cached (BoxShadowRasterCache) but every draw runs an ImageSharp CPU bicubic resize to device scale (CloningImageProcessor) — 4.7s in a 16s hover-toggle trace. Transition start/end also flips layer promotion (TransitionEngine.ActiveProperties -> IsElementAnimatingLayerRoot), re-keying the base slice and forcing extra base-tile re-rasters.
Work items
Companion to #81. Traced 2026-06-10 on the live GUI showing the animations demo (
http://localhost:8088/animations/, fixturetestdata/sites/animations/). The page freezes during hover transitions; the trace shows the UI thread is already saturated before any interaction.Findings
1. Layer-tree reuse never engages on this page.
app.jsauto-starts a WAAPI animation, and its rAF loop writesstatus.textContentevery frame. The text mutation bumpsLayoutInvalidationVersion, soWebviewPanel.LiveTick(src/Starling.Gui/Controls/WebviewPanel.cs:1591) takes the full relayout + rebuild path every tick and sets_animationTickPresent=false— the cached-tree fast path inNativeViewportRenderer.Present(src/Starling.Gui.Core/Rendering/NativeViewportRenderer.cs:126) is skipped on every frame. Idle trace: UI thread 100% busy, 7.3s of 8.5s insideLayerTreeBuilder.Build.2. The rebuild cost is dominated by the per-box style-override callback.
WebviewPanel.AnimatedStyleruns for every box duringBuild(viaDisplayListBuilder.EffectiveStyle, 5.1s of the 8.5s idle trace), enumerating Animation/TransitionActivePropertiesper box per frame.3. Hover transitions stack ~170ms synchronous present stalls on top. A hover state change is a non-animation present (whole-tree rebuild by design), and the 2048x512 tiles containing the hovered element re-raster. Those tiles hold cards with
box-shadow: 0 18px 40px: the shadow blur is cached (BoxShadowRasterCache) but every draw runs an ImageSharp CPU bicubic resize to device scale (CloningImageProcessor) — 4.7s in a 16s hover-toggle trace. Transition start/end also flips layer promotion (TransitionEngine.ActiveProperties->IsElementAnimatingLayerRoot), re-keying the base slice and forcing extra base-tile re-rasters.Work items
_animationTickPresentfor the rest of the tree.AnimatedStyleprobe during Build (e.g. an animating-element set computed once per frame instead of two enumerator probes per box).WasRecentlyMutated) so the base slice is not re-keyed twice per transition.