148 changed files with 3372 additions and 2355 deletions
@ -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; |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
|
|||
} |
|||
|
|||
|
|||
} |
|||
@ -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?>(); |
|||
} |
|||
} |
|||
@ -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>>(); |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Avalonia.Media; |
|||
|
|||
// ReSharper disable CheckNamespace
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal partial class ServerCompositionSimpleTransform : ITransform |
|||
{ |
|||
|
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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; } |
|||
|
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
|
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue