Browse Source

Composition aware resources

Nikita Tsukanov 2 năm trước cách đây
mục cha
commit
64610c264b
100 tập tin đã thay đổi với 2436 bổ sung1187 xóa
  1. 36 35
      nukebuild/RefAssemblyGenerator.cs
  2. 1 1
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  3. 1 1
      samples/ControlCatalog/ViewModels/TabControlPageViewModel.cs
  4. 3 3
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  5. 4 4
      samples/RenderDemo/Pages/PathMeasurementPage.cs
  6. 1 1
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  7. 1 1
      src/Avalonia.Base/Collections/Pooled/PooledStack.cs
  8. 2 2
      src/Avalonia.Base/Input/Cursor.cs
  9. 53 27
      src/Avalonia.Base/Media/Brush.cs
  10. 13 5
      src/Avalonia.Base/Media/ConicGradientBrush.cs
  11. 2 2
      src/Avalonia.Base/Media/DashStyle.cs
  12. 3 1
      src/Avalonia.Base/Media/Drawing.cs
  13. 48 13
      src/Avalonia.Base/Media/DrawingBrush.cs
  14. 1 1
      src/Avalonia.Base/Media/DrawingGroup.cs
  15. 3 1
      src/Avalonia.Base/Media/Effects/IEffect.cs
  16. 69 1
      src/Avalonia.Base/Media/Geometry.cs
  17. 1 1
      src/Avalonia.Base/Media/GeometryDrawing.cs
  18. 4 1
      src/Avalonia.Base/Media/GlyphRun.cs
  19. 1 1
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  20. 24 18
      src/Avalonia.Base/Media/GradientBrush.cs
  21. 1 1
      src/Avalonia.Base/Media/IAffectsRender.cs
  22. 9 1
      src/Avalonia.Base/Media/IImageBrush.cs
  23. 25 0
      src/Avalonia.Base/Media/IImmutableGlyphRunReference.cs
  24. 1 1
      src/Avalonia.Base/Media/IMutableBrush.cs
  25. 1 1
      src/Avalonia.Base/Media/IMutableExperimentalAcrylicMaterial.cs
  26. 2 0
      src/Avalonia.Base/Media/ITransform.cs
  27. 18 9
      src/Avalonia.Base/Media/ImageBrush.cs
  28. 1 1
      src/Avalonia.Base/Media/ImageDrawing.cs
  29. 7 3
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  30. 1 1
      src/Avalonia.Base/Media/Imaging/IBitmap.cs
  31. 1 1
      src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs
  32. 6 6
      src/Avalonia.Base/Media/ImmediateDrawingContext.cs
  33. 2 2
      src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
  34. 1 1
      src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs
  35. 13 5
      src/Avalonia.Base/Media/LinearGradientBrush.cs
  36. 64 48
      src/Avalonia.Base/Media/Pen.cs
  37. 3 3
      src/Avalonia.Base/Media/PlatformDrawingContext.cs
  38. 13 5
      src/Avalonia.Base/Media/RadialGradientBrush.cs
  39. 14 6
      src/Avalonia.Base/Media/SolidColorBrush.cs
  40. 11 12
      src/Avalonia.Base/Media/TileBrush.cs
  41. 19 1
      src/Avalonia.Base/Media/Transform.cs
  42. 52 12
      src/Avalonia.Base/Media/VisualBrush.cs
  43. 1 1
      src/Avalonia.Base/Metadata/PrivateApiAttribute.cs
  44. 1 1
      src/Avalonia.Base/Platform/IBitmapImpl.cs
  45. 3 3
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  46. 2 0
      src/Avalonia.Base/Platform/IDrawingContextWithAcrylicLikeSupport.cs
  47. 12 0
      src/Avalonia.Base/Reactive/Observable.cs
  48. 7 0
      src/Avalonia.Base/Rect.cs
  49. 61 0
      src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs
  50. 24 0
      src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs
  51. 37 0
      src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleImageBrush.cs
  52. 13 7
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  53. 4 10
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  54. 4 1
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  55. 11 0
      src/Avalonia.Base/Rendering/Composition/CompositionExperimentalAcrylicVisual.cs
  56. 19 9
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  57. 2 1
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  58. 1 1
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  59. 9 0
      src/Avalonia.Base/Rendering/Composition/CompositionTransform.cs
  60. 1 1
      src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
  61. 38 9
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  62. 0 129
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs
  63. 0 37
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawListSceneBrushContent.cs
  64. 0 371
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  65. 68 0
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs
  66. 47 0
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs
  67. 123 0
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositorResourceHelpers.cs
  68. 12 0
      src/Avalonia.Base/Rendering/Composition/Drawing/ICompositionRenderResource.cs
  69. 81 0
      src/Avalonia.Base/Rendering/Composition/Drawing/ImmediateRenderDataSceneBrushContent.cs
  70. 28 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs
  71. 61 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs
  72. 29 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs
  73. 35 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs
  74. 65 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs
  75. 214 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs
  76. 27 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs
  77. 25 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs
  78. 30 0
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs
  79. 349 0
      src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs
  80. 136 0
      src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs
  81. 12 0
      src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionSimplePen.cs
  82. 56 0
      src/Avalonia.Base/Rendering/Composition/Drawing/ServerResourceHelperExtensions.cs
  83. 10 0
      src/Avalonia.Base/Rendering/Composition/ICompositorSerializable.cs
  84. 1 1
      src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
  85. 6 3
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  86. 3 21
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  87. 26 0
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs
  88. 1 1
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs
  89. 1 1
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  90. 22 0
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.RenderResources.cs
  91. 21 2
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  92. 0 11
      src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs
  93. 4 28
      src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
  94. 123 0
      src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs
  95. 34 0
      src/Avalonia.Base/Rendering/Composition/Server/SimpleServerObject.cs
  96. 0 27
      src/Avalonia.Base/Rendering/SceneGraph/BrushDrawOperation.cs
  97. 0 87
      src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs
  98. 0 31
      src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs
  99. 0 56
      src/Avalonia.Base/Rendering/SceneGraph/DrawOperation.cs
  100. 0 97
      src/Avalonia.Base/Rendering/SceneGraph/EllipseNode.cs

+ 36 - 35
nukebuild/RefAssemblyGenerator.cs

@@ -63,50 +63,51 @@ public class RefAssemblyGenerator
         });
     }
 
+    static bool HasPrivateApi(IEnumerable<CustomAttribute> attrs) => attrs.Any(a =>
+        a.AttributeType.FullName == "Avalonia.Metadata.PrivateApiAttribute");
+    
     static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor)
     {
         foreach (var nested in type.NestedTypes)
             ProcessType(nested, obsoleteCtor);
-        if (type.IsInterface)
+
+        var hideMethods = (type.IsInterface && type.Name.EndsWith("Impl"))
+                          || HasPrivateApi(type.CustomAttributes);
+
+        var injectMethod = hideMethods
+                           || type.CustomAttributes.Any(a =>
+                               a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute");
+
+
+        
+        if (injectMethod)
         {
-            var hideMethods = type.Name.EndsWith("Impl")
-                              || (type.HasCustomAttributes && type.CustomAttributes.Any(a =>
-                                  a.AttributeType.FullName == "Avalonia.Metadata.PrivateApiAttribute"));
+            type.Methods.Add(new MethodDefinition(
+                "(This interface or abstract class is -not- implementable by user code !)",
+                MethodAttributes.Assembly
+                | MethodAttributes.Abstract
+                | MethodAttributes.NewSlot
+                | MethodAttributes.HideBySig, type.Module.TypeSystem.Void));
+        }
 
-            var injectMethod = hideMethods
-                               || type.CustomAttributes.Any(a =>
-                                   a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute");
-            
-            if (hideMethods)
-            {
-                foreach (var m in type.Methods)
-                {
-                    var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
-                                 MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
-                    m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
-                }
-            }
-            
-            if(injectMethod)
+        var forceUnstable = type.CustomAttributes.FirstOrDefault(a =>
+            a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute");
+
+        foreach (var m in type.Methods)
+        {
+            if (hideMethods || HasPrivateApi(m.CustomAttributes))
             {
-                type.Methods.Add(new MethodDefinition("NotClientImplementable",
-                    MethodAttributes.Assembly
-                    | MethodAttributes.Abstract
-                    | MethodAttributes.NewSlot
-                    | MethodAttributes.HideBySig, type.Module.TypeSystem.Void));
+                var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
+                             MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
+                m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
             }
-
-            var forceUnstable = type.CustomAttributes.FirstOrDefault(a =>
-                a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute");
-            
-            foreach (var m in type.Methods)
-                MarkAsUnstable(m, obsoleteCtor, forceUnstable);
-            foreach (var m in type.Properties)
-                MarkAsUnstable(m, obsoleteCtor, forceUnstable);
-            foreach (var m in type.Events)
-                MarkAsUnstable(m, obsoleteCtor, forceUnstable);
-            
+            MarkAsUnstable(m, obsoleteCtor, forceUnstable);
         }
+
+        foreach (var m in type.Properties)
+            MarkAsUnstable(m, obsoleteCtor, forceUnstable);
+        foreach (var m in type.Events)
+            MarkAsUnstable(m, obsoleteCtor, forceUnstable);
     }
 
     static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute unstableAttribute)

+ 1 - 1
samples/ControlCatalog/Pages/TabControlPage.xaml.cs

@@ -49,7 +49,7 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
-        private static IBitmap LoadBitmap(string uri)
+        private static Bitmap LoadBitmap(string uri)
         {
             var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
             return new Bitmap(assets.Open(new Uri(uri)));

+ 1 - 1
samples/ControlCatalog/ViewModels/TabControlPageViewModel.cs

@@ -21,6 +21,6 @@ public class TabControlPageViewModelItem
 {
     public string? Header { get; set; }
     public string? Text { get; set; }
-    public IBitmap? Image { get; set; }
+    public Bitmap? Image { get; set; }
     public bool IsEnabled { get; set; } = true;
 }

+ 3 - 3
samples/RenderDemo/Pages/CustomSkiaPage.cs

@@ -27,11 +27,11 @@ namespace RenderDemo.Pages
         
         class CustomDrawOp : ICustomDrawOperation
         {
-            private readonly GlyphRun _noSkia;
+            private readonly IImmutableGlyphRunReference _noSkia;
 
             public CustomDrawOp(Rect bounds, GlyphRun noSkia)
             {
-                _noSkia = noSkia;
+                _noSkia = noSkia.TryCreateImmutableGlyphRunReference();
                 Bounds = bounds;
             }
             
@@ -48,7 +48,7 @@ namespace RenderDemo.Pages
             {
                 var leaseFeature = context.TryGetFeature<ISkiaSharpApiLeaseFeature>();
                 if (leaseFeature == null)
-                    context.DrawGlyphRun(Brushes.Black, _noSkia.PlatformImpl);
+                    context.DrawGlyphRun(Brushes.Black, _noSkia);
                 else
                 {
                     using var lease = leaseFeature.Lease();

+ 4 - 4
samples/RenderDemo/Pages/PathMeasurementPage.cs

@@ -53,15 +53,15 @@ namespace RenderDemo.Pages
                 bitmapCtx.DrawGeometry(null, strokePen, basePath);
 
 
-                var length = basePath.PlatformImpl.ContourLength;
+                var length = basePath.ContourLength;
 
-                if (basePath.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1))
+                if (basePath.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1))
                     bitmapCtx.DrawGeometry(null, strokePen1, dst1);
 
-                if (basePath.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2))
+                if (basePath.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2))
                     bitmapCtx.DrawGeometry(null, strokePen2, dst2);
 
-                if (basePath.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3))
+                if (basePath.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3))
                     bitmapCtx.DrawGeometry(null, strokePen3, dst3);
                 
                 var pathBounds = basePath.GetRenderBounds(strokePen);

+ 1 - 1
src/Avalonia.Base/Collections/Pooled/PooledList.cs

@@ -1423,7 +1423,7 @@ namespace Avalonia.Collections.Pooled
 
         private static bool ShouldClear(ClearMode mode)
         {
-#if NETCOREAPP2_1
+#if NETCOREAPP2_1_OR_GREATER
             return mode == ClearMode.Always
                 || (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>());
 #else

+ 1 - 1
src/Avalonia.Base/Collections/Pooled/PooledStack.cs

@@ -596,7 +596,7 @@ namespace Avalonia.Collections.Pooled
 
         private static bool ShouldClear(ClearMode mode)
         {
-#if NETCOREAPP2_1
+#if NETCOREAPP2_1_OR_GREATER
             return mode == ClearMode.Always
                 || (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>());
 #else

+ 2 - 2
src/Avalonia.Base/Input/Cursor.cs

@@ -55,12 +55,12 @@ namespace Avalonia.Input
         {
         }
 
-        public Cursor(IBitmap cursor, PixelPoint hotSpot)
+        public Cursor(Bitmap cursor, PixelPoint hotSpot)
             : this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot), "BitmapCursor")
         {
         }
 
-        public ICursorImpl PlatformImpl { get; }
+        internal ICursorImpl PlatformImpl { get; }
 
         public void Dispose() => PlatformImpl.Dispose();
 

+ 53 - 27
src/Avalonia.Base/Media/Brush.cs

@@ -4,6 +4,10 @@ using Avalonia.Animation;
 using Avalonia.Animation.Animators;
 using Avalonia.Media.Immutable;
 using Avalonia.Reactive;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Drawing;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -11,7 +15,7 @@ namespace Avalonia.Media
     /// Describes how an area is painted.
     /// </summary>
     [TypeConverter(typeof(BrushConverter))]
-    public abstract class Brush : Animatable, IBrush
+    public abstract class Brush : Animatable, IBrush, ICompositionRenderResource<IBrush>, ICompositorSerializable
     {
         /// <summary>
         /// Defines the <see cref="Opacity"/> property.
@@ -30,14 +34,10 @@ namespace Avalonia.Media
         /// </summary>
         public static readonly StyledProperty<RelativePoint> TransformOriginProperty =
             AvaloniaProperty.Register<Brush, RelativePoint>(nameof(TransformOrigin));
-
-        /// <inheritdoc/>
-        public event EventHandler? Invalidated;
-
+        
         static Brush()
         {
             Animation.Animation.RegisterAnimator<BaseBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
-            AffectsRender<Brush>(OpacityProperty, TransformProperty);
         }
 
         /// <summary>
@@ -92,31 +92,57 @@ namespace Avalonia.Media
 
             throw new FormatException($"Invalid brush string: '{s}'.");
         }
+        
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            if (change.Property == TransformProperty) 
+                _resource.ProcessPropertyChangeNotification(change);
 
-        /// <summary>
-        /// Marks a property as affecting the brush's visual representation.
-        /// </summary>
-        /// <param name="properties">The properties.</param>
-        /// <remarks>
-        /// After a call to this method in a brush's static constructor, any change to the
-        /// property will cause the <see cref="Invalidated"/> event to be raised on the brush.
-        /// </remarks>
-        protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
-            where T : Brush
+            RegisterForSerialization();
+            
+            base.OnPropertyChanged(change);
+        }
+        
+        private protected void RegisterForSerialization() =>
+            _resource.RegisterForInvalidationOnAllCompositors(this);
+
+        private CompositorResourceHolder<ServerCompositionSimpleBrush> _resource;
+
+        IBrush ICompositionRenderResource<IBrush>.GetForCompositor(Compositor c) => _resource.GetForCompositor(c);
+
+        internal abstract Func<Compositor, ServerCompositionSimpleBrush> Factory { get; }
+
+        void ICompositionRenderResource.AddRefOnCompositor(Compositor c)
         {
-            var invalidateObserver = new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(
-                static e => (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty));
+            if (_resource.CreateOrAddRef(c, this, out _, Factory))
+                OnReferencedFromCompositor(c);
+        }
 
-            foreach (var property in properties)
-            {
-                property.Changed.Subscribe(invalidateObserver);
-            }
+        protected virtual void OnReferencedFromCompositor(Compositor c)
+        {
+            if (Transform is ICompositionRenderResource<ITransform> resource)
+                resource.AddRefOnCompositor(c);
         }
 
-        /// <summary>
-        /// Raises the <see cref="Invalidated"/> event.
-        /// </summary>
-        /// <param name="e">The event args.</param>
-        protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
+        void ICompositionRenderResource.ReleaseOnCompositor(Compositor c)
+        {
+            if(_resource.Release(c))
+                OnUnreferencedFromCompositor(c);
+        }
+        
+        protected virtual void OnUnreferencedFromCompositor(Compositor c)
+        {
+            if (Transform is ICompositionRenderResource<ITransform> resource)
+                resource.ReleaseOnCompositor(c);
+        }
+
+        SimpleServerObject? ICompositorSerializable.TryGetServer(Compositor c) => _resource.TryGetForCompositor(c);
+
+        private protected virtual void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            ServerCompositionSimpleBrush.SerializeAllChanges(writer, Opacity, TransformOrigin, Transform.GetServer(c));
+        }
+        
+        void ICompositorSerializable.SerializeChanges(Compositor c, BatchStreamWriter writer) => SerializeChanges(c, writer);
     }
 }

+ 13 - 5
src/Avalonia.Base/Media/ConicGradientBrush.cs

@@ -1,4 +1,8 @@
+using System;
 using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -23,11 +27,6 @@ namespace Avalonia.Media
                 nameof(Angle),
                 0);
         
-        static ConicGradientBrush()
-        {
-            AffectsRender<ConicGradientBrush>(CenterProperty, AngleProperty);
-        }
-
         /// <summary>
         /// Gets or sets the center point of the gradient.
         /// </summary>
@@ -51,5 +50,14 @@ namespace Avalonia.Media
         {
             return new ImmutableConicGradientBrush(this);
         }
+
+        internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
+            static c => new ServerCompositionSimpleConicGradientBrush(c.Server);
+
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            ServerCompositionSimpleConicGradientBrush.SerializeAllChanges(writer, Angle, Center);
+        }
     }
 }

+ 2 - 2
src/Avalonia.Base/Media/DashStyle.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Media
     /// <summary>
     /// Represents the sequence of dashes and gaps that will be applied by a <see cref="Pen"/>.
     /// </summary>
