Browse Source

Merge pull request #2117 from AvaloniaUI/fix-render-artifacts

Fix render artifacts caused by unified timers
pull/2123/head
Nikita Tsukanov 7 years ago
committed by GitHub
parent
commit
e3e0a9e5be
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 164
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  2. 8
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  3. 7
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  4. 3
      src/Windows/Avalonia.Win32/WindowImpl.cs
  5. 4
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

164
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -37,6 +37,7 @@ namespace Avalonia.Rendering
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef<IDrawOperation> _currentDraw;
private readonly IDeferredRendererLock _lock;
private readonly object _sceneLock = new object();
/// <summary>
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
@ -84,6 +85,7 @@ namespace Avalonia.Rendering
RenderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
Layers = new RenderLayers();
_lock = new ManagedDeferredRendererLock();
}
/// <inheritdoc/>
@ -118,8 +120,13 @@ namespace Avalonia.Rendering
/// </summary>
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<IVisual>();
}
@ -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> scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func<IDrawingContextImpl> contextFactory,
bool recursiveCall = false)
{
IRef<Scene> 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();
}
}
}

8
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();

7
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();
}
}
}

3
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);

4
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);

Loading…
Cancel
Save