Browse Source

Restore 0.10.x behavior for re-creating visual brush content on property change (#15838)

release/11.1.3
Nikita Tsukanov 2 years ago
committed by Max Katz
parent
commit
0fe5deba51
  1. 2
      src/Avalonia.Base/Media/Brush.cs
  2. 18
      src/Avalonia.Base/Media/DrawingBrush.cs
  3. 65
      src/Avalonia.Base/Media/VisualBrush.cs
  4. 11
      src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs
  5. 19
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs
  6. 12
      src/Avalonia.Base/Utilities/SmallDictionary.cs

2
src/Avalonia.Base/Media/Brush.cs

@ -101,6 +101,8 @@ namespace Avalonia.Media
private protected void RegisterForSerialization() => private protected void RegisterForSerialization() =>
_resource.RegisterForInvalidationOnAllCompositors(this); _resource.RegisterForInvalidationOnAllCompositors(this);
private protected bool IsOnCompositor(Compositor c) => _resource.TryGetForCompositor(c) != null;
private CompositorResourceHolder<ServerCompositionSimpleBrush> _resource; private CompositorResourceHolder<ServerCompositionSimpleBrush> _resource;
IBrush ICompositionRenderResource<IBrush>.GetForCompositor(Compositor c) => _resource.GetForCompositor(c); IBrush ICompositionRenderResource<IBrush>.GetForCompositor(Compositor c) => _resource.GetForCompositor(c);

18
src/Avalonia.Base/Media/DrawingBrush.cs

@ -58,7 +58,7 @@ namespace Avalonia.Media
internal override Func<Compositor, ServerCompositionSimpleBrush> Factory => internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
static c => new ServerCompositionSimpleContentBrush(c.Server); static c => new ServerCompositionSimpleContentBrush(c.Server);
private InlineDictionary<Compositor, CompositionRenderDataSceneBrushContent?> _renderDataDictionary; private InlineDictionary<Compositor, CompositionRenderData?> _renderDataDictionary;
private protected override void OnReferencedFromCompositor(Compositor c) private protected override void OnReferencedFromCompositor(Compositor c)
{ {
@ -69,33 +69,27 @@ namespace Avalonia.Media
protected override void OnUnreferencedFromCompositor(Compositor c) protected override void OnUnreferencedFromCompositor(Compositor c)
{ {
if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content)) if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content))
content?.RenderData.Dispose(); content?.Dispose();
base.OnUnreferencedFromCompositor(c); base.OnUnreferencedFromCompositor(c);
} }
private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer) private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
{ {
base.SerializeChanges(c, writer); base.SerializeChanges(c, writer);
if (_renderDataDictionary.TryGetValue(c, out var content)) if (_renderDataDictionary.TryGetValue(c, out var content) && content != null)
writer.WriteObject(content); writer.WriteObject(new CompositionRenderDataSceneBrushContent.Properties(content.Server, null, true));
else else
writer.WriteObject(null); writer.WriteObject(null);
} }
CompositionRenderDataSceneBrushContent? CreateServerContent(Compositor c) CompositionRenderData? CreateServerContent(Compositor c)
{ {
if (Drawing == null) if (Drawing == null)
return null; return null;
using var recorder = new RenderDataDrawingContext(c); using var recorder = new RenderDataDrawingContext(c);
Drawing?.Draw(recorder); Drawing?.Draw(recorder);
var renderData = recorder.GetRenderResults(); return recorder.GetRenderResults();
if (renderData == null)
return null;
return new CompositionRenderDataSceneBrushContent(
(ServerCompositionSimpleContentBrush)((ICompositionRenderResource<IBrush>)this).GetForCompositor(c),
renderData, null, true);
} }
} }
} }

65
src/Avalonia.Base/Media/VisualBrush.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Collections.Pooled;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
@ -62,31 +63,65 @@ namespace Avalonia.Media
internal override Func<Compositor, ServerCompositionSimpleBrush> Factory => internal override Func<Compositor, ServerCompositionSimpleBrush> Factory =>
static c => new ServerCompositionSimpleContentBrush(c.Server); static c => new ServerCompositionSimpleContentBrush(c.Server);
private InlineDictionary<Compositor, CompositionRenderDataSceneBrushContent?> _renderDataDictionary; class RenderDataItem(CompositionRenderData data, Rect rect) : IDisposable
private protected override void OnReferencedFromCompositor(Compositor c)
{ {
_renderDataDictionary.Add(c, CreateServerContent(c)); public CompositionRenderData Data { get; } = data;
base.OnReferencedFromCompositor(c); public Rect Rect { get; } = rect;
public bool IsDirty;
public void Dispose() => Data?.Dispose();
} }
private InlineDictionary<Compositor, RenderDataItem?> _renderDataDictionary;
protected override void OnUnreferencedFromCompositor(Compositor c) protected override void OnUnreferencedFromCompositor(Compositor c)
{ {
if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content)) if (_renderDataDictionary.TryGetAndRemoveValue(c, out var content))
content?.RenderData.Dispose(); content?.Dispose();
base.OnUnreferencedFromCompositor(c); base.OnUnreferencedFromCompositor(c);
} }
private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer) private protected override void SerializeChanges(Compositor c, BatchStreamWriter writer)
{ {
base.SerializeChanges(c, writer); base.SerializeChanges(c, writer);
if (_renderDataDictionary.TryGetValue(c, out var content)) CompositionRenderDataSceneBrushContent.Properties? content = null;
writer.WriteObject(content); if (IsOnCompositor(c)) // Should always be true here, but just in case do this check
else {
writer.WriteObject(null); _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) if (Visual == null)
return null; return null;
@ -99,10 +134,8 @@ namespace Avalonia.Media
var renderData = recorder.GetRenderResults(); var renderData = recorder.GetRenderResults();
if (renderData == null) if (renderData == null)
return null; return null;
return new CompositionRenderDataSceneBrushContent( return new(renderData, new(Visual.Bounds.Size));
(ServerCompositionSimpleContentBrush)((ICompositionRenderResource<IBrush>)this).GetForCompositor(c),
renderData, new(Visual.Bounds.Size), false);
} }
} }
} }

