diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index a268eff78a..b48efaa34e 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -37,6 +37,7 @@ namespace Avalonia.Rendering private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); private IRef _currentDraw; private readonly IDeferredRendererLock _lock; + private readonly object _sceneLock = new object(); /// /// Initializes a new instance of the class. @@ -84,6 +85,7 @@ namespace Avalonia.Rendering RenderTarget = renderTarget; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); + _lock = new ManagedDeferredRendererLock(); } /// @@ -118,8 +120,13 @@ namespace Avalonia.Rendering /// public void Dispose() { - var scene = Interlocked.Exchange(ref _scene, null); - scene?.Dispose(); + lock (_sceneLock) + { + var scene = _scene; + _scene = null; + scene?.Dispose(); + } + Stop(); Layers.Clear(); @@ -134,7 +141,8 @@ namespace Avalonia.Rendering // When unit testing the renderLoop may be null, so update the scene manually. UpdateScene(); } - + //It's safe to access _scene here without a lock since + //it's only changed from UI thread which we are currently on return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty(); } @@ -172,7 +180,8 @@ namespace Avalonia.Rendering } } - bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0; + bool NeedsUpdate => _dirty == null || _dirty.Count > 0; + bool IRenderLoopTask.NeedsUpdate => NeedsUpdate; void IRenderLoopTask.Update(TimeSpan time) => UpdateScene(); @@ -197,79 +206,105 @@ namespace Avalonia.Rendering internal void UnitTestUpdateScene() => UpdateScene(); - internal void UnitTestRender() => Render(_scene.Item, false); + internal void UnitTestRender() => Render(false); private void Render(bool forceComposite) { using (var l = _lock.TryLock()) - if (l != null) - using (var scene = _scene?.Clone()) - { - Render(scene?.Item, forceComposite); - } - } - - private void Render(Scene scene, bool forceComposite) - { - bool renderOverlay = DrawDirtyRects || DrawFps; - bool composite = false; - - if (RenderTarget == null) { - RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); - } + if (l == null) + return; - if (renderOverlay) - { - _dirtyRectsDisplay.Tick(); - } - - try - { - if (scene != null && scene.Size != Size.Empty) + IDrawingContextImpl context = null; + try { - IDrawingContextImpl context = null; - - if (scene.Generation != _lastSceneId) + try { - context = RenderTarget.CreateDrawingContext(this); - Layers.Update(scene, context); + IDrawingContextImpl GetContext() + { + if (context != null) + return context; + if (RenderTarget == null) + RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + return context = RenderTarget.CreateDrawingContext(this); - RenderToLayers(scene); + } - if (DebugFramesPath != null) + var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext); + using (scene) { - SaveDebugFrames(scene.Generation); + var overlay = DrawDirtyRects || DrawFps; + if (DrawDirtyRects) + _dirtyRectsDisplay.Tick(); + if (overlay) + RenderOverlay(scene.Item, GetContext()); + if (updated || forceComposite || overlay) + RenderComposite(scene.Item, GetContext()); } + } + finally + { + context?.Dispose(); + } + } + catch (RenderTargetCorruptedException ex) + { + Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); + RenderTarget?.Dispose(); + RenderTarget = null; + } + } + } - _lastSceneId = scene.Generation; + private (IRef scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func contextFactory, + bool recursiveCall = false) + { + IRef sceneRef; + lock (_sceneLock) + sceneRef = _scene?.Clone(); + if (sceneRef == null) + return (null, false); + using (sceneRef) + { + var scene = sceneRef.Item; + if (scene.Generation != _lastSceneId) + { + var context = contextFactory(); + Layers.Update(scene, context); - composite = true; - } + RenderToLayers(scene); - if (renderOverlay) + if (DebugFramesPath != null) { - context = context ?? RenderTarget.CreateDrawingContext(this); - RenderOverlay(scene, context); - RenderComposite(scene, context); + SaveDebugFrames(scene.Generation); } - else if (composite || forceComposite) + + lock (_sceneLock) + _lastSceneId = scene.Generation; + + + // 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) { - context = context ?? RenderTarget.CreateDrawingContext(this); - RenderComposite(scene, context); + UpdateScene(); + var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true); + return (rs, true); } - - context?.Dispose(); + + // Indicate that we have updated the layers + return (sceneRef.Clone(), true); } + + // Just return scene, layers weren't updated + return (sceneRef.Clone(), false); } - catch (RenderTargetCorruptedException ex) - { - Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); - RenderTarget?.Dispose(); - RenderTarget = null; - } + } + private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) { if (layer == null || node.LayerRoot == layer) @@ -405,6 +440,11 @@ namespace Avalonia.Rendering private void UpdateScene() { Dispatcher.UIThread.VerifyAccess(); + lock (_sceneLock) + { + if (_scene?.Item.Generation > _lastSceneId) + return; + } if (_root.IsVisible) { var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root)); @@ -423,15 +463,23 @@ namespace Avalonia.Rendering } } - var oldScene = Interlocked.Exchange(ref _scene, sceneRef); - oldScene?.Dispose(); + lock (_sceneLock) + { + var oldScene = _scene; + _scene = sceneRef; + oldScene?.Dispose(); + } _dirty.Clear(); } else { - var oldScene = Interlocked.Exchange(ref _scene, null); - oldScene?.Dispose(); + lock (_sceneLock) + { + var oldScene = _scene; + _scene = null; + oldScene?.Dispose(); + } } } diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index 186c55d9eb..8a5a725594 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -89,9 +89,11 @@ namespace Avalonia.Shared.PlatformSupport #if DEBUG if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId) { - Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: " - + Environment.StackTrace - + "\n\nBlob created by " + _backtrace); + lock(_lock) + if (!IsDisposed) + Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: " + + Environment.StackTrace + + "\n\nBlob created by " + _backtrace); } #endif DoDispose(); diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index c910703181..87c5a1bb02 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -5,7 +5,7 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32 { - class FramebufferManager : IFramebufferPlatformSurface, IDisposable + class FramebufferManager : IFramebufferPlatformSurface { private readonly IntPtr _hwnd; private WindowFramebuffer _fb; @@ -29,10 +29,5 @@ namespace Avalonia.Win32 } return _fb; } - - public void Dispose() - { - _fb?.Deallocate(); - } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4c08e985cd..18f0696cd8 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -13,6 +13,7 @@ using Avalonia.Input.Raw; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Threading; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -234,8 +235,6 @@ namespace Avalonia.Win32 public void Dispose() { - _framebuffer?.Dispose(); - _framebuffer = null; if (_hwnd != IntPtr.Zero) { UnmanagedMethods.DestroyWindow(_hwnd); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index a53809a029..ab5f5f37f7 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -405,7 +405,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering root.Renderer = new DeferredRenderer(root, null); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); - + + root.Renderer.Paint(Rect.Empty); var result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item1, result); @@ -421,6 +422,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering container.InvalidateArrange(); container.Arrange(new Rect(container.DesiredSize)); + root.Renderer.Paint(Rect.Empty); result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item2, result);