-    public class DashStyle : Animatable, IDashStyle, IAffectsRender
+    public class DashStyle : Animatable, IDashStyle
     {
         /// <summary>
         /// Defines the <see cref="Dashes"/> property.
@@ -104,7 +104,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Raised when the dash style changes.
         /// </summary>
-        public event EventHandler? Invalidated;
+        internal event EventHandler? Invalidated;
 
         /// <summary>
         /// Returns an immutable clone of the <see cref="DashStyle"/>.

+ 3 - 1
src/Avalonia.Base/Media/Drawing.cs

@@ -9,7 +9,9 @@
         /// Draws this drawing to the given <see cref="DrawingContext"/>.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        public abstract void Draw(DrawingContext context);
+        public void Draw(DrawingContext context) => DrawCore(context);
+
+        internal abstract void DrawCore(DrawingContext context);
 
         /// <summary>
         /// Gets the drawing's bounding rectangle.

+ 48 - 13
src/Avalonia.Base/Media/DrawingBrush.cs

@@ -1,26 +1,25 @@
+using System;
 using Avalonia.Media.Immutable;
 using Avalonia.Rendering;
 using Avalonia.Rendering.Composition;
 using Avalonia.Rendering.Composition.Drawing;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media
 {
     /// <summary>
     /// Paints an area with an <see cref="Drawing"/>.
     /// </summary>
-    public class DrawingBrush : TileBrush, ISceneBrush, IAffectsRender
+    public class DrawingBrush : TileBrush, ISceneBrush
     {
         /// <summary>
         /// Defines the <see cref="Drawing"/> property.
         /// </summary>
         public static readonly StyledProperty<Drawing?> DrawingProperty =
             AvaloniaProperty.Register<DrawingBrush, Drawing?>(nameof(Drawing));
-
-        static DrawingBrush()
-        {
-            AffectsRender<DrawingBrush>(DrawingProperty);
-        }
-
+        
         /// <summary>
         /// Initializes a new instance of the <see cref="DrawingBrush"/> class.
         /// </summary>
@@ -51,16 +50,52 @@ namespace Avalonia.Media
             if (Drawing == null)
                 return null;
             
+            using var recorder = new RenderDataDrawingContext(null);
+            Drawing?.Draw(recorder);
+            return recorder.GetImmediateSceneBrushContent(this, null, true);
+        }
+
+        internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
+            static c => new ServerCompositionSimpleContentBrush(c.Server);
+
+        private InlineDictionary<Compositor, CompositionRenderDataSceneBrushContent?> _renderDataDictionary;
+
+        protected override void OnReferencedFromCompositor(Compositor c)
+        {
+            _renderDataDictionary.Add(c, CreateServerContent(c));
+            base.OnReferencedFromCompositor(c);
+        }
+
+        protected override void OnUnreferencedFromCompositor(Compositor c)
+        {
+            if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content))
+                content?.RenderData.Dispose();
+            base.OnUnreferencedFromCompositor(c);
+        }
+        
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            if (_renderDataDictionary.TryGetValue(c, out var content))
+                writer.WriteObject(content);
+            else
+                writer.WriteObject(null);
+        }
+        
+        CompositionRenderDataSceneBrushContent? CreateServerContent(Compositor c)
+        {
+            if (Drawing == null)
+                return null;
             
-            var recorder = new CompositionDrawingContext();
-            recorder.BeginUpdate(null);
+            using var recorder = new RenderDataDrawingContext(c);
             Drawing?.Draw(recorder);
-            var drawList = recorder.EndUpdate();
-            if (drawList == null)
+            var renderData = recorder.GetRenderResults();
+            if (renderData == null)
                 return null;
             
-            return new CompositionDrawListSceneBrushContent(new ImmutableSceneBrush(this), drawList,
-                drawList.CalculateBounds(), true);
+            return new CompositionRenderDataSceneBrushContent(
+                (ServerCompositionSimpleContentBrush)((ICompositionRenderResource<IBrush>)this).GetForCompositor(c),
+                renderData, null, true);
         }
     }
 }

+ 1 - 1
src/Avalonia.Base/Media/DrawingGroup.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Media
 
         public DrawingContext Open() => new DrawingGroupDrawingContext(this);
 
-        public override void Draw(DrawingContext context)
+        internal override void DrawCore(DrawingContext context)
         {
             var bounds = GetBounds();
 

+ 3 - 1
src/Avalonia.Base/Media/Effects/IEffect.cs

@@ -2,16 +2,18 @@
 
 using System;
 using System.ComponentModel;
+using Avalonia.Metadata;
 
 namespace Avalonia.Media;
 
 [TypeConverter(typeof(EffectConverter))]
+[NotClientImplementable]
 public interface IEffect
 {
     
 }
 
-public interface IMutableEffect : IEffect, IAffectsRender
+public interface IMutableEffect : IEffect
 {
     /// <summary>
     /// Creates an immutable clone of the effect.

+ 69 - 1
src/Avalonia.Base/Media/Geometry.cs

@@ -1,6 +1,7 @@
 using System;
 using Avalonia.Platform;
 using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using Avalonia.Reactive;
 
@@ -40,7 +41,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets the platform-specific implementation of the geometry.
         /// </summary>
-        public IGeometryImpl? PlatformImpl
+        internal IGeometryImpl? PlatformImpl
         {
             get
             {
@@ -192,6 +193,12 @@ namespace Avalonia.Media
             control?.InvalidateGeometry();
         }
 
+        /// <summary>
+        /// Gets the geometry's total length as if all its contours are placed
+        /// in a straight line.
+        /// </summary>
+        public double ContourLength => PlatformImpl?.ContourLength ?? 0;
+
         /// <summary>
         /// Combines the two geometries using the specified <see cref="GeometryCombineMode"/> and applies the specified transform to the resulting geometry.
         /// </summary>
@@ -204,6 +211,67 @@ namespace Avalonia.Media
         {
             return new CombinedGeometry(combineMode, geometry1, geometry2, transform);
         }
+
+        /// <summary>
+        /// Attempts to get the corresponding point at the
+        /// specified distance
+        /// </summary>
+        /// <param name="distance">The contour distance to get from.</param>
+        /// <param name="point">The point in the specified distance.</param>
+        /// <returns>If there's valid point at the specified distance.</returns>
+        public bool TryGetPointAtDistance(double distance, out Point point)
+        {
+            if (PlatformImpl == null)
+            {
+                point = default;
+                return false;
+            }
+
+            return PlatformImpl.TryGetPointAtDistance(distance, out point);
+        }
+
+        /// <summary>
+        /// Attempts to get the corresponding point and
+        /// tangent from the specified distance along the
+        /// contour of the geometry.
+        /// </summary>
+        /// <param name="distance">The contour distance to get from.</param>
+        /// <param name="point">The point in the specified distance.</param>
+        /// <param name="tangent">The tangent in the specified distance.</param>
+        /// <returns>If there's valid point and tangent at the specified distance.</returns>
+        public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+        {
+            if (PlatformImpl == null)
+            {
+                point = tangent = default;
+                return false;
+            }
+
+            return PlatformImpl.TryGetPointAndTangentAtDistance(distance, out point, out tangent);
+        }
+
+        /// <summary>
+        /// Attempts to get the corresponding path segment
+        /// given by the two distances specified.
+        /// Imagine it like snipping a part of the current
+        /// geometry.
+        /// </summary>
+        /// <param name="startDistance">The contour distance to start snipping from.</param>
+        /// <param name="stopDistance">The contour distance to stop snipping to.</param>
+        /// <param name="startOnBeginFigure">If ture, the resulting snipped path will start with a BeginFigure call.</param>
+        /// <param name="segmentGeometry">The resulting snipped path.</param>
+        /// <returns>If the snipping operation is successful.</returns>
+        public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
+            [NotNullWhen(true)] out Geometry? segmentGeometry)
+        {
+            segmentGeometry = null;
+            if (PlatformImpl == null)
+                return false;
+            if (!PlatformImpl.TryGetSegment(startDistance, stopDistance, startOnBeginFigure, out var segment))
+                return false;
+            segmentGeometry = new PlatformGeometry(segment);
+            return true;
+        }
     }
 
     public class GeometryTypeConverter : TypeConverter

+ 1 - 1
src/Avalonia.Base/Media/GeometryDrawing.cs

@@ -58,7 +58,7 @@ namespace Avalonia.Media
             set => SetValue(PenProperty, value);
         }
 
-        public override void Draw(DrawingContext context)
+        internal override void DrawCore(DrawingContext context)
         {
             if (Geometry != null)
             {

+ 4 - 1
src/Avalonia.Base/Media/GlyphRun.cs

@@ -214,7 +214,7 @@ namespace Avalonia.Media
         /// <summary>
         /// The platform implementation of the <see cref="GlyphRun"/>.
         /// </summary>
-        public IRef<IGlyphRunImpl> PlatformImpl
+        internal IRef<IGlyphRunImpl> PlatformImpl
             => _platformImpl ??= CreateGlyphRunImpl();
 
         /// <summary>
@@ -851,5 +851,8 @@ namespace Avalonia.Media
         {
             return PlatformImpl.Item.GetIntersections(lowerLimit, upperLimit);
         }
+
+        public IImmutableGlyphRunReference? TryCreateImmutableGlyphRunReference()
+            => new ImmutableGlyphRunReference(PlatformImpl.Clone());
     }
 }

+ 1 - 1
src/Avalonia.Base/Media/GlyphRunDrawing.cs

@@ -20,7 +20,7 @@
             set => SetValue(GlyphRunProperty, value);
         }
 
-        public override void Draw(DrawingContext context)
+        internal override void DrawCore(DrawingContext context)
         {
             if (GlyphRun == null)
             {

+ 24 - 18
src/Avalonia.Base/Media/GradientBrush.cs

@@ -5,8 +5,11 @@ using System.ComponentModel;
 
 using Avalonia.Animation.Animators;
 using Avalonia.Collections;
+using Avalonia.Media.Immutable;
 using Avalonia.Metadata;
 using Avalonia.Reactive;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -29,12 +32,6 @@ namespace Avalonia.Media
 
         private IDisposable? _gradientStopsSubscription;
 
-        static GradientBrush()
-        {
-            GradientStopsProperty.Changed.Subscribe(GradientStopsChanged);
-            AffectsRender<LinearGradientBrush>(SpreadMethodProperty);
-        }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="GradientBrush"/> class.
         /// </summary>
@@ -63,37 +60,46 @@ namespace Avalonia.Media
         /// <inheritdoc/>
         IReadOnlyList<IGradientStop> IGradientBrush.GradientStops => GradientStops;
 
-        private static void GradientStopsChanged(AvaloniaPropertyChangedEventArgs e)
+
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
-            if (e.Sender is GradientBrush brush)
+            if (change.Property == GradientStopsProperty)
             {
-                var oldValue = (GradientStops?)e.OldValue;
-                var newValue = (GradientStops?)e.NewValue;
+                var (oldValue, newValue) = change.GetOldAndNewValue<GradientStops?>();
 
                 if (oldValue != null)
                 {
-                    oldValue.CollectionChanged -= brush.GradientStopsChanged;
-                    brush._gradientStopsSubscription?.Dispose();
+                    oldValue.CollectionChanged -= GradientStopsChanged;
+                    _gradientStopsSubscription?.Dispose();
                 }
 
                 if (newValue != null)
                 {
-                    newValue.CollectionChanged += brush.GradientStopsChanged;
-                    brush._gradientStopsSubscription = newValue.TrackItemPropertyChanged(brush.GradientStopChanged);
+                    newValue.CollectionChanged += GradientStopsChanged;
+                    _gradientStopsSubscription = newValue.TrackItemPropertyChanged(GradientStopChanged);
                 }
-
-                brush.RaiseInvalidated(EventArgs.Empty);
             }
+            base.OnPropertyChanged(change);
         }
 
         private void GradientStopsChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
-            RaiseInvalidated(EventArgs.Empty);
+            RegisterForSerialization();
         }
 
         private void GradientStopChanged(Tuple<object?, PropertyChangedEventArgs> e)
         {
-            RaiseInvalidated(EventArgs.Empty);
+            RegisterForSerialization();
+        }
+
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            writer.Write(SpreadMethod);
+            writer.Write(GradientStops.Count);
+            foreach (var stop in GradientStops) 
+                // TODO: Technically it allocates, so it would be better to sync stops individually
+                writer.WriteObject(new ImmutableGradientStop(stop.Offset, stop.Color));
         }
 
         public abstract IImmutableBrush ToImmutable();

+ 1 - 1
src/Avalonia.Base/Media/IAffectsRender.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Media
     /// Signals to a self-rendering control that changes to the resource should invoke
     /// <see cref="Visual.InvalidateVisual"/>.
     /// </summary>
-    public interface IAffectsRender
+    internal interface IAffectsRender
     {
         /// <summary>
         /// Raised when the resource changes visually.

+ 9 - 1
src/Avalonia.Base/Media/IImageBrush.cs

@@ -1,5 +1,7 @@
 using Avalonia.Media.Imaging;
 using Avalonia.Metadata;
+using Avalonia.Platform;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media
 {
@@ -12,6 +14,12 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets the image to draw.
         /// </summary>
-        IBitmap? Source { get; }
+        IImageBrushSource? Source { get; }
+    }
+
+    [NotClientImplementable]
+    public interface IImageBrushSource
+    {
+        internal IRef<IBitmapImpl>? Bitmap { get; }
     }
 }

+ 25 - 0
src/Avalonia.Base/Media/IImmutableGlyphRunReference.cs

@@ -0,0 +1,25 @@
+using System;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Media;
+
+public interface IImmutableGlyphRunReference : IDisposable
+{
+    internal IRef<IGlyphRunImpl>? GlyphRun { get; }
+}
+
+internal class ImmutableGlyphRunReference : IImmutableGlyphRunReference
+{
+    public ImmutableGlyphRunReference(IRef<IGlyphRunImpl>? glyphRun)
+    {
+        GlyphRun = glyphRun;
+    }
+
+    public IRef<IGlyphRunImpl>? GlyphRun { get; private set; }
+    public void Dispose()
+    {
+        GlyphRun?.Dispose();
+        GlyphRun = null;
+    }
+}

+ 1 - 1
src/Avalonia.Base/Media/IMutableBrush.cs

@@ -7,7 +7,7 @@ namespace Avalonia.Media
     /// Represents a mutable brush which can return an immutable clone of itself.
     /// </summary>
     [NotClientImplementable]
-    internal interface IMutableBrush : IBrush, IAffectsRender
+    internal interface IMutableBrush : IBrush
     {
         /// <summary>
         /// Creates an immutable clone of the brush.

+ 1 - 1
src/Avalonia.Base/Media/IMutableExperimentalAcrylicMaterial.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Media
     /// Represents a mutable brush which can return an immutable clone of itself.
     /// </summary>
     [NotClientImplementable]
-    public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender
+    public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial
     {
         /// <summary>
         /// Creates an immutable clone of the brush.

+ 2 - 0
src/Avalonia.Base/Media/ITransform.cs

@@ -1,8 +1,10 @@
 using System.ComponentModel;
+using Avalonia.Metadata;
 
 namespace Avalonia.Media
 {
     [TypeConverter(typeof(TransformConverter))]
+    [NotClientImplementable]
     public interface ITransform
     {
         Matrix Value { get; }

+ 18 - 9
src/Avalonia.Base/Media/ImageBrush.cs

@@ -1,5 +1,9 @@
+using System;
 using Avalonia.Media.Imaging;
 using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -11,13 +15,8 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="Visual"/> property.
         /// </summary>
-        public static readonly StyledProperty<IBitmap?> SourceProperty =
-            AvaloniaProperty.Register<ImageBrush, IBitmap?>(nameof(Source));
-
-        static ImageBrush()
-        {
-            AffectsRender<ImageBrush>(SourceProperty);
-        }
+        public static readonly StyledProperty<IImageBrushSource?> SourceProperty =
+            AvaloniaProperty.Register<ImageBrush, IImageBrushSource?>(nameof(Source));
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ImageBrush"/> class.
@@ -30,7 +29,7 @@ namespace Avalonia.Media
         /// Initializes a new instance of the <see cref="ImageBrush"/> class.
         /// </summary>
         /// <param name="source">The image to draw.</param>
-        public ImageBrush(IBitmap? source)
+        public ImageBrush(IImageBrushSource? source)
         {
             Source = source;
         }
@@ -38,7 +37,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets or sets the image to draw.
         /// </summary>
-        public IBitmap? Source
+        public IImageBrushSource? Source
         {
             get { return GetValue(SourceProperty); }
             set { SetValue(SourceProperty, value); }
@@ -49,5 +48,15 @@ namespace Avalonia.Media
         {
             return new ImmutableImageBrush(this);
         }
+
+        internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
+            static c => new ServerCompositionSimpleImageBrush(c.Server);
+
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            var clonedRef = Source?.Bitmap?.Clone();
+            writer.WriteObject(clonedRef);
+        }
     }
 }

+ 1 - 1
src/Avalonia.Base/Media/ImageDrawing.cs

@@ -37,7 +37,7 @@ namespace Avalonia.Media
             set => SetValue(RectProperty, value);
         }
 
-        public override void Draw(DrawingContext context)
+        internal override void DrawCore(DrawingContext context)
         {
             var imageSource = ImageSource;
             var rect = Rect;

+ 7 - 3
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Media.Imaging
     /// <summary>
     /// Holds a bitmap image.
     /// </summary>
-    public class Bitmap : IBitmap
+    public class Bitmap : IBitmap, IImageBrushSource
     {
         private bool _isTranscoded;
         /// <summary>
@@ -72,7 +72,7 @@ namespace Avalonia.Media.Imaging
         /// Initializes a new instance of the <see cref="Bitmap"/> class.
         /// </summary>
         /// <param name="impl">A platform-specific bitmap implementation.</param>
-        public Bitmap(IRef<IBitmapImpl> impl)
+        internal Bitmap(IRef<IBitmapImpl> impl)
         {
             PlatformImpl = impl.Clone();
         }
@@ -139,7 +139,9 @@ namespace Avalonia.Media.Imaging
         /// <summary>
         /// Gets the platform-specific bitmap implementation.
         /// </summary>
-        public IRef<IBitmapImpl> PlatformImpl { get; }
+        internal IRef<IBitmapImpl> PlatformImpl { get; }
+
+        IRef<IBitmapImpl> IBitmap.PlatformImpl => PlatformImpl;
 
         /// <summary>
         /// Saves the bitmap to a file.
@@ -237,5 +239,7 @@ namespace Avalonia.Media.Imaging
         {
             return AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
         }
+
+        IRef<IBitmapImpl> IImageBrushSource.Bitmap => PlatformImpl;
     }
 }

+ 1 - 1
src/Avalonia.Base/Media/Imaging/IBitmap.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Media.Imaging
     /// Represents a bitmap image.
     /// </summary>
     [NotClientImplementable]
-    public interface IBitmap : IImage, IDisposable
+    internal interface IBitmap : IImage, IDisposable
     {
         /// <summary>
         /// Gets the dots per inch (DPI) of the image.

+ 1 - 1
src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs

@@ -38,7 +38,7 @@ namespace Avalonia.Media.Imaging
         /// <summary>
         /// Gets the platform-specific bitmap implementation.
         /// </summary>
-        public new IRef<IRenderTargetBitmapImpl> PlatformImpl { get; }
+        internal new IRef<IRenderTargetBitmapImpl> PlatformImpl { get; }
 
         /// <summary>
         /// Renders a visual to the <see cref="RenderTargetBitmap"/>.

+ 6 - 6
src/Avalonia.Base/Media/ImmediateDrawingContext.cs

@@ -67,7 +67,7 @@ namespace Avalonia.Media
         /// </summary>
         /// <param name="source">The bitmap.</param>
         /// <param name="rect">The rect in the output to draw to.</param>
-        public void DrawBitmap(IBitmap source, Rect rect)
+        public void DrawBitmap(Bitmap source, Rect rect)
         {
             _ = source ?? throw new ArgumentNullException(nameof(source));
             DrawBitmap(source, new Rect(source.Size), rect);
@@ -79,10 +79,10 @@ namespace Avalonia.Media
         /// <param name="source">The bitmap.</param>
         /// <param name="sourceRect">The rect in the image to draw.</param>
         /// <param name="destRect">The rect in the output to draw to.</param>
-        public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect)
+        public void DrawBitmap(Bitmap source, Rect sourceRect, Rect destRect)
         {
             _ = source ?? throw new ArgumentNullException(nameof(source));
-            PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect);
+            PlatformImpl.DrawBitmap(source.PlatformImpl.Item, 1, sourceRect, destRect);
         }
 
         /// <summary>
@@ -180,11 +180,11 @@ namespace Avalonia.Media
         /// </summary>
         /// <param name="foreground">The foreground brush.</param>
         /// <param name="glyphRun">The glyph run.</param>
-        public void DrawGlyphRun(IImmutableBrush foreground, IRef<IGlyphRunImpl> glyphRun)
+        public void DrawGlyphRun(IImmutableBrush foreground, IImmutableGlyphRunReference glyphRun)
         {
             _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));
-
-            PlatformImpl.DrawGlyphRun(foreground, glyphRun);
+            if (glyphRun.GlyphRun != null)
+                PlatformImpl.DrawGlyphRun(foreground, glyphRun.GlyphRun.Item);
         }
 
         /// <summary>

+ 2 - 2
src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Media.Immutable
         /// </param>
         /// <param name="tileMode">The tile mode.</param>
         public ImmutableImageBrush(
-            IBitmap? source,
+            Bitmap? source,
             AlignmentX alignmentX = AlignmentX.Center,
             AlignmentY alignmentY = AlignmentY.Center,
             RelativeRect? destinationRect = null,
@@ -58,6 +58,6 @@ namespace Avalonia.Media.Immutable
         }
 
         /// <inheritdoc/>
-        public IBitmap? Source { get; }
+        public IImageBrushSource? Source { get; }
     }
 }

+ 1 - 1
src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable
         /// How the source rectangle will be stretched to fill the destination rect.
         /// </param>
         /// <param name="tileMode">The tile mode.</param>
-        protected ImmutableTileBrush(
+        protected internal ImmutableTileBrush(
             AlignmentX alignmentX,
             AlignmentY alignmentY,
             RelativeRect destinationRect,

+ 13 - 5
src/Avalonia.Base/Media/LinearGradientBrush.cs

@@ -1,4 +1,8 @@
+using System;
 using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -23,11 +27,6 @@ namespace Avalonia.Media
                 nameof(EndPoint), 
                 RelativePoint.BottomRight);
 
-        static LinearGradientBrush()
-        {
-            AffectsRender<LinearGradientBrush>(StartPointProperty, EndPointProperty);
-        }
-
         /// <summary>
         /// Gets or sets the start point for the gradient.
         /// </summary>
@@ -51,5 +50,14 @@ namespace Avalonia.Media
         {
             return new ImmutableLinearGradientBrush(this);
         }
+        
+        internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
+            static c => new ServerCompositionSimpleLinearGradientBrush(c.Server);
+
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            ServerCompositionSimpleLinearGradientBrush.SerializeAllChanges(writer, StartPoint, EndPoint);
+        }
     }
 }

+ 64 - 48
src/Avalonia.Base/Media/Pen.cs

@@ -1,5 +1,9 @@
 using System;
 using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Drawing;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 using Avalonia.Utilities;
 
 namespace Avalonia.Media
@@ -7,7 +11,7 @@ namespace Avalonia.Media
     /// <summary>
     /// Describes how a stroke is drawn.
     /// </summary>
-    public sealed class Pen : AvaloniaObject, IPen
+    public sealed class Pen : AvaloniaObject, IPen, ICompositionRenderResource<IPen>, ICompositorSerializable
     {
         /// <summary>
         /// Defines the <see cref="Brush"/> property.
@@ -45,9 +49,7 @@ namespace Avalonia.Media
         public static readonly StyledProperty<double> MiterLimitProperty =
             AvaloniaProperty.Register<Pen, double>(nameof(MiterLimit), 10.0);
 
-        private EventHandler? _invalidated;
-        private IAffectsRender? _subscribedToBrush;
-        private IAffectsRender? _subscribedToDashes;
+        private DashStyle? _subscribedToDashes;
         private TargetWeakEventSubscriber<Pen, EventArgs>? _weakSubscriber;
 
         /// <summary>
@@ -110,8 +112,8 @@ namespace Avalonia.Media
             set => SetValue(BrushProperty, value);
         }
 
-        private static readonly WeakEvent<IAffectsRender, EventArgs> InvalidatedWeakEvent =
-            WeakEvent.Register<IAffectsRender>(
+        private static readonly WeakEvent<DashStyle, EventArgs> InvalidatedWeakEvent =
+            WeakEvent.Register<DashStyle>(
                 (s, h) => s.Invalidated += h,
                 (s, h) => s.Invalidated -= h);
 
@@ -161,23 +163,6 @@ namespace Avalonia.Media
             set => SetValue(MiterLimitProperty, value);
         }
 
-        /// <summary>
-        /// Raised when the pen changes.
-        /// </summary>
-        public event EventHandler? Invalidated
-        {
-            add
-            {
-                _invalidated += value;
-                UpdateSubscriptions();
-            }
-            remove
-            {
-                _invalidated -= value; 
-                UpdateSubscriptions();
-            }
-        }
-
         /// <summary>
         /// Creates an immutable clone of the brush.
         /// </summary>
@@ -192,48 +177,79 @@ namespace Avalonia.Media
                 LineJoin,
                 MiterLimit);
         }
+        
+        void RegisterForSerialization()
+        {
+            _resource.RegisterForInvalidationOnAllCompositors(this);
+        }
 
         protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
-            _invalidated?.Invoke(this, EventArgs.Empty);
-            if(change.Property == BrushProperty)
-                UpdateSubscription(ref _subscribedToBrush, Brush);
+            RegisterForSerialization();
+            
+            if (change.Property == BrushProperty) 
+                _resource.ProcessPropertyChangeNotification(change);
+            
             if(change.Property == DashStyleProperty)
-                UpdateSubscription(ref _subscribedToDashes, DashStyle);
+                UpdateDashStyleSubscription();
             base.OnPropertyChanged(change);
         }
 
         
-        void UpdateSubscription(ref IAffectsRender? field, object? value)
+        void UpdateDashStyleSubscription()
+        {
+            var newValue = _resource.IsAttached ? DashStyle as DashStyle : null;
+            
+            if(ReferenceEquals(_subscribedToDashes, newValue))
+                return;
+
+            if (_subscribedToDashes != null && _weakSubscriber != null)
+            {
+                InvalidatedWeakEvent.Unsubscribe(_subscribedToDashes, _weakSubscriber);
+                _subscribedToDashes = null;
+            }
+
+            if (newValue != null)
+            {
+                _weakSubscriber ??= new TargetWeakEventSubscriber<Pen, EventArgs>(
+                    this, static (target, _, ev, _) =>
+                    {
+                        if (ev == InvalidatedWeakEvent)
+                            target.RegisterForSerialization();
+                    });
+                InvalidatedWeakEvent.Subscribe(newValue, _weakSubscriber);
+                _subscribedToDashes = newValue;
+            }
+        }
+        
+        private CompositorResourceHolder<ServerCompositionSimplePen> _resource;
+        
+        IPen ICompositionRenderResource<IPen>.GetForCompositor(Compositor c) => _resource.GetForCompositor(c);
+
+        void ICompositionRenderResource.AddRefOnCompositor(Compositor c)
         {
-            if ((_invalidated == null || field != value) && field != null)
+            if (_resource.CreateOrAddRef(c, this, out _, static c => new ServerCompositionSimplePen(c.Server)))
             {
-                if (_weakSubscriber != null)
-                    InvalidatedWeakEvent.Unsubscribe(field, _weakSubscriber);
-                field = null;
+                (Brush as ICompositionRenderResource)?.AddRefOnCompositor(c);
+                UpdateDashStyleSubscription();
             }
+        }
 
-            if (_invalidated != null && field != value && value is IAffectsRender affectsRender)
+        void ICompositionRenderResource.ReleaseOnCompositor(Compositor c)
+        {
+            if (_resource.Release(c))
             {
-                if (_weakSubscriber == null)
-                {
-                    _weakSubscriber = new TargetWeakEventSubscriber<Pen, EventArgs>(
-                        this, static (target, _, ev, _) =>
-                        {
-                            if (ev == InvalidatedWeakEvent)
-                                target._invalidated?.Invoke(target, EventArgs.Empty);
-                        });
-                }
-
-                InvalidatedWeakEvent.Subscribe(affectsRender, _weakSubscriber);
-                field = affectsRender;
+                (Brush as ICompositionRenderResource)?.ReleaseOnCompositor(c);
+                UpdateDashStyleSubscription();
             }
         }
 
-        void UpdateSubscriptions()
+        SimpleServerObject? ICompositorSerializable.TryGetServer(Compositor c) => _resource.TryGetForCompositor(c);
+
+        void ICompositorSerializable.SerializeChanges(Compositor c, BatchStreamWriter writer)
         {
-            UpdateSubscription(ref _subscribedToBrush, Brush);
-            UpdateSubscription(ref _subscribedToDashes, DashStyle);
+            ServerCompositionSimplePen.SerializeAllChanges(writer,
+                Brush.GetServer(c), DashStyle?.ToImmutable(), LineCap, LineJoin, MiterLimit, Thickness);
         }
     }
 }

+ 3 - 3
src/Avalonia.Base/Media/PlatformDrawingContext.cs

@@ -10,7 +10,7 @@ using Avalonia.Utilities;
 
 namespace Avalonia.Media;
 
-internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWithAcrylicLikeSupport
+internal sealed class PlatformDrawingContext : DrawingContext
 {
     private readonly IDrawingContextImpl _impl;
     private readonly bool _ownsImpl;
@@ -45,7 +45,7 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
     protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) => _impl.DrawEllipse(brush, pen, rect);
 
     internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect) =>
-        _impl.DrawBitmap(source, opacity, sourceRect, destRect);
+        _impl.DrawBitmap(source.Item, opacity, sourceRect, destRect);
 
     public override void Custom(ICustomDrawOperation custom)
     {
@@ -66,7 +66,7 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
         _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));
 
         if (foreground != null) 
-            _impl.DrawGlyphRun(foreground, glyphRun.PlatformImpl);
+            _impl.DrawGlyphRun(foreground, glyphRun.PlatformImpl.Item);
     }
 
     protected override void PushClipCore(RoundedRect rect) => _impl.PushClip(rect);

+ 13 - 5
src/Avalonia.Base/Media/RadialGradientBrush.cs

@@ -1,4 +1,8 @@
+using System;
 using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -31,11 +35,6 @@ namespace Avalonia.Media
                 nameof(Radius),
                 0.5);
         
-        static RadialGradientBrush()
-        {
-            AffectsRender<RadialGradientBrush>(CenterProperty, GradientOriginProperty, RadiusProperty);
-        }
-
         /// <summary>
         /// Gets or sets the start point for the gradient.
         /// </summary>
@@ -71,5 +70,14 @@ namespace Avalonia.Media
         {
             return new ImmutableRadialGradientBrush(this);
         }
+
+        internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
+            static c => new ServerCompositionSimpleRadialGradientBrush(c.Server);
+
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            ServerCompositionSimpleRadialGradientBrush.SerializeAllChanges(writer, Center, GradientOrigin, Radius);
+        }
     }
 }

+ 14 - 6
src/Avalonia.Base/Media/SolidColorBrush.cs

@@ -1,5 +1,9 @@
+using System;
 using Avalonia.Animation.Animators;
 using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -13,12 +17,7 @@ namespace Avalonia.Media
         /// </summary>
         public static readonly StyledProperty<Color> ColorProperty =
             AvaloniaProperty.Register<SolidColorBrush, Color>(nameof(Color));
-
-        static SolidColorBrush()
-        {
-            AffectsRender<SolidColorBrush>(ColorProperty);
-        }
-
+        
         /// <summary>
         /// Initializes a new instance of the <see cref="SolidColorBrush"/> class.
         /// </summary>
@@ -84,5 +83,14 @@ namespace Avalonia.Media
         {
             return new ImmutableSolidColorBrush(this);
         }
+        
+        internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
+            static c => new ServerCompositionSimpleSolidColorBrush(c.Server);
+
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            ServerCompositionSimpleSolidColorBrush.SerializeAllChanges(writer, Color);
+        }
     }
 }

+ 11 - 12
src/Avalonia.Base/Media/TileBrush.cs

@@ -1,4 +1,7 @@
 using Avalonia.Media.Imaging;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 
 namespace Avalonia.Media
 {
@@ -73,18 +76,7 @@ namespace Avalonia.Media
         /// </summary>
         public static readonly StyledProperty<TileMode> TileModeProperty =
             AvaloniaProperty.Register<TileBrush, TileMode>(nameof(TileMode));
-
-        static TileBrush()
-        {
-            AffectsRender<TileBrush>(
-                AlignmentXProperty,
-                AlignmentYProperty,
-                DestinationRectProperty,
-                SourceRectProperty,
-                StretchProperty,
-                TileModeProperty);
-        }
-
+        
         /// <summary>
         /// Gets or sets the horizontal alignment of a tile in the destination.
         /// </summary>
@@ -139,5 +131,12 @@ namespace Avalonia.Media
             get { return (TileMode)GetValue(TileModeProperty); }
             set { SetValue(TileModeProperty, value); }
         }
+
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            ServerCompositionSimpleTileBrush.SerializeAllChanges(writer, AlignmentX, AlignmentY, DestinationRect, SourceRect,
+                Stretch, TileMode);
+        }
     }
 }

+ 19 - 1
src/Avalonia.Base/Media/Transform.cs

@@ -2,6 +2,10 @@ using System;
 using Avalonia.Animation;
 using Avalonia.Animation.Animators;
 using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Drawing;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Media
@@ -9,7 +13,7 @@ namespace Avalonia.Media
     /// <summary>
     /// Represents a transform on an <see cref="Visual"/>.
     /// </summary>
-    public abstract class Transform : Animatable, IMutableTransform
+    public abstract class Transform : Animatable, IMutableTransform, ICompositionRenderResource<ITransform>, ICompositorSerializable
     {
         static Transform()
         {
@@ -62,5 +66,19 @@ namespace Avalonia.Media
         {
             return Value.ToString();
         }
+
+        private CompositorResourceHolder<ServerCompositionSimpleTransform> _resource;
+        ITransform ICompositionRenderResource<ITransform>.GetForCompositor(Compositor c) => _resource.GetForCompositor(c);
+        SimpleServerObject? ICompositorSerializable.TryGetServer(Compositor c) => _resource.TryGetForCompositor(c);
+        
+        void ICompositionRenderResource.AddRefOnCompositor(Compositor c)
+        {
+            _resource.CreateOrAddRef(c, this, out _, static (cc) => new ServerCompositionSimpleTransform(cc.Server));
+        }
+
+        void ICompositionRenderResource.ReleaseOnCompositor(Compositor c) => _resource.Release(c);
+        
+        void ICompositorSerializable.SerializeChanges(Compositor c, BatchStreamWriter writer) =>
+            ServerCompositionSimpleTransform.SerializeAllChanges(writer, Value);
     }
 }

+ 52 - 12
src/Avalonia.Base/Media/VisualBrush.cs

@@ -1,25 +1,25 @@
+using System;
 using Avalonia.Media.Immutable;
 using Avalonia.Rendering;
 using Avalonia.Rendering.Composition;
 using Avalonia.Rendering.Composition.Drawing;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media
 {
     /// <summary>
     /// Paints an area with an <see cref="Visual"/>.
     /// </summary>
-    public class VisualBrush : TileBrush, ISceneBrush, IAffectsRender
+    public class VisualBrush : TileBrush, ISceneBrush
     {
         /// <summary>
         /// Defines the <see cref="Visual"/> property.
         /// </summary>
         public static readonly StyledProperty<Visual?> VisualProperty =
             AvaloniaProperty.Register<VisualBrush, Visual?>(nameof(Visual));
-
-        static VisualBrush()
-        {
-            AffectsRender<VisualBrush>(VisualProperty);
-        }
+        
 
         /// <summary>
         /// Initializes a new instance of the <see cref="VisualBrush"/> class.
@@ -54,15 +54,55 @@ namespace Avalonia.Media
             if (Visual is IVisualBrushInitialize initialize)
                 initialize.EnsureInitialized();
             
-            var recorder = new CompositionDrawingContext();
-            recorder.BeginUpdate(null);
+            using var recorder = new RenderDataDrawingContext(null);
             ImmediateRenderer.Render(recorder, Visual, Visual.Bounds);
-            var drawList = recorder.EndUpdate();
-            if (drawList == null)
+            return recorder.GetImmediateSceneBrushContent(this, new(Visual.Bounds.Size), false);
+        }
+        
+        internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
+            static c => new ServerCompositionSimpleContentBrush(c.Server);
+
+        private InlineDictionary<Compositor, CompositionRenderDataSceneBrushContent?> _renderDataDictionary;
+
+        protected override void OnReferencedFromCompositor(Compositor c)
+        {
+            _renderDataDictionary.Add(c, CreateServerContent(c));
+            base.OnReferencedFromCompositor(c);
+        }
+
+        protected override void OnUnreferencedFromCompositor(Compositor c)
+        {
+            if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content))
+                content?.RenderData.Dispose();
+            base.OnUnreferencedFromCompositor(c);
+        }
+        
+        private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
+        {
+            base.SerializeChanges(c, writer);
+            if (_renderDataDictionary.TryGetValue(c, out var content))
+                writer.WriteObject(content);
+            else
+                writer.WriteObject(null);
+        }
+        
+        CompositionRenderDataSceneBrushContent? CreateServerContent(Compositor c)
+        {
+            if (Visual == null)
                 return null;
 
-            return new CompositionDrawListSceneBrushContent(new ImmutableSceneBrush(this), drawList,
-                new(Visual.Bounds.Size), false);
+            if (Visual is IVisualBrushInitialize initialize)
+                initialize.EnsureInitialized();
+
+            using var recorder = new RenderDataDrawingContext(c);
+            ImmediateRenderer.Render(recorder, Visual, Visual.Bounds);
+            var renderData = recorder.GetRenderResults();
+            if (renderData == null)
+                return null;
+            
+            return new CompositionRenderDataSceneBrushContent(
+                (ServerCompositionSimpleContentBrush)((ICompositionRenderResource<IBrush>)this).GetForCompositor(c),
+                renderData, new(Visual.Bounds.Size), false);
         }
     }
 }

+ 1 - 1
src/Avalonia.Base/Metadata/PrivateApiAttribute.cs

@@ -2,7 +2,7 @@ using System;
 
 namespace Avalonia.Metadata;
 
