|
|
@@ -1,780 +0,0 @@
|
|
|
-using System;
|
|
|
-using System.Collections.Generic;
|
|
|
-using System.Diagnostics.CodeAnalysis;
|
|
|
-using System.IO;
|
|
|
-using System.Linq;
|
|
|
-using System.Threading.Tasks;
|
|
|
-using Avalonia.Logging;
|
|
|
-using Avalonia.Media;
|
|
|
-using Avalonia.Media.Immutable;
|
|
|
-using Avalonia.Platform;
|
|
|
-using Avalonia.Rendering.SceneGraph;
|
|
|
-using Avalonia.Threading;
|
|
|
-using Avalonia.Utilities;
|
|
|
-using Avalonia.VisualTree;
|
|
|
-
|
|
|
-namespace Avalonia.Rendering
|
|
|
-{
|
|
|
- /// <summary>
|
|
|
- /// A renderer which renders the state of the visual tree to an intermediate scene graph
|
|
|
- /// representation which is then rendered on a rendering thread.
|
|
|
- /// </summary>
|
|
|
- public class DeferredRenderer : RendererBase, IRenderer, IRenderLoopTask, IVisualBrushRenderer
|
|
|
- {
|
|
|
- private readonly IDispatcher? _dispatcher;
|
|
|
- private readonly IRenderLoop? _renderLoop;
|
|
|
- private readonly Func<IRenderTarget>? _renderTargetFactory;
|
|
|
- private readonly PlatformRenderInterfaceContextManager? _renderInterface;
|
|
|
- private readonly Visual _root;
|
|
|
- private readonly ISceneBuilder _sceneBuilder;
|
|
|
-
|
|
|
- private bool _running;
|
|
|
- private bool _disposed;
|
|
|
- private volatile IRef<Scene>? _scene;
|
|
|
- private DirtyVisuals? _dirty;
|
|
|
- private HashSet<Visual>? _recalculateChildren;
|
|
|
- private IRef<IRenderTargetBitmapImpl>? _overlay;
|
|
|
- private int _lastSceneId = -1;
|
|
|
- private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
|
|
|
- private IRef<IDrawOperation>? _currentDraw;
|
|
|
- private readonly IDeferredRendererLock _lock;
|
|
|
- private readonly object _sceneLock = new object();
|
|
|
- private readonly object _startStopLock = new object();
|
|
|
- private readonly object _renderLoopIsRenderingLock = new object();
|
|
|
- private readonly Action _updateSceneIfNeededDelegate;
|
|
|
- private List<Action>? _pendingRenderThreadJobs;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
|
|
|
- /// </summary>
|
|
|
- /// <param name="root">The control to render.</param>
|
|
|
- /// <param name="renderLoop">The render loop.</param>
|
|
|
- /// <param name="renderTargetFactory">The target render factory.</param>
|
|
|
- /// <param name="renderInterface">The Platform Render Context.</param>
|
|
|
- /// <param name="sceneBuilder">The scene builder to use. Optional.</param>
|
|
|
- /// <param name="dispatcher">The dispatcher to use. Optional.</param>
|
|
|
- /// <param name="rendererLock">Lock object used before trying to access render target</param>
|
|
|
- public DeferredRenderer(
|
|
|
- IRenderRoot root,
|
|
|
- IRenderLoop renderLoop,
|
|
|
- Func<IRenderTarget> renderTargetFactory,
|
|
|
- PlatformRenderInterfaceContextManager? renderInterface = null,
|
|
|
- ISceneBuilder? sceneBuilder = null,
|
|
|
- IDispatcher? dispatcher = null,
|
|
|
- IDeferredRendererLock? rendererLock = null) : base(true)
|
|
|
- {
|
|
|
- _dispatcher = dispatcher ?? Dispatcher.UIThread;
|
|
|
- _root = root as Visual ?? throw new ArgumentNullException(nameof(root));
|
|
|
- _sceneBuilder = sceneBuilder ?? new SceneBuilder();
|
|
|
- Layers = new RenderLayers();
|
|
|
- _renderLoop = renderLoop;
|
|
|
- _renderTargetFactory = renderTargetFactory;
|
|
|
- _renderInterface = renderInterface;
|
|
|
- _lock = rendererLock ?? new ManagedDeferredRendererLock();
|
|
|
- _updateSceneIfNeededDelegate = UpdateSceneIfNeeded;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
|
|
|
- /// </summary>
|
|
|
- /// <param name="root">The control to render.</param>
|
|
|
- /// <param name="renderTarget">The render target.</param>
|
|
|
- /// <param name="sceneBuilder">The scene builder to use. Optional.</param>
|
|
|
- /// <remarks>
|
|
|
- /// This constructor is intended to be used for unit testing.
|
|
|
- /// </remarks>
|
|
|
- public DeferredRenderer(
|
|
|
- Visual root,
|
|
|
- IRenderTarget renderTarget,
|
|
|
- ISceneBuilder? sceneBuilder = null) : base(true)
|
|
|
- {
|
|
|
- _root = root ?? throw new ArgumentNullException(nameof(root));
|
|
|
- RenderTarget = renderTarget ?? throw new ArgumentNullException(nameof(renderTarget));
|
|
|
- _sceneBuilder = sceneBuilder ?? new SceneBuilder();
|
|
|
- Layers = new RenderLayers();
|
|
|
- _lock = new ManagedDeferredRendererLock();
|
|
|
- _updateSceneIfNeededDelegate = UpdateSceneIfNeeded;
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public bool DrawFps { get; set; }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public bool DrawDirtyRects { get; set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets or sets a path to which rendered frame should be rendered for debugging.
|
|
|
- /// </summary>
|
|
|
- public string? DebugFramesPath { get; set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Forces the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered
|
|
|
- /// </summary>
|
|
|
- public bool RenderOnlyOnRenderThread { get; set; }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the render layers.
|
|
|
- /// </summary>
|
|
|
- internal RenderLayers Layers { get; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the current render target.
|
|
|
- /// </summary>
|
|
|
- internal IRenderTarget? RenderTarget { get; private set; }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public void AddDirty(Visual visual)
|
|
|
- {
|
|
|
- _dirty?.Add(visual);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Disposes of the renderer and detaches from the render loop.
|
|
|
- /// </summary>
|
|
|
- public void Dispose()
|
|
|
- {
|
|
|
- lock (_sceneLock)
|
|
|
- {
|
|
|
- if (_disposed)
|
|
|
- return;
|
|
|
- _disposed = true;
|
|
|
- var scene = _scene;
|
|
|
- _scene = null;
|
|
|
- scene?.Dispose();
|
|
|
- }
|
|
|
-
|
|
|
- Stop();
|
|
|
- // Wait for any in-progress rendering to complete
|
|
|
- lock(_renderLoopIsRenderingLock){}
|
|
|
- DisposeRenderTarget();
|
|
|
- }
|
|
|
-
|
|
|
- public void RecalculateChildren(Visual visual) => _recalculateChildren?.Add(visual);
|
|
|
-
|
|
|
- void DisposeRenderTarget()
|
|
|
- {
|
|
|
- using (var l = _lock.TryLock())
|
|
|
- {
|
|
|
- if(l == null)
|
|
|
- {
|
|
|
- // We are still trying to render on the render thread, try again a bit later
|
|
|
- DispatcherTimer.RunOnce(DisposeRenderTarget, TimeSpan.FromMilliseconds(50),
|
|
|
- DispatcherPriority.Background);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- Layers.Clear();
|
|
|
- RenderTarget?.Dispose();
|
|
|
- RenderTarget = null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public IEnumerable<Visual> HitTest(Point p, Visual root, Func<Visual, bool>? filter)
|
|
|
- {
|
|
|
- EnsureCanHitTest();
|
|
|
-
|
|
|
- //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<Visual>();
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public Visual? HitTestFirst(Point p, Visual root, Func<Visual, bool>? filter)
|
|
|
- {
|
|
|
- EnsureCanHitTest();
|
|
|
-
|
|
|
- //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.HitTestFirst(p, root, filter);
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public void Paint(Rect rect)
|
|
|
- {
|
|
|
- if (RenderOnlyOnRenderThread)
|
|
|
- {
|
|
|
- // Renderer is stopped and doesn't tick on the render thread
|
|
|
- // This indicates a bug somewhere in our code
|
|
|
- // (currently happens when a window gets minimized with Show desktop on Windows 10)
|
|
|
- if(!_running)
|
|
|
- return;
|
|
|
-
|
|
|
- while (true)
|
|
|
- {
|
|
|
- Scene? scene;
|
|
|
- bool? updated;
|
|
|
- lock (_sceneLock)
|
|
|
- {
|
|
|
- updated = UpdateScene();
|
|
|
- scene = _scene?.Item;
|
|
|
- }
|
|
|
-
|
|
|
- // Renderer is in invalid state, skip drawing
|
|
|
- if(updated == null)
|
|
|
- return;
|
|
|
-
|
|
|
- // Wait for the scene to be rendered or disposed
|
|
|
- scene?.Rendered.Wait();
|
|
|
-
|
|
|
- // That was an up-to-date scene, we can return immediately
|
|
|
- if (updated == true)
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var t = (IRenderLoopTask)this;
|
|
|
- if (t.NeedsUpdate)
|
|
|
- UpdateScene();
|
|
|
- if (_scene?.Item != null)
|
|
|
- Render(true);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public void Resized(Size size)
|
|
|
- {
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public void Start()
|
|
|
- {
|
|
|
- lock (_startStopLock)
|
|
|
- {
|
|
|
- if (!_running && _renderLoop != null)
|
|
|
- {
|
|
|
- _renderLoop.Add(this);
|
|
|
- _running = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- public void Stop()
|
|
|
- {
|
|
|
- lock (_startStopLock)
|
|
|
- {
|
|
|
- if (_running && _renderLoop != null)
|
|
|
- {
|
|
|
- _renderLoop.Remove(this);
|
|
|
- _running = false;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
|
|
|
- {
|
|
|
- if (_renderInterface == null)
|
|
|
- return new((object?)null);
|
|
|
-
|
|
|
- var tcs = new TaskCompletionSource<object?>();
|
|
|
- _pendingRenderThreadJobs ??= new();
|
|
|
- _pendingRenderThreadJobs.Add(() =>
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- using (_renderInterface.EnsureCurrent())
|
|
|
- {
|
|
|
- tcs.TrySetResult(_renderInterface.Value.TryGetFeature(featureType));
|
|
|
- }
|
|
|
- }
|
|
|
- catch (Exception e)
|
|
|
- {
|
|
|
- tcs.TrySetException(e);
|
|
|
- }
|
|
|
- });
|
|
|
- return new ValueTask<object?>(tcs.Task);
|
|
|
- }
|
|
|
-
|
|
|
- bool NeedsUpdate => _dirty == null || _dirty.Count > 0;
|
|
|
- bool IRenderLoopTask.NeedsUpdate => NeedsUpdate;
|
|
|
-
|
|
|
- void IRenderLoopTask.Update(TimeSpan time) => UpdateScene();
|
|
|
-
|
|
|
- void IRenderLoopTask.Render()
|
|
|
- {
|
|
|
- lock (_renderLoopIsRenderingLock)
|
|
|
- {
|
|
|
- lock(_startStopLock)
|
|
|
- if(!_running)
|
|
|
- return;
|
|
|
- Render(false);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static Scene? TryGetChildScene(IRef<IDrawOperation>? op) => (op?.Item as BrushDrawOperation)?.Aux as Scene;
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
|
|
|
- {
|
|
|
- return TryGetChildScene(_currentDraw)?.Size ?? default;
|
|
|
- }
|
|
|
-
|
|
|
- /// <inheritdoc/>
|
|
|
- void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
|
|
|
- {
|
|
|
- var childScene = TryGetChildScene(_currentDraw);
|
|
|
-
|
|
|
- if (childScene != null)
|
|
|
- {
|
|
|
- Render(context, (VisualNode)childScene.Root, null, new Rect(childScene.Size));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- internal void UnitTestUpdateScene() => UpdateScene();
|
|
|
-
|
|
|
- internal void UnitTestRender() => Render(false);
|
|
|
-
|
|
|
- internal Scene? UnitTestScene() => _scene?.Item;
|
|
|
-
|
|
|
- private void EnsureCanHitTest()
|
|
|
- {
|
|
|
- if (_renderLoop == null && (_dirty == null || _dirty.Count > 0))
|
|
|
- {
|
|
|
- // When unit testing the renderLoop may be null, so update the scene manually.
|
|
|
- UpdateScene();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- internal void Render(bool forceComposite)
|
|
|
- {
|
|
|
- using (var l = _lock.TryLock())
|
|
|
- {
|
|
|
- if (l == null)
|
|
|
- return;
|
|
|
-
|
|
|
- IDrawingContextImpl? context = null;
|
|
|
- try
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context);
|
|
|
- if (updated)
|
|
|
- FpsTick();
|
|
|
- using (scene)
|
|
|
- {
|
|
|
- if (scene?.Item != null)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- var overlay = DrawDirtyRects || DrawFps;
|
|
|
- if (DrawDirtyRects)
|
|
|
- _dirtyRectsDisplay.Tick();
|
|
|
- if (overlay)
|
|
|
- RenderOverlay(scene.Item, ref context);
|
|
|
- if (updated || forceComposite || overlay)
|
|
|
- RenderComposite(scene.Item, ref context);
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- if(scene.Item.RenderThreadJobs!=null)
|
|
|
- foreach (var job in scene.Item.RenderThreadJobs)
|
|
|
- job();
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- scene.Item.MarkAsRendered();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- context?.Dispose();
|
|
|
- }
|
|
|
- }
|
|
|
- catch (RenderTargetCorruptedException ex)
|
|
|
- {
|
|
|
- Logger.TryGet(LogEventLevel.Information, LogArea.Animations)?.Log(this, "Render target was corrupted. Exception: {0}", ex);
|
|
|
- RenderTarget?.Dispose();
|
|
|
- RenderTarget = null;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private (IRef<Scene>? scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(ref IDrawingContextImpl? context,
|
|
|
- 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)
|
|
|
- {
|
|
|
- EnsureDrawingContext(ref context);
|
|
|
-
|
|
|
- Layers.Update(scene, context);
|
|
|
-
|
|
|
- RenderToLayers(scene);
|
|
|
-
|
|
|
- if (DebugFramesPath != null)
|
|
|
- {
|
|
|
- SaveDebugFrames(scene.Generation);
|
|
|
- }
|
|
|
-
|
|
|
- lock (_sceneLock)
|
|
|
- _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 && 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.Post(_updateSceneIfNeededDelegate, DispatcherPriority.Render);
|
|
|
-
|
|
|
- // Indicate that we have updated the layers
|
|
|
- return (sceneRef.Clone(), true);
|
|
|
- }
|
|
|
-
|
|
|
- // Just return scene, layers weren't updated
|
|
|
- return (sceneRef.Clone(), false);
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- private void Render(IDrawingContextImpl context, VisualNode node, Visual? layer, Rect clipBounds)
|
|
|
- {
|
|
|
- if (layer == null || node.LayerRoot == layer)
|
|
|
- {
|
|
|
- clipBounds = node.ClipBounds.Intersect(clipBounds);
|
|
|
-
|
|
|
- if (!clipBounds.IsDefault && node.Opacity > 0)
|
|
|
- {
|
|
|
- var isLayerRoot = node.Visual == layer;
|
|
|
-
|
|
|
- node.BeginRender(context, isLayerRoot);
|
|
|
-
|
|
|
- var drawOperations = node.DrawOperations;
|
|
|
- var drawOperationsCount = drawOperations.Count;
|
|
|
- for (int i = 0; i < drawOperationsCount; i++)
|
|
|
- {
|
|
|
- var operation = drawOperations[i];
|
|
|
- _currentDraw = operation;
|
|
|
- operation.Item.Render(context);
|
|
|
- _currentDraw = null;
|
|
|
- }
|
|
|
-
|
|
|
- var children = node.Children;
|
|
|
- var childrenCount = children.Count;
|
|
|
- for (int i = 0; i < childrenCount; i++)
|
|
|
- {
|
|
|
- var child = children[i];
|
|
|
- Render(context, (VisualNode)child, layer, clipBounds);
|
|
|
- }
|
|
|
-
|
|
|
- node.EndRender(context, isLayerRoot);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void RenderToLayers(Scene scene)
|
|
|
- {
|
|
|
- foreach (var layer in scene.Layers)
|
|
|
- {
|
|
|
- var renderLayer = Layers[layer.LayerRoot];
|
|
|
- if (layer.Dirty.IsEmpty && !renderLayer.IsEmpty)
|
|
|
- continue;
|
|
|
- var renderTarget = renderLayer.Bitmap;
|
|
|
- var node = (VisualNode?)scene.FindNode(layer.LayerRoot);
|
|
|
-
|
|
|
- if (node != null)
|
|
|
- {
|
|
|
- using (var context = renderTarget.Item.CreateDrawingContext(this))
|
|
|
- {
|
|
|
- if (renderLayer.IsEmpty)
|
|
|
- {
|
|
|
- // Render entire layer root node
|
|
|
- context.Clear(Colors.Transparent);
|
|
|
- context.Transform = Matrix.Identity;
|
|
|
- context.PushClip(node.ClipBounds);
|
|
|
- Render(context, node, layer.LayerRoot, node.ClipBounds);
|
|
|
- context.PopClip();
|
|
|
- if (DrawDirtyRects)
|
|
|
- {
|
|
|
- _dirtyRectsDisplay.Add(node.ClipBounds);
|
|
|
- }
|
|
|
-
|
|
|
- renderLayer.IsEmpty = false;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var scale = scene.Scaling;
|
|
|
-
|
|
|
- foreach (var rect in layer.Dirty)
|
|
|
- {
|
|
|
- var snappedRect = SnapToDevicePixels(rect, scale);
|
|
|
- context.Transform = Matrix.Identity;
|
|
|
- context.PushClip(snappedRect);
|
|
|
- context.Clear(Colors.Transparent);
|
|
|
- Render(context, node, layer.LayerRoot, snappedRect);
|
|
|
- context.PopClip();
|
|
|
-
|
|
|
- if (DrawDirtyRects)
|
|
|
- {
|
|
|
- _dirtyRectsDisplay.Add(snappedRect);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static Rect SnapToDevicePixels(Rect rect, double scale)
|
|
|
- {
|
|
|
- return new Rect(
|
|
|
- new Point(
|
|
|
- Math.Floor(rect.X * scale) / scale,
|
|
|
- Math.Floor(rect.Y * scale) / scale),
|
|
|
- new Point(
|
|
|
- Math.Ceiling(rect.Right * scale) / scale,
|
|
|
- Math.Ceiling(rect.Bottom * scale) / scale));
|
|
|
- }
|
|
|
-
|
|
|
- private void RenderOverlay(Scene scene, ref IDrawingContextImpl? parentContent)
|
|
|
- {
|
|
|
- EnsureDrawingContext(ref parentContent);
|
|
|
-
|
|
|
- if (DrawDirtyRects)
|
|
|
- {
|
|
|
- var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
|
|
|
-
|
|
|
- using (var context = overlay.Item.CreateDrawingContext(this))
|
|
|
- {
|
|
|
- context.Clear(Colors.Transparent);
|
|
|
- RenderDirtyRects(context);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _overlay?.Dispose();
|
|
|
- _overlay = null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void RenderDirtyRects(IDrawingContextImpl context)
|
|
|
- {
|
|
|
- foreach (var r in _dirtyRectsDisplay)
|
|
|
- {
|
|
|
- var brush = new ImmutableSolidColorBrush(Colors.Magenta, r.Opacity);
|
|
|
- context.DrawRectangle(brush,null, r.Rect);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void RenderComposite(Scene scene, ref IDrawingContextImpl? context)
|
|
|
- {
|
|
|
- EnsureDrawingContext(ref context);
|
|
|
-
|
|
|
- context.Clear(Colors.Transparent);
|
|
|
-
|
|
|
- var clientRect = new Rect(scene.Size);
|
|
|
-
|
|
|
- var firstLayer = true;
|
|
|
- foreach (var layer in scene.Layers)
|
|
|
- {
|
|
|
- var bitmap = Layers[layer.LayerRoot].Bitmap;
|
|
|
- var sourceRect = new Rect(0, 0, bitmap.Item.PixelSize.Width, bitmap.Item.PixelSize.Height);
|
|
|
-
|
|
|
- if (layer.GeometryClip != null)
|
|
|
- {
|
|
|
- context.PushGeometryClip(layer.GeometryClip);
|
|
|
- }
|
|
|
-
|
|
|
- if (layer.OpacityMask == null)
|
|
|
- {
|
|
|
- if (firstLayer && bitmap.Item.CanBlit)
|
|
|
- bitmap.Item.Blit(context);
|
|
|
- else
|
|
|
- context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- context.DrawBitmap(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
|
|
|
- }
|
|
|
-
|
|
|
- if (layer.GeometryClip != null)
|
|
|
- {
|
|
|
- context.PopGeometryClip();
|
|
|
- }
|
|
|
-
|
|
|
- firstLayer = false;
|
|
|
- }
|
|
|
-
|
|
|
- if (_overlay != null)
|
|
|
- {
|
|
|
- var sourceRect = new Rect(0, 0, _overlay.Item.PixelSize.Width, _overlay.Item.PixelSize.Height);
|
|
|
- context.DrawBitmap(_overlay, 0.5, sourceRect, clientRect);
|
|
|
- }
|
|
|
-
|
|
|
- if (DrawFps)
|
|
|
- {
|
|
|
- using (var c = new DrawingContext(context, false))
|
|
|
- {
|
|
|
- RenderFps(c, clientRect, scene.Layers.Count);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void EnsureDrawingContext([NotNull] ref IDrawingContextImpl? context)
|
|
|
- {
|
|
|
- if (context != null)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (RenderTarget?.IsCorrupted == true)
|
|
|
- {
|
|
|
- RenderTarget!.Dispose();
|
|
|
- RenderTarget = null;
|
|
|
- }
|
|
|
-
|
|
|
- if (RenderTarget == null)
|
|
|
- {
|
|
|
- RenderTarget = _renderTargetFactory!();
|
|
|
- }
|
|
|
-
|
|
|
- context = RenderTarget.CreateDrawingContext(this);
|
|
|
- }
|
|
|
-
|
|
|
- private void UpdateSceneIfNeeded()
|
|
|
- {
|
|
|
- if(NeedsUpdate)
|
|
|
- UpdateScene();
|
|
|
- }
|
|
|
-
|
|
|
- private bool? UpdateScene()
|
|
|
- {
|
|
|
- Dispatcher.UIThread.VerifyAccess();
|
|
|
- using var noPump = NonPumpingLockHelper.Use();
|
|
|
- lock (_sceneLock)
|
|
|
- {
|
|
|
- if (_disposed)
|
|
|
- return null;
|
|
|
- if (_scene?.Item.Generation > _lastSceneId)
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (_root.IsVisible)
|
|
|
- {
|
|
|
- var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root)
|
|
|
- {
|
|
|
- RenderThreadJobs = _pendingRenderThreadJobs
|
|
|
- });
|
|
|
- _pendingRenderThreadJobs = null;
|
|
|
- var scene = sceneRef.Item;
|
|
|
-
|
|
|
- if (_dirty == null)
|
|
|
- {
|
|
|
- _dirty = new DirtyVisuals();
|
|
|
- _recalculateChildren = new HashSet<Visual>();
|
|
|
- _sceneBuilder.UpdateAll(scene);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- foreach (var visual in _recalculateChildren!)
|
|
|
- {
|
|
|
- var node = scene.FindNode(visual);
|
|
|
- ((VisualNode?)node)?.SortChildren(scene);
|
|
|
- }
|
|
|
-
|
|
|
- _recalculateChildren.Clear();
|
|
|
-
|
|
|
- foreach (var visual in _dirty)
|
|
|
- {
|
|
|
- _sceneBuilder.Update(scene, visual);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- lock (_sceneLock)
|
|
|
- {
|
|
|
- var oldScene = _scene;
|
|
|
- _scene = sceneRef;
|
|
|
- oldScene?.Dispose();
|
|
|
- }
|
|
|
-
|
|
|
- _dirty.Clear();
|
|
|
-
|
|
|
- if (SceneInvalidated != null)
|
|
|
- {
|
|
|
- var rect = new Rect();
|
|
|
-
|
|
|
- foreach (var layer in scene.Layers)
|
|
|
- {
|
|
|
- foreach (var dirty in layer.Dirty)
|
|
|
- {
|
|
|
- rect = rect.Union(dirty);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- lock (_sceneLock)
|
|
|
- {
|
|
|
- var oldScene = _scene;
|
|
|
- _scene = null;
|
|
|
- oldScene?.Dispose();
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private IRef<IRenderTargetBitmapImpl> GetOverlay(
|
|
|
- IDrawingContextImpl parentContext,
|
|
|
- Size size,
|
|
|
- double scaling)
|
|
|
- {
|
|
|
- var pixelSize = size * scaling;
|
|
|
-
|
|
|
- if (_overlay == null ||
|
|
|
- _overlay.Item.PixelSize.Width != pixelSize.Width ||
|
|
|
- _overlay.Item.PixelSize.Height != pixelSize.Height)
|
|
|
- {
|
|
|
- _overlay?.Dispose();
|
|
|
- _overlay = RefCountable.Create(parentContext.CreateLayer(size));
|
|
|
- }
|
|
|
-
|
|
|
- return _overlay;
|
|
|
- }
|
|
|
-
|
|
|
- private void SaveDebugFrames(int id)
|
|
|
- {
|
|
|
- var index = 0;
|
|
|
-
|
|
|
- foreach (var layer in Layers)
|
|
|
- {
|
|
|
- var fileName = Path.Combine(DebugFramesPath ?? string.Empty, FormattableString.Invariant($"frame-{id}-layer-{index++}.png"));
|
|
|
- layer.Bitmap.Item.Save(fileName);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|