Browse Source

Merge branch 'master' into feature/SpringAnimation

pull/8992/head
Max Katz 3 years ago
committed by GitHub
parent
commit
e3ba2cbee9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  2. 2
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  3. 32
      src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
  4. 134
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  5. 10
      src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
  6. 11
      src/Avalonia.Base/Threading/DispatcherPriority.cs

31
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Collections.Pooled; using Avalonia.Collections.Pooled;
using Avalonia.Media; using Avalonia.Media;
@ -27,8 +28,7 @@ public class CompositingRenderer : IRendererWithCompositor
private HashSet<Visual> _dirty = new(); private HashSet<Visual> _dirty = new();
private HashSet<Visual> _recalculateChildren = new(); private HashSet<Visual> _recalculateChildren = new();
private bool _queuedUpdate; private bool _queuedUpdate;
private Action _update; private Action<Task> _update;
private Action _invalidateScene;
private bool _updating; private bool _updating;
internal CompositionTarget CompositionTarget; internal CompositionTarget CompositionTarget;
@ -47,7 +47,6 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget); CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget);
CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor);
_update = Update; _update = Update;
_invalidateScene = InvalidateScene;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -72,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate) if(_queuedUpdate)
return; return;
_queuedUpdate = true; _queuedUpdate = true;
_compositor.InvokeWhenReadyForNextCommit(_update); _compositor.InvokeBeforeNextCommit(_update);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -151,12 +150,6 @@ public class CompositingRenderer : IRendererWithCompositor
if (compositionChildren.Count == visualChildren.Count) if (compositionChildren.Count == visualChildren.Count)
{ {
bool mismatch = false; bool mismatch = false;
if (v.HasNonUniformZIndexChildren)
{
}
if (sortedChildren != null) if (sortedChildren != null)
for (var c = 0; c < visualChildren.Count; c++) for (var c = 0; c < visualChildren.Count; c++)
{ {
@ -202,9 +195,6 @@ public class CompositingRenderer : IRendererWithCompositor
} }
} }
private void InvalidateScene() =>
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
private void UpdateCore() private void UpdateCore()
{ {
_queuedUpdate = false; _queuedUpdate = false;
@ -252,10 +242,15 @@ public class CompositingRenderer : IRendererWithCompositor
_recalculateChildren.Clear(); _recalculateChildren.Clear();
CompositionTarget.Size = _root.ClientSize; CompositionTarget.Size = _root.ClientSize;
CompositionTarget.Scaling = _root.RenderScaling; CompositionTarget.Scaling = _root.RenderScaling;
Compositor.InvokeOnNextCommit(_invalidateScene);
} }
private void Update() private async void TriggerSceneInvalidatedOnBatchCompletion(Task batchCompletion)
{
await batchCompletion;
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
}
private void Update(Task batchCompletion)
{ {
if(_updating) if(_updating)
return; return;
@ -276,10 +271,10 @@ public class CompositingRenderer : IRendererWithCompositor
public void Paint(Rect rect) public void Paint(Rect rect)
{ {
Update(); QueueUpdate();
CompositionTarget.RequestRedraw(); CompositionTarget.RequestRedraw();
if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground) if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground)
Compositor.RequestCommitAsync().Wait(); Compositor.Commit().Wait();
else else
CompositionTarget.ImmediateUIThreadRender(); CompositionTarget.ImmediateUIThreadRender();
} }
@ -299,7 +294,7 @@ public class CompositingRenderer : IRendererWithCompositor
// Wait for the composition batch to be applied and rendered to guarantee that // Wait for the composition batch to be applied and rendered to guarantee that
// render target is not used anymore and can be safely disposed // render target is not used anymore and can be safely disposed
if (Compositor.Loop.RunsInBackground) if (Compositor.Loop.RunsInBackground)
_compositor.RequestCommitAsync().Wait(); _compositor.Commit().Wait();
} }
/// <summary> /// <summary>

2
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -129,7 +129,7 @@ namespace Avalonia.Rendering.Composition
/// </summary> /// </summary>
internal void ImmediateUIThreadRender() internal void ImmediateUIThreadRender()
{ {
Compositor.RequestCommitAsync(); Compositor.Commit();
Compositor.Server.Render(); Compositor.Server.Render();
} }
} }

32
src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs

@ -0,0 +1,32 @@
using System;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition;
public partial class Compositor
{
/// <summary>
/// Creates a new CompositionTarget
/// </summary>
/// <param name="renderTargetFactory">A factory method to create IRenderTarget to be called from the render thread</param>
/// <returns></returns>
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory)
{
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory));
}
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));
public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this);
public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this)
{
Expression = expression
};
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this);
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this);
}

134
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -10,6 +10,7 @@ using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
using Avalonia.Threading; using Avalonia.Threading;
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> // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
@ -24,16 +25,18 @@ namespace Avalonia.Rendering.Composition
{ {
internal IRenderLoop Loop { get; } internal IRenderLoop Loop { get; }
private ServerCompositor _server; private ServerCompositor _server;
private bool _implicitBatchCommitQueued; private TaskCompletionSource<int>? _nextCommit;
private Action _implicitBatchCommit; private Action _commit;
private BatchStreamObjectPool<object?> _batchObjectPool = new(); private BatchStreamObjectPool<object?> _batchObjectPool = new();
private BatchStreamMemoryPool _batchMemoryPool = new(); private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new(); private List<CompositionObject> _objectsForSerialization = new();
private Queue<Action<Task>> _invokeBeforeCommit = new();
internal ServerCompositor Server => _server; internal ServerCompositor Server => _server;
private Task? _pendingBatch;
private readonly object _pendingBatchLock = new();
internal IEasing DefaultEasing { get; } internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary> /// <summary>
/// Creates a new compositor on a specified render loop that would use a particular GPU /// Creates a new compositor on a specified render loop that would use a particular GPU
@ -44,21 +47,15 @@ namespace Avalonia.Rendering.Composition
{ {
Loop = loop; Loop = loop;
_server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool); _server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool);
_implicitBatchCommit = ImplicitBatchCommit; _commit = () =>
{
Console.WriteLine("Dispatcher:Commit");
Commit();
};
DefaultEasing = new CubicBezierEasing(new Point(0.25f, 0.1f), new Point(0.25f, 1f)); DefaultEasing = new CubicBezierEasing(new Point(0.25f, 0.1f), new Point(0.25f, 1f));
} }
/// <summary>
/// Creates a new CompositionTarget
/// </summary>
/// <param name="renderTargetFactory">A factory method to create IRenderTarget to be called from the render thread</param>
/// <returns></returns>
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory)
{
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory));
}
/// <summary> /// <summary>
/// Requests pending changes in the composition objects to be serialized and sent to the render thread /// Requests pending changes in the composition objects to be serialized and sent to the render thread
/// </summary> /// </summary>
@ -66,7 +63,35 @@ namespace Avalonia.Rendering.Composition
public Task RequestCommitAsync() public Task RequestCommitAsync()
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
var batch = new Batch(); if (_nextCommit == null)
{
_nextCommit = new TaskCompletionSource<int>();
var pending = _pendingBatch;
if (pending != null)
{
pending.ContinueWith(_ =>
{
Dispatcher.UIThread.Post(_commit, DispatcherPriority.Composition);
});
}
else
Dispatcher.UIThread.Post(_commit, DispatcherPriority.Composition);
}
return _nextCommit.Task;
}
internal Task Commit()
{
Dispatcher.UIThread.VerifyAccess();
using var noPump = NonPumpingLockHelper.Use();
_nextCommit ??= new TaskCompletionSource<int>();
while (_invokeBeforeCommit.Count > 0)
_invokeBeforeCommit.Dequeue()(_nextCommit.Task);
var batch = new Batch(_nextCommit);
using (var writer = new BatchStreamWriter(batch.Changes, _batchMemoryPool, _batchObjectPool)) using (var writer = new BatchStreamWriter(batch.Changes, _batchMemoryPool, _batchObjectPool))
{ {
@ -84,71 +109,36 @@ namespace Avalonia.Rendering.Composition
batch.CommitedAt = Server.Clock.Elapsed; batch.CommitedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch); _server.EnqueueBatch(batch);
if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed);
return _lastBatchCompleted = batch.Completed; lock (_pendingBatchLock)
} {
_pendingBatch = _nextCommit.Task;
async void ScheduleCommitCallbacks(Task task) _pendingBatch.ContinueWith(t =>
{ {
var list = _invokeOnNextCommit; lock (_pendingBatchLock)
_invokeOnNextCommit = null; {
await task; if (_pendingBatch == t)
foreach (var i in list!) _pendingBatch = null;
i(); }
list.Clear(); }, TaskContinuationOptions.ExecuteSynchronously);
_invokeListPool.Push(list); _nextCommit = null;
}
return _pendingBatch;
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server)); }
public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this);
public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this)
{
Expression = expression
};
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this);
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this);
private void QueueImplicitBatchCommit()
{
if(_implicitBatchCommitQueued)
return;
_implicitBatchCommitQueued = true;
Dispatcher.UIThread.Post(_implicitBatchCommit, DispatcherPriority.CompositionBatch);
}
private void ImplicitBatchCommit()
{
_implicitBatchCommitQueued = false;
RequestCommitAsync();
} }
internal void RegisterForSerialization(CompositionObject compositionObject) internal void RegisterForSerialization(CompositionObject compositionObject)
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_objectsForSerialization.Add(compositionObject); _objectsForSerialization.Add(compositionObject);
QueueImplicitBatchCommit(); RequestCommitAsync();
}
internal void InvokeOnNextCommit(Action action)
{
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action);
} }
public void InvokeWhenReadyForNextCommit(Action action) internal void InvokeBeforeNextCommit(Action<Task> action)
{ {
if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) Dispatcher.UIThread.VerifyAccess();
Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); _invokeBeforeCommit.Enqueue(action);
else RequestCommitAsync();
_lastBatchCompleted.ContinueWith(
static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition),
action);
} }
} }
} }

10
src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs

@ -15,16 +15,19 @@ namespace Avalonia.Rendering.Composition.Transport
{ {
private static long _nextSequenceId = 1; private static long _nextSequenceId = 1;
private static ConcurrentBag<BatchStreamData> _pool = new(); private static ConcurrentBag<BatchStreamData> _pool = new();
private readonly TaskCompletionSource<int>? _tcs;
public long SequenceId { get; } public long SequenceId { get; }
public Batch() public Batch(TaskCompletionSource<int>? tcs)
{ {
_tcs = tcs;
SequenceId = Interlocked.Increment(ref _nextSequenceId); SequenceId = Interlocked.Increment(ref _nextSequenceId);
if (!_pool.TryTake(out var lst)) if (!_pool.TryTake(out var lst))
lst = new BatchStreamData(); lst = new BatchStreamData();
Changes = lst; Changes = lst;
} }
private TaskCompletionSource<int> _tcs = new TaskCompletionSource<int>();
public BatchStreamData Changes { get; private set; } public BatchStreamData Changes { get; private set; }
public TimeSpan CommitedAt { get; set; } public TimeSpan CommitedAt { get; set; }
@ -33,9 +36,8 @@ namespace Avalonia.Rendering.Composition.Transport
_pool.Add(Changes); _pool.Add(Changes);
Changes = null!; Changes = null!;
_tcs.TrySetResult(0); _tcs?.TrySetResult(0);
} }
public Task Completed => _tcs.Task;
} }
} }

11
src/Avalonia.Base/Threading/DispatcherPriority.cs

@ -61,21 +61,16 @@ namespace Avalonia.Threading
/// The job will be processed with the same priority as render. /// The job will be processed with the same priority as render.
/// </summary> /// </summary>
public static readonly DispatcherPriority Render = new(5); public static readonly DispatcherPriority Render = new(5);
/// <summary>
/// The job will be processed with the same priority as composition batch commit.
/// </summary>
public static readonly DispatcherPriority CompositionBatch = new(6);
/// <summary> /// <summary>
/// The job will be processed with the same priority as composition updates. /// The job will be processed with the same priority as composition updates.
/// </summary> /// </summary>
public static readonly DispatcherPriority Composition = new(7); public static readonly DispatcherPriority Composition = new(6);
/// <summary> /// <summary>
/// The job will be processed with the same priority as render. /// The job will be processed with the same priority as render.
/// </summary> /// </summary>
public static readonly DispatcherPriority Layout = new(8); public static readonly DispatcherPriority Layout = new(7);
/// <summary> /// <summary>
/// The job will be processed with the same priority as data binding. /// The job will be processed with the same priority as data binding.
@ -85,7 +80,7 @@ namespace Avalonia.Threading
/// <summary> /// <summary>
/// The job will be processed before other asynchronous operations. /// The job will be processed before other asynchronous operations.
/// </summary> /// </summary>
public static readonly DispatcherPriority Send = new(9); public static readonly DispatcherPriority Send = new(8);
/// <summary> /// <summary>
/// Maximum possible priority /// Maximum possible priority

Loading…
Cancel
Save