Browse Source

WIP: Adding render layers.

scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
d34c279ba1
  1. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  2. 20
      src/Avalonia.Base/Threading/Dispatcher.cs
  3. 39
      src/Avalonia.Base/Threading/IDispatcher.cs
  4. 6
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  5. 28
      src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs
  6. 99
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  7. 11
      src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs
  8. 45
      src/Avalonia.Visuals/Rendering/LayerDirtyRects.cs
  9. 51
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  10. 61
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  11. 8
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  12. 10
      src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs
  13. 1
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  14. 78
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  15. 13
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  16. 1
      src/Avalonia.Visuals/Visual.cs
  17. 2
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  18. 24
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  19. 13
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  20. 12
      src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
  21. 1
      tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
  22. 32
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs
  23. 27
      tests/Avalonia.UnitTests/TestRoot.cs
  24. 1
      tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj
  25. 240
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  26. 11
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
  27. 125
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -111,6 +111,7 @@
<Compile Include="Threading\Dispatcher.cs" />
<Compile Include="Threading\DispatcherPriority.cs" />
<Compile Include="Threading\DispatcherTimer.cs" />
<Compile Include="Threading\IDispatcher.cs" />
<Compile Include="Threading\JobRunner.cs" />
<Compile Include="Threading\AvaloniaScheduler.cs" />
<Compile Include="Threading\AvaloniaSynchronizationContext.cs" />

20
src/Avalonia.Base/Threading/Dispatcher.cs

@ -15,7 +15,7 @@ namespace Avalonia.Threading
/// In Avalonia, there is usually only a single <see cref="Dispatcher"/> in the application -
/// the one for the UI thread, retrieved via the <see cref="UIThread"/> property.
/// </remarks>
public class Dispatcher
public class Dispatcher : IDispatcher
{
private readonly IPlatformThreadingInterface _platform;
private readonly JobRunner _jobRunner;
@ -33,15 +33,16 @@ namespace Avalonia.Threading
_platform.Signaled += _jobRunner.RunJobs;
}
/// <inheritdoc/>
public bool CheckAccess() => _platform?.CurrentThreadIsLoopThread ?? true;
/// <inheritdoc/>
public void VerifyAccess()
{
if (!CheckAccess())
throw new InvalidOperationException("Call from invalid thread");
}
/// <summary>
/// Runs the dispatcher's main loop.
/// </summary>
@ -63,24 +64,13 @@ namespace Avalonia.Threading
_jobRunner?.RunJobs();
}
/// <summary>
/// Invokes a method on the dispatcher thread.
/// </summary>
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns>
/// <inheritdoc/>
public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
return _jobRunner?.InvokeAsync(action, priority);
}
/// <summary>
/// Post action that will be invoked on main thread
/// </summary>
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
// TODO: The naming of this method is confusing: the Async suffix usually means return a task.
// Remove this and rename InvokeTaskAsync as InvokeAsync.
/// <inheritdoc/>
public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
_jobRunner?.Post(action, priority);

39
src/Avalonia.Base/Threading/IDispatcher.cs

@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
namespace Avalonia.Threading
{
/// <summary>
/// Dispatches jobs to a thread.
/// </summary>
public interface IDispatcher
{
/// <summary>
/// Determines whether the calling thread is the thread associated with this <see cref="IDispatcher"/>.
/// </summary>
/// <returns>True if he calling thread is the thread associated with the dispatched, otherwise false.</returns>
bool CheckAccess();
/// <summary>
/// Throws an exception if the calling thread is not the thread associated with this <see cref="IDispatcher"/>.
/// </summary>
void VerifyAccess();
/// <summary>
/// Invokes a method on the dispatcher thread.
/// </summary>
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns>
void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Post action that will be invoked on main thread
/// </summary>
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
// TODO: The naming of this method is confusing: the Async suffix usually means return a task.
// Remove this and rename InvokeTaskAsync as InvokeAsync. See #816.
Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
}
}

6
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -106,18 +106,24 @@
<Compile Include="Rect.cs" />
<Compile Include="RelativePoint.cs" />
<Compile Include="RelativeRect.cs" />
<Compile Include="Rendering\DefaultRenderLayerFactory.cs" />
<Compile Include="Rendering\DeferredRenderer.cs" />
<Compile Include="Rendering\IRenderer.cs" />
<Compile Include="Rendering\IRendererFactory.cs" />
<Compile Include="Rendering\IRenderLoop.cs" />
<Compile Include="Rendering\DirtyRects.cs" />
<Compile Include="Rendering\LayerDirtyRects.cs" />
<Compile Include="Rendering\Renderer.cs" />
<Compile Include="Rendering\RendererMixin.cs" />
<Compile Include="Rendering\DefaultRenderLoop.cs" />
<Compile Include="Rendering\IRenderLayerFactory.cs" />
<Compile Include="Rendering\RenderLayer.cs" />
<Compile Include="Rendering\RenderLayers.cs" />
<Compile Include="Rendering\SceneGraph\DeferredDrawingContextImpl.cs" />
<Compile Include="Rendering\SceneGraph\GeometryNode.cs" />
<Compile Include="Rendering\SceneGraph\IDrawOperation.cs" />
<Compile Include="Rendering\SceneGraph\ImageNode.cs" />
<Compile Include="Rendering\SceneGraph\ISceneBuilder.cs" />
<Compile Include="Rendering\SceneGraph\IVisualNode.cs" />
<Compile Include="Rendering\SceneGraph\LineNode.cs" />
<Compile Include="Rendering\SceneGraph\RectangleNode.cs" />

28
src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs

@ -0,0 +1,28 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class DefaultRenderLayerFactory : IRenderLayerFactory
{
private IPlatformRenderInterface _renderInterface;
public DefaultRenderLayerFactory()
: this(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>())
{
}
public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface)
{
_renderInterface = renderInterface;
}
public IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size)
{
return _renderInterface.CreateRenderTargetBitmap(
(int)Math.Ceiling(size.Width),
(int)Math.Ceiling(size.Height));
}
}
}

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

@ -11,12 +11,15 @@ namespace Avalonia.Rendering
{
public class DeferredRenderer : IRenderer
{
private readonly IDispatcher _dispatcher;
private readonly IRenderLoop _renderLoop;
private readonly IRenderRoot _root;
private readonly ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers;
private Scene _scene;
private IRenderTarget _renderTarget;
private List<IVisual> _dirty;
private DirtyRects _dirtyRects;
private LayerDirtyRects _dirtyRects;
private bool _updateQueued;
private bool _rendering;
@ -26,12 +29,20 @@ namespace Avalonia.Rendering
private int _fps;
private TimeSpan _lastFpsUpdate;
public DeferredRenderer(IRenderRoot root, IRenderLoop renderLoop)
public DeferredRenderer(
IRenderRoot root,
IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null,
IDispatcher dispatcher = null)
{
Contract.Requires<ArgumentNullException>(root != null);
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_scene = new Scene(root);
_layers = new RenderLayers(layerFactory ?? new DefaultRenderLayerFactory());
if (renderLoop != null)
{
@ -70,25 +81,28 @@ namespace Avalonia.Rendering
{
}
private void Render(IDrawingContextImpl context, IVisualNode node, Rect clipBounds)
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
{
clipBounds = node.ClipBounds.Intersect(clipBounds);
if (!clipBounds.IsEmpty)
if (node.LayerRoot == layer)
{
node.BeginRender(context);
clipBounds = node.ClipBounds.Intersect(clipBounds);
foreach (var operation in node.DrawOperations)
if (!clipBounds.IsEmpty)
{
operation.Render(context);
}
node.BeginRender(context);
foreach (var child in node.Children)
{
Render(context, child, clipBounds);
}
foreach (var operation in node.DrawOperations)
{
operation.Render(context);
}
foreach (var child in node.Children)
{
Render(context, (VisualNode)child, layer, clipBounds);
}
node.EndRender(context);
node.EndRender(context);
}
}
}
@ -125,19 +139,18 @@ namespace Avalonia.Rendering
try
{
var scene = _scene.Clone();
var dirtyRects = new DirtyRects();
var dirtyRects = new LayerDirtyRects();
if (_dirty == null)
{
_dirty = new List<IVisual>();
SceneBuilder.UpdateAll(scene);
dirtyRects.Add(new Rect(_root.ClientSize));
_sceneBuilder.UpdateAll(scene, dirtyRects);
}
else if (_dirty.Count > 0)
{
foreach (var visual in _dirty)
{
SceneBuilder.Update(scene, visual, dirtyRects);
_sceneBuilder.Update(scene, visual, dirtyRects);
}
dirtyRects.Coalesce();
@ -167,14 +180,16 @@ namespace Avalonia.Rendering
if (!_updateQueued && (_dirty == null || _dirty.Count > 0 || _dirtyRects != null))
{
Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render);
_updateQueued = true;
_dispatcher.InvokeAsync(UpdateScene, DispatcherPriority.Render);
}
_rendering = true;
_totalFrames++;
Scene scene;
DirtyRects dirtyRects;
LayerDirtyRects dirtyRects;
int updateCount = 0;
lock (_scene)
{
@ -182,6 +197,30 @@ namespace Avalonia.Rendering
dirtyRects = _dirtyRects;
}
var toRemove = new List<IVisual>();
if (dirtyRects != null)
{
foreach (var layer in dirtyRects)
{
var renderTarget = GetRenderTargetForLayer(layer.Key);
var node = (VisualNode)scene.FindNode(layer.Key);
using (var context = renderTarget.CreateDrawingContext())
{
foreach (var rect in layer.Value)
{
context.PushClip(rect);
Render(context, node, layer.Key, rect);
context.PopClip();
++updateCount;
}
}
}
_layers.RemoveUnused(scene);
}
try
{
if (_renderTarget == null)
@ -191,18 +230,13 @@ namespace Avalonia.Rendering
using (var context = _renderTarget.CreateDrawingContext())
{
int updateCount = 0;
_totalFrames++;
if (dirtyRects != null)
{
foreach (var rect in dirtyRects)
var rect = new Rect(_root.ClientSize);
foreach (var layer in _layers)
{
context.PushClip(rect);
Render(context, _scene.Root, rect);
context.PopClip();
++updateCount;
context.DrawImage(layer.Bitmap, layer.LayerRoot.Opacity, rect, rect);
}
}
@ -221,5 +255,10 @@ namespace Avalonia.Rendering
_rendering = false;
}
private IRenderTargetBitmapImpl GetRenderTargetForLayer(IVisual layerRoot)
{
return (_layers.Get(layerRoot) ?? _layers.Add(layerRoot, _root.ClientSize)).Bitmap;
}
}
}

11
src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs

@ -0,0 +1,11 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public interface IRenderLayerFactory
{
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size);
}
}

45
src/Avalonia.Visuals/Rendering/LayerDirtyRects.cs

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class LayerDirtyRects : Dictionary<IVisual, DirtyRects>
{
public bool IsEmpty
{
get
{
foreach (var i in Values)
{
if (!i.IsEmpty)
{
return false;
}
}
return true;
}
}
public void Add(IVisual layerRoot, Rect rect)
{
DirtyRects rects;
if (!TryGetValue(layerRoot, out rects))
{
Add(layerRoot, rects = new DirtyRects());
}
rects.Add(rect);
}
public void Coalesce()
{
foreach (var i in Values)
{
i.Coalesce();
}
}
}
}

51
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -0,0 +1,51 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class RenderLayer : IComparable<RenderLayer>
{
public RenderLayer(
IRenderTargetBitmapImpl bitmap,
Size size,
IVisual layerRoot)
{
Bitmap = bitmap;
Size = size;
LayerRoot = layerRoot;
Order = GetDistanceFromRenderRoot(layerRoot);
}
public IRenderTargetBitmapImpl Bitmap { get; }
public Size Size { get; }
public IVisual LayerRoot { get; }
public int Order { get; }
private static int GetDistanceFromRenderRoot(IVisual visual)
{
var root = visual as IRenderRoot;
var result = 0;
while (root == null)
{
++result;
visual = visual.VisualParent;
if (visual == null)
{
throw new AvaloniaInternalException("Visual is not rooted.");
}
root = visual as IRenderRoot;
}
return result;
}
public int CompareTo(RenderLayer other)
{
return Order - other.Order;
}
}
}

61
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -0,0 +1,61 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class RenderLayers : IEnumerable<RenderLayer>
{
private readonly IRenderLayerFactory _factory;
private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public RenderLayers(IRenderLayerFactory factory)
{
_factory = factory;
}
public RenderLayer Add(IVisual layerRoot, Size size)
{
RenderLayer result;
if (!_index.TryGetValue(layerRoot, out result))
{
var bitmap = _factory.CreateLayer(layerRoot, size);
result = new RenderLayer(bitmap, size, layerRoot);
_inner.Add(result);
_index.Add(layerRoot, result);
}
return result;
}
public RenderLayer Get(IVisual layerRoot)
{
RenderLayer result;
_index.TryGetValue(layerRoot, out result);
return result;
}
public void RemoveUnused(Scene scene)
{
for (var i = _inner.Count - 1; i >= 0; --i)
{
var layer = _inner[i];
var node = (VisualNode)scene.FindNode(layer.LayerRoot);
if (node == null || node.LayerRoot != layer.LayerRoot)
{
layer.Bitmap.Dispose();
_inner.RemoveAt(i);
_index.Remove(layer.LayerRoot);
}
}
}
public IEnumerator<RenderLayer> GetEnumerator() => _inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
}
}

8
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -14,18 +14,18 @@ namespace Avalonia.Rendering.SceneGraph
private int _drawOperationindex;
public DeferredDrawingContextImpl()
: this(new DirtyRects())
: this(new LayerDirtyRects())
{
}
public DeferredDrawingContextImpl(DirtyRects dirty)
public DeferredDrawingContextImpl(LayerDirtyRects dirty)
{
Dirty = dirty;
}
public Matrix Transform { get; set; } = Matrix.Identity;
public DirtyRects Dirty { get; }
public LayerDirtyRects Dirty { get; }
public UpdateState BeginUpdate(VisualNode node)
{
@ -205,7 +205,7 @@ namespace Avalonia.Rendering.SceneGraph
foreach (var operation in Owner._node.DrawOperations)
{
Owner.Dirty.Add(operation.Bounds);
Owner.Dirty.Add(Owner._node.LayerRoot, operation.Bounds);
}
Owner._node = Node;

10
src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs

@ -0,0 +1,10 @@
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
public interface ISceneBuilder
{
bool Update(Scene scene, IVisual visual, LayerDirtyRects dirty);
void UpdateAll(Scene scene, LayerDirtyRects dirty);
}
}

1
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -23,6 +23,7 @@ namespace Avalonia.Rendering.SceneGraph
_index = index;
Root = root;
root.LayerRoot = root.Visual;
}
public IVisualNode Root { get; }

78
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -6,31 +6,24 @@ using System.Linq;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
using System.Collections.Generic;
namespace Avalonia.Rendering.SceneGraph
{
public static class SceneBuilder
public class SceneBuilder : ISceneBuilder
{
public static void UpdateAll(Scene scene)
public void UpdateAll(Scene scene, LayerDirtyRects dirty)
{
Contract.Requires<ArgumentNullException>(scene != null);
Dispatcher.UIThread.VerifyAccess();
using (var impl = new DeferredDrawingContextImpl())
using (var impl = new DeferredDrawingContextImpl(dirty))
using (var context = new DrawingContext(impl))
{
Update(context, scene, (VisualNode)scene.Root, scene.Root.Visual.Bounds, true);
}
}
public static bool Update(Scene scene, IVisual visual)
{
var dirty = new DirtyRects();
return Update(scene, visual, dirty);
}
public static bool Update(Scene scene, IVisual visual, DirtyRects dirty)
public bool Update(Scene scene, IVisual visual, LayerDirtyRects dirty)
{
Contract.Requires<ArgumentNullException>(scene != null);
Contract.Requires<ArgumentNullException>(visual != null);
@ -131,7 +124,7 @@ namespace Avalonia.Rendering.SceneGraph
var bounds = new Rect(visual.Bounds.Size);
var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
contextImpl.Dirty.Add(node.Bounds);
contextImpl.Dirty.Add(node.LayerRoot, node.Bounds);
if (visual.IsVisible)
{
@ -161,6 +154,15 @@ namespace Avalonia.Rendering.SceneGraph
node.Opacity = opacity;
node.OpacityMask = visual.OpacityMask;
if (opacity < 1)
{
SetLayer(node, node.Visual);
}
else if (node.LayerRoot == node.Visual && node.Parent != null)
{
ClearLayer(node, contextImpl.Dirty);
}
if (node.ClipToBounds)
{
clip = clip.Intersect(node.ClipBounds);
@ -191,26 +193,27 @@ namespace Avalonia.Rendering.SceneGraph
}
}
private static VisualNode CreateNode(Scene scene, IVisual visual, IVisualNode parent)
private static VisualNode CreateNode(Scene scene, IVisual visual, VisualNode parent)
{
var node = new VisualNode(visual, parent);
node.LayerRoot = parent.LayerRoot;
scene.Add(node);
return node;
}
private static void Deindex(Scene scene, VisualNode node, DirtyRects dirty)
private static void Deindex(Scene scene, VisualNode node, LayerDirtyRects dirty)
{
scene.Remove(node);
node.SubTreeUpdated = true;
foreach (var child in node.Children)
foreach (VisualNode child in node.Children)
{
var geometry = child as IDrawOperation;
var visual = child as VisualNode;
if (geometry != null)
{
dirty.Add(geometry.Bounds);
dirty.Add(child.LayerRoot, geometry.Bounds);
}
if (visual != null)
@ -220,13 +223,48 @@ namespace Avalonia.Rendering.SceneGraph
}
}
private static void AddSubtreeBounds(VisualNode node, DirtyRects dirty)
private static void AddSubtreeBounds(VisualNode node, LayerDirtyRects dirty)
{
dirty.Add(node.Bounds);
dirty.Add(node.LayerRoot, node.Bounds);
foreach (var child in node.Children)
foreach (VisualNode child in node.Children)
{
AddSubtreeBounds((VisualNode)child, dirty);
if (child.LayerRoot == node.LayerRoot)
{
AddSubtreeBounds(child, dirty);
}
}
}
private static void ClearLayer(VisualNode node, LayerDirtyRects dirty)
{
var parent = (VisualNode)node.Parent;
var newLayerRoot = parent.LayerRoot;
var existingDirtyRects = dirty[node.LayerRoot];
existingDirtyRects.Coalesce();
foreach (var r in existingDirtyRects)
{
dirty.Add(newLayerRoot, r);
}
dirty.Remove(node.LayerRoot);
SetLayer(node, newLayerRoot);
}
private static void SetLayer(VisualNode node, IVisual layerRoot)
{
node.LayerRoot = layerRoot;
foreach (VisualNode child in node.Children)
{
// If the child is not the start of a new layer, recurse.
if (child.LayerRoot != child.Visual)
{
SetLayer(child, layerRoot);
}
}
}
}

13
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -94,6 +94,8 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
public bool OpacityChanged { get; private set; }
public IVisual LayerRoot { get; set; }
/// <inheritdoc/>
public IReadOnlyList<IVisualNode> Children => _children ?? EmptyChildren;
@ -197,6 +199,7 @@ namespace Avalonia.Rendering.SceneGraph
OpacityMask = OpacityMask,
_drawOperations = _drawOperations,
_drawOperationsCloned = true,
LayerRoot= LayerRoot,
};
}
@ -219,11 +222,6 @@ namespace Avalonia.Rendering.SceneGraph
{
context.Transform = Transform;
if (Opacity != 1)
{
context.PushOpacity(Opacity);
}
if (ClipToBounds)
{
context.PushClip(ClipBounds * Transform.Invert());
@ -237,11 +235,6 @@ namespace Avalonia.Rendering.SceneGraph
{
context.PopClip();
}
if (Opacity != 1)
{
context.PopOpacity();
}
}
private Rect CalculateBounds()

1
src/Avalonia.Visuals/Visual.cs

@ -180,7 +180,6 @@ namespace Avalonia
set { SetValue(OpacityProperty, value); }
}
/// <summary>
/// Gets the opacity mask of the scene graph node.
/// </summary>

2
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -95,7 +95,7 @@ namespace Avalonia.Direct2D1
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
{
return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Factory, width, height);
return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Factory, s_dwfactory, width, height);
}
public IStreamGeometryImpl CreateStreamGeometry()

24
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -18,14 +18,7 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The Direct2D1 render target.
/// </summary>
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
/// <summary>
/// The DirectWrite factory.
/// </summary>
private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary>
@ -77,14 +70,15 @@ namespace Avalonia.Direct2D1.Media
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
{
BitmapImpl impl = (BitmapImpl)source;
Bitmap d2d = impl.GetDirect2DBitmap(_renderTarget);
_renderTarget.DrawBitmap(
d2d,
destRect.ToSharpDX(),
(float)opacity,
BitmapInterpolationMode.Linear,
sourceRect.ToSharpDX());
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
{
_renderTarget.DrawBitmap(
d2d,
destRect.ToSharpDX(),
(float)opacity,
BitmapInterpolationMode.Linear,
sourceRect.ToSharpDX());
}
}
/// <summary>

13
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -15,8 +15,6 @@ namespace Avalonia.Direct2D1.Media
{
private readonly ImagingFactory _factory;
private SharpDX.Direct2D1.Bitmap _direct2D;
/// <summary>
/// Initializes a new instance of the <see cref="BitmapImpl"/> class.
/// </summary>
@ -93,14 +91,9 @@ namespace Avalonia.Direct2D1.Media
/// <returns>The Direct2D bitmap.</returns>
public SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
if (_direct2D == null)
{
FormatConverter converter = new FormatConverter(_factory);
converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA);
_direct2D = SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
}
return _direct2D;
FormatConverter converter = new FormatConverter(_factory);
converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
}
/// <summary>

12
src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs

@ -4,19 +4,21 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media
{
public class RenderTargetBitmapImpl : BitmapImpl, IRenderTargetBitmapImpl, IDisposable
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target;
public RenderTargetBitmapImpl(
ImagingFactory imagingFactory,
Factory d2dFactory,
DirectWriteFactory dwriteFactory,
int width,
int height)
: base(imagingFactory, width, height)
@ -31,6 +33,8 @@ namespace Avalonia.Direct2D1.Media
d2dFactory,
WicImpl,
props);
_dwriteFactory = dwriteFactory;
}
public override void Dispose()
@ -39,7 +43,9 @@ namespace Avalonia.Direct2D1.Media
base.Dispose();
}
public IDrawingContextImpl CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
public IDrawingContextImpl CreateDrawingContext()
{
return new DrawingContextImpl(_target, _dwriteFactory);
}
}
}

1
tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

@ -54,6 +54,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ImmediateDispatcher.cs" />
<Compile Include="InvariantCultureFixture.cs" />
<Compile Include="NotifyingBase.cs" />
<Compile Include="TestLogSink.cs" />

32
tests/Avalonia.UnitTests/ImmediateDispatcher.cs

@ -0,0 +1,32 @@
using System;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Avalonia.UnitTests
{
/// <summary>
/// Immediately invokes dispatched jobs on the current thread.
/// </summary>
public class ImmediateDispatcher : IDispatcher
{
public bool CheckAccess()
{
return true;
}
public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
action();
}
public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
action();
return Task.FromResult<object>(null);
}
public void VerifyAccess()
{
}
}
}

27
tests/Avalonia.UnitTests/TestRoot.cs

@ -5,15 +5,19 @@ using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Moq;
namespace Avalonia.UnitTests
{
public class TestRoot : Decorator, IFocusScope, ILayoutRoot, IInputRoot, INameScope, IRenderRoot, IStyleRoot
{
private readonly NameScope _nameScope = new NameScope();
private readonly IRenderTarget _renderTarget = Mock.Of<IRenderTarget>(
x => x.CreateDrawingContext() == Mock.Of<IDrawingContextImpl>());
public TestRoot()
{
@ -48,32 +52,17 @@ namespace Avalonia.UnitTests
public IRenderTarget RenderTarget => null;
public IRenderer Renderer { get; }
public IRenderer Renderer { get; set; }
public IAccessKeyHandler AccessKeyHandler
{
get
{
throw new NotImplementedException();
}
}
public IAccessKeyHandler AccessKeyHandler => null;
public IKeyboardNavigationHandler KeyboardNavigationHandler
{
get
{
throw new NotImplementedException();
}
}
public IKeyboardNavigationHandler KeyboardNavigationHandler => null;
public IInputElement PointerOverElement { get; set; }
public bool ShowAccessKeys { get; set; }
public IRenderTarget CreateRenderTarget()
{
throw new NotImplementedException();
}
public IRenderTarget CreateRenderTarget() => _renderTarget;
public void Invalidate(Rect rect)
{

1
tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj

@ -81,6 +81,7 @@
<Compile Include="Media\PathMarkupParserTests.cs" />
<Compile Include="RelativeRectComparer.cs" />
<Compile Include="RelativeRectTests.cs" />
<Compile Include="Rendering\DeferredRendererTests.cs" />
<Compile Include="Rendering\SceneGraph\DeferredDrawingContextImplTests.cs" />
<Compile Include="Rendering\SceneGraph\SceneBuilderTests.cs" />
<Compile Include="Rendering\SceneGraph\VisualNodeTests.cs" />

240
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -0,0 +1,240 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Rendering
{
public class DeferredRendererTests
{
[Fact]
public void First_Frame_Calls_UpdateScene_On_Dispatcher()
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var dispatcher = new Mock<IDispatcher>();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: null,
dispatcher: dispatcher.Object);
RunFrame(loop);
dispatcher.Verify(x =>
x.InvokeAsync(
It.Is<Action>(a => a.Method.Name == "UpdateScene"),
DispatcherPriority.Render));
}
[Fact]
public void First_Frame_Calls_SceneBuilder_UpdateAll()
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = new Mock<ISceneBuilder>();
var dispatcher = new ImmediateDispatcher();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
dispatcher: dispatcher);
RunFrame(loop);
sceneBuilder.Verify(x => x.UpdateAll(It.IsAny<Scene>(), It.IsAny<LayerDirtyRects>()));
}
[Fact]
public void Frame_Does_Not_Call_SceneBuilder_If_No_Dirty_Controls()
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = new Mock<ISceneBuilder>();
var dispatcher = new ImmediateDispatcher();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
dispatcher: dispatcher);
IgnoreFirstFrame(loop, sceneBuilder);
RunFrame(loop);
sceneBuilder.Verify(x => x.UpdateAll(It.IsAny<Scene>(), It.IsAny<LayerDirtyRects>()), Times.Never);
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), It.IsAny<Visual>(), It.IsAny<LayerDirtyRects>()), Times.Never);
}
[Fact]
public void Frame_Should_Call_SceneBuilder_Update_With_Dirty_Controls()
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = new Mock<ISceneBuilder>();
var dispatcher = new ImmediateDispatcher();
var control1 = new Border();
var control2 = new Canvas();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
dispatcher: dispatcher);
IgnoreFirstFrame(loop, sceneBuilder);
target.AddDirty(control1);
target.AddDirty(control2);
RunFrame(loop);
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), control1, It.IsAny<LayerDirtyRects>()));
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), control2, It.IsAny<LayerDirtyRects>()));
}
[Fact]
public void Frame_Should_Create_Layer_For_Root()
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var rootLayer = new Mock<IRenderTargetBitmapImpl>();
var dispatcher = new ImmediateDispatcher();
var sceneBuilder = new Mock<ISceneBuilder>();
sceneBuilder.Setup(x => x.UpdateAll(It.IsAny<Scene>(), It.IsAny<LayerDirtyRects>()))
.Callback<Scene, LayerDirtyRects>((scene, dirty) =>
{
var rects = new DirtyRects();
rects.Add(new Rect(root.ClientSize));
dirty.Add(root, rects);
});
var layers = new Mock<IRenderLayerFactory>();
layers.Setup(x => x.CreateLayer(root, root.ClientSize)).Returns(CreateLayer());
var renderInterface = new Mock<IPlatformRenderInterface>();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: layers.Object,
dispatcher: dispatcher);
RunFrame(loop);
layers.Verify(x => x.CreateLayer(root, root.ClientSize));
}
[Fact]
public void Should_Create_And_Delete_Layers_For_Transparent_Controls()
{
Border border;
var root = new TestRoot
{
Width = 100,
Height = 100,
Child = new Border
{
Background = Brushes.Red,
Child = border = new Border
{
Background = Brushes.Green,
}
}
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>();
var layerFactory = new MockRenderLayerFactory(new Dictionary<IVisual, IRenderTargetBitmapImpl>
{
{ root, CreateLayer() },
{ border, CreateLayer() },
});
var target = new DeferredRenderer(
root,
loop.Object,
layerFactory: layerFactory,
dispatcher: new ImmediateDispatcher());
root.Renderer = target;
RunFrame(loop);
var rootContext = layerFactory.GetMockDrawingContext(root);
var borderContext = layerFactory.GetMockDrawingContext(border);
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
rootContext.ResetCalls();
borderContext.ResetCalls();
border.Opacity = 0.5;
RunFrame(loop);
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never);
borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.ResetCalls();
borderContext.ResetCalls();
border.Opacity = 1;
RunFrame(loop);
layerFactory.GetMockBitmap(border).Verify(x => x.Dispose());
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
}
private void IgnoreFirstFrame(Mock<IRenderLoop> loop, Mock<ISceneBuilder> sceneBuilder)
{
RunFrame(loop);
sceneBuilder.ResetCalls();
}
private void RunFrame(Mock<IRenderLoop> loop)
{
loop.Raise(x => x.Tick += null, EventArgs.Empty);
}
private IRenderTargetBitmapImpl CreateLayer()
{
return Mock.Of<IRenderTargetBitmapImpl>(x =>
x.CreateDrawingContext() == Mock.Of<IDrawingContextImpl>());
}
private class MockRenderLayerFactory : IRenderLayerFactory
{
private IDictionary<IVisual, IRenderTargetBitmapImpl> _layers;
public MockRenderLayerFactory(IDictionary<IVisual, IRenderTargetBitmapImpl> layers)
{
_layers = layers;
}
public IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size)
{
return _layers[layerRoot];
}
public Mock<IRenderTargetBitmapImpl> GetMockBitmap(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot]);
}
public Mock<IDrawingContextImpl> GetMockDrawingContext(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot].CreateDrawingContext());
}
}
}
}

11
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs

@ -88,6 +88,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var node = new VisualNode(Mock.Of<IVisual>(), null);
var target = new DeferredDrawingContextImpl();
node.LayerRoot = node.Visual;
using (target.BeginUpdate(node))
{
target.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100));
@ -106,6 +108,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0);
var target = new DeferredDrawingContextImpl();
node.LayerRoot = node.Visual;
node.AddDrawOperation(operation);
using (target.BeginUpdate(node))
@ -126,6 +129,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0);
var target = new DeferredDrawingContextImpl();
node.LayerRoot = node.Visual;
node.AddDrawOperation(operation);
using (target.BeginUpdate(node))
@ -144,15 +148,17 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
var node = new VisualNode(Mock.Of<IVisual>(), null);
var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0);
var dirtyRects = new DirtyRects();
var dirtyRects = new LayerDirtyRects();
var target = new DeferredDrawingContextImpl(dirtyRects);
node.LayerRoot = node.Visual;
using (target.BeginUpdate(node))
{
target.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100));
}
Assert.Equal(new Rect(0, 0, 100, 100), dirtyRects.Single());
Assert.Equal(new Rect(0, 0, 100, 100), dirtyRects.Single().Value.Single());
}
[Fact]
@ -160,6 +166,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
var node = new VisualNode(Mock.Of<IVisual>(), null);
node.LayerRoot = node.Visual;
node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 10, 100), 0));
node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 20, 100), 0));
node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 30, 100), 0));

125
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@ -38,8 +38,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var result = new Scene(tree);
SceneBuilder.UpdateAll(result);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(result, new LayerDirtyRects());
Assert.Same(tree, ((VisualNode)result.Root).LayerRoot);
Assert.Equal(1, result.Root.Children.Count);
var borderNode = (VisualNode)result.Root.Children[0];
@ -85,14 +87,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var result = new Scene(tree);
SceneBuilder.UpdateAll(result);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(result, new LayerDirtyRects());
var canvasNode = result.FindNode(canvas);
Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds);
// Initial ClipBounds are correct, make sure they're still correct after updating canvas.
result = result.Clone();
Assert.True(SceneBuilder.Update(result, canvas));
Assert.True(sceneBuilder.Update(result, canvas, new LayerDirtyRects()));
canvasNode = result.FindNode(canvas);
Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds);
@ -134,14 +137,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var scene = new Scene(tree);
SceneBuilder.UpdateAll(scene);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene, new LayerDirtyRects());
var borderNode = scene.FindNode(border);
Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds);
// Initial ClipBounds are correct, make sure they're still correct after updating border.
scene = scene.Clone();
Assert.True(SceneBuilder.Update(scene, border));
Assert.True(sceneBuilder.Update(scene, border, new LayerDirtyRects()));
borderNode = scene.FindNode(border);
Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds);
@ -174,7 +178,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
var result = new Scene(tree);
SceneBuilder.UpdateAll(result);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(result, new LayerDirtyRects());
var panelNode = result.FindNode(tree.Child);
var expected = new IVisual[] { back, front };
@ -207,7 +212,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var result = new Scene(tree);
SceneBuilder.UpdateAll(result);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(result, new LayerDirtyRects());
var targetNode = result.FindNode(target);
@ -241,7 +247,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var initial = new Scene(tree);
SceneBuilder.UpdateAll(initial);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(initial, new LayerDirtyRects());
var initialBackgroundNode = initial.FindNode(border).Children[0];
var initialTextNode = initial.FindNode(textBlock).DrawOperations[0];
@ -252,7 +259,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
border.Background = Brushes.Green;
var result = initial.Clone();
SceneBuilder.Update(result, border);
sceneBuilder.Update(result, border, new LayerDirtyRects());
var borderNode = (VisualNode)result.Root.Children[0];
Assert.Same(border, borderNode.Visual);
@ -295,16 +302,17 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var initial = new Scene(tree);
SceneBuilder.UpdateAll(initial);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(initial, new LayerDirtyRects());
border.Child = decorator;
var result = initial.Clone();
Assert.True(SceneBuilder.Update(result, decorator));
Assert.True(sceneBuilder.Update(result, decorator, new LayerDirtyRects()));
// Updating canvas should result in no-op as it should have been updated along
// with decorator as part of the add opeation.
Assert.False(SceneBuilder.Update(result, canvas));
Assert.False(sceneBuilder.Update(result, canvas, new LayerDirtyRects()));
var borderNode = (VisualNode)result.Root.Children[0];
Assert.Equal(1, borderNode.Children.Count);
@ -349,13 +357,14 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var initial = new Scene(tree);
SceneBuilder.UpdateAll(initial);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(initial, new LayerDirtyRects());
border.Child = null;
var result = initial.Clone();
Assert.True(SceneBuilder.Update(result, decorator));
Assert.False(SceneBuilder.Update(result, canvas));
Assert.True(sceneBuilder.Update(result, decorator, new LayerDirtyRects()));
Assert.False(sceneBuilder.Update(result, canvas, new LayerDirtyRects()));
var borderNode = (VisualNode)result.Root.Children[0];
Assert.Equal(0, borderNode.Children.Count);
@ -391,13 +400,14 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Arrange(new Rect(tree.DesiredSize));
var initial = new Scene(tree);
SceneBuilder.UpdateAll(initial);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(initial, new LayerDirtyRects());
border.IsVisible = false;
var result = initial.Clone();
Assert.True(SceneBuilder.Update(result, border));
Assert.False(SceneBuilder.Update(result, canvas));
Assert.True(sceneBuilder.Update(result, border, new LayerDirtyRects()));
Assert.False(sceneBuilder.Update(result, canvas, new LayerDirtyRects()));
var decoratorNode = (VisualNode)result.Root.Children[0];
Assert.Equal(0, decoratorNode.Children.Count);
@ -433,7 +443,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
layout.ExecuteInitialLayoutPass(tree);
var scene = new Scene(tree);
SceneBuilder.UpdateAll(scene);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene, new LayerDirtyRects());
var borderNode = scene.FindNode(border);
var canvasNode = scene.FindNode(canvas);
@ -444,7 +455,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
layout.ExecuteLayoutPass();
scene = scene.Clone();
SceneBuilder.Update(scene, decorator);
sceneBuilder.Update(scene, decorator, new LayerDirtyRects());
borderNode = scene.FindNode(border);
canvasNode = scene.FindNode(canvas);
@ -480,7 +491,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
layout.ExecuteInitialLayoutPass(tree);
var scene = new Scene(tree);
SceneBuilder.UpdateAll(scene);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene, new LayerDirtyRects());
var borderNode = scene.FindNode(border);
var canvasNode = scene.FindNode(canvas);
@ -492,14 +504,79 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
scene = scene.Clone();
var dirty = new DirtyRects();
SceneBuilder.Update(scene, decorator, dirty);
var dirty = new LayerDirtyRects();
sceneBuilder.Update(scene, decorator, dirty);
var rects = dirty.ToArray();
var rects = dirty.Single().Value.ToArray();
Assert.Equal(new[] { new Rect(0, 10, 100, 90) }, rects);
}
}
[Fact]
public void Control_With_Transparency_Should_Start_New_Layer()
{
using (TestApplication())
{
Decorator decorator;
Border border;
Canvas canvas;
var tree = new TestRoot
{
Padding = new Thickness(10),
Width = 100,
Height = 120,
Child = decorator = new Decorator
{
Padding = new Thickness(11),
Child = border = new Border
{
Opacity = 0.5,
Background = Brushes.Red,
Padding = new Thickness(12),
Child = canvas = new Canvas(),
}
}
};
var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
layout.ExecuteInitialLayoutPass(tree);
var dirty = new LayerDirtyRects();
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene, dirty);
var rootNode = (VisualNode)scene.Root;
var borderNode = (VisualNode)scene.FindNode(border);
var canvasNode = (VisualNode)scene.FindNode(canvas);
Assert.Same(tree, rootNode.LayerRoot);
Assert.Same(border, borderNode.LayerRoot);
Assert.Same(border, canvasNode.LayerRoot);
Assert.Equal(2, dirty.Count());
Assert.Empty(dirty.Select(x => x.Key).Except(new IVisual[] { tree, border }));
border.Opacity = 1;
scene = scene.Clone();
dirty = new LayerDirtyRects();
sceneBuilder.Update(scene, border, dirty);
rootNode = (VisualNode)scene.Root;
borderNode = (VisualNode)scene.FindNode(border);
canvasNode = (VisualNode)scene.FindNode(canvas);
Assert.Same(tree, rootNode.LayerRoot);
Assert.Same(tree, borderNode.LayerRoot);
Assert.Same(tree, canvasNode.LayerRoot);
Assert.Equal(1, dirty.Count());
Assert.Equal(tree, dirty.Single().Key);
Assert.Equal(new Rect(21, 21, 58, 78), dirty.Single().Value.Single());
}
}
private IDisposable TestApplication()
{
return UnitTestApplication.Start(

Loading…
Cancel
Save