11
src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleContentBrush.cs

@ -7,18 +7,21 @@ namespace Avalonia.Rendering.Composition.Server;
internal sealed class ServerCompositionSimpleContentBrush : ServerCompositionSimpleTileBrush, ITileBrush, ISceneBrush internal sealed class ServerCompositionSimpleContentBrush : ServerCompositionSimpleTileBrush, ITileBrush, ISceneBrush
{ {
private CompositionRenderDataSceneBrushContent? _content; private CompositionRenderDataSceneBrushContent.Properties? _content;
internal ServerCompositionSimpleContentBrush(ServerCompositor compositor) : base(compositor) internal ServerCompositionSimpleContentBrush(ServerCompositor compositor) : base(compositor)
{ {
} }
// TODO: Figure out something about disposable public ISceneBrushContent? CreateContent() =>
public ISceneBrushContent? CreateContent() => _content; _content == null || _content.RenderData.IsDisposed
? null
: new CompositionRenderDataSceneBrushContent(this, _content);
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
{ {
base.DeserializeChangesCore(reader, committedAt); base.DeserializeChangesCore(reader, committedAt);
_content = reader.ReadObject<CompositionRenderDataSceneBrushContent?>(); _content = reader.ReadObject<CompositionRenderDataSceneBrushContent.Properties?>();
} }
} }

19
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs

@ -6,20 +6,21 @@ namespace Avalonia.Rendering.Composition.Drawing;
internal class CompositionRenderDataSceneBrushContent : ISceneBrushContent internal class CompositionRenderDataSceneBrushContent : ISceneBrushContent
{ {
public CompositionRenderData RenderData { get; } public ServerCompositionRenderData RenderData { get; }
private readonly Rect? _rect; private readonly Rect? _rect;
public CompositionRenderDataSceneBrushContent(ITileBrush brush, CompositionRenderData renderData, Rect? rect, public record Properties(ServerCompositionRenderData RenderData, Rect? Rect, bool UseScalableRasterization);
bool useScalableRasterization)
public CompositionRenderDataSceneBrushContent(ITileBrush brush, Properties properties)
{ {
Brush = brush; Brush = brush;
_rect = rect; _rect = properties.Rect;
UseScalableRasterization = useScalableRasterization; UseScalableRasterization = properties.UseScalableRasterization;
RenderData = renderData; RenderData = properties.RenderData;
} }
public ITileBrush Brush { get; } 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 double Opacity => Brush.Opacity;
public ITransform? Transform => Brush.Transform; public ITransform? Transform => Brush.Transform;
@ -36,11 +37,11 @@ internal class CompositionRenderDataSceneBrushContent : ISceneBrushContent
{ {
var oldTransform = context.Transform; var oldTransform = context.Transform;
context.Transform = transform.Value * oldTransform; context.Transform = transform.Value * oldTransform;
RenderData.Server.Render(context); RenderData.Render(context);
context.Transform = oldTransform; context.Transform = oldTransform;
} }
else else
RenderData.Server.Render(context); RenderData.Render(context);
} }
public bool UseScalableRasterization { get; } public bool UseScalableRasterization { get; }

12
src/Avalonia.Base/Utilities/SmallDictionary.cs

@ -127,6 +127,18 @@ internal struct InlineDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey,
return false; return false;
} }
public void Clear()
{
if(_data == null)
return;
if(_data is KeyValuePair[] arr)
Array.Clear(arr, 0, arr.Length);
else if (_data is Dictionary<TKey, TValue?> dic)
dic.Clear();
else
_data = null;
}
public bool HasEntries => _data != null; public bool HasEntries => _data != null;
public bool TryGetValue(TKey key, [MaybeNullWhen(false)]out TValue value) public bool TryGetValue(TKey key, [MaybeNullWhen(false)]out TValue value)

Loading…
Cancel
Save