Browse Source

Fix layers with opacity.

We were pushing the opacity twice.
pull/1306/head
Steven Kirk 9 years ago
parent
commit
1d1f31794e
  1. 28
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  2. 6
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  3. 10
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  4. 88
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

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

@ -28,7 +28,6 @@ namespace Avalonia.Rendering
private bool _running; private bool _running;
private Scene _scene; private Scene _scene;
private IRenderTarget _renderTarget;
private DirtyVisuals _dirty; private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay; private IRenderTargetBitmapImpl _overlay;
private bool _updateQueued; private bool _updateQueued;
@ -77,7 +76,7 @@ namespace Avalonia.Rendering
Contract.Requires<ArgumentNullException>(renderTarget != null); Contract.Requires<ArgumentNullException>(renderTarget != null);
_root = root; _root = root;
_renderTarget = renderTarget; RenderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
Layers = new RenderLayers(); Layers = new RenderLayers();
} }
@ -98,6 +97,11 @@ namespace Avalonia.Rendering
/// </summary> /// </summary>
internal RenderLayers Layers { get; } internal RenderLayers Layers { get; }
/// <summary>
/// Gets the current render target.
/// </summary>
internal IRenderTarget RenderTarget { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public void AddDirty(IVisual visual) public void AddDirty(IVisual visual)
{ {
@ -177,9 +181,9 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps; bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false; bool composite = false;
if (_renderTarget == null) if (RenderTarget == null)
{ {
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
} }
if (renderOverlay) if (renderOverlay)
@ -195,7 +199,7 @@ namespace Avalonia.Rendering
if (scene.Generation != _lastSceneId) if (scene.Generation != _lastSceneId)
{ {
context = _renderTarget.CreateDrawingContext(this); context = RenderTarget.CreateDrawingContext(this);
Layers.Update(scene, context); Layers.Update(scene, context);
RenderToLayers(scene); RenderToLayers(scene);
@ -212,13 +216,13 @@ namespace Avalonia.Rendering
if (renderOverlay) if (renderOverlay)
{ {
context = context ?? _renderTarget.CreateDrawingContext(this); context = context ?? RenderTarget.CreateDrawingContext(this);
RenderOverlay(scene, context); RenderOverlay(scene, context);
RenderComposite(scene, context); RenderComposite(scene, context);
} }
else if (composite) else if (composite)
{ {
context = context ?? _renderTarget.CreateDrawingContext(this); context = context ?? RenderTarget.CreateDrawingContext(this);
RenderComposite(scene, context); RenderComposite(scene, context);
} }
@ -228,8 +232,8 @@ namespace Avalonia.Rendering
catch (RenderTargetCorruptedException ex) catch (RenderTargetCorruptedException ex)
{ {
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose(); RenderTarget?.Dispose();
_renderTarget = null; RenderTarget = null;
} }
} }
@ -241,7 +245,9 @@ namespace Avalonia.Rendering
if (!clipBounds.IsEmpty && node.Opacity > 0) if (!clipBounds.IsEmpty && node.Opacity > 0)
{ {
node.BeginRender(context); var isLayerRoot = node.Visual == layer;
node.BeginRender(context, isLayerRoot);
foreach (var operation in node.DrawOperations) foreach (var operation in node.DrawOperations)
{ {
@ -255,7 +261,7 @@ namespace Avalonia.Rendering
Render(context, (VisualNode)child, layer, clipBounds); Render(context, (VisualNode)child, layer, clipBounds);
} }
node.EndRender(context); node.EndRender(context, isLayerRoot);
} }
} }
} }

6
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. /// Sets up the drawing context for rendering the node's geometry.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
void BeginRender(IDrawingContextImpl context); /// <param name="skipOpacity">Whether to skip pushing the control's opacity.</param>
void BeginRender(IDrawingContextImpl context, bool skipOpacity);
/// <summary> /// <summary>
/// Resets the drawing context after rendering the node's geometry. /// Resets the drawing context after rendering the node's geometry.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
void EndRender(IDrawingContextImpl context); /// <param name="skipOpacity">Whether to skip popping the control's opacity.</param>
void EndRender(IDrawingContextImpl context, bool skipOpacity);
/// <summary> /// <summary>
/// Hit test the geometry in this node. /// Hit test the geometry in this node.

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

@ -218,15 +218,15 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <inheritdoc/> /// <inheritdoc/>
public void BeginRender(IDrawingContextImpl context) public void BeginRender(IDrawingContextImpl context, bool skipOpacity)
{ {
if (ClipToBounds) if (ClipToBounds)
{ {
context.Transform = Matrix.Identity; context.Transform = Matrix.Identity;
context.PushClip(ClipBounds); context.PushClip(ClipBounds);
} }
if (Opacity != 1) if (Opacity != 1 && !skipOpacity)
{ {
context.PushOpacity(Opacity); context.PushOpacity(Opacity);
} }
@ -240,14 +240,14 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <inheritdoc/> /// <inheritdoc/>
public void EndRender(IDrawingContextImpl context) public void EndRender(IDrawingContextImpl context, bool skipOpacity)
{ {
if (GeometryClip != null) if (GeometryClip != null)
{ {
context.PopGeometryClip(); context.PopGeometryClip();
} }
if (Opacity != 1) if (Opacity != 1 && !skipOpacity)
{ {
context.PopOpacity(); context.PopOpacity();
} }

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

@ -201,6 +201,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
Background = Brushes.Green, Background = Brushes.Green,
Child = new Canvas(), 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)); 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<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>();
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<double>(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<double>(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<Rect>(), It.IsAny<Rect>()));
}
private DeferredRenderer CreateTargetAndRunFrame( private DeferredRenderer CreateTargetAndRunFrame(
TestRoot root, TestRoot root,
Mock<IRenderLoop> loop = null, Mock<IRenderLoop> loop = null,

Loading…
Cancel
Save