csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
7.7 KiB
264 lines
7.7 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Collections;
|
|
using Avalonia.Collections.Pooled;
|
|
using Avalonia.Diagnostics;
|
|
using Avalonia.Media;
|
|
using Avalonia.Rendering.Composition.Drawing;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
|
|
namespace Avalonia.Rendering.Composition;
|
|
|
|
/// <summary>
|
|
/// A renderer that utilizes <see cref="Avalonia.Rendering.Composition.Compositor"/> to render the visual tree
|
|
/// </summary>
|
|
internal class CompositingRenderer : IRendererWithCompositor, IHitTester
|
|
{
|
|
private readonly IRenderRoot _root;
|
|
private readonly Compositor _compositor;
|
|
private readonly RenderDataDrawingContext _recorder;
|
|
private readonly HashSet<Visual> _dirty = new();
|
|
private readonly HashSet<Visual> _recalculateChildren = new();
|
|
private readonly Action _update;
|
|
|
|
private bool _queuedUpdate;
|
|
private bool _queuedSceneInvalidation;
|
|
private bool _updating;
|
|
private bool _isDisposed;
|
|
|
|
internal CompositionTarget CompositionTarget { get; }
|
|
|
|
/// <inheritdoc/>
|
|
public RendererDiagnostics Diagnostics { get; }
|
|
|
|
/// <inheritdoc />
|
|
public Compositor Compositor => _compositor;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of <see cref="CompositingRenderer"/>
|
|
/// </summary>
|
|
/// <param name="root">The render root using this renderer.</param>
|
|
/// <param name="compositor">The associated compositors.</param>
|
|
/// <param name="surfaces">
|
|
/// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
|
|
/// </param>
|
|
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces)
|
|
{
|
|
_root = root;
|
|
_compositor = compositor;
|
|
_recorder = new(compositor);
|
|
CompositionTarget = compositor.CreateCompositionTarget(surfaces);
|
|
CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
|
|
_update = Update;
|
|
Diagnostics = new RendererDiagnostics();
|
|
Diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
|
|
}
|
|
|
|
private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
switch (e.PropertyName)
|
|
{
|
|
case nameof(RendererDiagnostics.DebugOverlays):
|
|
CompositionTarget.DebugOverlays = Diagnostics.DebugOverlays;
|
|
break;
|
|
case nameof(RendererDiagnostics.LastLayoutPassTiming):
|
|
CompositionTarget.LastLayoutPassTiming = Diagnostics.LastLayoutPassTiming;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
|
|
|
|
private void QueueUpdate()
|
|
{
|
|
if(_queuedUpdate)
|
|
return;
|
|
_queuedUpdate = true;
|
|
_compositor.RequestCompositionUpdate(_update);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void AddDirty(Visual visual)
|
|
{
|
|
if (_isDisposed)
|
|
return;
|
|
if (_updating)
|
|
throw new InvalidOperationException("Visual was invalidated during the render pass");
|
|
_dirty.Add(visual);
|
|
QueueUpdate();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IEnumerable<Visual> HitTest(Point p, Visual? root, Func<Visual, bool>? filter)
|
|
{
|
|
using var _ = Diagnostic.PerformingHitTest();
|
|
|
|
CompositionVisual? rootVisual = null;
|
|
if (root != null)
|
|
{
|
|
if (root.CompositionVisual == null)
|
|
yield break;
|
|
rootVisual = root.CompositionVisual;
|
|
}
|
|
|
|
Func<CompositionVisual, bool>? f = null;
|
|
if (filter != null)
|
|
f = v =>
|
|
{
|
|
if (v is CompositionDrawListVisual dlv)
|
|
return filter(dlv.Visual);
|
|
return true;
|
|
};
|
|
|
|
using var res = CompositionTarget.TryHitTest(p, rootVisual, f);
|
|
if(res == null)
|
|
yield break;
|
|
foreach(var v in res)
|
|
{
|
|
if (v is CompositionDrawListVisual dv)
|
|
{
|
|
if (filter == null || filter(dv.Visual))
|
|
yield return dv.Visual;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public Visual? HitTestFirst(Point p, Visual root, Func<Visual, bool>? filter)
|
|
{
|
|
// TODO: Optimize
|
|
return HitTest(p, root, filter).FirstOrDefault();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void RecalculateChildren(Visual visual)
|
|
{
|
|
if (_isDisposed)
|
|
return;
|
|
if (_updating)
|
|
throw new InvalidOperationException("Visual was invalidated during the render pass");
|
|
_recalculateChildren.Add(visual);
|
|
QueueUpdate();
|
|
}
|
|
|
|
private void UpdateCore()
|
|
{
|
|
_queuedUpdate = false;
|
|
foreach (var visual in _dirty)
|
|
{
|
|
var comp = visual.CompositionVisual;
|
|
if(comp == null)
|
|
continue;
|
|
|
|
visual.SynchronizeCompositionProperties();
|
|
|
|
try
|
|
{
|
|
visual.Render(_recorder);
|
|
comp.DrawList = _recorder.GetRenderResults();
|
|
}
|
|
finally
|
|
{
|
|
_recorder.Reset();
|
|
}
|
|
|
|
visual.SynchronizeCompositionChildVisuals();
|
|
}
|
|
foreach(var v in _recalculateChildren)
|
|
if (!_dirty.Contains(v))
|
|
v.SynchronizeCompositionChildVisuals();
|
|
_dirty.Clear();
|
|
_recalculateChildren.Clear();
|
|
|
|
CompositionTarget.PixelSize = PixelSize.FromSizeRounded(_root.ClientSize, _root.RenderScaling);
|
|
CompositionTarget.Scaling = _root.RenderScaling;
|
|
|
|
var commit = _compositor.RequestCommitAsync();
|
|
if (!_queuedSceneInvalidation)
|
|
{
|
|
_queuedSceneInvalidation = true;
|
|
commit.ContinueWith(_ => Dispatcher.UIThread.Post(() =>
|
|
{
|
|
_queuedSceneInvalidation = false;
|
|
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
|
|
}, DispatcherPriority.Input), TaskContinuationOptions.ExecuteSynchronously);
|
|
}
|
|
}
|
|
|
|
public void TriggerSceneInvalidatedForUnitTests(Rect rect) =>
|
|
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, rect));
|
|
|
|
private void Update()
|
|
{
|
|
if(_updating)
|
|
return;
|
|
_updating = true;
|
|
try
|
|
{
|
|
using (Diagnostic.BeginLayoutRenderPass())
|
|
UpdateCore();
|
|
}
|
|
finally
|
|
{
|
|
_updating = false;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Resized(Size size)
|
|
{
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Paint(Rect rect) => Paint(rect, true);
|
|
public void Paint(Rect rect, bool catchExceptions)
|
|
{
|
|
if (_isDisposed)
|
|
return;
|
|
|
|
QueueUpdate();
|
|
CompositionTarget.RequestRedraw();
|
|
MediaContext.Instance.ImmediateRenderRequested(CompositionTarget, catchExceptions);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Start()
|
|
{
|
|
if (_isDisposed)
|
|
return;
|
|
|
|
CompositionTarget.IsEnabled = true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Stop()
|
|
=> CompositionTarget.IsEnabled = false;
|
|
|
|
/// <inheritdoc />
|
|
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
|
|
=> Compositor.TryGetRenderInterfaceFeature(featureType);
|
|
|
|
public bool IsDisposed => _isDisposed;
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
if (_isDisposed)
|
|
return;
|
|
|
|
_isDisposed = true;
|
|
_dirty.Clear();
|
|
_recalculateChildren.Clear();
|
|
SceneInvalidated = null;
|
|
|
|
Stop();
|
|
|
|
MediaContext.Instance.SyncDisposeCompositionTarget(CompositionTarget);
|
|
}
|
|
}
|
|
|