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