-[AttributeUsage(AttributeTargets.Interface)]
+[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method)]
 public sealed class PrivateApiAttribute : Attribute
 {
 

+ 1 - 1
src/Avalonia.Base/Platform/IBitmapImpl.cs

@@ -7,7 +7,7 @@ namespace Avalonia.Platform
     /// <summary>
     /// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
     /// </summary>
-    [Unstable]
+    [PrivateApi]
     public interface IBitmapImpl : IDisposable
     {
         /// <summary>

+ 3 - 3
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@@ -35,7 +35,7 @@ namespace Avalonia.Platform
         /// <param name="opacity">The opacity to draw with.</param>
         /// <param name="sourceRect">The rect in the image to draw.</param>
         /// <param name="destRect">The rect in the output to draw to.</param>
-        void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect);
+        void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect);
 
         /// <summary>
         /// Draws a bitmap image.
@@ -44,7 +44,7 @@ namespace Avalonia.Platform
         /// <param name="opacityMask">The opacity mask to draw with.</param>
         /// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
         /// <param name="destRect">The rect in the output to draw to.</param>
-        void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
+        void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
 
         /// <summary>
         /// Draws a line.
@@ -94,7 +94,7 @@ namespace Avalonia.Platform
         /// </summary>
         /// <param name="foreground">The foreground.</param>
         /// <param name="glyphRun">The glyph run.</param>
-        void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun);
+        void DrawGlyphRun(IBrush? foreground, IGlyphRunImpl glyphRun);
 
         /// <summary>
         /// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer

+ 2 - 0
src/Avalonia.Base/Platform/IDrawingContextWithAcrylicLikeSupport.cs

@@ -1,7 +1,9 @@
 using Avalonia.Media;
+using Avalonia.Metadata;
 
 namespace Avalonia.Platform
 {
+    [PrivateApi]
     public interface IDrawingContextWithAcrylicLikeSupport
     {
         void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect);

+ 12 - 0
src/Avalonia.Base/Reactive/Observable.cs

@@ -165,6 +165,18 @@ internal static class Observable
         });
     }
     
+    public static IObservable<T> FromEventPattern<T>(Action<EventHandler<T>> addHandler, Action<EventHandler<T>> removeHandler) where T : EventArgs
+    {
+        return Create<T>(observer =>
+        {
+            var handler = new Action<T>(observer.OnNext);
+            var converted = new EventHandler<T>((_, args) => handler(args));
+            addHandler(converted);
+
+            return Disposable.Create(() => removeHandler(converted));
+        });
+    }
+    
     public static IObservable<T> Return<T>(T value)
     {
         return new ReturnImpl<T>(value);

+ 7 - 0
src/Avalonia.Base/Rect.cs

@@ -607,5 +607,12 @@ namespace Avalonia
                 );
             }
         }
+
+        /// <summary>
+        /// This method should be used internally to check for the rect emptiness
+        /// Once we add support for WPF-like empty rects, there will be an actual implementation
+        /// For now it's internal to keep some loud community members happy about the API being pretty 
+        /// </summary>
+        internal bool IsEmpty() => this == default;
     }
 }

+ 61 - 0
src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition.Drawing;
+using Avalonia.Rendering.Composition.Transport;
+
+// ReSharper disable CheckNamespace
+
+namespace Avalonia.Rendering.Composition.Server
+{
+    internal partial class ServerCompositionSimpleBrush : IBrush
+    {
+        ITransform? IBrush.Transform => Transform;
+    }
+
+    class ServerCompositionSimpleGradientBrush : ServerCompositionSimpleBrush, IGradientBrush
+    {
+        
+        internal ServerCompositionSimpleGradientBrush(ServerCompositor compositor) : base(compositor)
+        {
+            
+        }
+
+        private readonly List<IGradientStop> _gradientStops = new();
+        public IReadOnlyList<IGradientStop> GradientStops => _gradientStops;
+        public GradientSpreadMethod SpreadMethod { get; private set; }
+
+        protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
+        {
+            base.DeserializeChangesCore(reader, committedAt);
+            SpreadMethod = reader.Read<GradientSpreadMethod>();
+            _gradientStops.Clear();
+            var count = reader.Read<int>();
+            for (var c = 0; c < count; c++)
+                _gradientStops.Add(reader.ReadObject<ImmutableGradientStop>());
+        }
+    }
+
+    partial class ServerCompositionSimpleConicGradientBrush : IConicGradientBrush
+    {
+        
+    }
+    
+    partial class ServerCompositionSimpleLinearGradientBrush : ILinearGradientBrush
+    {
+        
+    }
+    
+    partial class ServerCompositionSimpleRadialGradientBrush : IRadialGradientBrush
+    {
+        
+    }
+    
+    partial class ServerCompositionSimpleSolidColorBrush : ISolidColorBrush
+    {
+        
+    }
+    
+    
+}

+ 24 - 0
src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs

@@ -0,0 +1,24 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Rendering.Composition.Drawing;
+using Avalonia.Rendering.Composition.Transport;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal class ServerCompositionSimpleContentBrush : ServerCompositionSimpleTileBrush, ITileBrush, ISceneBrush
+{
+    private CompositionRenderDataSceneBrushContent? _content;
+
+    internal ServerCompositionSimpleContentBrush(ServerCompositor compositor) : base(compositor)
+    {
+    }
+
+    // TODO: Figure out something about disposable
+    public ISceneBrushContent? CreateContent() => _content;
+
+    protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
+    {
+        base.DeserializeChangesCore(reader, committedAt);
+        _content = reader.ReadObject<CompositionRenderDataSceneBrushContent?>();
+    }
+}

+ 37 - 0
src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleImageBrush.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Data;
+using System.IO;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition.Transport;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal class ServerCompositionSimpleImageBrush : ServerCompositionSimpleTileBrush, 
+    IImageBrush, IImageBrushSource
+{
+    public IImageBrushSource? Source => this;
+    public IRef<IBitmapImpl>? Bitmap { get; private set; }
+    
+    internal ServerCompositionSimpleImageBrush(ServerCompositor compositor) : base(compositor)
+    {
+    }
+
+    public override void Dispose()
+    {
+        Bitmap?.Dispose();
+        Bitmap = null;
+        base.Dispose();
+    }
+
+
+    protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
+    {
+        base.DeserializeChangesCore(reader, committedAt);
+        Bitmap?.Dispose();
+        Bitmap = null;
+        Bitmap = reader.ReadObject<IRef<IBitmapImpl>>();
+    }
+}

+ 13 - 7
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
 using Avalonia.Collections;
 using Avalonia.Collections.Pooled;
 using Avalonia.Media;
+using Avalonia.Rendering.Composition.Drawing;
 using Avalonia.VisualTree;
 
 // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
@@ -20,8 +21,7 @@ public class CompositingRenderer : IRendererWithCompositor
 {
     private readonly IRenderRoot _root;
     private readonly Compositor _compositor;
-    private readonly CompositionDrawingContext _recorder = new();
-    private readonly DrawingContext _recordingContext;
+    private readonly RenderDataDrawingContext _recorder;
     private readonly HashSet<Visual> _dirty = new();
     private readonly HashSet<Visual> _recalculateChildren = new();
     private readonly Action _update;
@@ -55,7 +55,7 @@ public class CompositingRenderer : IRendererWithCompositor
     {
         _root = root;
         _compositor = compositor;
-        _recordingContext = _recorder;
+        _recorder = new(compositor);
         CompositionTarget = compositor.CreateCompositionTarget(surfaces);
         CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
         _update = Update;
@@ -276,10 +276,16 @@ public class CompositingRenderer : IRendererWithCompositor
 
             comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform);
 
-            _recorder.BeginUpdate(comp.DrawList);
-            visual.Render(_recordingContext);
-            comp.DrawList = _recorder.EndUpdate();
-
+            try
+            {
+                visual.Render(_recorder);
+                comp.DrawList = _recorder.GetRenderResults();
+            }
+            finally
+            {
+                _recorder.Reset();
+            }
+            
             SyncChildren(visual);
         }
         foreach(var v in _recalculateChildren)

+ 4 - 10
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@@ -1,9 +1,6 @@
-using System;
-using System.Numerics;
 using Avalonia.Rendering.Composition.Drawing;
 using Avalonia.Rendering.Composition.Server;
 using Avalonia.Rendering.Composition.Transport;
-using Avalonia.VisualTree;
 
 // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
 
@@ -21,12 +18,12 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
     public Visual Visual { get; }
 
     private bool _drawListChanged;
-    private CompositionDrawList? _drawList;
+    private CompositionRenderData? _drawList;
     
     /// <summary>
     /// The list of drawing commands
     /// </summary>
-    public CompositionDrawList? DrawList
+    public CompositionRenderData? DrawList
     {
         get => _drawList;
         set
@@ -43,7 +40,7 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
         writer.Write((byte)(_drawListChanged ? 1 : 0));
         if (_drawListChanged)
         {
-            writer.WriteObject(DrawList?.Clone());
+            writer.WriteObject(DrawList?.Server);
             _drawListChanged = false;
         }
         base.SerializeChangesCore(writer);
@@ -64,9 +61,6 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
             return custom.HitTest(pt);
         }
 
-        foreach (var op in DrawList!)
-            if (op.Item.HitTest(pt))
-                return true;
-        return false;
+        return DrawList?.HitTest(pt) ?? false;
     }
 }

+ 4 - 1
src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs

@@ -1,10 +1,11 @@
+using System;
 using System.Threading.Tasks;
 using Avalonia.Rendering.Composition.Server;
 using Avalonia.Threading;
 
 namespace Avalonia.Rendering.Composition;
 
-public class CompositionDrawingSurface : CompositionSurface
+public class CompositionDrawingSurface : CompositionSurface, IDisposable
 {
     internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server!;
     internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server))
@@ -57,4 +58,6 @@ public class CompositionDrawingSurface : CompositionSurface
     {
         Dispatcher.UIThread.Post(Dispose);
     }
+
+    public new void Dispose() => base.Dispose();
 }

+ 11 - 0
src/Avalonia.Base/Rendering/Composition/CompositionExperimentalAcrylicVisual.cs

@@ -0,0 +1,11 @@
+using Avalonia.Rendering.Composition.Server;
+
+namespace Avalonia.Rendering.Composition;
+
+internal partial class CompositionExperimentalAcrylicVisual
+{
+    internal CompositionExperimentalAcrylicVisual(Compositor compositor, Visual visual) : base(compositor,
+        new ServerCompositionExperimentalAcrylicVisual(compositor.Server, visual), visual)
+    {
+    }
+}

+ 19 - 9
src/Avalonia.Base/Rendering/Composition/CompositionObject.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
 using Avalonia.Rendering.Composition.Animations;
 using Avalonia.Rendering.Composition.Expressions;
 using Avalonia.Rendering.Composition.Server;
@@ -14,7 +16,7 @@ namespace Avalonia.Rendering.Composition
     /// Composition objects are the visual tree structure on which all other features of the composition API use and build on.
     /// The API allows developers to define and create one or many <see cref="CompositionVisual" /> objects each representing a single node in a Visual tree.
     /// </summary>
-    public abstract class CompositionObject : IDisposable
+    public abstract class CompositionObject : ICompositorSerializable
     {
         /// <summary>
         /// The collection of implicit animations attached to this object.
@@ -22,7 +24,7 @@ namespace Avalonia.Rendering.Composition
         public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
 
         private protected InlineDictionary<CompositionProperty, IAnimationInstance> PendingAnimations;
-        internal CompositionObject(Compositor compositor, ServerObject? server)
+        internal CompositionObject(Compositor compositor, SimpleServerObject? server)
         {
             Compositor = compositor;
             Server = server;
@@ -32,16 +34,25 @@ namespace Avalonia.Rendering.Composition
         /// The associated Compositor
         /// </summary>
         public Compositor Compositor { get; }
-        internal ServerObject? Server { get; }
+
+        SimpleServerObject ICompositorSerializable.TryGetServer(Compositor c)
+        {
+            Debug.Assert(c == Compositor);
+            return Server ?? ThrowInvalidOperation();
+        }
+
+        internal SimpleServerObject? Server { get; }
         public bool IsDisposed { get; private set; }
         private bool _registeredForSerialization;
 
-        private static void ThrowInvalidOperation() =>
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static SimpleServerObject ThrowInvalidOperation() =>
             throw new InvalidOperationException("There is no server-side counterpart for this object");
 
-        public void Dispose()
+        protected internal void Dispose()
         {
-            RegisterForSerialization();
+            if (!IsDisposed && Server != null)
+                Compositor.DisposeOnNextBatch(Server);
             IsDisposed = true;
         }
 
@@ -128,16 +139,15 @@ namespace Avalonia.Rendering.Composition
             Compositor.RegisterForSerialization(this);
         }
 
-        internal void SerializeChanges(BatchStreamWriter writer)
+        void ICompositorSerializable.SerializeChanges(Compositor c, BatchStreamWriter writer)
         {
+            Debug.Assert(c == Compositor);
             _registeredForSerialization = false;
             SerializeChangesCore(writer);
         }
 
         private protected virtual void SerializeChangesCore(BatchStreamWriter writer)
         {
-            if (Server is IDisposable)
-                writer.Write((byte)(IsDisposed ? 1 : 0));
         }
     }
 }

+ 2 - 1
src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Numerics;
 using Avalonia.Rendering.Composition.Animations;
 using Avalonia.Rendering.Composition.Expressions;
+using Avalonia.Rendering.Composition.Server;
 using Avalonia.Rendering.Composition.Transport;
 
 // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
@@ -130,7 +131,7 @@ namespace Avalonia.Rendering.Composition
                 else if (o.Value.Server == null)
                     throw new InvalidOperationException($"Object of type {o.Value.GetType()} is not allowed");
                 else
-                    dic[o.Key] = new PropertySetSnapshot.Value(o.Value.Server);
+                    dic[o.Key] = new PropertySetSnapshot.Value((ServerObject)o.Value.Server);
             }
 
             foreach (var v in _variants)

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Rendering.Composition
     /// <summary>
     /// Represents the composition output (e. g. a window, embedded control, entire screen)
     /// </summary>
-    public partial class CompositionTarget
+    internal partial class CompositionTarget
     {
         partial void OnRootChanged()
         {

+ 9 - 0
src/Avalonia.Base/Rendering/Composition/CompositionTransform.cs

@@ -0,0 +1,9 @@
+using Avalonia.Media;
+
+// ReSharper disable CheckNamespace
+namespace Avalonia.Rendering.Composition.Server;
+
+internal partial class ServerCompositionSimpleTransform : ITransform
+{
+    
+}

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs

@@ -12,7 +12,7 @@ public partial class Compositor
     /// </summary>
     /// <param name="surfaces">A factory method to create IRenderTarget to be called from the render thread</param>
     /// <returns></returns>
-    public CompositionTarget CreateCompositionTarget(Func<IEnumerable<object>> surfaces)
+    internal CompositionTarget CreateCompositionTarget(Func<IEnumerable<object>> surfaces)
     {
         return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces, DiagnosticTextRenderer));
     }

+ 38 - 9
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@@ -5,6 +5,7 @@ using System.Numerics;
 using System.Threading.Tasks;
 using Avalonia.Animation.Easings;
 using Avalonia.Media;
+using Avalonia.Metadata;
 using Avalonia.Platform;
 using Avalonia.Rendering.Composition.Animations;
 using Avalonia.Rendering.Composition.Server;
@@ -29,8 +30,10 @@ namespace Avalonia.Rendering.Composition
         private Action _commit;
         private BatchStreamObjectPool<object?> _batchObjectPool = new();
         private BatchStreamMemoryPool _batchMemoryPool = new();
-        private List<CompositionObject> _objectsForSerialization = new();
+        private Queue<ICompositorSerializable> _objectSerializationQueue = new();
+        private HashSet<ICompositorSerializable> _objectSerializationHashSet = new();
         private Queue<Action> _invokeBeforeCommitWrite = new(), _invokeBeforeCommitRead = new();
+        private HashSet<IDisposable> _disposeOnNextBatch = new();
         internal ServerCompositor Server => _server;
         private Task? _pendingBatch;
         private readonly object _pendingBatchLock = new();
@@ -44,11 +47,13 @@ namespace Avalonia.Rendering.Composition
 
         internal event Action? AfterCommit;
 
+        
         /// <summary>
         /// Creates a new compositor on a specified render loop that would use a particular GPU
         /// </summary>
         /// <param name="loop"></param>
         /// <param name="gpu"></param>
+        [PrivateApi]
         public Compositor(IRenderLoop loop, IPlatformGraphics? gpu)
         {
             Loop = loop;
@@ -112,16 +117,30 @@ namespace Avalonia.Rendering.Composition
             
             using (var writer = new BatchStreamWriter(batch.Changes, _batchMemoryPool, _batchObjectPool))
             {
-                foreach (var obj in _objectsForSerialization)
+                while(_objectSerializationQueue.TryDequeue(out var obj))
                 {
-                    writer.WriteObject(obj.Server);
-                    obj.SerializeChanges(writer);
+                    var serverObject = obj.TryGetServer(this);
+                    if (serverObject != null)
+                    {
+                        writer.WriteObject(serverObject);
+                        obj.SerializeChanges(this, writer);
 #if DEBUG_COMPOSITOR_SERIALIZATION
-                    writer.Write(BatchStreamDebugMarkers.ObjectEndMagic);
-                    writer.WriteObject(BatchStreamDebugMarkers.ObjectEndMarker);
+                        writer.Write(BatchStreamDebugMarkers.ObjectEndMagic);
+                        writer.WriteObject(BatchStreamDebugMarkers.ObjectEndMarker);
 #endif
+                    }
+                }
+                _objectSerializationHashSet.Clear();
+
+                if (_disposeOnNextBatch.Count != 0)
+                {
+                    writer.WriteObject(ServerCompositor.RenderThreadDisposeStartMarker);
+                    writer.Write(_disposeOnNextBatch.Count);
+                    foreach (var d in _disposeOnNextBatch)
+                        writer.WriteObject(d);
+                    _disposeOnNextBatch.Clear();
                 }
-                _objectsForSerialization.Clear();
+
                 if (_pendingServerCompositorJobs.Count > 0)
                 {
                     writer.WriteObject(ServerCompositor.RenderThreadJobsStartMarker);
@@ -152,13 +171,20 @@ namespace Avalonia.Rendering.Composition
             }
         }
 
-        internal void RegisterForSerialization(CompositionObject compositionObject)
+        internal void RegisterForSerialization(ICompositorSerializable compositionObject)
         {
             Dispatcher.UIThread.VerifyAccess();
-            _objectsForSerialization.Add(compositionObject);
+            if(_objectSerializationHashSet.Add(compositionObject))
+                _objectSerializationQueue.Enqueue(compositionObject);
             RequestCommitAsync();
         }
 
+        internal void DisposeOnNextBatch(SimpleServerObject obj)
+        {
+            if (obj is IDisposable disposable && _disposeOnNextBatch.Add(disposable))
+                RequestCommitAsync();
+        }
+        
         /// <summary>
         /// Enqueues a callback to be called before the next scheduled commit.
         /// If there is no scheduled commit it automatically schedules one
@@ -227,5 +253,8 @@ namespace Avalonia.Rendering.Composition
                     return new CompositionInterop(this, feature);
                 }
             }));
