diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 2bc7121d73..041d8f8f6b 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -28,7 +28,6 @@ namespace Avalonia.Rendering private bool _running; private Scene _scene; - private IRenderTarget _renderTarget; private DirtyVisuals _dirty; private IRenderTargetBitmapImpl _overlay; private bool _updateQueued; @@ -77,7 +76,7 @@ namespace Avalonia.Rendering Contract.Requires(renderTarget != null); _root = root; - _renderTarget = renderTarget; + RenderTarget = renderTarget; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); } @@ -98,6 +97,11 @@ namespace Avalonia.Rendering /// internal RenderLayers Layers { get; } + /// + /// Gets the current render target. + /// + internal IRenderTarget RenderTarget { get; private set; } + /// public void AddDirty(IVisual visual) { @@ -177,9 +181,9 @@ namespace Avalonia.Rendering bool renderOverlay = DrawDirtyRects || DrawFps; bool composite = false; - if (_renderTarget == null) + if (RenderTarget == null) { - _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); } if (renderOverlay) @@ -195,7 +199,7 @@ namespace Avalonia.Rendering if (scene.Generation != _lastSceneId) { - context = _renderTarget.CreateDrawingContext(this); + context = RenderTarget.CreateDrawingContext(this); Layers.Update(scene, context); RenderToLayers(scene); @@ -212,13 +216,13 @@ namespace Avalonia.Rendering if (renderOverlay) { - context = context ?? _renderTarget.CreateDrawingContext(this); + context = context ?? RenderTarget.CreateDrawingContext(this); RenderOverlay(scene, context); RenderComposite(scene, context); } else if (composite) { - context = context ?? _renderTarget.CreateDrawingContext(this); + context = context ?? RenderTarget.CreateDrawingContext(this); RenderComposite(scene, context); } @@ -228,8 +232,8 @@ namespace Avalonia.Rendering catch (RenderTargetCorruptedException ex) { Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); - _renderTarget?.Dispose(); - _renderTarget = null; + RenderTarget?.Dispose(); + RenderTarget = null; } } @@ -241,7 +245,9 @@ namespace Avalonia.Rendering if (!clipBounds.IsEmpty && node.Opacity > 0) { - node.BeginRender(context); + var isLayerRoot = node.Visual == layer; + + node.BeginRender(context, isLayerRoot); foreach (var operation in node.DrawOperations) { @@ -255,7 +261,7 @@ namespace Avalonia.Rendering Render(context, (VisualNode)child, layer, clipBounds); } - node.EndRender(context); + node.EndRender(context, isLayerRoot); } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs index 0d2fc17b95..234cadbf31 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs @@ -72,13 +72,15 @@ namespace Avalonia.Rendering.SceneGraph /// Sets up the drawing context for rendering the node's geometry. /// /// The drawing context. - void BeginRender(IDrawingContextImpl context); + /// Whether to skip pushing the control's opacity. + void BeginRender(IDrawingContextImpl context, bool skipOpacity); /// /// Resets the drawing context after rendering the node's geometry. /// /// The drawing context. - void EndRender(IDrawingContextImpl context); + /// Whether to skip popping the control's opacity. + void EndRender(IDrawingContextImpl context, bool skipOpacity); /// /// Hit test the geometry in this node. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index ea3b07ffe3..e9e20557bd 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -218,15 +218,15 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void BeginRender(IDrawingContextImpl context) + public void BeginRender(IDrawingContextImpl context, bool skipOpacity) { if (ClipToBounds) { context.Transform = Matrix.Identity; context.PushClip(ClipBounds); } - - if (Opacity != 1) + + if (Opacity != 1 && !skipOpacity) { context.PushOpacity(Opacity); } @@ -240,14 +240,14 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void EndRender(IDrawingContextImpl context) + public void EndRender(IDrawingContextImpl context, bool skipOpacity) { if (GeometryClip != null) { context.PopGeometryClip(); } - if (Opacity != 1) + if (Opacity != 1 && !skipOpacity) { context.PopOpacity(); } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index e3b2577e79..21065cb6b5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -201,6 +201,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { Background = Brushes.Green, Child = new Canvas(), + Opacity = 0.9, } } }; @@ -225,6 +226,93 @@ namespace Avalonia.Visuals.UnitTests.Rendering Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot)); } + [Fact] + public void Should_Not_Create_Layer_For_Childless_Control_With_Animated_Opacity() + { + Border border; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new Border + { + Background = Brushes.Red, + Child = border = new Border + { + Background = Brushes.Green, + } + } + }; + + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var loop = new Mock(); + var target = CreateTargetAndRunFrame(root, loop: loop); + + Assert.Single(target.Layers); + } + + [Fact] + public void Should_Not_Push_Opacity_For_Transparent_Layer_Root_Control() + { + Border border; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = border = new Border + { + Background = Brushes.Red, + Child = new Canvas(), + } + }; + + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var target = CreateTargetAndRunFrame(root); + var context = GetLayerContext(target, border); + + context.Verify(x => x.PushOpacity(0.5), Times.Never); + context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + context.Verify(x => x.PopOpacity(), Times.Never); + } + + [Fact] + public void Should_Draw_Transparent_Layer_With_Correct_Opacity() + { + Border border; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = border = new Border + { + Background = Brushes.Red, + Child = new Canvas(), + } + }; + + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var target = CreateTargetAndRunFrame(root); + var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null)); + var borderLayer = target.Layers[border].Bitmap; + + context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny(), It.IsAny())); + } + private DeferredRenderer CreateTargetAndRunFrame( TestRoot root, Mock loop = null,