Skip to content

Improve High-Frequency Rendering by enhancing allocations for GC #19363

Description

@xdi1

Problem Overview

I'm building a real-time UI (a timeline-based audio editor) in Avalonia 11.3.2 and targeting 60–144 fps animations. However, I'm running into noticeable frame stutter, which profiling shows is due to GC activity caused by small per-frame allocations in Avalonia APIs.

Even with GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency, the GC still unpredictably collects, leading to visible jitter in UI animations.

Application Context

  • Per-frame rendering is done using DrawingContext.DrawText, DrawRect, and DrawLine.
  • I've tried both rendering loops:
    • DispatcherTimer.Tick += OnTick
    • A custom Stopwatch loop using Dispatcher.UIThread.Post(...)
  • UI controls like Slider, ScrollBar, and TextBlock participate in two-way bindings for timeline state.
  • All of the above create per-frame allocations (from formatting, layout, bindings, boxed events, etc).

GC Allocation Hotspots

  1. Text rendering via DrawText:

    • Creates SKTextBlob and other objects on every frame.
  2. Binding system:

    • Two-way bindings allocate AvaloniaPropertyChangedEventArgs.
  3. Dispatcher use:

    • Dispatcher.UIThread.Post or DispatcherTimer allocates per invocation.
    • This is more minimal and can be acceptable.
Image

Request

This issue is intended to highlight a common pattern of allocations for apps requiring per-frame updates. While some of these allocations are minor, they still lead to unpredictable GC behavior and (even Gen0 collections) create noticeable stutters. I understand my use case can be niche but it would be helpful to optimize and add more support for high-framerate app.

Solution

  1. DrawText/DrawRect/DrawLine
  • Expose methods that takes in required object for rendering like SKTextBlob, so they can be cached in ViewModels.
  1. Dispatcher
  • Not sure if this is changeable, allocation on this one is minimal though.
  1. Two-Way Binding
  • Cache an AvaloniaPropertyChangedEventArgs in control and reuse.

Notes

I have also tried using an ICustomDrawOperation with

public void Render(ImmediateDrawingContext context)
{
    var leaseFeature = context.TryGetFeature<ISkiaSharpApiLeaseFeature>();
    if (leaseFeature is not null)
    {
        using var lease = leaseFeature.Lease();
        lease.SkCanvas.DrawText(blob, 0, (float)Position.Y, paint);
    }
}

to replace DrawText, while this does eliminate allocation, I cannot get good result with it. The draw methods in Avalonia.Media give much smoother animation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions