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() =>
_resource.RegisterForInvalidationOnAllCompositors(this);
private protected bool IsOnCompositor(Compositor c) => _resource.TryGetForCompositor(c) != null;
private CompositorResourceHolder<ServerCompositionSimpleBrush> _resource;
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 =>
static c => new ServerCompositionSimpleContentBrush(c.Server);
private InlineDictionary<Compositor, CompositionRenderDataSceneBrushContent?> _renderDataDictionary;
private InlineDictionary<Compositor, CompositionRenderData?> _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<IBrush>)this).GetForCompositor(c),
renderData, null, true);
return recorder.GetRenderResults();
}
}
}

65
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<Compositor, ServerCompositionSimpleBrush> Factory =>
static c => new ServerCompositionSimpleContentBrush(c.Server);
private InlineDictionary<Compositor, CompositionRenderDataSceneBrushContent?> _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<Compositor, RenderDataItem?> _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<IBrush>)this).GetForCompositor(c),
renderData, new(Visual.Bounds.Size), false);
return new(renderData, new(Visual.Bounds.Size));
}
}
}

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
{
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<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
{
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; }

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

@ -127,6 +127,18 @@ internal struct InlineDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey,
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 TryGetValue(TKey key, [MaybeNullWhen(false)]out TValue value)

Loading…
Cancel
Save