Goal
SkiaOptions, RenderOptions, timers, and diagnostics tools.Why this matters
Prerequisites
Visuals/Controls). When properties change, visuals mark themselves dirty (e.g., via InvalidateVisual).SceneGraph.cs).IRenderTimer) asks the renderer to draw frames while work is pending.Avalonia uses two main threads: UI thread and render thread. Keep the UI thread free of long-running work so animations, input dispatch, and composition stay responsive.
Visuals have properties (Bounds, Opacity, Transform, etc.) that trigger redraw when changed.InvalidateVisual() marks a visual dirty. Most controls call this automatically when a property changes.IRenderer (see IRenderer.cs) exposes methods:
AddDirty(Visual visual) — mark dirty region.Paint — handle paint request (e.g., OS says "redraw now").Resized — update when target size changes.Start/Stop — hook into render loop lifetime.Avalonia ships both CompositingRenderer (default) and DeferredRenderer. The renderer uses dirty rectangles to redraw minimal regions and produces scene graph nodes consumed by Skia.
CompositionTarget abstracts the surface being rendered. It holds references to swapchains, frame buffers, and frame timing metrics. You usually observe it through IRenderer.Diagnostics (frame times, dirty rect counts) or via DevTools/remote diagnostics rather than accessing the object directly.
ImmediateRenderer renders a visual subtree synchronously into a DrawingContext. Used for RenderTargetBitmap, VisualBrush, etc. Not used for normal window presentation.
The compositor orchestrates UI → render thread updates (see Compositor.cs).
RenderLoop ticks at platform-defined cadence (vsync/animation timers). When there's dirty content or CompositionTarget animations, it schedules a frame.IRenderTimer (see IRenderTimer.cs) abstracts ticking. Implementations include DefaultRenderTimer, DispatcherRenderTimer, and headless timers used in tests.AppBuilder.UseRenderLoop(new RenderLoop(new DispatcherRenderTimer())) to integrate external timing sources (e.g., game loops).Tick on the render thread. Avoid heavy work in handlers: queue work through the UI thread if necessary.Each RenderLoop tick calls Compositor.CommitScenes. The compositor transforms dirty visuals into render passes, prunes unchanged branches, and tracks retained GPU resources for reuse across frames.
Avalonia targets multiple render interfaces via IRenderInterface. Skia is the default implementation and chooses GPU versus CPU paths per platform.
Force a backend with UseSkia(new SkiaOptions { RenderMode = RenderMode.Software }) or by setting AVALONIA_RENDERER environment variable (e.g., software, open_gl). Always pair overrides with tests on target hardware.
SkiaOptions exposes GPU cache limits and toggles like UseOpacitySaveLayer.IRenderSurface implementations (swapchains, framebuffers) own platform handles; leaks appear as rising RendererDiagnostics.SceneGraphDirtyRectCount.Avalonia uses Skia for cross-platform drawing:
UseSkia(new SkiaOptions { ... }) in AppBuilder to tune.AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseSkia(new SkiaOptions
{
MaxGpuResourceSizeBytes = 64L * 1024 * 1024,
UseOpacitySaveLayer = false
})
.LogToTrace();
MaxGpuResourceSizeBytes: limit Skia resource cache.UseOpacitySaveLayer: forces Skia to use save layers for opacity stacking (accuracy vs performance).RenderOptions attached properties influence interpolation and text rendering:
BitmapInterpolationMode: Low/Medium/High quality vs default.BitmapBlendingMode: blend mode for images.TextRenderingMode: Default, Antialias, SubpixelAntialias, Aliased.EdgeMode: Antialias vs Aliased for geometry edges.RequiresFullOpacityHandling: handle complex opacity composition.Example:
RenderOptions.SetBitmapInterpolationMode(image, BitmapInterpolationMode.HighQuality);
RenderOptions.SetTextRenderingMode(smallText, TextRenderingMode.Aliased);
RenderOptions apply to a visual and flow down to children unless overridden.
Prevent unnecessary redraws:
RendererDiagnostics (see RendererDiagnostics.cs) via RenderRoot.Renderer.Diagnostics. Metrics include dirty rectangle counts, render phase durations, and draw call tallies.SceneInvalidated/RenderLoop timestamps to push frame data into tracing systems such as EventSource or Prometheus exporters.F12 to open DevTools.Diagnostics panel toggles overlays and displays frame timing graphs.Rendering view (when available) shows render loop cadence, render thread load, and GPU backend in use.AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace(LogEventLevel.Debug, new[] { LogArea.Rendering, LogArea.Layout })
.StartWithClassicDesktopLifetime(args);
RendererDebugOverlays (see RendererDebugOverlays.cs) enable overlays showing dirty rectangles, FPS, layout costs.
if (TopLevel is { Renderer: { } renderer })
renderer.DebugOverlays = RendererDebugOverlays.Fps | RendererDebugOverlays.LayoutTimeGraph;
dotnet-counters to monitor GC while animating UI.Avalonia.Diagnostics.RenderingDebugOverlays integrates with Avalonia.Remote.Protocol. Use avalonia-devtools:// clients to stream metrics from remote devices (Chapter 24).var bitmap = new RenderTargetBitmap(new PixelSize(300, 200), new Vector(96, 96));
await bitmap.RenderAsync(myControl);
bitmap.Save("snapshot.png");
Uses ImmediateRenderer to render a control off-screen.
DrawingContext allows custom drawing via immediate renderer.
RenderScaling.IRenderTimer implementation and graph frame cadence using timestamps collected from SceneInvalidated.SkiaOptions.RenderMode to force software rendering, then switch back to GPU; profile render time using overlays in both modes.RendererDebugOverlays.LayoutTimeGraph | RenderTimeGraph) during an animation and export metrics for analysis.RenderRoot.Renderer.Diagnostics to log dirty rectangle counts when toggling InvalidateVisual; correlate with DevTools overlays.IRenderer.csCompositor.csRenderDataDrawingContext.csImmediateRenderer.csRenderLoop.csIRenderTimer.csRenderOptions.csSkiaOptions.cs, PlatformRenderInterface.csRendererDiagnostics.csRendererDebugOverlays.csInvalidateVisual lead to a new frame?SkiaOptions.MaxGpuResourceSizeBytes vs RenderOptions.BitmapInterpolationMode?What's next