diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 90b973c3a8..13af75e1e3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -83,6 +83,31 @@ namespace Avalonia.Rendering.Composition.Server _redrawRequested = true; _fullRedrawRequested = true; } + + + public void Update() + { + Revision++; + + _overlays.MarkUpdateCallStart(); + using (Diagnostic.BeginCompositorUpdatePass()) + { + var transform = Matrix.CreateScale(Scaling, Scaling); + // Update happens in a separate phase to extend dirty rect if needed + Root!.Update(this, transform); + + while (_adornerUpdateQueue.Count > 0) + { + var adorner = _adornerUpdateQueue.Dequeue(); + adorner.Update(this, transform); + } + + _updateRequested = false; + Readback.CompleteWrite(Revision); + + _overlays.MarkUpdateCallEnd(); + } + } public void Render() { @@ -120,26 +145,7 @@ namespace Avalonia.Rendering.Composition.Server if (DirtyRects.IsEmpty && !_redrawRequested && !_updateRequested) return; - Revision++; - - _overlays.MarkUpdateCallStart(); - using (Diagnostic.BeginCompositorUpdatePass()) - { - var transform = Matrix.CreateScale(Scaling, Scaling); - // Update happens in a separate phase to extend dirty rect if needed - Root.Update(this, transform); - - while (_adornerUpdateQueue.Count > 0) - { - var adorner = _adornerUpdateQueue.Dequeue(); - adorner.Update(this, transform); - } - - _updateRequested = false; - Readback.CompleteWrite(Revision); - - _overlays.MarkUpdateCallEnd(); - } + Update(); if (!_redrawRequested) return; diff --git a/tests/Avalonia.Benchmarks/Compositor/CompositionTargetUpdate.cs b/tests/Avalonia.Benchmarks/Compositor/CompositionTargetUpdate.cs new file mode 100644 index 0000000000..eca15fa212 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Compositor/CompositionTargetUpdate.cs @@ -0,0 +1,121 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Rendering.Composition; +using Avalonia.Rendering.Composition.Drawing; +using Avalonia.Rendering.Composition.Server; +using Avalonia.Threading; +using Avalonia.UnitTests; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks; + +public class CompositionTargetUpdateOnly : IDisposable +{ + private readonly bool _includeRender; + private readonly IDisposable _app; + private readonly Compositor _compositor; + private readonly CompositionTarget _target; + + class Timer : IRenderTimer + { + public event Action Tick; + public bool RunsInBackground { get; } + } + + class ManualScheduler : ICompositorScheduler + { + public void CommitRequested(Compositor compositor) + { + + } + } + + class NullFramebuffer : IFramebufferPlatformSurface + { + private static readonly IntPtr Buffer = Marshal.AllocHGlobal(4); + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => + new FuncFramebufferRenderTarget(() => new LockedFramebuffer(Buffer, new PixelSize(1, 1), 4, new Vector(96, 96), PixelFormat.Rgba8888, + null)); + } + + + public CompositionTargetUpdateOnly() : this(false) + { + + } + + protected CompositionTargetUpdateOnly(bool includeRender) + { + _includeRender = includeRender; + _app = UnitTestApplication.Start(TestServices.StyledWindow); + _compositor = new Compositor(new RenderLoop(new Timer()), null, true, new ManualScheduler(), true, + Dispatcher.UIThread, null); + _target = _compositor.CreateCompositionTarget(() => [new NullFramebuffer()]); + _target.PixelSize = new PixelSize(1000, 1000); + _target.Scaling = 1; + var root = _compositor.CreateContainerVisual(); + root.Size = new Vector(1000, 1000); + _target.Root = root; + if (includeRender) + _target.IsEnabled = true; + CreatePyramid(root, 7); + _compositor.Commit(); + } + + void CreatePyramid(CompositionContainerVisual visual, int depth) + { + for (var c = 0; c < 4; c++) + { + var child = new CompositionDrawListVisual(visual.Compositor, + new ServerCompositionDrawListVisual(visual.Compositor.Server, null!), null!); + double right = c == 1 || c == 3 ? 1 : 0; + double bottom = c > 1 ? 1 : 0; + + var rect = new Rect( + visual.Size.X / 2 * right, + visual.Size.Y / 2 * bottom, + visual.Size.X / 2, + visual.Size.Y / 2 + ); + child.Offset = new(rect.X, rect.Y, 0); + child.Size = new Vector(rect.Width, rect.Height); + + var ctx = new RenderDataDrawingContext(child.Compositor); + ctx.DrawRectangle(Brushes.Aqua, null, new Rect(rect.Size)); + child.DrawList = ctx.GetRenderResults(); + child.Visible = true; + visual.Children.Add(child); + if (depth > 0) + CreatePyramid(child, depth - 1); + } + } + + [Benchmark] + public void TargetUpdate() + { + _target.Root.Offset = new Vector3D(_target.Root.Offset.X == 0 ? 1 : 0, 0, 0); + _compositor.Commit(); + _compositor.Server.Render(); + if (!_includeRender) + _target.Server.Update(); + + } + + + public void Dispose() + { + _app.Dispose(); + + } +} + +public class CompositionTargetUpdateWithRender : CompositionTargetUpdateOnly +{ + public CompositionTargetUpdateWithRender() : base(true) + { + } +} \ No newline at end of file