+
+        internal bool UnitTestIsRegisteredForSerialization(ICompositorSerializable serializable) =>
+            _objectSerializationHashSet.Contains(serializable);
     }
 }

+ 0 - 129
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs

@@ -1,129 +0,0 @@
-using System;
-using Avalonia.Collections.Pooled;
-using Avalonia.Platform;
-using Avalonia.Rendering.Composition.Server;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Utilities;
-
-// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
-
-namespace Avalonia.Rendering.Composition.Drawing;
-
-/// <summary>
-/// A list of serialized drawing commands
-/// </summary>
-internal class CompositionDrawList : PooledList<IRef<IDrawOperation>>
-{
-    public CompositionDrawList()
-    {
-        
-    }
-
-    public CompositionDrawList(int capacity) : base(capacity)
-    {
-        
-    }
-    
-    public override void Dispose()
-    {
-        foreach(var item in this)
-            item.Dispose();
-        base.Dispose();
-    }
-
-    public CompositionDrawList Clone()
-    {
-        var clone = new CompositionDrawList(Count);
-        foreach (var r in this)
-            clone.Add(r.Clone());
-        return clone;
-    }
-
-    public void Render(IDrawingContextImpl canvas)
-    {
-        foreach (var cmd in this)
-        {
-            if (cmd.Item is IDrawOperationWithTransform hasTransform)
-                canvas.Transform = hasTransform.Transform;
-            cmd.Item.Render(canvas);
-        }
-    }
-    
-    public void Render(IDrawingContextImpl canvas, Matrix transform)
-    {
-        foreach (var cmd in this)
-        {
-            if (cmd.Item is IDrawOperationWithTransform hasTransform)
-                canvas.Transform = hasTransform.Transform * transform;
-            cmd.Item.Render(canvas);
-        }
-    }
-
-
-    public Rect CalculateBounds()
-    {
-        var rect = default(Rect);
-        foreach (var cmd in this)
-            rect = rect.Union(cmd.Item.Bounds);
-        return rect;
-    }
-
-    public bool HitTest(Point pt)
-    {
-        foreach (var op in this)
-            if (op.Item.HitTest(pt))
-                return true;
-        return false;
-    }
-}
-
-/// <summary>
-/// An helper class for building <see cref="CompositionDrawList"/>
-/// </summary>
-internal class CompositionDrawListBuilder
-{
-    private CompositionDrawList? _operations;
-    private bool _owns;
-
-    public void Reset(CompositionDrawList? previousOperations)
-    {
-        _operations = previousOperations;
-        _owns = false;
-    }
-
-    public int Count => _operations?.Count ?? 0;
-    public CompositionDrawList? DrawOperations => _operations;
-
-    void MakeWritable(int atIndex)
-    {
-        if(_owns)
-            return;
-        _owns = true;
-        var newOps = new CompositionDrawList(_operations?.Count ?? Math.Max(1, atIndex));
-        if (_operations != null)
-        {
-            for (var c = 0; c < atIndex; c++)
-                newOps.Add(_operations[c].Clone());
-        }
-
-        _operations = newOps;
-    }
-
-    public void ReplaceDrawOperation(int index, IDrawOperation node)
-    {
-        MakeWritable(index);
-        DrawOperations!.Add(RefCountable.Create(node));
-    }
-
-    public void AddDrawOperation(IDrawOperation node)
-    {
-        MakeWritable(Count);
-        DrawOperations!.Add(RefCountable.Create(node));
-    }
-
-    public void TrimTo(int count)
-    {
-        if (count < Count)
-            _operations!.RemoveRange(count, _operations.Count - count);
-    }
-}

+ 0 - 37
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawListSceneBrushContent.cs

@@ -1,37 +0,0 @@
-using Avalonia.Media;
-using Avalonia.Media.Immutable;
-using Avalonia.Platform;
-
-namespace Avalonia.Rendering.Composition.Drawing;
-
-internal class CompositionDrawListSceneBrushContent : ISceneBrushContent
-{
-    private readonly CompositionDrawList _drawList;
-
-    public CompositionDrawListSceneBrushContent(ImmutableTileBrush brush, CompositionDrawList drawList, Rect rect, bool useScalableRasterization)
-    {
-        Brush = brush;
-        Rect = rect;
-        UseScalableRasterization = useScalableRasterization;
-        _drawList = drawList;
-    }
-
-    public ITileBrush Brush { get; }
-    public Rect Rect { get; }
-    
-    public double Opacity => Brush.Opacity;
-    public ITransform? Transform => Brush.Transform;
-    public RelativePoint TransformOrigin => Brush.TransformOrigin;
-    
-    public void Dispose() => _drawList.Dispose();
-
-    public void Render(IDrawingContextImpl context, Matrix? transform)
-    {
-        if (transform.HasValue)
-            _drawList.Render(context, transform.Value);
-        else
-            _drawList.Render(context);
-    }
-
-    public bool UseScalableRasterization { get; }
-}

