committed by
GitHub
199 changed files with 4242 additions and 2699 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); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue