Browse Source

Only create render layers when Opacity is animating.

Previously we were creating a new render layer for all controls with `Opacity != 1`. This was causing the `Calendar` page in ControlCatalog to render _really_ slowly. Instead, only create a new render layer when `Opacity` is being animated.

Also don't even render controls where `Opacity == 0`.
pull/1306/head
Steven Kirk 8 years ago
parent
commit
68bdbca899
  1. 3
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  2. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  3. 10
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  4. 98
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  5. 6
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  6. 100
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

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

@ -235,7 +235,7 @@ namespace Avalonia.Rendering
{
clipBounds = node.ClipBounds.Intersect(clipBounds);
if (!clipBounds.IsEmpty)
if (!clipBounds.IsEmpty && node.Opacity > 0)
{
node.BeginRender(context);
@ -353,7 +353,6 @@ namespace Avalonia.Rendering
if (DrawFps)
{
RenderFps(context, clientRect, true);
RenderFps(context, clientRect, scene.Layers.Count);
}
}

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

@ -167,7 +167,7 @@ namespace Avalonia.Rendering.SceneGraph
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
var startLayer = opacity < 1 || visual.OpacityMask != null;
var startLayer = (visual as IAvaloniaObject)?.IsAnimating(Visual.OpacityProperty) ?? false;
var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
forceRecurse = forceRecurse ||

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

@ -226,6 +226,11 @@ namespace Avalonia.Rendering.SceneGraph
context.PushClip(ClipBounds);
}
if (Opacity != 1)
{
context.PushOpacity(Opacity);
}
context.Transform = Transform;
if (GeometryClip != null)
@ -242,6 +247,11 @@ namespace Avalonia.Rendering.SceneGraph
context.PopGeometryClip();
}
if (Opacity != 1)
{
context.PopOpacity();
}
if (ClipToBounds)
{
context.PopClip();

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

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
@ -126,6 +128,94 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Assert.Equal(new List<IVisual> { root, decorator, border, canvas }, result);
}
[Fact]
public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity()
{
var root = new TestRoot
{
Width = 100,
Height = 100,
Child = new Border
{
Background = Brushes.Red,
Opacity = 0.5,
}
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var rootLayer = CreateLayer();
var borderLayer = CreateLayer();
var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
.Returns(rootLayer)
.Returns(borderLayer);
var loop = new Mock<IRenderLoop>();
var target = new DeferredRenderer(
root,
loop.Object,
dispatcher: new ImmediateDispatcher());
root.Renderer = target;
target.Start();
RunFrame(loop);
var context = Mock.Get(rootLayer.CreateDrawingContext(null));
var animation = new BehaviorSubject<double>(0.5);
context.Verify(x => x.PushOpacity(0.5), Times.Once);
context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
context.Verify(x => x.PopOpacity(), Times.Once);
}
[Fact]
public void Should_Not_Draw_Controls_With_0_Opacity()
{
var root = new TestRoot
{
Width = 100,
Height = 100,
Child = new Border
{
Background = Brushes.Red,
Opacity = 0,
Child = new Border
{
Background = Brushes.Green,
}
}
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var rootLayer = CreateLayer();
var borderLayer = CreateLayer();
var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
.Returns(rootLayer)
.Returns(borderLayer);
var loop = new Mock<IRenderLoop>();
var target = new DeferredRenderer(
root,
loop.Object,
dispatcher: new ImmediateDispatcher());
root.Renderer = target;
target.Start();
RunFrame(loop);
var context = Mock.Get(rootLayer.CreateDrawingContext(null));
var animation = new BehaviorSubject<double>(0.5);
context.Verify(x => x.PushOpacity(0.5), Times.Never);
context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Never);
context.Verify(x => x.PopOpacity(), Times.Never);
}
[Fact]
public void Frame_Should_Create_Layer_For_Root()
{
@ -148,7 +238,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
//layerFactory: layers.Object,
dispatcher: dispatcher);
target.Start();
@ -159,7 +248,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
[Fact]
public void Should_Create_And_Delete_Layers_For_Transparent_Controls()
public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity()
{
Border border;
var root = new TestRoot
@ -198,6 +287,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null));
var animation = new BehaviorSubject<double>(0.5);
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);
@ -205,7 +295,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
rootContext.ResetCalls();
borderContext.ResetCalls();
border.Opacity = 0.5;
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
RunFrame(loop);
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
@ -214,7 +304,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
rootContext.ResetCalls();
borderContext.ResetCalls();
border.Opacity = 1;
animation.OnCompleted();
RunFrame(loop);
Mock.Get(borderLayer).Verify(x => x.Dispose());

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

@ -9,6 +9,8 @@ using Xunit;
using Avalonia.Layout;
using Moq;
using Avalonia.Platform;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
@ -620,13 +622,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Margin = new Thickness(0, 10, 0, 0),
Child = border = new Border
{
Opacity = 0.5,
Background = Brushes.Red,
Child = canvas = new Canvas(),
}
}
};
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);

100
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

@ -7,14 +7,15 @@ using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
using Avalonia.Layout;
using Avalonia.Rendering;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
public partial class SceneBuilderTests
{
[Fact]
public void Control_With_Transparency_Should_Start_New_Layer()
public void Control_With_Animated_Opacity_Should_Start_New_Layer()
{
using (TestApplication())
{
@ -31,7 +32,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Padding = new Thickness(11),
Child = border = new Border
{
Opacity = 0.5,
Background = Brushes.Red,
Padding = new Thickness(12),
Child = canvas = new Canvas(),
@ -42,6 +42,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
layout.ExecuteInitialLayoutPass(tree);
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
@ -58,7 +61,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.Equal(2, scene.Layers.Count());
Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border }));
border.Opacity = 1;
animation.OnCompleted();
scene = scene.Clone();
sceneBuilder.Update(scene, border);
@ -80,7 +83,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
[Fact]
public void Control_With_OpacityMask_Should_Start_New_Layer()
public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers()
{
using (TestApplication())
{
@ -97,10 +100,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Padding = new Thickness(11),
Child = border = new Border
{
OpacityMask = Brushes.Red,
Background = Brushes.Red,
Padding = new Thickness(12),
Child = canvas = new Canvas(),
Child = canvas = new Canvas()
}
}
};
@ -108,74 +110,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
layout.ExecuteInitialLayoutPass(tree);
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
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(Brushes.Red, scene.Layers[border].OpacityMask);
Assert.Equal(2, scene.Layers.Count());
Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border }));
border.OpacityMask = null;
scene = scene.Clone();
sceneBuilder.Update(scene, border);
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.Single(scene.Layers);
var rootDirty = scene.Layers[tree].Dirty;
Assert.Single(rootDirty);
Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single());
}
}
[Fact]
public void Removing_Transparent_Control_Should_Remove_Layers()
{
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
{
Opacity = 0.75,
},
}
}
};
var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
layout.ExecuteInitialLayoutPass(tree);
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
@ -210,13 +147,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Padding = new Thickness(11),
Child = border = new Border
{
Opacity = 0.5,
Background = Brushes.Red,
Padding = new Thickness(12),
Child = canvas = new Canvas
{
Opacity = 0.75,
},
Child = canvas = new Canvas(),
}
}
};
@ -224,6 +157,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
layout.ExecuteInitialLayoutPass(tree);
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
@ -263,6 +200,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
layout.ExecuteInitialLayoutPass(tree);
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);

Loading…
Cancel
Save