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

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

@ -129,7 +129,7 @@ namespace Avalonia.Rendering.Composition
/// </summary>
internal void ImmediateUIThreadRender()
{
Compositor.RequestCommitAsync();
Compositor.Commit();
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.Transport;
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>
@ -24,16 +25,18 @@ namespace Avalonia.Rendering.Composition
{
internal IRenderLoop Loop { get; }
private ServerCompositor _server;
private bool _implicitBatchCommitQueued;
private Action _implicitBatchCommit;
private TaskCompletionSource<int>? _nextCommit;
private Action _commit;
private BatchStreamObjectPool<object?> _batchObjectPool = new();
private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new();
private Queue<Action<Task>> _invokeBeforeCommit = new();
internal ServerCompositor Server => _server;
private Task? _pendingBatch;
private readonly object _pendingBatchLock = new();
internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary>
/// 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;
_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));
}
/// <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>
/// Requests pending changes in the composition objects to be serialized and sent to the render thread
/// </summary>
@ -66,7 +63,35 @@ namespace Avalonia.Rendering.Composition
public Task RequestCommitAsync()
{
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))
{
@ -84,71 +109,36 @@ namespace Avalonia.Rendering.Composition
batch.CommitedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch);
if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed);
return _lastBatchCompleted = batch.Completed;
}
async void ScheduleCommitCallbacks(Task task)
{
var list = _invokeOnNextCommit;
_invokeOnNextCommit = null;
await task;
foreach (var i in list!)
i();
list.Clear();
_invokeListPool.Push(list);
}
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();
lock (_pendingBatchLock)
{
_pendingBatch = _nextCommit.Task;
_pendingBatch.ContinueWith(t =>
{
lock (_pendingBatchLock)
{
if (_pendingBatch == t)
_pendingBatch = null;
}
}, TaskContinuationOptions.ExecuteSynchronously);
_nextCommit = null;
return _pendingBatch;
}
}
internal void RegisterForSerialization(CompositionObject compositionObject)
{
Dispatcher.UIThread.VerifyAccess();
_objectsForSerialization.Add(compositionObject);
QueueImplicitBatchCommit();
}
internal void InvokeOnNextCommit(Action action)
{
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action);
RequestCommitAsync();
}
public void InvokeWhenReadyForNextCommit(Action action)
internal void InvokeBeforeNextCommit(Action<Task> action)
{
if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted)
Dispatcher.UIThread.Post(action, DispatcherPriority.Composition);
else
_lastBatchCompleted.ContinueWith(
static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition),
action);
Dispatcher.UIThread.VerifyAccess();
_invokeBeforeCommit.Enqueue(action);
RequestCommitAsync();
}
}
}

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 ConcurrentBag<BatchStreamData> _pool = new();
private readonly TaskCompletionSource<int>? _tcs;
public long SequenceId { get; }
public Batch()
public Batch(TaskCompletionSource<int>? tcs)
{
_tcs = tcs;
SequenceId = Interlocked.Increment(ref _nextSequenceId);
if (!_pool.TryTake(out var lst))
lst = new BatchStreamData();
Changes = lst;
}
private TaskCompletionSource<int> _tcs = new TaskCompletionSource<int>();
public BatchStreamData Changes { get; private set; }
public TimeSpan CommitedAt { get; set; }
@ -33,9 +36,8 @@ namespace Avalonia.Rendering.Composition.Transport
_pool.Add(Changes);
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.
/// </summary>
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>
/// The job will be processed with the same priority as composition updates.
/// </summary>
public static readonly DispatcherPriority Composition = new(7);
public static readonly DispatcherPriority Composition = new(6);
/// <summary>
/// The job will be processed with the same priority as render.
/// </summary>
public static readonly DispatcherPriority Layout = new(8);
public static readonly DispatcherPriority Layout = new(7);
/// <summary>
/// The job will be processed with the same priority as data binding.
@ -85,7 +80,7 @@ namespace Avalonia.Threading
/// <summary>
/// The job will be processed before other asynchronous operations.
/// </summary>
public static readonly DispatcherPriority Send = new(9);
public static readonly DispatcherPriority Send = new(8);
/// <summary>
/// Maximum possible priority

Loading…
Cancel
Save