diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 5aded281b3..5bdd7847c4 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -17,7 +17,6 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor { private readonly IRenderRoot _root; private readonly Compositor _compositor; - private readonly IDeferredRendererLock? _rendererLock; CompositionDrawingContext _recorder = new(); DrawingContext _recordingContext; private HashSet _dirty = new(); @@ -26,14 +25,17 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor private bool _queuedUpdate; private Action _update; + /// + /// Forces the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered + /// + public bool RenderOnlyOnRenderThread { get; set; } = true; + public CompositingRenderer(IRenderRoot root, - Compositor compositor, - IDeferredRendererLock? rendererLock = null) + Compositor compositor) { _root = root; _compositor = compositor; _recordingContext = new DrawingContext(_recorder); - _rendererLock = rendererLock ?? new ManagedDeferredRendererLock(); _target = compositor.CreateCompositionTarget(root.CreateRenderTarget); _target.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); _update = Update; @@ -228,10 +230,12 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor public void Paint(Rect rect) { - // We render only on the render thread for now Update(); _target.RequestRedraw(); - Compositor.RequestCommitAsync().Wait(); + if(RenderOnlyOnRenderThread) + Compositor.RequestCommitAsync().Wait(); + else + _target.ImmediateUIThreadRender(); } public void Start() => _target.IsEnabled = true; diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 31431a4e36..7e50c43ac1 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -109,5 +109,11 @@ namespace Avalonia.Rendering.Composition } public void RequestRedraw() => RegisterForSerialization(); + + internal void ImmediateUIThreadRender() + { + Compositor.RequestCommitAsync(); + Compositor.Server.Render(); + } } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index c44150f756..811fffd8eb 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -20,6 +20,7 @@ namespace Avalonia.Rendering.Composition.Server private List _animationsToUpdate = new(); private BatchStreamObjectPool _batchObjectPool; private BatchStreamMemoryPool _batchMemoryPool; + private object _lock = new object(); public IPlatformGpuContext? GpuContext { get; } public ServerCompositor(IRenderLoop renderLoop, IPlatformGpu? platformGpu, @@ -88,7 +89,15 @@ namespace Avalonia.Rendering.Composition.Server { } - void IRenderLoopTask.Render() + public void Render() + { + lock (_lock) + { + RenderCore(); + } + } + + private void RenderCore() { ApplyPendingBatches(); diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 4802fed554..7b08ed00dc 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -8,6 +8,8 @@ using Avalonia.Native.Interop; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Rendering.Composition; +using JetBrains.Annotations; namespace Avalonia.Native { @@ -21,6 +23,7 @@ namespace Avalonia.Native static extern IntPtr CreateAvaloniaNative(); internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice(); + [CanBeNull] internal static Compositor Compositor { get; private set; } public Size DoubleClickSize => new Size(4, 4); @@ -101,6 +104,7 @@ namespace Avalonia.Native _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0); } + var renderLoop = new RenderLoop(); AvaloniaLocator.CurrentMutable .Bind() .ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())) @@ -110,7 +114,7 @@ namespace Avalonia.Native .Bind().ToConstant(this) .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) - .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(renderLoop) .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt)) @@ -124,19 +128,27 @@ namespace Avalonia.Native hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - + if (_options.UseGpu) { try { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(_platformGl = new AvaloniaNativePlatformOpenGlInterface(_factory.ObtainGlDisplay())); + _platformGl = new AvaloniaNativePlatformOpenGlInterface(_factory.ObtainGlDisplay()); + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(_platformGl) + .Bind().ToConstant(_platformGl); + } catch (Exception) { // ignored } } + + if (_options.UseDeferredRendering && _options.UseCompositor) + { + Compositor = new Compositor(renderLoop, _platformGl); + } } public ITrayIconImpl CreateTrayIcon() diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 10619d675b..1320903ca2 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -39,6 +39,11 @@ namespace Avalonia /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. /// public bool UseDeferredRendering { get; set; } = true; + + /// + /// Enables new compositing rendering with UWP-like API + /// + public bool UseCompositor { get; set; } /// /// Determines whether to use GPU for rendering in your project. The default value is true. diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 94a3a5ed9b..922750fbb0 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -12,6 +12,7 @@ using Avalonia.Native.Interop; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Rendering.Composition; using Avalonia.Threading; namespace Avalonia.Native @@ -363,13 +364,15 @@ namespace Avalonia.Native public IRenderer CreateRenderer(IRenderRoot root) { + var customRendererFactory = AvaloniaLocator.Current.GetService(); + var loop = AvaloniaLocator.Current.GetService(); + if (customRendererFactory != null) + return customRendererFactory.Create(root, loop); + if (_deferredRendering) { - var loop = AvaloniaLocator.Current.GetService(); - var customRendererFactory = AvaloniaLocator.Current.GetService(); - - if (customRendererFactory != null) - return customRendererFactory.Create(root, loop); + if (AvaloniaNativePlatform.Compositor != null) + return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor); return new DeferredRenderer(root, loop); }