소스 검색

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`.
Steven Kirk 8 년 전
부모
커밋
68bdbca899

+ 1 - 2
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);
             }
         }

+ 1 - 1
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 - 0
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();

+ 94 - 4
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());

+ 5 - 1
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);

+ 20 - 80
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);