From 4af87777c5a3d1339cf3530ed2bbe5d7c07cdebd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 2 May 2020 19:25:50 +0300 Subject: [PATCH 1/5] Preemptively schedule a scene update when rendering an updated SceneInvalidated Effective FPS is now 60 instead of 30 --- .../Rendering/DeferredRenderer.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index d5e361ca0e..24ff600ca9 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -318,17 +318,25 @@ namespace Avalonia.Rendering _lastSceneId = scene.Generation; + var isUiThread = Dispatcher.UIThread.CheckAccess(); // We have consumed the previously available scene, but there might be some dirty // rects since the last update. *If* we are on UI thread, we can force immediate scene // rebuild before rendering anything on-screen // We are calling the same method recursively here - if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate) + if (!recursiveCall && isUiThread && NeedsUpdate) { UpdateScene(); var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true); return (rs, true); } + // We are rendering a new scene version, so it's highly likely + // that there is already a pending update for animations + // So we are scheduling an update call so UI thread could prepare a scene before + // the next render timer tick + if (!recursiveCall && !isUiThread) + Dispatcher.UIThread.InvokeAsync(UpdateSceneIfNeeded, DispatcherPriority.Render); + // Indicate that we have updated the layers return (sceneRef.Clone(), true); } @@ -534,6 +542,12 @@ namespace Avalonia.Rendering context = RenderTarget.CreateDrawingContext(this); } + private void UpdateSceneIfNeeded() + { + if(NeedsUpdate) + UpdateScene(); + } + private void UpdateScene() { Dispatcher.UIThread.VerifyAccess(); From b201e8c59173942ad3a2186dfe26ce7a2e99c52e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 2 May 2020 19:29:01 +0300 Subject: [PATCH 2/5] Show the effective rendered frame count instead of render timer tick count --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 5 +++-- src/Avalonia.Visuals/Rendering/RendererBase.cs | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 24ff600ca9..b89649d591 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -49,7 +49,7 @@ namespace Avalonia.Rendering IRenderLoop renderLoop, ISceneBuilder sceneBuilder = null, IDispatcher dispatcher = null, - IDeferredRendererLock rendererLock = null) + IDeferredRendererLock rendererLock = null) : base(true) { Contract.Requires(root != null); @@ -261,7 +261,8 @@ namespace Avalonia.Rendering try { var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context); - + if (updated) + FpsTick(); using (scene) { if (scene?.Item != null) diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs index 1e7b5c2923..b37d5d660b 100644 --- a/src/Avalonia.Visuals/Rendering/RendererBase.cs +++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs @@ -7,6 +7,7 @@ namespace Avalonia.Rendering { public class RendererBase { + private readonly bool _useManualFpsCounting; private static int s_fontSize = 18; private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private int _framesThisSecond; @@ -14,8 +15,9 @@ namespace Avalonia.Rendering private FormattedText _fpsText; private TimeSpan _lastFpsUpdate; - public RendererBase() + public RendererBase(bool useManualFpsCounting = false) { + _useManualFpsCounting = useManualFpsCounting; _fpsText = new FormattedText { Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily.Default), @@ -23,12 +25,15 @@ namespace Avalonia.Rendering }; } + protected void FpsTick() => _framesThisSecond++; + protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount) { var now = _stopwatch.Elapsed; var elapsed = now - _lastFpsUpdate; - ++_framesThisSecond; + if (!_useManualFpsCounting) + ++_framesThisSecond; if (elapsed.TotalSeconds > 1) { From 159cea5f4c7d2f6cf77b2c1114d6e6f21d0c4110 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 2 May 2020 19:38:13 +0300 Subject: [PATCH 3/5] Broken mocks every-freaking-where --- tests/Avalonia.RenderTests/TestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 1e2443aff8..a0fbf704cf 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -184,7 +184,7 @@ namespace Avalonia.Direct2D1.RenderTests public void Signal(DispatcherPriority prio) { - throw new NotImplementedException(); + // No-op } public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) From 07288d20875dbcb8417a33fb67082905e4b99065 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 2 May 2020 19:55:41 +0300 Subject: [PATCH 4/5] Reduce allocations --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index b89649d591..ccb889ece9 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -35,6 +35,7 @@ namespace Avalonia.Rendering private IRef _currentDraw; private readonly IDeferredRendererLock _lock; private readonly object _sceneLock = new object(); + private readonly Action _updateSceneIfNeededDelegate; /// /// Initializes a new instance of the class. @@ -59,6 +60,7 @@ namespace Avalonia.Rendering Layers = new RenderLayers(); _renderLoop = renderLoop; _lock = rendererLock ?? new ManagedDeferredRendererLock(); + _updateSceneIfNeededDelegate = UpdateSceneIfNeeded; } /// @@ -336,7 +338,7 @@ namespace Avalonia.Rendering // So we are scheduling an update call so UI thread could prepare a scene before // the next render timer tick if (!recursiveCall && !isUiThread) - Dispatcher.UIThread.InvokeAsync(UpdateSceneIfNeeded, DispatcherPriority.Render); + Dispatcher.UIThread.Post(_updateSceneIfNeededDelegate, DispatcherPriority.Render); // Indicate that we have updated the layers return (sceneRef.Clone(), true); From 6311b913e19e8d47ca2f576f66601201d867a6df Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 2 May 2020 20:09:52 +0300 Subject: [PATCH 5/5] Second ctor --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index ccb889ece9..0e6dda1710 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -75,7 +75,7 @@ namespace Avalonia.Rendering public DeferredRenderer( IVisual root, IRenderTarget renderTarget, - ISceneBuilder sceneBuilder = null) + ISceneBuilder sceneBuilder = null) : base(true) { Contract.Requires(root != null); Contract.Requires(renderTarget != null); @@ -85,6 +85,7 @@ namespace Avalonia.Rendering _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); _lock = new ManagedDeferredRendererLock(); + _updateSceneIfNeededDelegate = UpdateSceneIfNeeded; } ///