AvaloniaUI/Avalonia · error · InvalidOperationException

{o.GetType()} belongs to a different compositor

Error message

{o.GetType()} belongs to a different compositor

What it means

In GetServer for IBrush, when a CompositionBrush is encountered the helper checks that its Compositor matches the drawing compositor; a mismatch calls ThrowForeignCompositor. A composition object's server belongs to its own compositor's render loop and cannot be shared across two compositors/render threads.

Source

Thrown at src/Avalonia.Base/Rendering/Composition/Drawing/ServerResourceHelperExtensions.cs:55

        if (compositor == null)
            return pen;
        if (pen == null)
            return null;
        if (pen is ImmutablePen immutable)
            return immutable;
        if (pen is ICompositionRenderResource<IPen> resource)
            return resource.GetForCompositor(compositor);
        ThrowNotCompatible(pen);
        return null;
    }

    [MethodImpl(MethodImplOptions.NoInlining), DoesNotReturn]
    static void ThrowNotCompatible(object o) =>
        throw new InvalidOperationException(o.GetType() + " is not compatible with composition");

    [MethodImpl(MethodImplOptions.NoInlining), DoesNotReturn]
    static void ThrowForeignCompositor(CompositionObject o) =>
        throw new InvalidOperationException(o.GetType() + " belongs to a different compositor");
    
    public static ITransform? GetServer(this ITransform? transform, Compositor? compositor)
    {
        if (compositor == null)
            return transform;
        if (transform == null)
            return null;
        if (transform is ImmutableTransform immutable)
            return immutable;
        if (transform is ICompositionRenderResource<ITransform> resource)
            resource.GetForCompositor(compositor);
        return new ImmutableTransform(transform.Value);
    }

    public static IRenderDataGeometry? GetServer(this Geometry? geometry, Compositor? compositor)
    {
        if (geometry == null)
            return null;

View on GitHub (pinned to 11c5427268)

Solutions

  1. Create the CompositionBrush on the same Compositor that owns the drawing context.
  2. Per-compositor, recreate shared brushes rather than reusing one across compositors.
  3. Check brush.Compositor == targetCompositor before drawing, and recreate if not.

Example fix

// before
var brush = compositorA.CreateColorBrush(color);
// drawing on a surface backed by compositorB
ctx.DrawRectangle(brush, null, rect); // throws: belongs to a different compositor

// after
var brush = compositorB.CreateColorBrush(color);
ctx.DrawRectangle(brush, null, rect);
Defensive patterns

Strategy: validation

Validate before calling

if (brush is CompositionBrush cb && cb.Compositor != targetCompositor)
    brush = targetCompositor.CreateColorBrush(/* same color */);
ctx.DrawRectangle(brush, null, rect);

Type guard

static bool BelongsTo(CompositionObject o, Compositor c) => o.Compositor == c;

Try / catch

try { ctx.DrawRectangle(brush, null, rect); }
catch (InvalidOperationException ex) when (ex.Message.Contains("different compositor"))
{ brush = targetCompositor.CreateColorBrush(color); ctx.DrawRectangle(brush, null, rect); }

Prevention

When it happens

Trigger: Drawing with a CompositionBrush that was created by a different Compositor than the one backing the current RenderDataDrawingContext.

Common situations: Sharing a CompositionBrush/visual across multiple top-level windows or surfaces that each have their own Compositor; caching a brush from one compositor and reusing it on another.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/1ae31a64e86180d7. Report an issue: GitHub.