A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

243 lines
10 KiB

using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionVisual
{
protected virtual bool HasEffect => Effect != null;
struct UpdateContext : IServerTreeVisitor, IDisposable
{
private TreeWalkContext _context;
private IDirtyRectCollector _dirtyRegion;
private int _dirtyRegionDisableCount;
private Stack<int> _dirtyRegionDisableCountStack;
private Stack<IDirtyRectCollector> _dirtyRegionCollectorStack;
private bool AreDirtyRegionsDisabled() => _dirtyRegionDisableCount != 0;
public UpdateContext(CompositorPools pools, IDirtyRectCollector dirtyRects, Matrix transform, LtrbRect clip)
{
_dirtyRegion = dirtyRects;
_context = new TreeWalkContext(pools, transform, clip);
_dirtyRegionDisableCountStack = pools.IntStackPool.Rent();
_dirtyRegionCollectorStack = pools.DirtyRectCollectorStackPool.Rent();
}
private void PushCacheIfNeeded(ServerCompositionVisual visual)
{
if (visual.Cache != null)
{
_dirtyRegionCollectorStack.Push(_dirtyRegion);
_dirtyRegion = visual.Cache.DirtyRectCollector;
_dirtyRegionDisableCountStack.Push(_dirtyRegionDisableCount);
_dirtyRegionDisableCount = 0;
_context.PushSetTransform(Matrix.Identity);
_context.ResetClip(LtrbRect.Infinite);
}
}
private void PopCacheIfNeeded(ServerCompositionVisual visual)
{
if (visual.Cache != null)
{
_context.PopClip();
_context.PopTransform();
_dirtyRegion = _dirtyRegionCollectorStack.Pop();
_dirtyRegionDisableCount = _dirtyRegionDisableCountStack.Pop();
if (visual.Cache.IsDirty)
AddToDirtyRegion(visual._subTreeBounds);
}
}
private bool NeedToPushBoundsAffectingProperties(ServerCompositionVisual node)
{
return (node._isDirtyForRenderInSubgraph || node._needsToAddExtraDirtyRectToDirtyRegion || node._contentChanged);
}
public void PreSubgraph(ServerCompositionVisual node, out bool visitChildren)
{
visitChildren = node._isDirtyForRenderInSubgraph || node._needsBoundingBoxUpdate;
// If this node has an alpha mask an we caused its inner bounds to change
// then treat the node as if _isDirtyForRender was set.
if (node is { _needsBoundingBoxUpdate: true, OpacityMaskBrush: not null })
node._isDirtyForRender = true;
// Special handling for effects: just add the entire node's old subtree bounds as a dirty region
// WPF does this because they had legacy effects with non-affine transforms, we do this because TBD
if (node._isDirtyForRender || node is { _isDirtyForRenderInSubgraph: true, HasEffect: true })
{
// If bounds haven't actually changed, there is no point in adding them now since they will be added
// again in PostSubgraph.
if (node._needsBoundingBoxUpdate && !AreDirtyRegionsDisabled())
{
// We add this node's bbox to the dirty region. Alternatively we could walk the sub-graph and add the
// bbox of each node's content to the dirty region. Note that this is much harder to do because if the
// transform changes we don't know anymore the old transform. We would have to use to a two phased dirty
// region algorithm.
AddToDirtyRegion(node._transformedSubTreeBounds);
}
// If we added a node in the parent chain to the bbox we don't need to add anything below this node
// to the dirty region.
_dirtyRegionDisableCount++;
}
// If a node in the sub-graph of this node is dirty for render and we haven't collected the bbox of one of pNode's
// ascendants as dirty region, then we need to maintain the transform and clip stack so that we have a world transform
// when we need to collect the bbox of the descendant node that is dirty for render. If something has changed
// in the contents or subgraph, we need to update the cache on this node.
if (NeedToPushBoundsAffectingProperties(node))
{
// Dirty regions will be enabled if we haven't collected an ancestor's bbox or if they were re-enabled
// by an ancestor's cache.
if (!AreDirtyRegionsDisabled())
{
PushBoundsAffectingProperties(node);
}
PushCacheIfNeeded(node);
}
if (node._needsBoundingBoxUpdate)
{
// This node's bbox needs to be updated. We start out by setting his bbox to the bbox of its content. All its
// children will union their bbox into their parent's bbox. PostSubgraph will clip the bbox and transform it
// to outer space.
node._subTreeBounds = node._ownContentBounds;
}
}
public void PostSubgraph(ServerCompositionVisual node)
{
var parent = node.Parent;
if (node._needsBoundingBoxUpdate)
{
//
// If pNode's bbox got recomputed it is at this point still in inner
// space. We need to apply the clip and transform.
//
FinalizeSubtreeBounds(node);
}
//
// Update state on the parent node if we have a parent.
if (parent != null)
{
// Update the bounding box on the parent.
if (parent._needsBoundingBoxUpdate)
parent._subTreeBounds = LtrbRect.FullUnion(parent._subTreeBounds, node._transformedSubTreeBounds);
}
//
// If there are additional dirty regions, pick them up. (Additional dirty regions are
// specified before the tranform, i.e. in inner space, hence we have to pick them
// up before we pop the transform from the transform stack.
//
if (node._needsToAddExtraDirtyRectToDirtyRegion)
{
AddToDirtyRegion(node._extraDirtyRect);
}
// If we pushed transforms here, we need to pop them again. If we're handling a cache we need
// to finish handling it here as well.
if (NeedToPushBoundsAffectingProperties(node))
{
PopCacheIfNeeded(node);
if(!AreDirtyRegionsDisabled())
PopBoundsAffectingProperties(node);
}
// Special handling for effects: just add the entire node's old subtree bounds as a dirty region
// WPF does this because they had legacy effects with non-affine transforms, we do this because TBD
if(node._isDirtyForRender || node is { _isDirtyForRenderInSubgraph: true, Effect: not null })
{
_dirtyRegionDisableCount--;
AddToDirtyRegion(node._transformedSubTreeBounds);
}
node._isDirtyForRender = false;
node._isDirtyForRenderInSubgraph = false;
node._needsBoundingBoxUpdate = false;
node._needsToAddExtraDirtyRectToDirtyRegion = false;
node._contentChanged = false;
}
private void FinalizeSubtreeBounds(ServerCompositionVisual node)
{
// WPF simply removes drawing commands from every visual in invisible subtree (on UI thread).
// We set the bounds to null when computing subtree bounds for invisible nodes.
if (!node.Visible)
node._subTreeBounds = null;
if (node._subTreeBounds != null)
{
if (node.Effect != null)
node._subTreeBounds = node._subTreeBounds.Value.Inflate(node.Effect.GetEffectOutputPadding());
if (node._ownClipRect.HasValue)
node._subTreeBounds = node._subTreeBounds.Value.IntersectOrNull(node._ownClipRect.Value);
}
if (node._subTreeBounds == null)
node._transformedSubTreeBounds = null;
else if (node._ownTransform.HasValue)
node._transformedSubTreeBounds = node._subTreeBounds?.TransformToAABB(node._ownTransform.Value);
else
node._transformedSubTreeBounds = node._subTreeBounds;
node.EnqueueForReadbackUpdate();
}
private void AddToDirtyRegion(LtrbRect? bounds)
{
if(_dirtyRegionDisableCount != 0 || !bounds.HasValue)
return;
var transformed = bounds.Value.TransformToAABB(_context.Transform).IntersectOrEmpty(_context.Clip);
if(transformed.IsZeroSize)
return;
_dirtyRegion.AddRect(transformed);
}
private void PushBoundsAffectingProperties(ServerCompositionVisual node)
{
if (node._ownTransform.HasValue)
_context.PushTransform(node._ownTransform.Value);
if (node._ownClipRect.HasValue)
_context.PushClip(node._ownClipRect.Value.TransformToAABB(_context.Transform));
}
private void PopBoundsAffectingProperties(ServerCompositionVisual node)
{
if (node._ownTransform.HasValue)
_context.PopTransform();
if (node._ownClipRect.HasValue)
_context.PopClip();
}
public void Dispose()
{
_context.Pools.IntStackPool.Return(ref _dirtyRegionDisableCountStack);
_context.Pools.DirtyRectCollectorStackPool.Return(ref _dirtyRegionCollectorStack);
_context.Dispose();
}
}
public void UpdateRoot(IDirtyRectCollector tracker, Matrix transform, LtrbRect clip)
{
var context = new UpdateContext(Compositor.Pools, tracker, transform, clip);
ServerTreeWalker<UpdateContext>.Walk(ref context, this);
context.Dispose();
}
}