+ 0 - 371
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@@ -1,371 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Numerics;
-using Avalonia.Media;
-using Avalonia.Media.Imaging;
-using Avalonia.Platform;
-using Avalonia.Rendering.Composition.Drawing;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Utilities;
-using Avalonia.Threading;
-
-// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
-
-namespace Avalonia.Rendering.Composition;
-
-/// <summary>
-/// An IDrawingContextImpl implementation that builds <see cref="CompositionDrawList"/>
-/// </summary>
-internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContextWithAcrylicLikeSupport
-{
-    private CompositionDrawListBuilder _builder = new();
-    private int _drawOperationIndex;
-    
-    private static ThreadSafeObjectPool<Stack<Matrix>> TransformStackPool { get; } =
-        ThreadSafeObjectPool<Stack<Matrix>>.Default;
-
-    private Stack<Matrix>? _transforms;
-
-    private static ThreadSafeObjectPool<Stack<bool>> OpacityMaskPopStackPool { get; } =
-        ThreadSafeObjectPool<Stack<bool>>.Default;
-
-    private Stack<bool>? _needsToPopOpacityMask;
-
-    public Matrix Transform { get; set; } = Matrix.Identity;
-    
-    public void BeginUpdate(CompositionDrawList? list)
-    {
-        _builder.Reset(list);
-        _drawOperationIndex = 0;
-    }
-
-    public CompositionDrawList? EndUpdate()
-    {
-        // Make sure that any pending pop operations are completed
-        Dispose();
-        
-        _builder.TrimTo(_drawOperationIndex);
-        return _builder.DrawOperations;
-    }
-    
-    protected override void DisposeCore()
-    {
-        if (_transforms != null)
-        {
-            _transforms.Clear();
-            TransformStackPool.ReturnAndSetNull(ref _transforms);
-        }
-
-        if (_needsToPopOpacityMask != null)
-        {
-            _needsToPopOpacityMask.Clear();
-            _needsToPopOpacityMask = null;
-        }
-    }
-    
-    protected override void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry)
-    {
-        var next = NextDrawAs<GeometryNode>();
-
-        if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
-        {
-            Add(new GeometryNode(Transform, ConvertBrush(brush), pen, geometry));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
-    {
-        var next = NextDrawAs<ImageNode>();
-
-        if (next == null ||
-            !next.Item.Equals(Transform, source, opacity, sourceRect, destRect))
-        {
-            Add(new ImageNode(Transform, source, opacity, sourceRect, destRect));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    /// <inheritdoc/>
-    protected override void DrawLineCore(IPen? pen, Point p1, Point p2)
-    {
-        if (pen is null)
-        {
-            return;
-        }
-
-        var next = NextDrawAs<LineNode>();
-
-        if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
-        {
-            Add(new LineNode(Transform, pen, p1, p2));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    /// <inheritdoc/>
-    protected override void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rect,
-        BoxShadows boxShadows = default)
-    {
-        var next = NextDrawAs<RectangleNode>();
-
-        if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows))
-        {
-            Add(new RectangleNode(Transform, ConvertBrush(brush), pen, rect, boxShadows));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    /// <inheritdoc/>
-    public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
-    {
-        var next = NextDrawAs<ExperimentalAcrylicNode>();
-
-        if (next == null || !next.Item.Equals(Transform, material, rect))
-        {
-            Add(new ExperimentalAcrylicNode(Transform, material, rect));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect)
-    {
-        var next = NextDrawAs<EllipseNode>();
-
-        if (next == null || !next.Item.Equals(Transform, brush, pen, rect))
-        {
-            Add(new EllipseNode(Transform, ConvertBrush(brush), pen, rect));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-    
-    public override void Custom(ICustomDrawOperation custom)
-    {
-        var next = NextDrawAs<CustomDrawOperation>();
-        if (next == null || !next.Item.Equals(Transform, custom))
-            Add(new CustomDrawOperation(custom, Transform));
-        else
-            ++_drawOperationIndex;
-    }
-
-    public override void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun)
-    {
-        if (foreground is null)
-        {
-            return;
-        }
-
-        var next = NextDrawAs<GlyphRunNode>();
-
-        if (next == null || !next.Item.Equals(Transform, foreground, glyphRun.PlatformImpl))
-        {
-            Add(new GlyphRunNode(Transform, ConvertBrush(foreground)!, glyphRun.PlatformImpl));
-        }
-
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    protected override void PushTransformCore(Matrix matrix)
-    {
-        _transforms ??= TransformStackPool.Get();
-        _transforms.Push(Transform);
-        Transform = matrix * Transform;
-    }
-    
-    protected override void PopTransformCore() =>
-        Transform = (_transforms ?? throw new InvalidOperationException()).Pop();
-
-    protected override void PopClipCore()
-    {
-        var next = NextDrawAs<ClipNode>();
-
-        if (next == null || !next.Item.Equals(null))
-        {
-            Add(new ClipNode());
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    /// <inheritdoc/>
-    protected override void PopGeometryClipCore()
-    {
-        var next = NextDrawAs<GeometryClipNode>();
-
-        if (next == null || !next.Item.Equals(null))
-        {
-            Add(new GeometryClipNode());
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    protected override void PopOpacityCore()
-    {
-        var next = NextDrawAs<OpacityNode>();
-
-        if (next == null || !next.Item.Equals(null))
-        {
-            Add(new OpacityNode());
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    protected override void PopOpacityMaskCore()
-    {
-        if (!_needsToPopOpacityMask!.Pop())
-            return;
-        
-        var next = NextDrawAs<OpacityMaskNode>();
-
-        if (next == null || !next.Item.Equals(null, null))
-        {
-            Add(new OpacityMaskPopNode());
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-
-    protected override void PushClipCore(Rect clip)
-    {
-        var next = NextDrawAs<ClipNode>();
-
-        if (next == null || !next.Item.Equals(Transform, clip))
-        {
-            Add(new ClipNode(Transform, clip));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    protected override void PushClipCore(RoundedRect clip)
-    {
-        var next = NextDrawAs<ClipNode>();
-
-        if (next == null || !next.Item.Equals(Transform, clip))
-        {
-            Add(new ClipNode(Transform, clip));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    protected override void PushGeometryClipCore(Geometry clip)
-    {
-        if (clip.PlatformImpl is null)
-            return;
-
-        var next = NextDrawAs<GeometryClipNode>();
-
-        if (next == null || !next.Item.Equals(Transform, clip.PlatformImpl))
-        {
-            Add(new GeometryClipNode(Transform, clip.PlatformImpl));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-    
-    protected override void PushOpacityCore(double opacity, Rect bounds)
-    {
-        var next = NextDrawAs<OpacityNode>();
-
-        if (next == null || !next.Item.Equals(opacity, bounds))
-        {
-            Add(new OpacityNode(opacity, bounds));
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-    }
-
-    protected override void PushOpacityMaskCore(IBrush mask, Rect bounds)
-    {
-        var next = NextDrawAs<OpacityMaskNode>();
-
-        bool needsToPop = true;
-        if (next == null || !next.Item.Equals(mask, bounds))
-        {
-            var immutableMask = ConvertBrush(mask);
-            if (immutableMask != null)
-                Add(new OpacityMaskNode(immutableMask, bounds));
-            else
-                needsToPop = false;
-        }
-        else
-        {
-            ++_drawOperationIndex;
-        }
-
-        _needsToPopOpacityMask ??= OpacityMaskPopStackPool.Get();
-        _needsToPopOpacityMask.Push(needsToPop);
-    }
-
-    private void Add<T>(T node) where T : class, IDrawOperation
-    {
-        if (_drawOperationIndex < _builder.Count)
-        {
-            _builder.ReplaceDrawOperation(_drawOperationIndex, node);
-        }
-        else
-        {
-            _builder.AddDrawOperation(node);
-        }
-
-        ++_drawOperationIndex;
-    }
-
-    private IRef<T>? NextDrawAs<T>() where T : class, IDrawOperation
-    {
-        return _drawOperationIndex < _builder.Count
-            ? _builder.DrawOperations![_drawOperationIndex] as IRef<T>
-            : null;
-    }
-    
-    private IImmutableBrush? ConvertBrush(IBrush? brush)
-    {
-        if (brush is IMutableBrush mutable)
-            return mutable.ToImmutable();
-        if (brush is ISceneBrush sceneBrush)
-            return sceneBrush.CreateContent();
-        return (IImmutableBrush?)brush;
-    }
-}

+ 68 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition.Drawing.Nodes;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Drawing;
+
+internal class CompositionRenderData : ICompositorSerializable, IDisposable
+{
+    private readonly Compositor _compositor;
+
+    public CompositionRenderData(Compositor compositor)
+    {
+        _compositor = compositor;
+        Server = new ServerCompositionRenderData(compositor.Server);
+    }
+
+    public ServerCompositionRenderData Server { get; }
+    private PooledInlineList<ICompositionRenderResource> _resources;
+    private PooledInlineList<IRenderDataItem> _items;
+    private bool _itemsSent;
+    public void AddResource(ICompositionRenderResource resource) => _resources.Add(resource);
+
+    public void Add(IRenderDataItem item) => _items.Add(item);
+    
+    public void Dispose()
+    {
+        if (!_itemsSent)
+        {
+            foreach(var i in _items)
+                if (i is IDisposable disp)
+                    disp.Dispose();
+        }
+        
+        _items.Dispose();
+        _itemsSent = false;
+        foreach(var r in _resources)
+            r.ReleaseOnCompositor(_compositor);
+        _resources.Dispose();
+        
+        _compositor.DisposeOnNextBatch(Server);
+    }
+
+    public SimpleServerObject TryGetServer(Compositor c) => Server;
+
+    public void SerializeChanges(Compositor c, BatchStreamWriter writer)
+    {
+        writer.Write(_items.Count);
+        foreach (var item in _items) 
+            writer.WriteObject(item);
+        _itemsSent = true;
+    }
+
+    public bool HitTest(Point pt)
+    {
+        foreach (var op in _items)
+        {
+            if (op.HitTest(pt))
+                return true;
+        }
+
+        return false;
+    }
+}

+ 47 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs

@@ -0,0 +1,47 @@
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition.Drawing;
+
+internal class CompositionRenderDataSceneBrushContent : ISceneBrushContent
+{
+    public CompositionRenderData RenderData { get; }
+    private Rect? _rect;
+
+    public CompositionRenderDataSceneBrushContent(ITileBrush brush, CompositionRenderData renderData, Rect? rect,
+        bool useScalableRasterization)
+    {
+        Brush = brush;
+        _rect = rect;
+        UseScalableRasterization = useScalableRasterization;
+        RenderData = renderData;
+    }
+
+    public ITileBrush Brush { get; }
+    public Rect Rect => _rect ?? (RenderData.Server?.Bounds ?? default);
+
+    public double Opacity => Brush.Opacity;
+    public ITransform? Transform => Brush.Transform;
+    public RelativePoint TransformOrigin => Brush.TransformOrigin;
+
+    public void Dispose()
+    {
+        // No-op on server
+    }
+
+    public void Render(IDrawingContextImpl context, Matrix? transform)
+    {
+        if (transform.HasValue)
+        {
+            var oldTransform = context.Transform;
+            context.Transform = transform.Value * oldTransform;
+            RenderData.Server.Render(context);
+            context.Transform = oldTransform;
+        }
+        else
+            RenderData.Server.Render(context);
+    }
+
+    public bool UseScalableRasterization { get; }
+}

+ 123 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/CompositorResourceHelpers.cs

@@ -0,0 +1,123 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Avalonia.Media;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Drawing;
+
+internal class CompositorRefCountableResource<T> where T : SimpleServerObject
+{
+    public T Value { get; private set; }
+    public int RefCount { get; private set; }
+
+    public CompositorRefCountableResource(T value)
+    {
+        Value = value;
+        RefCount = 1;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static void ThrowInvalidOperation() => throw new InvalidOperationException("This resource is disposed");
+    
+    public void AddRef()
+    {
+        if (RefCount <= 0)
+            ThrowInvalidOperation();
+        RefCount++;
+    }
+
+    public bool Release(Compositor c)
+    {
+        if (RefCount <= 0)
+            ThrowInvalidOperation();
+        RefCount--;
+        if (RefCount == 0)
+        {
+            c.DisposeOnNextBatch(Value);
+            return true;
+        }
+
+        return false;
+    }
+}
+
+internal struct CompositorResourceHolder<T> where T : SimpleServerObject
+{
+    private InlineDictionary<Compositor, CompositorRefCountableResource<T>> _dictionary;
+
+    public bool IsAttached => _dictionary.HasEntries;
+    
+    public bool CreateOrAddRef(Compositor compositor, ICompositorSerializable owner, out T resource, Func<Compositor, T> factory)
+    {
+        if (_dictionary.TryGetValue(compositor, out var handle))
+        {
+            handle.AddRef();
+            resource = handle.Value;
+            return false;
+        }
+
+        resource = factory(compositor);
+        _dictionary.Add(compositor, new CompositorRefCountableResource<T>(resource));
+        compositor.RegisterForSerialization(owner);
+        return true;
+    }
+
+    public T? TryGetForCompositor(Compositor compositor)
+    {
+        if (_dictionary.TryGetValue(compositor, out var handle))
+            return handle.Value;
+        return default;
+    }
+    
+    public T GetForCompositor(Compositor compositor)
+    {
+        if (_dictionary.TryGetValue(compositor, out var handle))
+            return handle.Value;
+        ThrowDoesNotExist();
+        return default;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining), DoesNotReturn]
+    static void ThrowDoesNotExist() => throw new InvalidOperationException("This resource doesn't exist on that compositor");
+    
+    public bool Release(Compositor compositor)
+    {
+        if (!_dictionary.TryGetValue(compositor, out var handle))
+            ThrowDoesNotExist();
+        if (handle.Release(compositor))
+        {
+            _dictionary.Remove(compositor);
+            return true;
+        }
+
+        return false;
+    }
+
+    public void ProcessPropertyChangeNotification(AvaloniaPropertyChangedEventArgs change)
+    {
+        if (change.OldValue is ICompositionRenderResource oldResource)
+            TransitiveReleaseAll(oldResource);
+        if (change.NewValue is ICompositionRenderResource newResource)
+            TransitiveAddRefAll(newResource);
+    }
+
+    public void TransitiveReleaseAll(ICompositionRenderResource oldResource)
+    {
+        foreach(var kv in _dictionary)
+            oldResource.ReleaseOnCompositor(kv.Key);
+    }
+
+    public void TransitiveAddRefAll(ICompositionRenderResource newResource)
+    {
+        foreach (var kv in _dictionary)
+            newResource.AddRefOnCompositor(kv.Key);
+    }
+
+    public void RegisterForInvalidationOnAllCompositors(ICompositorSerializable serializable)
+    {
+        foreach (var kv in _dictionary)
+            kv.Key.RegisterForSerialization(serializable);
+    }
+}

+ 12 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/ICompositionRenderResource.cs

@@ -0,0 +1,12 @@
+namespace Avalonia.Rendering.Composition.Drawing;
+
+internal interface ICompositionRenderResource
+{
+    void AddRefOnCompositor(Compositor c);
+    void ReleaseOnCompositor(Compositor c);
+}
+
+internal interface ICompositionRenderResource<T> : ICompositionRenderResource where T : class
+{
+    T GetForCompositor(Compositor c);
+}

+ 81 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/ImmediateRenderDataSceneBrushContent.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition.Drawing.Nodes;
+using Avalonia.Threading;
+
+namespace Avalonia.Rendering.Composition.Drawing;
+
+internal class ImmediateRenderDataSceneBrushContent : ISceneBrushContent
+{
+    private List<IRenderDataItem>? _items;
+    private readonly ThreadSafeObjectPool<List<IRenderDataItem>> _pool;
+
+    public ImmediateRenderDataSceneBrushContent(ITileBrush brush, List<IRenderDataItem> items, Rect? rect,
+        bool useScalableRasterization, ThreadSafeObjectPool<List<IRenderDataItem>> pool)
+    {
+        Brush = brush;
+        _items = items;
+        _pool = pool;
+        UseScalableRasterization = useScalableRasterization;
+        if (rect == null)
+        {
+            foreach (var i in _items)
+                rect = Rect.Union(rect, i.Bounds);
+            rect = ServerCompositionRenderData.ApplyRenderBoundsRounding(rect);
+        }
+
+        Rect = rect ?? default;
+    }
+
+    public ITileBrush Brush { get; }
+    public Rect Rect { get; }
+
+    public double Opacity => Brush.Opacity;
+    public ITransform? Transform => Brush.Transform;
+    public RelativePoint TransformOrigin => Brush.TransformOrigin;
+
+    public void Dispose()
+    {
+        if(_items == null)
+            return;
+        foreach (var i in _items)
+            (i as IDisposable)?.Dispose();
+        _items.Clear();
+        _pool.ReturnAndSetNull(ref _items);
+    }
+
+    void Render(IDrawingContextImpl context)
+    {
+        if (_items == null)
+            return;
+        
+        var ctx = new RenderDataNodeRenderContext(context);
+        try
+        {
+            foreach (var i in _items)
+                i.Invoke(ref ctx);
+        }
+        finally
+        {
+            ctx.Dispose();
+        }
+    }
+    
+    public void Render(IDrawingContextImpl context, Matrix? transform)
+    {
+        if (transform.HasValue)
+        {
+            var oldTransform = context.Transform;
+            context.Transform = transform.Value * oldTransform;
+            Render(context);
+            context.Transform = oldTransform;
+        }
+        else
+            Render(context);
+    }
+
+    public bool UseScalableRasterization { get; }
+
+}

+ 28 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs

@@ -0,0 +1,28 @@
+using System;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataBitmapNode : IRenderDataItem, IDisposable
+{
+    public IRef<IBitmapImpl>? Bitmap { get; set; }
+    public double Opacity { get; set; }
+    public Rect SourceRect { get; set; }
+    public Rect DestRect { get; set; }
+    
+    public bool HitTest(Point p) => DestRect.Contains(p);
+
+    public void Invoke(ref RenderDataNodeRenderContext context)
+    {
+        if (Bitmap != null)
+            context.Context.DrawBitmap(Bitmap.Item, Opacity, SourceRect, DestRect);
+    }
+
+    public Rect? Bounds => DestRect;
+    public void Dispose()
+    {
+        Bitmap?.Dispose();
+        Bitmap = null;
+    }
+}

+ 61 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs

@@ -0,0 +1,61 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataEllipseNode :RenderDataBrushAndPenNode
+{
+    public Rect Rect { get; set; }
+    
+    bool Contains(double dx, double dy, double radiusX, double radiusY)
+    {
+        var rx2 = radiusX * radiusX;
+        var ry2 = radiusY * radiusY;
+
+        var distance = ry2 * dx * dx + rx2 * dy * dy;
+
+        return distance <= rx2 * ry2;
+    }
+    
+    public override bool HitTest(Point p)
+    {
+        var center = Rect.Center;
+
+        var strokeThickness = ClientPen?.Thickness ?? 0;
+
+        var rx = Rect.Width / 2 + strokeThickness / 2;
+        var ry = Rect.Height / 2 + strokeThickness / 2;
+
+        var dx = p.X - center.X;
+        var dy = p.Y - center.Y;
+
+        if (Math.Abs(dx) > rx || Math.Abs(dy) > ry)
+        {
+            return false;
+        }
+
+        if (ServerBrush != null)
+        {
+            return Contains(dx, dy, rx, ry);
+        }
+        else if (strokeThickness > 0)
+        {
+            bool inStroke = Contains(dx, dy, rx, ry);
+
+            rx = Rect.Width / 2 - strokeThickness / 2;
+            ry = Rect.Height / 2 - strokeThickness / 2;
+
+            bool inInner = Contains(dx, dy, rx, ry);
+
+            return inStroke && !inInner;
+        }
+        
+        return false;
+    }
+
+    public override void Invoke(ref RenderDataNodeRenderContext context) =>
+        context.Context.DrawEllipse(ServerBrush, ServerPen, Rect);
+
+    public override Rect? Bounds => Rect.Inflate(ServerPen?.Thickness ?? 0);
+}

+ 29 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs

@@ -0,0 +1,29 @@
+using System.Diagnostics;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataGeometryNode : RenderDataBrushAndPenNode
+{
+    public IGeometryImpl? Geometry { get; set; }
+    
+    public override bool HitTest(Point p)
+    {
+        if (Geometry == null)
+            return false;
+        
+        return (ServerBrush != null // null check is safe
+                && Geometry.FillContains(p)) ||
+               (ClientPen != null && Geometry.StrokeContains(ClientPen, p));
+    }
+    
+    public override void Invoke(ref RenderDataNodeRenderContext context)
+    {
+        Debug.Assert(Geometry != null);
+        context.Context.DrawGeometry(ServerBrush, ServerPen, Geometry!);
+    }
+
+    public override Rect? Bounds => Geometry?.GetRenderBounds(ServerPen).CalculateBoundsWithLineCaps(ServerPen) ?? default;
+}

+ 35 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Diagnostics;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataGlyphRunNode : IRenderDataItemWithServerResources, IDisposable
+{
+    public IBrush? ServerBrush { get; set; }
+    // Dispose only happens once, so it's safe to have one reference
+    public IRef<IGlyphRunImpl>? GlyphRun { get; set; }
+
+    public bool HitTest(Point p) => GlyphRun?.Item.Bounds.ContainsExclusive(p) ?? false;
+    
+    public void Invoke(ref RenderDataNodeRenderContext context)
+    {
+        Debug.Assert(GlyphRun!.Item != null);
+        context.Context.DrawGlyphRun(ServerBrush, GlyphRun.Item);
+    }
+
+    public Rect? Bounds => GlyphRun?.Item?.Bounds ?? default;
+
+    public void Collect(IRenderDataServerResourcesCollector collector)
+    {
+        collector.AddRenderDataServerResource(ServerBrush);
+    }
+
+    public void Dispose()
+    {
+        GlyphRun?.Dispose();
+        GlyphRun = null;
+    }
+}

+ 65 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs

@@ -0,0 +1,65 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataLineNode : IRenderDataItemWithServerResources
+{
+    public IPen? ServerPen { get; set; }
+    public IPen? ClientPen { get; set; }
+    public Point P1 { get; set; }
+    public Point P2 { get; set; }
+    
+    public bool HitTest(Point p)
+    {
+        if (ClientPen == null)
+            return false;
+        var halfThickness = ClientPen.Thickness / 2;
+        var minX = Math.Min(P1.X, P2.X) - halfThickness;
+        var maxX = Math.Max(P1.X, P2.X) + halfThickness;
+        var minY = Math.Min(P1.Y, P2.Y) - halfThickness;
+        var maxY = Math.Max(P1.Y, P2.Y) + halfThickness;
+
+        if (p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY)
+            return false;
+
+        var a = P1;
+        var b = P2;
+
+        //If dot1 or dot2 is negative, then the angle between the perpendicular and the segment is obtuse.
+        //The distance from a point to a straight line is defined as the
+        //length of the vector formed by the point and the closest point of the segment
+
+        Vector ap = p - a;
+        var dot1 = Vector.Dot(b - a, ap);
+
+        if (dot1 < 0)
+            return ap.Length <= ClientPen.Thickness / 2;
+
+        Vector bp = p - b;
+        var dot2 = Vector.Dot(a - b, bp);
+
+        if (dot2 < 0)
+            return bp.Length <= halfThickness;
+
+        var bXaX = b.X - a.X;
+        var bYaY = b.Y - a.Y;
+
+        var distance = (bXaX * (p.Y - a.Y) - bYaY * (p.X - a.X)) /
+                       (Math.Sqrt(bXaX * bXaX + bYaY * bYaY));
+
+        return Math.Abs(distance) <= halfThickness;
+    }
+    
+
+    public void Invoke(ref RenderDataNodeRenderContext context) 
+        => context.Context.DrawLine(ServerPen, P1, P2);
+
+    public Rect? Bounds => LineBoundsHelper.CalculateBounds(P1, P2, ServerPen!);
+    public void Collect(IRenderDataServerResourcesCollector collector)
+    {
+        collector.AddRenderDataServerResource(ServerPen);
+    }
+}

+ 214 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs

@@ -0,0 +1,214 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Threading;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+enum RenderDataPopNodeType
+{
+    Transform,
+    Clip,
+    GeometryClip,
+    Opacity,
+    OpacityMask
+}
+
+interface IRenderDataServerResourcesCollector
+{
+    void AddRenderDataServerResource(object? obj);
+}
+
+interface IRenderDataItemWithServerResources : IRenderDataItem
+{
+    void Collect(IRenderDataServerResourcesCollector collector);
+}
+
+struct RenderDataNodeRenderContext : IDisposable
+{
+    private Stack<Matrix>? _stack;
+    private static readonly ThreadSafeObjectPool<Stack<Matrix>> s_matrixStackPool = new();
+    
+    public RenderDataNodeRenderContext(IDrawingContextImpl context)
+    {
+        Context = context;
+    }
+    public IDrawingContextImpl Context { get; }
+
+    public Stack<Matrix> MatrixStack
+    {
+        get => _stack ??= s_matrixStackPool.Get();
+    }
+
+    public void Dispose()
+    {
+        if (_stack != null)
+        {
+            _stack.Clear();
+            s_matrixStackPool.ReturnAndSetNull(ref _stack);
+        }
+    }
+}
+
+interface IRenderDataItem
+{
+    /// <summary>
+    /// Renders the node to a drawing context.
+    /// </summary>
+    /// <param name="context">The drawing context.</param>
+    void Invoke(ref RenderDataNodeRenderContext context);
+    
+    /// <summary>
+    /// Gets the bounds of the visible content in the node in global coordinates.
+    /// </summary>
+    Rect? Bounds { get; }
+    
+    /// <summary>
+    /// Hit test the geometry in this node.
+    /// </summary>
+    /// <param name="p">The point in global coordinates.</param>
+    /// <returns>True if the point hits the node's geometry; otherwise false.</returns>
+    /// <remarks>
+    /// This method does not recurse to childs, if you want
+    /// to hit test children they must be hit tested manually.
+    /// </remarks>
+    bool HitTest(Point p);
+}
+
+class RenderDataCustomNode : IRenderDataItem
+{
+    public ICustomDrawOperation? Operation { get; set; }
+    public bool HitTest(Point p) => Operation?.HitTest(p) ?? false;
+    public void Invoke(ref RenderDataNodeRenderContext context) => Operation?.Render(new(context.Context, false));
+
+    public Rect? Bounds => Operation?.Bounds;
+}
+
+abstract class RenderDataPushNode : IRenderDataItem, IDisposable
+{
+    public PooledInlineList<IRenderDataItem> Children;
+    public abstract void Push(ref RenderDataNodeRenderContext context);
+    public abstract void Pop(ref RenderDataNodeRenderContext context);
+    public void Invoke(ref RenderDataNodeRenderContext context)
+    {
+        if (Children.Count == 0)
+            return;
+        Push(ref context);
+        foreach (var ch in Children) 
+            ch.Invoke(ref context);
+        Pop(ref context);
+    }
+
+    public virtual Rect? Bounds
+    {
+        get
+        {
+            if (Children.Count == 0)
+                return null;
+            Rect? union = null;
+            foreach (var i in Children)
+                union = Rect.Union(union, i.Bounds);
+            return union;
+        }
+    }
+
+    public virtual bool HitTest(Point p)
+    {
+        if (Children.Count == 0)
+            return false;
+        foreach(var ch in Children)
+            if (ch.HitTest(p))
+                return true;
+        return false;
+    }
+
+    public void Dispose()
+    {
+        if (Children.Count > 0)
+        {
+            foreach(var ch in Children)
+                if (ch is RenderDataPushNode node)
+                    node.Dispose();
+            Children.Dispose();
+        }
+    }
+}
+
+class RenderDataClipNode : RenderDataPushNode
+{
+    public RoundedRect Rect { get; set; }
+    public override void Push(ref RenderDataNodeRenderContext context) =>
+        context.Context.PushClip(Rect);
+
+    public override void Pop(ref RenderDataNodeRenderContext context) =>
+        context.Context.PopClip();
+
+    public override bool HitTest(Point p)
+    {
+        if (!Rect.Rect.Contains(p))
+            return false;
+        return base.HitTest(p);
+    }
+}
+
+class RenderDataGeometryClipNode : RenderDataPushNode
+{
+    public IGeometryImpl? Geometry { get; set; }
+    public bool Contains(Point p) => Geometry?.FillContains(p) ?? false;
+    
+    public override void Push(ref RenderDataNodeRenderContext context)
+    {
+        if (Geometry != null)
+            context.Context.PushGeometryClip(Geometry);
+    }
+
+    public override void Pop(ref RenderDataNodeRenderContext context)
+    {
+        if (Geometry != null)
+            context.Context.PopGeometryClip();
+    }
+
+    public override bool HitTest(Point p)
+    {
+        if (Geometry != null && !Geometry.FillContains(p))
+            return false;
+        return base.HitTest(p);
+    }
+}
+
+class RenderDataOpacityNode : RenderDataPushNode
+{
+    public double Opacity { get; set; }
+    public Rect BoundsRect { get; set; }
+    public override void Push(ref RenderDataNodeRenderContext context)
+    {
+        if (Opacity != 1)
+            context.Context.PushOpacity(Opacity, BoundsRect);
+    }
+
+    public override void Pop(ref RenderDataNodeRenderContext context)
+    {
+        if (Opacity != 1)
+            context.Context.PopOpacity();
+    }
+}
+
+abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources
+{
+    public IBrush? ServerBrush { get; set; }
+    public IPen? ServerPen { get; set; }
+    public IPen? ClientPen { get; set; }
+    
+    public void Collect(IRenderDataServerResourcesCollector collector)
+    {
+        collector.AddRenderDataServerResource(ServerBrush);
+        collector.AddRenderDataServerResource(ServerPen);
+    }
+
+    public abstract void Invoke(ref RenderDataNodeRenderContext context);
+    public abstract Rect? Bounds { get; }
+    public abstract bool HitTest(Point p);
+}

+ 27 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs

@@ -0,0 +1,27 @@
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataPushMatrixNode : RenderDataPushNode
+{
+    public Matrix Matrix { get; set; }
+
+    public override void Push(ref RenderDataNodeRenderContext context)
+    {
+        var current = context.Context.Transform;
+        context.MatrixStack.Push(current);
+        context.Context.Transform = Matrix * current;
+    }
+
+    public override void Pop(ref RenderDataNodeRenderContext context)
+    {
+        context.Context.Transform = context.MatrixStack.Pop();
+    }
+
+    public override bool HitTest(Point p)
+    {
+        if (Matrix.TryInvert(out var inverted))
+            return base.HitTest(p.Transform(inverted));
+        return false;
+    }
+
+    public override Rect? Bounds => base.Bounds?.TransformToAABB(Matrix);
+}

+ 25 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs

@@ -0,0 +1,25 @@
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataOpacityMaskNode : RenderDataPushNode, IRenderDataItemWithServerResources
+{
+    public IBrush? ServerBrush { get; set; }
+
+    public Rect BoundsRect { get; set; }
+
+    public void Collect(IRenderDataServerResourcesCollector collector)
+    {
+        collector.AddRenderDataServerResource(ServerBrush);
+    }
+
+    public override void Push(ref RenderDataNodeRenderContext context)
+    {
+        if (ServerBrush != null)
+            context.Context.PushOpacityMask(ServerBrush, BoundsRect);
+    }
+
+    public override void Pop(ref RenderDataNodeRenderContext context) =>
+        context.Context.PopOpacityMask();
+}

+ 30 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs

@@ -0,0 +1,30 @@
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition.Drawing.Nodes;
+
+class RenderDataRectangleNode : RenderDataBrushAndPenNode
+{
+    public RoundedRect Rect { get; set; }
+    public BoxShadows BoxShadows { get; set; }
+    
+    public override bool HitTest(Point p)
+    {
+        if (ServerBrush != null) // it's safe to check for null
+        {
+            var rect = Rect.Rect.Inflate((ClientPen?.Thickness / 2) ?? 0);
+            return rect.ContainsExclusive(p);
+        }
+        else
+        {
+            var borderRect = Rect.Rect.Inflate((ClientPen?.Thickness / 2) ?? 0);
+            var emptyRect = Rect.Rect.Deflate((ClientPen?.Thickness / 2) ?? 0);
+            return borderRect.ContainsExclusive(p) && !emptyRect.ContainsExclusive(p);
+        }
+    }
+
+    public override void Invoke(ref RenderDataNodeRenderContext context) =>
+        context.Context.DrawRectangle(ServerBrush, ServerPen, Rect, BoxShadows);
+
+    public override Rect? Bounds => BoxShadows.TransformBounds(Rect.Rect).Inflate((ServerPen?.Thickness ?? 0) / 2);
+}

+ 349 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs

@@ -0,0 +1,349 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition.Drawing.Nodes;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Threading;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Drawing;
+
+internal class RenderDataDrawingContext : DrawingContext
+{
+    private readonly Compositor? _compositor;
+    private CompositionRenderData? _renderData;
+    private HashSet<object>? _resourcesHashSet;
+    private static readonly ThreadSafeObjectPool<HashSet<object>> s_hashSetPool = new();
+    private CompositionRenderData RenderData
+    {
+        get
+        {
+            Debug.Assert(_compositor != null);
+            return _renderData ??= new(_compositor);
+        }
+    }
+    
+    struct ParentStackItem
+    {
+        public RenderDataPushNode? Node;
+        public List<IRenderDataItem> Items;
+    }
+    
+    private List<IRenderDataItem>? _currentItemList;
+    private static readonly ThreadSafeObjectPool<List<IRenderDataItem>> s_listPool = new();
+
+    private Stack<ParentStackItem>? _parentNodeStack;
+    private static readonly ThreadSafeObjectPool<Stack<ParentStackItem>> s_parentStackPool = new();
+
+    public RenderDataDrawingContext(Compositor? compositor)
+    {
+        _compositor = compositor;
+    }
+
+    void Add(IRenderDataItem item)
+    {
+        _currentItemList ??= s_listPool.Get();
+        _currentItemList.Add(item);
+    }
+
+    void Push(RenderDataPushNode? node = null)
+    {
+        // Push a fake no-op node so something could be popped by the corresponding Pop call
+        // Since there is no nesting, we don't update the item list
+        if (node == null)
+        {
+            (_parentNodeStack ??= s_parentStackPool.Get()).Push(default);
+            return;
+        }    
+        Add(node);
+        (_parentNodeStack ??= s_parentStackPool.Get()).Push(new ParentStackItem
+        {
+            Node = node,
+            Items = _currentItemList!
+        });
+        _currentItemList = null;
+    }
+
+    void Pop<T>() where T : IRenderDataItem
+    {
+        var parent = _parentNodeStack!.Pop();
+        
+        // No-op node
+        if (parent.Node == null)
+            return;
+
+        if (!(parent.Node is T))
+            throw new InvalidOperationException("Invalid Pop operation");
+        
+        foreach(var item in _currentItemList!)
+            parent.Node.Children.Add(item);
+        _currentItemList.Clear();
+        s_listPool.ReturnAndSetNull(ref _currentItemList);
+        _currentItemList = parent.Items;
+    }
+
+    void AddResource(object? resource)
+    {
+        if (_compositor == null)
+            return;
+        
+        if (resource == null
+            || resource is IImmutableBrush
+            || resource is ImmutablePen
+            || resource is ImmutableTransform)
+            return;
+        
+        if (resource is ICompositionRenderResource renderResource)
+        {
+            _resourcesHashSet ??= s_hashSetPool.Get();
+            if (!_resourcesHashSet.Add(renderResource))
+                return;
+            
+            renderResource.AddRefOnCompositor(_compositor);
+            RenderData.AddResource(renderResource);
+            return;
+        }
+
+        throw new InvalidOperationException(resource.GetType().FullName + " can not be used with this DrawingContext");
+    }
+    
+    protected override void DrawLineCore(IPen? pen, Point p1, Point p2)
+    {
+        if(pen == null)
+            return;
+        AddResource(pen);
+        Add(new RenderDataLineNode
+        {
+            ClientPen = pen,
+            ServerPen = pen.GetServer(_compositor),
+            P1 = p1,
+            P2 = p2
+        });
+    }
+
+    protected override void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry)
+    {
+        if (brush == null && pen == null)
+            return;
+        AddResource(brush);
+        AddResource(pen);
+        Add(new RenderDataGeometryNode
+        {
+            ServerBrush = brush.GetServer(_compositor),
+            ServerPen = pen.GetServer(_compositor),
+            ClientPen = pen,
+            Geometry = geometry
+        });
+    }
+
+    protected override void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default)
+    {
+        if (rrect.IsEmpty())
+            return;
+        if(brush == null && pen == null && boxShadows == default)
+            return;
+        AddResource(brush);
+        AddResource(pen);
+        Add(new RenderDataRectangleNode
+        {
+            ServerBrush = brush.GetServer(_compositor),
+            ServerPen = pen.GetServer(_compositor),
+            ClientPen = pen,
+            Rect = rrect,
+            BoxShadows = boxShadows
+        });
+    }
+
+    protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect)
+    {
+        if (rect.IsEmpty())
+            return;
+        
+        if(brush == null && pen == null)
+            return;
+        AddResource(brush);
+        AddResource(pen);
+        Add(new RenderDataEllipseNode
+        {
+            ServerBrush = brush.GetServer(_compositor),
+            ServerPen = pen.GetServer(_compositor),
+            ClientPen = pen,
+            Rect = rect,
+        });
+    }
+
+    public override void Custom(ICustomDrawOperation custom) => Add(new RenderDataCustomNode());
+
+    public override void DrawGlyphRun(IBrush? foreground, GlyphRun? glyphRun)
+    {
+        if (foreground == null || glyphRun == null)
+            return;
+        AddResource(foreground);
+        Add(new RenderDataGlyphRunNode
+        {
+            ServerBrush = foreground.GetServer(_compositor),
+            GlyphRun = glyphRun.PlatformImpl.Clone()
+        });
+    }
+
+    protected override void PushClipCore(RoundedRect rect) => Push(new RenderDataClipNode
+    {
+        Rect = rect
+    });
+
+    protected override void PushClipCore(Rect rect) => Push(new RenderDataClipNode
+    {
+        Rect = rect
+    });
+
+    protected override void PushGeometryClipCore(Geometry? clip)
+    {
+        if (clip == null)
+            Push();
+        else
+            Push(new RenderDataGeometryClipNode
+            {
+                Geometry = clip?.PlatformImpl
+            });
+    }
+
+    protected override void PushOpacityCore(double opacity, Rect bounds)
+    {
+        if (opacity == 1)
+            Push();
+        else
+            Push(new RenderDataOpacityNode
+            {
+                Opacity = opacity,
+                BoundsRect = bounds
+            });
+    }
+
+    protected override void PushOpacityMaskCore(IBrush? mask, Rect bounds)
+    {
+        if(mask == null)
+            Push();
+        else
+        {
+            AddResource(mask);
+            Push(new RenderDataOpacityMaskNode
+            {
+                ServerBrush = mask.GetServer(_compositor),
+                BoundsRect = bounds
+            });
+        }
+    }
+
+    protected override void PushTransformCore(Matrix matrix)
+    {
+        if (matrix.IsIdentity)
+            Push();
+        else
+            Push(new RenderDataPushMatrixNode()
+            {
+                Matrix = matrix
+            });
+    }
+
+    protected override void PopClipCore() => Pop<RenderDataClipNode>();
+
+    protected override void PopGeometryClipCore() => Pop<RenderDataGeometryClipNode>();
+
+    protected override void PopOpacityCore() => Pop<RenderDataOpacityNode>();
+
+    protected override void PopOpacityMaskCore() => Pop<RenderDataOpacityMaskNode>();
+
+    protected override void PopTransformCore() => Pop<RenderDataPushMatrixNode>();
+
+    internal override void DrawBitmap(IRef<IBitmapImpl>? source, double opacity, Rect sourceRect, Rect destRect)
+    {
+        if (source == null || sourceRect.IsEmpty() || destRect.IsEmpty())
+            return;
+        Add(new RenderDataBitmapNode
+        {
+            Bitmap = source.Clone(),
+            Opacity = opacity,
+            SourceRect = sourceRect,
+            DestRect = destRect
+        });
+    }
+
+
+    void FlushStack()
+    {
+        // Flush stack
+        if (_parentNodeStack != null)
+        {
+            // TODO: throw error, unbalanced stack
+            while (_parentNodeStack.Count > 0) 
+                Pop<IRenderDataItem>();
+        }
+        
+
+    }
+    
+    public CompositionRenderData? GetRenderResults()
+    {
+        Debug.Assert(_compositor != null);
+        
+        FlushStack();
+        
+        // Transfer items to RenderData
+        if (_currentItemList is { Count: > 0 })
+        {
+            foreach (var i in _currentItemList)
+                RenderData.Add(i);
+            _currentItemList.Clear();
+        }
+
+        var rv = _renderData;
+        _renderData = null;
+        _resourcesHashSet?.Clear();
+        
+        if (rv != null)
+            _compositor.RegisterForSerialization(rv);
+        return rv;
+    }
+
+    public ImmediateRenderDataSceneBrushContent? GetImmediateSceneBrushContent(ITileBrush brush, Rect? rect, bool useScalableRasterization)
+    {
+        Debug.Assert(_compositor == null);
+        Debug.Assert(_resourcesHashSet == null);
+        Debug.Assert(_renderData == null);
+        
+        FlushStack();
+        if (_currentItemList == null || _currentItemList.Count == 0)
+            return null;
+
+        var itemList = _currentItemList;
+        _currentItemList = null;
+
+        return new ImmediateRenderDataSceneBrushContent(brush, itemList, rect, useScalableRasterization, s_listPool);
+    }
+
+    public void Reset()
+    {
+        // This means that render data should be discarded
+        if (_renderData != null)
+        {
+            _renderData.Dispose();
+            _renderData = null;
+        }
+
+        _currentItemList?.Clear();
+        _parentNodeStack?.Clear();
+        _resourcesHashSet?.Clear();
+    }
+    
+    protected override void DisposeCore()
+    {
+        Reset();
+        if (_resourcesHashSet != null) 
+            s_hashSetPool.ReturnAndSetNull(ref _resourcesHashSet);
+    }
+}

+ 136 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs

@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition.Drawing.Nodes;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
+using Avalonia.Threading;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Drawing;
+
+class ServerCompositionRenderData : SimpleServerRenderResource
+{
+    private PooledInlineList<IRenderDataItem> _items;
+    private PooledInlineList<IServerRenderResource> _referencedResources;
+    private Rect? _bounds;
+    private bool _boundsValid;
+    private static readonly ThreadSafeObjectPool<Collector> s_resourceHashSetPool = new();
+
+    public ServerCompositionRenderData(ServerCompositor compositor) : base(compositor)
+    {
+    }
+
+    class Collector : IRenderDataServerResourcesCollector
+    {
+        public readonly HashSet<IServerRenderResource> Resources = new();
+        public void AddRenderDataServerResource(object? obj)
+        {
+            if (obj is IServerRenderResource res)
+                Resources.Add(res);
+        }
+    }
+    
+    protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
+    {
+        Reset();
+        
+        var count = reader.Read<int>();
+        _items.EnsureCapacity(count);
+        for (var c = 0; c < count; c++)
+            _items.Add(reader.ReadObject<IRenderDataItem>());
+        
+        var collector = s_resourceHashSetPool.Get();
+        foreach(var item in _items)
+            if (item is IRenderDataItemWithServerResources resourceItem)
+                resourceItem.Collect(collector);
+        
+        foreach (var r in collector.Resources)
+        {
+            _referencedResources.Add(r);
+            r.AddObserver(this);
+        }
+        
+        collector.Resources.Clear();
+        s_resourceHashSetPool.ReturnAndSetNull(ref collector);
+        
+        base.DeserializeChangesCore(reader, committedAt);
+    }
+
+    public Rect? Bounds
+    {
+        get
+        {
+            if (!_boundsValid)
+            {
+                _bounds = CalculateRenderBounds();
+                _boundsValid = true;
+            }
+            return _bounds;
+        }
+    }
+
+    private Rect? CalculateRenderBounds()
+    {
+        Rect? totalBounds = null;
+        foreach (var item in _items) 
+            totalBounds = Rect.Union(totalBounds, item.Bounds);
+        
+        return ApplyRenderBoundsRounding(totalBounds);
+    }
+
+    public static Rect? ApplyRenderBoundsRounding(Rect? rect)
+    {
+        if (rect != null)
+        {
+            var r = rect.Value;
+            // I don't believe that it's correct to do here (rather than in CompositionVisual),
+            // but it's the old behavior, so I'm keeping it for now
+            return new Rect(
+                new Point(Math.Floor(r.X), Math.Floor(r.Y)),
+                new Point(Math.Ceiling(r.Right), Math.Ceiling(r.Bottom)));
+        }
+
+        return null;
+    }
+
+    public override void DependencyQueuedInvalidate(IServerRenderResource sender)
+    {
+        _boundsValid = false;
+        base.DependencyQueuedInvalidate(sender);
+    }
+    
+    public void Render(IDrawingContextImpl context)
+    {
+        var ctx = new RenderDataNodeRenderContext(context);
+        try
+        {
+            foreach (var item in _items) 
+                item.Invoke(ref ctx);
+        }
+        finally
+        {
+            ctx.Dispose();
+        }
+    }
+
+    void Reset()
+    {
+        _bounds = null;
+        _boundsValid = false;
+        foreach (var r in _referencedResources)
+            r.RemoveObserver(this);
+        _referencedResources.Dispose();
+        foreach(var i in _items)
+            if (i is IDisposable disp)
+                disp.Dispose();
+        _items.Dispose();
+    }
+    
+    public override void Dispose()
+    {
+        Reset();
+        base.Dispose();
+    }
+}

+ 12 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionSimplePen.cs

@@ -0,0 +1,12 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal partial class ServerCompositionSimplePen : IPen
+{
+    IDashStyle? IPen.DashStyle => DashStyle;
+}

+ 56 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/ServerResourceHelperExtensions.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+
+namespace Avalonia.Rendering.Composition.Drawing;
+
+static class ServerResourceHelperExtensions
+{
+    public static IBrush? GetServer(this IBrush? brush, Compositor? compositor)
+    {
+        if (compositor == null)
+            return brush;
+        if (brush == null)
+            return null;
+        if (brush is IImmutableBrush immutable)
+            return immutable;
+        if (brush is ICompositionRenderResource<IBrush> resource)
+            return resource.GetForCompositor(compositor);
+        ThrowNotCompatible(brush);
+        return null;
+    }
+
+    public static IPen? GetServer(this IPen? pen, Compositor? compositor)
+    {
+        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");
+    
+    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);
+        ThrowNotCompatible(transform);
+        return null;
+    }
+}

+ 10 - 0
src/Avalonia.Base/Rendering/Composition/ICompositorSerializable.cs

@@ -0,0 +1,10 @@
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Rendering.Composition.Transport;
+
+namespace Avalonia.Rendering.Composition;
+
+internal interface ICompositorSerializable
+{
+    SimpleServerObject? TryGetServer(Compositor c);
+    void SerializeChanges(Compositor c, BatchStreamWriter writer);
+}

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs

@@ -68,7 +68,7 @@ namespace Avalonia.Rendering.Composition.Server
                 var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
                 var run = _runs[effectiveChar - FirstChar];
                 context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
-                context.DrawGlyphRun(foreground, run.PlatformImpl);
+                context.DrawGlyphRun(foreground, run.PlatformImpl.Item);
                 offset += run.Bounds.Width;
             }
 

+ 6 - 3
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@@ -2,6 +2,7 @@ using System;
 using System.Numerics;
 using Avalonia.Media;
 using Avalonia.Media.Imaging;
+using Avalonia.Media.Immutable;
 using Avalonia.Platform;
 using Avalonia.Rendering.Composition.Drawing;
 using Avalonia.Rendering.SceneGraph;
@@ -53,12 +54,12 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
         _impl.Clear(color);
     }
 
-    public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
+    public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
     {
         _impl.DrawBitmap(source, opacity, sourceRect, destRect);
     }
 
-    public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
+    public void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
     {
         _impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect);
     }
@@ -83,7 +84,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
         _impl.DrawEllipse(brush, pen, rect);
     }
 
-    public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
+    public void DrawGlyphRun(IBrush? foreground, IGlyphRunImpl glyphRun)
     {
         _impl.DrawGlyphRun(foreground, glyphRun);
     }
@@ -145,6 +146,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
     {
         if (_impl is IDrawingContextWithAcrylicLikeSupport acrylic) 
             acrylic.DrawRectangle(material, rect);
+        else
+            _impl.DrawRectangle(new ImmutableSolidColorBrush(material.FallbackColor), null, rect);
     }
 
     public void PushEffect(IEffect effect)

+ 3 - 21
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@@ -20,7 +20,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
     // This is needed for debugging purposes so we could see inspect the associated visual from debugger
     public readonly Visual UiVisual;
 #endif
-    private CompositionDrawList? _renderCommands;
+    private ServerCompositionRenderData? _renderCommands;
     
     public ServerCompositionDrawListVisual(ServerCompositor compositor, Visual v) : base(compositor)
     {
@@ -29,32 +29,14 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
 #endif
     }
 
-    Rect? _contentBounds;
-
-    public override Rect OwnContentBounds
-    {
-        get
-        {
-            if (_contentBounds == null)
-            {
-                var rect = default(Rect);
-                if(_renderCommands!=null)
-                    foreach (var cmd in _renderCommands)
-                        rect = rect.Union(cmd.Item.Bounds);
-                _contentBounds = rect;
-            }
-
-            return _contentBounds.Value;
-        }
-    }
+    public override Rect OwnContentBounds => _renderCommands?.Bounds ?? default;
 
     protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
     {
         if (reader.Read<byte>() == 1)
         {
             _renderCommands?.Dispose();
-            _renderCommands = reader.ReadObject<CompositionDrawList?>();
-            _contentBounds = null;
+            _renderCommands = reader.ReadObject<ServerCompositionRenderData?>();
         }
         base.DeserializeChangesCore(reader, committedAt);
     }

+ 26 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs

@@ -0,0 +1,26 @@
+using Avalonia.Media.Immutable;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal partial class ServerCompositionExperimentalAcrylicVisual
+{
+    protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
+    {
+        var cornerRadius = CornerRadius;
+        canvas.DrawRectangle(
+            Material,
+            new RoundedRect(
+                new Rect(0, 0, Size.X, Size.Y),
+                cornerRadius.TopLeft, cornerRadius.TopRight,
+                cornerRadius.BottomRight, cornerRadius.BottomLeft));
+
+        base.RenderCore(canvas, currentTransformedClip);
+    }
+
+    public override Rect OwnContentBounds => new(0, 0, Size.X, Size.Y);
+
+    public ServerCompositionExperimentalAcrylicVisual(ServerCompositor compositor, Visual v) : base(compositor, v)
+    {
+    }
+}

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs

@@ -13,7 +13,7 @@ internal partial class ServerCompositionSurfaceVisual
         var bmp = Surface.Bitmap.Item;
 
         //TODO: add a way to always render the whole bitmap instead of just assuming 96 DPI
-        canvas.DrawBitmap(Surface.Bitmap, 1, new Rect(bmp.PixelSize.ToSize(1)), new Rect(
+        canvas.DrawBitmap(Surface.Bitmap.Item, 1, new Rect(bmp.PixelSize.ToSize(1)), new Rect(
             new Size(Size.X, Size.Y)));
     }
 

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@@ -179,7 +179,7 @@ namespace Avalonia.Rendering.Composition.Server
                 if (_layer.CanBlit)
                     _layer.Blit(targetContext);
                 else
-                    targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1,
+                    targetContext.DrawBitmap(_layer, 1,
                         new Rect(_layerSize),
                         new Rect(Size));
 

+ 22 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.RenderResources.cs

@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+partial class ServerCompositor
+{
+    private Queue<IServerRenderResource> _renderResourcesInvalidationQueue = new();
+    private HashSet<IServerRenderResource> _renderResourcesInvalidationSet = new();
+    
+    public void ApplyEnqueuedRenderResourceChanges()
+    {
+        while (_renderResourcesInvalidationQueue.TryDequeue(out var obj)) 
+            obj.QueuedInvalidate();
+        _renderResourcesInvalidationSet.Clear();
+    }
+
+    public void EnqueueRenderResourceForInvalidation(IServerRenderResource resource)
+    {
+        if (_renderResourcesInvalidationSet.Add(resource))
+            _renderResourcesInvalidationQueue.Enqueue(resource);
+    }
+}

+ 21 - 2
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Rendering.Composition.Server
     /// 2) triggers animation ticks
     /// 3) asks composition targets to render themselves
     /// </summary>
-    internal class ServerCompositor : IRenderLoopTask
+    internal partial class ServerCompositor : IRenderLoopTask
     {
         private readonly IRenderLoop _renderLoop;
 
@@ -35,6 +35,7 @@ namespace Avalonia.Rendering.Composition.Server
         private object _lock = new object();
         private Thread? _safeThread;
         public PlatformRenderInterfaceContextManager RenderInterface { get; }
+        internal static readonly object RenderThreadDisposeStartMarker = new();
         internal static readonly object RenderThreadJobsStartMarker = new();
         internal static readonly object RenderThreadJobsEndMarker = new();
 
@@ -79,8 +80,14 @@ namespace Avalonia.Rendering.Composition.Server
                             ReadServerJobs(stream);
                             continue;
                         }
+
+                        if (readObject == RenderThreadDisposeStartMarker)
+                        {
+                            ReadDisposeJobs(stream);
+                            continue;
+                        }
                         
-                        var target = (ServerObject)readObject!;
+                        var target = (SimpleServerObject)readObject!;
                         target.DeserializeChanges(stream, batch);
 #if DEBUG_COMPOSITOR_SERIALIZATION
                         if (stream.ReadObject() != BatchStreamDebugMarkers.ObjectEndMarker)
@@ -105,6 +112,16 @@ namespace Avalonia.Rendering.Composition.Server
                 _receivedJobQueue.Enqueue((Action)readObject!);
         }
 
+        void ReadDisposeJobs(BatchStreamReader reader)
+        {
+            var count = reader.Read<int>();
+            while (count > 0)
+            {
+                (reader.ReadObject() as IDisposable)?.Dispose();
+                count--;
+            }
+        }
+
         void ExecuteServerJobs()
         {
             while(_receivedJobQueue.Count > 0)
@@ -160,6 +177,8 @@ namespace Avalonia.Rendering.Composition.Server
                 animation.OnTick();
             
             _clockItemsToUpdate.Clear();
+
+            ApplyEnqueuedRenderResourceChanges();
             
             try
             {

+ 0 - 11
src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs

@@ -26,17 +26,6 @@ namespace Avalonia.Rendering.Composition.Server
             base.DeserializeChangesCore(reader, committedAt);
         }
 
-        public override long LastChangedBy
-        {
-            get
-            {
-                var seq = base.LastChangedBy;
-                foreach (var i in List)
-                    seq = Math.Max(i.LastChangedBy, seq);
-                return seq;
-            }
-        }
-
         public List<T>.Enumerator GetEnumerator() => List.GetEnumerator();
 
         public ServerList(ServerCompositor compositor) : base(compositor)

+ 4 - 28
src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs

@@ -15,12 +15,8 @@ namespace Avalonia.Rendering.Composition.Server
     /// Server-side <see cref="CompositionObject" /> counterpart.
     /// Is responsible for animation activation and invalidation
     /// </summary>
-    internal abstract class ServerObject : IExpressionObject
+    internal abstract class ServerObject : SimpleServerObject, IExpressionObject
     {
-        public ServerCompositor Compositor { get; }
-
-        public virtual long LastChangedBy => ItselfLastChangedBy;
-        public long ItselfLastChangedBy { get; private set; }
         private uint _activationCount;
         public bool IsActive => _activationCount != 0;
         private InlineDictionary<CompositionProperty, ServerObjectSubscriptionStore> _subscriptions;
@@ -42,9 +38,8 @@ namespace Avalonia.Rendering.Composition.Server
             }
         }
             
-        public ServerObject(ServerCompositor compositor)
+        public ServerObject(ServerCompositor compositor) : base(compositor)
         {
-            Compositor = compositor;
         }
 
         public virtual ExpressionVariant GetPropertyForAnimation(string name)
@@ -90,13 +85,13 @@ namespace Avalonia.Rendering.Composition.Server
                 subs.Invalidate();
         }
 
-        protected void SetValue<T>(CompositionProperty prop, out T field, T value)
+        protected new void SetValue<T>(CompositionProperty prop, ref T field, T value)
         {
             field = value;
             InvalidateSubscriptions(prop);
         }
 
-        protected T GetValue<T>(CompositionProperty prop, ref T field)
+        protected new T GetValue<T>(CompositionProperty prop, ref T field)
         {
             if (_subscriptions.TryGetValue(prop, out var subs))
                 subs.IsValid = true;
@@ -143,11 +138,6 @@ namespace Avalonia.Rendering.Composition.Server
             ValuesInvalidated();
         }
 
-        protected virtual void ValuesInvalidated()
-        {
-            
-        }
-
         public void SubscribeToInvalidation(CompositionProperty member, IAnimationInstance animation)
         {
             if (!_subscriptions.TryGetValue(member, out var store))
@@ -164,19 +154,5 @@ namespace Avalonia.Rendering.Composition.Server
         }
 
         public virtual CompositionProperty? GetCompositionProperty(string fieldName) => null;
-
-        protected virtual void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
-        {
-            if (this is IDisposable disp
-                && reader.Read<byte>() == 1)
-                disp.Dispose();
-        }
-        
-        public void DeserializeChanges(BatchStreamReader reader, Batch batch)
-        {
-            DeserializeChangesCore(reader, batch.CommittedAt);
-            ValuesInvalidated();
-            ItselfLastChangedBy = batch.SequenceId;
-        }
     }
 }

+ 123 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs

@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Avalonia.Rendering.Composition.Transport;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal interface IServerRenderResourceObserver
+{
+    void DependencyQueuedInvalidate(IServerRenderResource sender);
+}
+
+internal interface IServerRenderResource : IServerRenderResourceObserver
+{
+    void AddObserver(IServerRenderResource observer);
+    void RemoveObserver(IServerRenderResource observer);
+    void QueuedInvalidate();
+}
+
+internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderResource, IDisposable
+{
+    private bool _pendingInvalidation;
+    private bool _disposed;
+    public bool IsDisposed => _disposed;
+    private RefCountingSmallDictionary<IServerRenderResource> _observers;
+    
+    public SimpleServerRenderResource(ServerCompositor compositor) : base(compositor)
+    {
+    }
+
+    protected new void SetValue<T>(CompositionProperty prop, ref T field, T value) => SetValue(ref field, value);
+    
+    protected void SetValue<T>(ref T field, T value)
+    {
+        if (EqualityComparer<T>.Default.Equals(field, value))
+            return;
+        
+        if (_disposed)
+        {
+            field = value;
+            return;
+        }
+
+        if (field is IServerRenderResource oldChild)
+            oldChild.RemoveObserver(this);
+        else if (field is IServerRenderResource[] oldChildren)
+        {
+            foreach (var ch in oldChildren)
+                ch?.RemoveObserver(this);
+        }
+        field = value;
+        if (field is IServerRenderResource newChild)
+            newChild.AddObserver(this);
+        else if (field is IServerRenderResource[] newChildren)
+        {
+            foreach (var ch in newChildren)
+                ch.AddObserver(this);
+        }
+        Invalidated();
+    }
+
+    protected void Invalidated()
+    {
+        // This is needed to avoid triggering on multiple property changes
+        if (!_pendingInvalidation)
+        {
+            _pendingInvalidation = true;
+            Compositor.EnqueueRenderResourceForInvalidation(this);
+            PropertyChanged();
+        }
+    }
+
+    protected override void ValuesInvalidated()
+    {
+        Invalidated();
+        base.ValuesInvalidated();
+    }
+
+    protected void RemoveObserversFromProperty<T>(ref T field)
+    {
+        (field as IServerRenderResource)?.RemoveObserver(this);
+    }
+
+    public virtual void Dispose()
+    {
+        _disposed = true;
+        // TODO: dispose once we implement pooling
+        _observers = default;
+    }
+
+    public virtual void DependencyQueuedInvalidate(IServerRenderResource sender) =>
+        Compositor.EnqueueRenderResourceForInvalidation(this);
+
+    protected virtual void PropertyChanged()
+    {
+        
+    }
+    
+    public void AddObserver(IServerRenderResource observer)
+    {
+        Debug.Assert(!_disposed);
+        if(_disposed)
+            return;
+        _observers.Add(observer);
+    }
+
+    public void RemoveObserver(IServerRenderResource observer)
+    {
+        if (_disposed)
+            return;
+        _observers.Remove(observer);
+    }
+
+    public virtual void QueuedInvalidate()
+    {
+        _pendingInvalidation = false;
+
+        foreach (var observer in _observers)
+            observer.Key.DependencyQueuedInvalidate(this);
+
+    }
+}

+ 34 - 0
src/Avalonia.Base/Rendering/Composition/Server/SimpleServerObject.cs

@@ -0,0 +1,34 @@
+using System;
+using Avalonia.Rendering.Composition.Transport;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+class SimpleServerObject
+{
+    public ServerCompositor Compositor { get; }
+
+    public SimpleServerObject(ServerCompositor compositor)
+    {
+        Compositor = compositor;
+    }
+
+    protected virtual void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
+    {
+
+    }
+
+    public void DeserializeChanges(BatchStreamReader reader, Batch batch)
+    {
+        DeserializeChangesCore(reader, batch.CommittedAt);
+        ValuesInvalidated();
+    }
+
+    protected virtual void ValuesInvalidated()
+    {
+
+    }
+    
+    protected void SetValue<T>(CompositionProperty prop, ref T field, T value) => field = value;
+
+    protected T GetValue<T>(CompositionProperty prop, ref T field) => field;
+}

+ 0 - 27
src/Avalonia.Base/Rendering/SceneGraph/BrushDrawOperation.cs

@@ -1,27 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Media;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
-    /// <summary>
-    /// Base class for draw operations that can use a brush.
-    /// </summary>
-    internal abstract class BrushDrawOperation : DrawOperationWithTransform
-    {
-        public IImmutableBrush? Brush { get; }
-
-        public BrushDrawOperation(Rect bounds, Matrix transform, IImmutableBrush? brush)
-            : base(bounds, transform)
-        {
-            Brush = brush;
-        }
-
-        public override void Dispose()
-        {
-            (Brush as ISceneBrushContent)?.Dispose();
-            base.Dispose();
-        }
-    }
-}

+ 0 - 87
src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs

@@ -1,87 +0,0 @@
-using Avalonia.Platform;
-
-namespace Avalonia.Rendering.SceneGraph
-{
-    /// <summary>
-    /// A node in the scene graph which represents a clip push or pop.
-    /// </summary>
-    internal class ClipNode : IDrawOperationWithTransform
-    {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
-        /// clip push.
-        /// </summary>
-        /// <param name="transform">The current transform.</param>
-        /// <param name="clip">The clip to push.</param>
-        public ClipNode(Matrix transform, Rect clip)
-        {
-            Transform = transform;
-            Clip = clip;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
-        /// clip push.
-        /// </summary>
-        /// <param name="transform">The current transform.</param>
-        /// <param name="clip">The clip to push.</param>
-        public ClipNode(Matrix transform, RoundedRect clip)
-        {
-            Transform = transform;
-            Clip = clip;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
-        /// clip pop.
-        /// </summary>
-        public ClipNode()
-        {
-        }
-
-        /// <inheritdoc/>
-        public Rect Bounds => default;
-
-        /// <summary>
-        /// Gets the clip to be pushed or null if the operation represents a pop.
-        /// </summary>
-        public RoundedRect? Clip { get; }
-
-        /// <summary>
-        /// Gets the transform with which the node will be drawn.
-        /// </summary>
-        public Matrix Transform { get; }
-
-        /// <inheritdoc/>
-        public bool HitTest(Point p) => false;
-
-        /// <summary>
-        /// Determines if this draw operation equals another.
-        /// </summary>
-        /// <param name="transform">The transform of the other draw operation.</param>
-        /// <param name="clip">The clip of the other draw operation.</param>
-        /// <returns>True if the draw operations are the same, otherwise false.</returns>
-        /// <remarks>
-        /// The properties of the other draw operation are passed in as arguments to prevent
-        /// allocation of a not-yet-constructed draw operation object.
-        /// </remarks>
-        public bool Equals(Matrix transform, RoundedRect? clip) => Transform == transform && Clip == clip;
-
-        /// <inheritdoc/>
-        public void Render(IDrawingContextImpl context)
-        {
-            if (Clip.HasValue)
-            {
-                context.PushClip(Clip.Value);
-            }
-            else
-            {
-                context.PopClip();
-            }
-        }
-
-        public void Dispose()
-        {
-        }
-    }
-}

+ 0 - 31
src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs

@@ -5,37 +5,6 @@ using Avalonia.Platform;
 
 namespace Avalonia.Rendering.SceneGraph
 {
-    internal sealed class CustomDrawOperation : DrawOperationWithTransform
-    {
-        public ICustomDrawOperation Custom { get; }
-        public CustomDrawOperation(ICustomDrawOperation custom, Matrix transform) 
-            : base(custom.Bounds, transform)
-        {
-            Custom = custom;
-        }
-
-        public override bool HitTestTransformed(Point p) => Custom.HitTest(p);
-
-        public override void Render(IDrawingContextImpl context)
-        {
-            using var immediateDrawingContext = new ImmediateDrawingContext(context, false);
-            try
-            {
-                Custom.Render(immediateDrawingContext);
-            }
-            catch (Exception e)
-            {
-                Logger.TryGet(LogEventLevel.Error, LogArea.Visual)
-                    ?.Log(Custom, $"Exception in {Custom.GetType().Name}.{nameof(ICustomDrawOperation.Render)} {{0}}", e);
-            }
-        }
-
-        public override void Dispose() => Custom.Dispose();
-
-        public bool Equals(Matrix transform, ICustomDrawOperation custom) =>
-            Transform == transform && Custom?.Equals(custom) == true;
-    }
-
     public interface ICustomDrawOperation : IEquatable<ICustomDrawOperation>, IDisposable
     {
         /// <summary>

+ 0 - 56
src/Avalonia.Base/Rendering/SceneGraph/DrawOperation.cs

@@ -1,56 +0,0 @@
-using System;
-using Avalonia.Media;
-using Avalonia.Platform;
-
-namespace Avalonia.Rendering.SceneGraph
-{
-    /// <summary>
-    /// Base class for draw operations that have bounds.
-    /// </summary>
-    internal abstract class DrawOperation : IDrawOperation
-    {
-        public DrawOperation(Rect bounds, Matrix transform)
-        {
-            bounds = bounds.Normalize().TransformToAABB(transform);
-
-            Bounds = new Rect(
-                new Point(Math.Floor(bounds.X), Math.Floor(bounds.Y)),
-                new Point(Math.Ceiling(bounds.Right), Math.Ceiling(bounds.Bottom)));
-        }
-
-        public Rect Bounds { get; }
-
-        public abstract bool HitTest(Point p);
-
-        public abstract void Render(IDrawingContextImpl context);
-
-        public virtual void Dispose()
-        {
-        }
-    }
-
-    internal abstract class DrawOperationWithTransform : DrawOperation, IDrawOperationWithTransform
-    {
-        protected DrawOperationWithTransform(Rect bounds, Matrix transform) : base(bounds, transform)
-        {
-            Transform = transform;
-        }
-
-        public Matrix Transform { get; }
-
-        public sealed override bool HitTest(Point p)
-        {
-            if (Transform.IsIdentity)
-                return HitTestTransformed(p);
-
-            if (!Transform.HasInverse)
-                return false;
-
-            var transformedPoint = Transform.Invert().Transform(p);
-
-            return HitTestTransformed(transformedPoint);
-        }
-
-        public abstract bool HitTestTransformed(Point p);
-    }
-}

+ 0 - 97
src/Avalonia.Base/Rendering/SceneGraph/EllipseNode.cs

@@ -1,97 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Media;
-using Avalonia.Media.Immutable;
-using Avalonia.Platform;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering.SceneGraph
-{
-    /// <summary>
-    /// A node in the scene graph which represents an ellipse draw.
-    /// </summary>
-    internal class EllipseNode : BrushDrawOperation
-    {
-        public EllipseNode(
-            Matrix transform, 
-            IImmutableBrush? brush, 
-            IPen? pen, 
-            Rect rect) 
-            : base(rect.Inflate(pen?.Thickness ?? 0), transform, brush)
-        {
-            Pen = pen?.ToImmutable();
-            Rect = rect;
-        }
-
-        /// <summary>
-        /// Gets the stroke pen.
-        /// </summary>
-        public ImmutablePen? Pen { get; }
-
-        /// <summary>
-        /// Gets the rect of the ellipse to draw.
-        /// </summary>
-        public Rect Rect { get; }
-
-        public bool Equals(Matrix transform, IBrush? brush, IPen? pen, Rect rect)
-        {
-            return transform == Transform &&
-                   Equals(brush, Brush) &&
-                   Equals(Pen, pen) &&
-                   rect.Equals(Rect);
-        }
-
-        public override void Render(IDrawingContextImpl context) => context.DrawEllipse(Brush, Pen, Rect);
-
-        public override bool HitTestTransformed(Point p)
-        {
-            var center = Rect.Center;
-
-            var strokeThickness = Pen?.Thickness ?? 0;
-
-            var rx = Rect.Width / 2 + strokeThickness / 2;
-            var ry = Rect.Height / 2 + strokeThickness / 2;
-
-            var dx = p.X - center.X;
-            var dy = p.Y - center.Y;
-
-            if (Math.Abs(dx) > rx || Math.Abs(dy) > ry)
-            {
-                return false;
-            }
-
-            if (Brush != null)
-            {
-                return Contains(rx, ry);
-            }
-            else if (strokeThickness > 0)
-            {
-                bool inStroke = Contains(rx, ry);
-
-                rx = Rect.Width / 2 - strokeThickness / 2;
-                ry = Rect.Height / 2 - strokeThickness / 2;
-
-                bool inInner = Contains(rx, ry);
-
-                return inStroke && !inInner;
-            }
-
-            bool Contains(double radiusX, double radiusY)
-            {
-                var rx2 = radiusX * radiusX;
-                var ry2 = radiusY * radiusY;
-
-                var distance = ry2 * dx * dx + rx2 * dy * dy;
-
-                return distance <= rx2 * ry2;
-            }
-
-            return false;
-        }
-
-        public override void Dispose()
-        {
-            (Brush as ISceneBrushContent)?.Dispose();
-        }
-    }
-}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác