Browse Source

Imporve dirty rect tracking, optimize invalidation

pull/8105/head
Nikita Tsukanov 4 years ago
parent
commit
2f8dcda203
  1. 4
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  2. 12
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  3. 14
      src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs
  4. 2
      src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
  5. 76
      src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.DirtyProperties.cs
  6. 160
      src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs
  7. 13
      src/tools/DevGenerators/CompositionGenerator/Generator.cs

4
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@ -57,13 +57,13 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.DeserializeChangesCore(reader, commitedAt);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
if (_renderCommands != null)
{
_renderCommands.Render(canvas);
}
base.RenderCore(canvas);
base.RenderCore(canvas, currentTransformedClip);
}
#if DEBUG

12
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -35,6 +35,7 @@ namespace Avalonia.Rendering.Composition.Server
public ReadbackIndices Readback { get; } = new();
public int RenderedVisuals { get; set; }
public ServerCompositionTarget(ServerCompositor compositor, Func<IRenderTarget> renderTargetFactory) :
base(compositor)
@ -85,12 +86,12 @@ namespace Avalonia.Rendering.Composition.Server
Revision++;
// Update happens in a separate phase to extend dirty rect if needed
Root.Update(this, Matrix4x4.Identity);
Root.Update(this);
while (_adornerUpdateQueue.Count > 0)
{
var adorner = _adornerUpdateQueue.Dequeue();
adorner.Update(this, adorner.AdornedVisual?.GlobalTransformMatrix ?? Matrix4x4.Identity);
adorner.Update(this);
}
Readback.CompleteWrite(Revision);
@ -114,7 +115,7 @@ namespace Avalonia.Rendering.Composition.Server
{
context.PushClip(_dirtyRect);
context.Clear(Colors.Transparent);
Root.Render(new CompositorDrawingContextProxy(context, visualBrushHelper));
Root.Render(new CompositorDrawingContextProxy(context, visualBrushHelper), _dirtyRect);
context.PopClip();
}
}
@ -143,8 +144,9 @@ namespace Avalonia.Rendering.Composition.Server
(Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) *
Compositor.BatchObjectPool.ArraySize *
IntPtr.Size), false);
_fpsCounter.RenderFps(targetContext, $"M:{managedMem} / N:{nativeMem}");
_fpsCounter.RenderFps(targetContext, $"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}");
}
RenderedVisuals = 0;
_dirtyRect = Rect.Empty;
}
@ -163,6 +165,8 @@ namespace Avalonia.Rendering.Composition.Server
public void AddDirtyRect(Rect rect)
{
if(rect.IsEmpty)
return;
var snapped = SnapToDevicePixels(rect, Scaling);
_dirtyRect = _dirtyRect.Union(snapped);
_redrawRequested = true;

14
src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs

@ -12,26 +12,28 @@ namespace Avalonia.Rendering.Composition.Server
{
public ServerCompositionVisualCollection Children { get; private set; } = null!;
protected override void RenderCore(CompositorDrawingContextProxy canvas)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
base.RenderCore(canvas);
base.RenderCore(canvas, currentTransformedClip);
foreach (var ch in Children)
{
ch.Render(canvas);
ch.Render(canvas, currentTransformedClip);
}
}
public override void Update(ServerCompositionTarget root, Matrix4x4 transform)
public override void Update(ServerCompositionTarget root)
{
base.Update(root, transform);
base.Update(root);
foreach (var child in Children)
{
if (child.AdornedVisual != null)
root.EnqueueAdornerUpdate(child);
else
child.Update(root, GlobalTransformMatrix);
child.Update(root);
}
IsDirtyComposition = false;
}
partial void Initialize()

2
src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs

@ -81,7 +81,7 @@ namespace Avalonia.Rendering.Composition.Server
new IntPtr(offset)));
}
public void NotifyAnimatedValueChanged(int offset)
public virtual void NotifyAnimatedValueChanged(int offset)
{
ref var store = ref GetStoreFromOffset(offset);
store.Invalidate();

76
src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.DirtyProperties.cs

@ -0,0 +1,76 @@
namespace Avalonia.Rendering.Composition.Server;
partial class ServerCompositionVisual
{
protected bool IsDirtyComposition;
private bool _combinedTransformDirty;
private bool _clipSizeDirty;
private const CompositionVisualChangedFields CompositionFieldsMask
= CompositionVisualChangedFields.Opacity
| CompositionVisualChangedFields.OpacityAnimated
| CompositionVisualChangedFields.OpacityMaskBrush
| CompositionVisualChangedFields.Clip
| CompositionVisualChangedFields.ClipToBounds
| CompositionVisualChangedFields.ClipToBoundsAnimated
| CompositionVisualChangedFields.Size
| CompositionVisualChangedFields.SizeAnimated;
private const CompositionVisualChangedFields CombinedTransformFieldsMask =
CompositionVisualChangedFields.Size
| CompositionVisualChangedFields.SizeAnimated
| CompositionVisualChangedFields.AnchorPoint
| CompositionVisualChangedFields.AnchorPointAnimated
| CompositionVisualChangedFields.CenterPoint
| CompositionVisualChangedFields.CenterPointAnimated
| CompositionVisualChangedFields.AdornedVisual
| CompositionVisualChangedFields.TransformMatrix
| CompositionVisualChangedFields.Scale
| CompositionVisualChangedFields.ScaleAnimated
| CompositionVisualChangedFields.RotationAngle
| CompositionVisualChangedFields.RotationAngleAnimated
| CompositionVisualChangedFields.Orientation
| CompositionVisualChangedFields.OrientationAnimated
| CompositionVisualChangedFields.Offset
| CompositionVisualChangedFields.OffsetAnimated;
private const CompositionVisualChangedFields ClipSizeDirtyMask =
CompositionVisualChangedFields.Size
| CompositionVisualChangedFields.SizeAnimated
| CompositionVisualChangedFields.ClipToBounds
| CompositionVisualChangedFields.ClipToBoundsAnimated;
partial void OnFieldsDeserialized(CompositionVisualChangedFields changed)
{
if ((changed & CompositionFieldsMask) != 0)
IsDirtyComposition = true;
if ((changed & CombinedTransformFieldsMask) != 0)
_combinedTransformDirty = true;
if ((changed & ClipSizeDirtyMask) != 0)
_clipSizeDirty = true;
}
public override void NotifyAnimatedValueChanged(int offset)
{
base.NotifyAnimatedValueChanged(offset);
if (offset == s_OffsetOf_clipToBounds
|| offset == s_OffsetOf_opacity
|| offset == s_OffsetOf_size)
IsDirtyComposition = true;
if (offset == s_OffsetOf_size
|| offset == s_OffsetOf_anchorPoint
|| offset == s_OffsetOf_centerPoint
|| offset == s_OffsetOf_adornedVisual
|| offset == s_OffsetOf_transformMatrix
|| offset == s_OffsetOf_scale
|| offset == s_OffsetOf_rotationAngle
|| offset == s_OffsetOf_orientation
|| offset == s_OffsetOf_offset)
_combinedTransformDirty = true;
if (offset == s_OffsetOf_clipToBounds
|| offset == s_OffsetOf_size)
_clipSizeDirty = true;
}
}

160
src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs

@ -16,22 +16,30 @@ namespace Avalonia.Rendering.Composition.Server
/// </summary>
partial class ServerCompositionVisual : ServerObject
{
private bool _isDirty;
private bool _isDirtyComposition;
private CompositionProperties _oldCompositionProperties;
private bool _isDirtyForUpdate;
private Rect _oldOwnContentBounds;
private bool _isBackface;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas)
private Rect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
}
public void Render(CompositorDrawingContextProxy canvas)
public void Render(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
_isDirtyComposition = false;
if(Visible == false || IsVisibleInFrame == false)
return;
if(Opacity == 0)
return;
currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds);
if(currentTransformedClip.IsEmpty)
return;
Root!.RenderedVisuals++;
var transform = GlobalTransformMatrix;
canvas.PreTransform = MatrixUtils.ToMatrix(transform);
canvas.Transform = Matrix.Identity;
@ -45,7 +53,7 @@ namespace Avalonia.Rendering.Composition.Server
if(OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
RenderCore(canvas);
RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix
canvas.PreTransform = MatrixUtils.ToMatrix(transform);
@ -76,18 +84,29 @@ namespace Avalonia.Rendering.Composition.Server
return ref _readback2;
}
public Matrix4x4 CombinedTransformMatrix { get; private set; }
public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity;
public Matrix4x4 GlobalTransformMatrix { get; private set; }
public virtual void Update(ServerCompositionTarget root, Matrix4x4 transform)
public virtual void Update(ServerCompositionTarget root)
{
// Calculate new parent-relative transform
CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint,
// HACK: Ignore RenderTransform set by the adorner layer
AdornedVisual != null ? Matrix4x4.Identity : TransformMatrix,
Scale, RotationAngle, Orientation, Offset);
if(Parent == null && Root == null)
return;
var newTransform = CombinedTransformMatrix * transform;
var wasVisible = IsVisibleInFrame;
// Calculate new parent-relative transform
if (_combinedTransformDirty)
{
CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint,
// HACK: Ignore RenderTransform set by the adorner layer
AdornedVisual != null ? Matrix4x4.Identity : TransformMatrix,
Scale, RotationAngle, Orientation, Offset);
_combinedTransformDirty = false;
}
var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity;
var newTransform = CombinedTransformMatrix * parentTransform;
// Check if visual was moved and recalculate face orientation
var positionChanged = false;
@ -97,36 +116,69 @@ namespace Avalonia.Rendering.Composition.Server
new Vector3(0, 0, float.PositiveInfinity), GlobalTransformMatrix).Z <= 0;
positionChanged = true;
}
var oldTransformedContentBounds = TransformedOwnContentBounds;
var oldCombinedTransformedClipBounds = _combinedTransformedClipBounds;
if (_parent.Value?.IsDirtyComposition == true)
{
IsDirtyComposition = true;
_isDirtyForUpdate = true;
}
GlobalTransformMatrix = newTransform;
var wasVisible = IsVisibleInFrame;
var ownBounds = OwnContentBounds;
if (ownBounds != _oldOwnContentBounds || positionChanged)
{
_oldOwnContentBounds = ownBounds;
if (ownBounds.IsEmpty)
TransformedOwnContentBounds = default;
else
TransformedOwnContentBounds =
ownBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix));
}
if (_clipSizeDirty || positionChanged)
{
_transformedClipBounds = ClipToBounds
? new Rect(new Size(Size.X, Size.Y))
.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix))
: null;
_clipSizeDirty = false;
}
_combinedTransformedClipBounds = Parent?._combinedTransformedClipBounds ?? new Rect(Root!.Size);
if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1);
IsVisibleInFrame = Visible && EffectiveOpacity > 0.04 && !_isBackface;
IsVisibleInFrame = Visible && EffectiveOpacity > 0.04 && !_isBackface &&
!_combinedTransformedClipBounds.IsEmpty;
if (wasVisible != IsVisibleInFrame)
_isDirtyForUpdate = true;
// Invalidate previous rect and queue new rect based on visibility
if (positionChanged)
{
if(wasVisible)
Root!.AddDirtyRect(TransformedOwnContentBounds);
AddDirtyRect(oldTransformedContentBounds.Intersect(oldCombinedTransformedClipBounds));
if (IsVisibleInFrame)
_isDirty = true;
_isDirtyForUpdate = true;
}
// Invalidate new bounds
if (IsVisibleInFrame && _isDirtyForUpdate)
AddDirtyRect(TransformedOwnContentBounds.Intersect(_combinedTransformedClipBounds));
if (wasVisible != IsVisibleInFrame)
_isDirty = true;
if (_parent.Value?._isDirtyComposition == true)
_isDirty = true;
GlobalTransformMatrix = newTransform;
//TODO: Cache
TransformedOwnContentBounds = OwnContentBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix));
if (IsVisibleInFrame && _isDirty)
Root!.AddDirtyRect(TransformedOwnContentBounds);
_isDirty = false;
_isDirtyForUpdate = false;
// Update readback indices
var i = Root!.Readback;
@ -135,15 +187,13 @@ namespace Avalonia.Rendering.Composition.Server
readback.Matrix = CombinedTransformMatrix;
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;
}
void AddDirtyRect(Rect rc)
{
if(rc == Rect.Empty)
return;
Root?.AddDirtyRect(rc);
}
/// <summary>
@ -176,36 +226,10 @@ namespace Avalonia.Rendering.Composition.Server
protected override void ValuesInvalidated()
{
_isDirty = true;
if (IsVisibleInFrame)
Root?.AddDirtyRect(TransformedOwnContentBounds);
else
Root?.Invalidate();
_isDirtyForUpdate = true;
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 double EffectiveOpacity { get; set; }
public Rect TransformedOwnContentBounds { get; set; }

13
src/tools/DevGenerators/CompositionGenerator/Generator.cs

@ -324,10 +324,16 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration(
$"protected override void Deactivated(){{}}")!).WithBody(deactivatedBody));
if (cl.Properties.Count > 0)
{
server = server.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration(
$"protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt){{}}")
!)
.WithBody(deserializeMethodBody));
.WithBody(ApplyDeserializeChangesEpilogue(deserializeMethodBody, cl)));
server = server.AddMembers(MethodDeclaration(ParseTypeName("void"), "OnFieldsDeserialized")
.WithParameterList(ParameterList(SingletonSeparatedList(Parameter(Identifier("changed"))
.WithType(ParseTypeName(ChangedFieldsTypeName(cl))))))
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
}
client = client.AddMembers(
MethodDeclaration(ParseTypeName("void"), "InitializeDefaults").WithBody(defaultsMethodBody))
@ -545,6 +551,11 @@ DeserializeChangesExtra(reader);
var changed = reader.Read<{ChangedFieldsTypeName(cl)}>();
"));
}
private BlockSyntax ApplyDeserializeChangesEpilogue(BlockSyntax body, GClass cl)
{
return body.AddStatements(ParseStatement("OnFieldsDeserialized(changed);"));
}
BlockSyntax ApplyDeserializeField(BlockSyntax body, GClass cl, GProperty prop, string serverType, bool isObject)
{

Loading…
Cancel
Save