diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index 21be08b4af..8c3491ed33 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -101,6 +101,8 @@ namespace Avalonia.Media private protected void RegisterForSerialization() => _resource.RegisterForInvalidationOnAllCompositors(this); + private protected bool IsOnCompositor(Compositor c) => _resource.TryGetForCompositor(c) != null; + private CompositorResourceHolder _resource; IBrush ICompositionRenderResource.GetForCompositor(Compositor c) => _resource.GetForCompositor(c); diff --git a/src/Avalonia.Base/Media/DrawingBrush.cs b/src/Avalonia.Base/Media/DrawingBrush.cs index b710351b98..c4e5dc8d13 100644 --- a/src/Avalonia.Base/Media/DrawingBrush.cs +++ b/src/Avalonia.Base/Media/DrawingBrush.cs @@ -58,7 +58,7 @@ namespace Avalonia.Media internal override Func Factory => static c => new ServerCompositionSimpleContentBrush(c.Server); - private InlineDictionary _renderDataDictionary; + private InlineDictionary _renderDataDictionary; private protected override void OnReferencedFromCompositor(Compositor c) { @@ -69,33 +69,27 @@ namespace Avalonia.Media protected override void OnUnreferencedFromCompositor(Compositor c) { if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content)) - content?.RenderData.Dispose(); + content?.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); + if (_renderDataDictionary.TryGetValue(c, out var content) && content != null) + writer.WriteObject(new CompositionRenderDataSceneBrushContent.Properties(content.Server, null, true)); else writer.WriteObject(null); } - CompositionRenderDataSceneBrushContent? CreateServerContent(Compositor c) + CompositionRenderData? CreateServerContent(Compositor c) { if (Drawing == null) return null; using var recorder = new RenderDataDrawingContext(c); Drawing?.Draw(recorder); - var renderData = recorder.GetRenderResults(); - if (renderData == null) - return null; - - return new CompositionRenderDataSceneBrushContent( - (ServerCompositionSimpleContentBrush)((ICompositionRenderResource)this).GetForCompositor(c), - renderData, null, true); + return recorder.GetRenderResults(); } } } diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index 15d4f39d6c..1e315688e9 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Collections.Pooled; using Avalonia.Media.Immutable; using Avalonia.Rendering; using Avalonia.Rendering.Composition; @@ -62,31 +63,65 @@ namespace Avalonia.Media internal override Func Factory => static c => new ServerCompositionSimpleContentBrush(c.Server); - private InlineDictionary _renderDataDictionary; - - private protected override void OnReferencedFromCompositor(Compositor c) + class RenderDataItem(CompositionRenderData data, Rect rect) : IDisposable { - _renderDataDictionary.Add(c, CreateServerContent(c)); - base.OnReferencedFromCompositor(c); + public CompositionRenderData Data { get; } = data; + public Rect Rect { get; } = rect; + public bool IsDirty; + public void Dispose() => Data?.Dispose(); } - + + private InlineDictionary _renderDataDictionary; + protected override void OnUnreferencedFromCompositor(Compositor c) { if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content)) - content?.RenderData.Dispose(); + content?.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.Properties? content = null; + if (IsOnCompositor(c)) // Should always be true here, but just in case do this check + { + _renderDataDictionary.TryGetValue(c, out var data); + if (data == null || data.IsDirty) + { + var created = CreateServerContent(c); + // Dispose the old render list _after_ creating a new one to avoid unnecessary detach/attach + // sequence for referenced resources + if (data != null) + data.Dispose(); + + _renderDataDictionary[c] = data = created; + } + + if (data != null) + content = new(data.Data.Server, data.Rect, false); + } + + writer.WriteObject(content); } - CompositionRenderDataSceneBrushContent? CreateServerContent(Compositor c) + void InvalidateContent() + { + foreach(var item in _renderDataDictionary) + if (item.Value != null) + item.Value.IsDirty = true; + RegisterForSerialization(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + // We are supposed to be only calling this when content is actually changed, + // but instead we are calling this on brush property change for backwards compat with 0.10.x + InvalidateContent(); + base.OnPropertyChanged(change); + } + + RenderDataItem? CreateServerContent(Compositor c) { if (Visual == null) return null; @@ -99,10 +134,8 @@ namespace Avalonia.Media var renderData = recorder.GetRenderResults(); if (renderData == null) return null; - - return new CompositionRenderDataSceneBrushContent( - (ServerCompositionSimpleContentBrush)((ICompositionRenderResource)this).GetForCompositor(c), - renderData, new(Visual.Bounds.Size), false); + + return new(renderData, new(Visual.Bounds.Size)); } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs index 31eaf47925..673804f7bd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs +++ b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs @@ -7,18 +7,21 @@ namespace Avalonia.Rendering.Composition.Server; internal sealed class ServerCompositionSimpleContentBrush : ServerCompositionSimpleTileBrush, ITileBrush, ISceneBrush { - private CompositionRenderDataSceneBrushContent? _content; + private CompositionRenderDataSceneBrushContent.Properties? _content; + internal ServerCompositionSimpleContentBrush(ServerCompositor compositor) : base(compositor) { } - // TODO: Figure out something about disposable - public ISceneBrushContent? CreateContent() => _content; + public ISceneBrushContent? CreateContent() => + _content == null || _content.RenderData.IsDisposed + ? null + : new CompositionRenderDataSceneBrushContent(this, _content); protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { base.DeserializeChangesCore(reader, committedAt); - _content = reader.ReadObject(); + _content = reader.ReadObject(); } } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs index 4a46b07294..a8b9da743a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs @@ -6,20 +6,21 @@ namespace Avalonia.Rendering.Composition.Drawing; internal class CompositionRenderDataSceneBrushContent : ISceneBrushContent { - public CompositionRenderData RenderData { get; } + public ServerCompositionRenderData RenderData { get; } private readonly Rect? _rect; - public CompositionRenderDataSceneBrushContent(ITileBrush brush, CompositionRenderData renderData, Rect? rect, - bool useScalableRasterization) + public record Properties(ServerCompositionRenderData RenderData, Rect? Rect, bool UseScalableRasterization); + + public CompositionRenderDataSceneBrushContent(ITileBrush brush, Properties properties) { Brush = brush; - _rect = rect; - UseScalableRasterization = useScalableRasterization; - RenderData = renderData; + _rect = properties.Rect; + UseScalableRasterization = properties.UseScalableRasterization; + RenderData = properties.RenderData; } public ITileBrush Brush { get; } - public Rect Rect => _rect ?? (RenderData.Server?.Bounds?.ToRect() ?? default); + public Rect Rect => _rect ?? (RenderData?.Bounds?.ToRect() ?? default); public double Opacity => Brush.Opacity; public ITransform? Transform => Brush.Transform; @@ -36,11 +37,11 @@ internal class CompositionRenderDataSceneBrushContent : ISceneBrushContent { var oldTransform = context.Transform; context.Transform = transform.Value * oldTransform; - RenderData.Server.Render(context); + RenderData.Render(context); context.Transform = oldTransform; } else - RenderData.Server.Render(context); + RenderData.Render(context); } public bool UseScalableRasterization { get; } diff --git a/src/Avalonia.Base/Utilities/SmallDictionary.cs b/src/Avalonia.Base/Utilities/SmallDictionary.cs index a78c7988f7..ea6769c1ba 100644 --- a/src/Avalonia.Base/Utilities/SmallDictionary.cs +++ b/src/Avalonia.Base/Utilities/SmallDictionary.cs @@ -127,6 +127,18 @@ internal struct InlineDictionary : IEnumerable dic) + dic.Clear(); + else + _data = null; + } + public bool HasEntries => _data != null; public bool TryGetValue(TKey key, [MaybeNullWhen(false)]out TValue value)