Browse Source

Some optimizations

pull/21310/head
Steven He 5 months ago
parent
commit
a94992be1a
  1. 55
      src/Avalonia.Base/Rendering/Composition/CompositionHitTestAabbTree.cs
  2. 31
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  3. 25
      src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs

55
src/Avalonia.Base/Rendering/Composition/CompositionHitTestAabbTree.cs

@ -7,6 +7,11 @@ namespace Avalonia.Rendering.Composition;
internal sealed class CompositionHitTestAabbTree internal sealed class CompositionHitTestAabbTree
{ {
internal interface IQueryHitTester
{
CompositionVisual? HitTest(CompositionVisual visual);
}
private const int Null = -1; private const int Null = -1;
private const double FatBoundsPadding = 1; private const double FatBoundsPadding = 1;
private static readonly CandidateComparer s_candidateComparer = new(); private static readonly CandidateComparer s_candidateComparer = new();
@ -124,6 +129,56 @@ internal sealed class CompositionHitTestAabbTree
_queryCandidates.Clear(); _queryCandidates.Clear();
} }
public CompositionVisual? QueryFirst<T>(Point point, ref T hitTest)
where T : struct, IQueryHitTester
{
_queryCandidates.Clear();
if (_root != Null)
{
var stackCount = 0;
PushQueryNode(ref stackCount, _root);
while (stackCount > 0)
{
var nodeIndex = _queryStack[--stackCount];
var node = _nodes[nodeIndex];
if (!node.Bounds.Contains(point))
continue;
if (node.IsLeaf)
{
if (node.Visual != null)
_queryCandidates.Add(new Candidate(node.Visual, node.Order));
}
else
{
PushQueryNode(ref stackCount, node.Child1);
PushQueryNode(ref stackCount, node.Child2);
}
}
}
foreach (var candidate in _unbounded)
_queryCandidates.Add(new Candidate(candidate.Key, candidate.Value));
_queryCandidates.Sort(s_candidateComparer);
foreach (var candidate in _queryCandidates)
{
var hit = hitTest.HitTest(candidate.Visual);
if (hit != null)
{
_queryCandidates.Clear();
return hit;
}
}
_queryCandidates.Clear();
return null;
}
private void Add(CompositionVisual visual, int order) private void Add(CompositionVisual visual, int order)
{ {
var state = GetBoundsState(visual, out var bounds); var state = GetBoundsState(visual, out var bounds);

31
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -189,23 +189,12 @@ namespace Avalonia.Rendering.Composition
var queriedIndexedChildren = false; var queriedIndexedChildren = false;
if (cv.Children.Count >= CompositionContainerVisual.HitTestAabbTreeThreshold) if (cv.Children.Count >= CompositionContainerVisual.HitTestAabbTreeThreshold)
{ {
var candidates = RentHitTestChildCandidates(out var releaseToField); var query = new FirstHitTestQuery(this, point, filter, resultFilter);
try if (cv.TryQueryFirstHitTestChild(point, ref query, out var hit))
{ {
if (cv.TryQueryHitTestChildren(point, candidates)) queriedIndexedChildren = true;
{ if (hit != null)
queriedIndexedChildren = true; return hit;
foreach (var child in candidates)
{
var hit = HitTestFirstCore(child, point, filter, resultFilter);
if (hit != null)
return hit;
}
}
}
finally
{
ReleaseHitTestChildCandidates(candidates, releaseToField);
} }
} }
@ -223,6 +212,16 @@ namespace Avalonia.Rendering.Composition
return visual.HitTest(point) && (resultFilter == null || resultFilter(visual)) ? visual : null; return visual.HitTest(point) && (resultFilter == null || resultFilter(visual)) ? visual : null;
} }
private readonly struct FirstHitTestQuery(
CompositionTarget target,
Point point,
Func<CompositionVisual, bool>? filter,
Func<CompositionVisual, bool>? resultFilter) : CompositionHitTestAabbTree.IQueryHitTester
{
public CompositionVisual? HitTest(CompositionVisual visual) =>
target.HitTestFirstCore(visual, point, filter, resultFilter);
}
/// <summary> /// <summary>
/// Registers the composition target for explicit redraw /// Registers the composition target for explicit redraw
/// </summary> /// </summary>

25
src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs

@ -8,7 +8,7 @@ namespace Avalonia.Rendering.Composition
/// </summary> /// </summary>
public partial class CompositionContainerVisual : CompositionVisual public partial class CompositionContainerVisual : CompositionVisual
{ {
internal static readonly int HitTestAabbTreeThreshold = CompositionHitTestAabbTree.IsEnabled ? 1 : int.MaxValue; internal static readonly int HitTestAabbTreeThreshold = CompositionHitTestAabbTree.IsEnabled ? 32 : int.MaxValue;
private CompositionHitTestAabbTree? _hitTestChildren; private CompositionHitTestAabbTree? _hitTestChildren;
private bool _hitTestChildrenDirty = true; private bool _hitTestChildrenDirty = true;
@ -67,5 +67,28 @@ namespace Avalonia.Rendering.Composition
_hitTestChildren.Query(point, results); _hitTestChildren.Query(point, results);
return true; return true;
} }
internal bool TryQueryFirstHitTestChild<T>(Point point, ref T hitTest, out CompositionVisual? hit)
where T : struct, CompositionHitTestAabbTree.IQueryHitTester
{
if (Children.Count < HitTestAabbTreeThreshold)
{
_hitTestChildren?.Clear();
_hitTestChildren = null;
hit = null;
return false;
}
_hitTestChildren ??= new CompositionHitTestAabbTree();
if (_hitTestChildrenDirty)
{
_hitTestChildren.Rebuild(Children);
_hitTestChildrenDirty = false;
}
hit = _hitTestChildren.QueryFirst(point, ref hitTest);
return true;
}
} }
} }

Loading…
Cancel
Save