From 7d0651f643059c7c909528bd54d47249a8385f26 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Jun 2022 12:42:16 +0300 Subject: [PATCH] Fix opacity (and other composition-affected properties) invalidation --- .../Server/ServerCompositionDrawListVisual.cs | 2 +- .../Server/ServerCompositionTarget.cs | 2 +- .../Composition/Server/ServerVisual.cs | 73 +++++++++++++++---- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index eff1af65e2..df3c78b1bc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -29,7 +29,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua Rect? _contentBounds; - public override Rect ContentBounds + public override Rect OwnContentBounds { get { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 3faba5095f..9fd1a9f191 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -191,7 +191,7 @@ namespace Avalonia.Rendering.Composition.Server if (_attachedVisuals.Remove(visual) && IsEnabled) visual.Deactivate(); if(visual.IsVisibleInFrame) - AddDirtyRect(visual.TransformedBounds); + AddDirtyRect(visual.TransformedOwnContentBounds); } public void EnqueueAdornerUpdate(ServerCompositionVisual visual) => _adornerUpdateQueue.Enqueue(visual); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs index 0c58647319..f2b6b4d880 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs @@ -1,4 +1,6 @@ +using System; using System.Numerics; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.Composition.Transport; @@ -13,6 +15,8 @@ namespace Avalonia.Rendering.Composition.Server partial class ServerCompositionVisual : ServerObject { private bool _isDirty; + private bool _isDirtyComposition; + private CompositionProperties _oldCompositionProperties; private bool _isBackface; protected virtual void RenderCore(CompositorDrawingContextProxy canvas) { @@ -21,7 +25,8 @@ namespace Avalonia.Rendering.Composition.Server public void Render(CompositorDrawingContextProxy canvas) { - if(Visible == false) + _isDirtyComposition = false; + if(Visible == false || IsVisibleInFrame == false) return; if(Opacity == 0) return; @@ -38,7 +43,6 @@ namespace Avalonia.Rendering.Composition.Server if(OpacityMaskBrush != null) canvas.PushOpacityMask(OpacityMaskBrush, boundsRect); - //TODO: Check clip RenderCore(canvas); // Hack to force invalidation of SKMatrix @@ -93,30 +97,34 @@ namespace Avalonia.Rendering.Composition.Server } var wasVisible = IsVisibleInFrame; - //TODO: check effective opacity too - IsVisibleInFrame = Visible && Opacity > 0 && !_isBackface; + + EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1); + IsVisibleInFrame = Visible && EffectiveOpacity > 0.04 && !_isBackface; // Invalidate previous rect and queue new rect based on visibility if (positionChanged) { if(wasVisible) - Root!.AddDirtyRect(TransformedBounds); + Root!.AddDirtyRect(TransformedOwnContentBounds); if (IsVisibleInFrame) _isDirty = true; } + + if (wasVisible != IsVisibleInFrame) + _isDirty = true; + + if (_parent.Value?._isDirtyComposition == true) + _isDirty = true; GlobalTransformMatrix = newTransform; //TODO: Cache - TransformedBounds = ContentBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix)); + TransformedOwnContentBounds = OwnContentBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix)); - if (!IsVisibleInFrame) - _isDirty = false; - else if (_isDirty) - { - Root!.AddDirtyRect(TransformedBounds); - _isDirty = false; - } + if (IsVisibleInFrame && _isDirty) + Root!.AddDirtyRect(TransformedOwnContentBounds); + + _isDirty = false; // Update readback indices var i = Root!.Readback; @@ -126,6 +134,14 @@ namespace Avalonia.Rendering.Composition.Server readback.TargetId = Root.Id; readback.Visible = IsVisibleInFrame; + // Forcefully mark any children visuals are dirty if any of the composition + // properties were changed since the last update + var newProps = GetCompositionProperties(); + if (!newProps.Equals(_oldCompositionProperties)) + { + _isDirtyComposition = true; + _oldCompositionProperties = newProps; + } } /// @@ -160,13 +176,38 @@ namespace Avalonia.Rendering.Composition.Server { _isDirty = true; if (IsVisibleInFrame) - Root?.AddDirtyRect(TransformedBounds); + Root?.AddDirtyRect(TransformedOwnContentBounds); else Root?.Invalidate(); } + + struct CompositionProperties + { + public double EffectiveOpacity { get; set; } + public Vector2? ClipSize { get; set; } + public IGeometryImpl? Clip { get; set; } + public IBrush? OpacityMaskBrush { get; set; } + + public bool Equals(CompositionProperties other) => + EffectiveOpacity == other.EffectiveOpacity + && ClipSize == other.ClipSize + && Clip == other.Clip + && OpacityMaskBrush == other.OpacityMaskBrush; + } + + private CompositionProperties GetCompositionProperties() => new CompositionProperties + { + EffectiveOpacity = EffectiveOpacity, + Clip = Clip, + ClipSize = ClipToBounds ? Size : null, + OpacityMaskBrush = OpacityMaskBrush + }; + + public bool IsVisibleInFrame { get; set; } - public Rect TransformedBounds { get; set; } - public virtual Rect ContentBounds => new Rect(0, 0, Size.X, Size.Y); + public double EffectiveOpacity { get; set; } + public Rect TransformedOwnContentBounds { get; set; } + public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y); }