diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 38b7ce874d..f3b26ce46c 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Numerics; using Avalonia.Collections.Pooled; @@ -9,8 +10,13 @@ namespace Avalonia.Rendering.Composition /// internal partial class CompositionTarget { + private static readonly HitTestCandidateComparer s_hitTestCandidateComparer = new(); private readonly CompositionHitTestRTree _hitTestIndex = new(); + private readonly PooledList _hitTestCandidates = new(); + private readonly PooledList _hitTestPath = new(); private bool _hitTestIndexDirty = true; + private bool _hitTestCandidatesInUse; + private bool _hitTestPathInUse; partial void OnRootChanged() { @@ -38,32 +44,64 @@ namespace Avalonia.Rendering.Composition if (root == null) return null; - using var candidates = QueryHitTestCandidates(root, point, out var rootParentPoint); - if (candidates == null) - return null; + var candidates = RentHitTestCandidates(out var releaseToField); + try + { + if (!QueryHitTestCandidates(root, point, candidates, out var rootParentPoint)) + return null; - var res = new PooledList(); + var res = new PooledList(); + + foreach (var candidate in candidates) + { + if (HitTestCandidate(root, candidate.Visual, rootParentPoint, filter)) + res.Add(candidate.Visual); + } + + return res; + } + finally + { + ReleaseHitTestCandidates(candidates, releaseToField); + } + } - foreach (var candidate in candidates) + PooledList RentHitTestCandidates(out bool releaseToField) + { + if (!_hitTestCandidatesInUse) { - if (HitTestCandidate(root, candidate.Visual, rootParentPoint, filter)) - res.Add(candidate.Visual); + _hitTestCandidatesInUse = true; + releaseToField = true; + _hitTestCandidates.Clear(); + return _hitTestCandidates; } - return res; + releaseToField = false; + return new PooledList(); + } + + void ReleaseHitTestCandidates(PooledList candidates, bool releaseToField) + { + if (releaseToField) + { + candidates.Clear(); + _hitTestCandidatesInUse = false; + } + else + { + candidates.Dispose(); + } } - PooledList? QueryHitTestCandidates( - CompositionVisual root, - Point point, - out Point rootParentPoint) + bool QueryHitTestCandidates(CompositionVisual root, Point point, PooledList candidates, out Point rootParentPoint) { rootParentPoint = default; + candidates.Clear(); Server.Compositor.Readback.NextRead(); if (Root == null || root.Root != this) - return null; + return false; var readRevision = Server.Compositor.Readback.ReadRevision; if (_hitTestIndexDirty || !_hitTestIndex.IsCurrent(Root, readRevision)) @@ -74,47 +112,77 @@ namespace Avalonia.Rendering.Composition if (!TryGetOwnTransform(root, out var rootTransform) || !TryGetGlobalTransform(root, out var globalTransform)) - return null; + return false; rootParentPoint = point.Transform(rootTransform); var indexPoint = point.Transform(globalTransform); - var candidates = new PooledList(); _hitTestIndex.Query(indexPoint, candidates); - candidates.Sort(static (left, right) => left.Order.CompareTo(right.Order)); - return candidates; + candidates.Sort(s_hitTestCandidateComparer); + return true; } - static bool HitTestCandidate(CompositionVisual root, CompositionVisual visual, Point rootParentPoint, - Func? filter) + PooledList RentHitTestPath(out bool releaseToField) { - using var path = new PooledList(); - - for (var current = visual; current != null; current = current.Parent) + if (!_hitTestPathInUse) { - path.Add(current); - if (ReferenceEquals(current, root)) - break; + _hitTestPathInUse = true; + releaseToField = true; + _hitTestPath.Clear(); + return _hitTestPath; } - if (path.Count == 0 || !ReferenceEquals(path[path.Count - 1], root)) - return false; + releaseToField = false; + return new PooledList(); + } + + void ReleaseHitTestPath(PooledList path, bool releaseToField) + { + if (releaseToField) + { + path.Clear(); + _hitTestPathInUse = false; + } + else + { + path.Dispose(); + } + } - var parentPoint = rootParentPoint; - Point point = default; - for (var c = path.Count - 1; c >= 0; c--) + bool HitTestCandidate(CompositionVisual root, CompositionVisual visual, Point rootParentPoint, Func? filter) + { + var path = RentHitTestPath(out var releaseToField); + try { - if (!HitTestVisual(path[c], parentPoint, filter, out point)) + for (var current = visual; current != null; current = current.Parent) + { + path.Add(current); + if (ReferenceEquals(current, root)) + break; + } + + if (path.Count == 0 || !ReferenceEquals(path[path.Count - 1], root)) return false; - parentPoint = point; - } + var parentPoint = rootParentPoint; + Point point = default; + for (var c = path.Count - 1; c >= 0; c--) + { + if (!HitTestVisual(path[c], parentPoint, filter, out point)) + return false; + + parentPoint = point; + } - return visual.HitTest(point); + return visual.HitTest(point); + } + finally + { + ReleaseHitTestPath(path, releaseToField); + } } - static bool HitTestVisual(CompositionVisual visual, Point parentPoint, Func? filter, - out Point point) + static bool HitTestVisual(CompositionVisual visual, Point parentPoint, Func? filter, out Point point) { point = default; @@ -184,22 +252,35 @@ namespace Avalonia.Rendering.Composition if (root == null) return null; - using var candidates = QueryHitTestCandidates(root, point, out var rootParentPoint); - if (candidates == null) - return null; + var candidates = RentHitTestCandidates(out var releaseToField); + try + { + if (!QueryHitTestCandidates(root, point, candidates, out var rootParentPoint)) + return null; + + foreach (var candidate in candidates) + { + if (HitTestCandidate(root, candidate.Visual, rootParentPoint, filter)) + return candidate.Visual; + } - foreach (var candidate in candidates) + return null; + } + finally { - if (HitTestCandidate(root, candidate.Visual, rootParentPoint, filter)) - return candidate.Visual; + ReleaseHitTestCandidates(candidates, releaseToField); } - - return null; } /// /// Registers the composition target for explicit redraw /// public void RequestRedraw() => RegisterForSerialization(); + + private sealed class HitTestCandidateComparer : IComparer + { + public int Compare(CompositionHitTestCandidate left, CompositionHitTestCandidate right) => + left.Order.CompareTo(right.Order); + } } }