Improve composition hit testing performance with per-visual AABBs#21310
Improve composition hit testing performance with per-visual AABBs#21310hez2010 wants to merge 13 commits into
Conversation
|
You can test this PR using the following package version. |
|
You can test this PR using the following package version. |
|
Why do we need to populate a separate update list? You should be able detect stale subtree data by comparing readback revision with previously captured one. |
|
i. e. _needsBoundlingBoxUpdate is propagated to parent which triggers So if any descendant has changed it's hit-test data you'll get a bumped readback revision. |
Oops didn't realize the readback revision could be used for this. Resolved in 252d107. PTAL. |
|
You can test this PR using the following package version. |
|
Latest benchmark result: Static visuals (linear when < 32, AABB when >= 32):
Animated visuals (linear when < 32, AABB when >= 32):
The threshold of switching to use AABB is 32, that's why there's a drop at 32 in the static visual tests. For reference, the existing linear hit testing:
|
What does the pull request do?
Adds a dynamic AABB tree for composition hit testing so containers with many composition children can avoid linearly scanning every child for each hit test.
The hit-test index is grouped by child z-order buckets. Each bucket owns its own AABB tree and is queried from top-most bucket to bottom-most bucket, so
HitTestFirstcan stop as soon as a real hit is found while preserving visual order.It also adds a Hit Testing page in RenderDemo for manually stressing composition hit testing with many static and animated visuals.
What is the current behavior?
Composition hit testing scans child visuals linearly. For containers with many children,
HitTestFirstcan scale with the number of siblings even when only a small subset can contain the query point.What is the updated/expected behavior with this PR?
Composition containers switch to a bucketed dynamic AABB tree once the child count is large enough. Hit testing first searches likely candidates by bounds, then preserves existing z-order semantics by processing buckets from top-most to bottom-most and sorting candidates only within each bucket.
The threshold is based on benchmark tradeoffs. Below this point the linear path avoids AABB-tree overhead; at and above this point the indexed path can significantly improve large static or sparse-hit sibling scenarios while keeping animated-update overhead bounded.
When there're 16384 static visuals on 4 layers of subtrees, it can speed up the hit testing by ~1200x.
Source Code
Static benchmark
Animated benchmark
The comparison of the number of updates per second when performing 100,000 times (a batch) of hit testing per composition update shows a ~12x speed up in actual app:
Linear Hit Testing (existing)
Rate: 1.5 batches/s
AABB Hit Testing (new)
Rate: 17.9 batches/s
How was the solution implemented (if it's not obvious)?
The hit-test index is maintained from composition child add/remove/order changes. Bounds are refreshed lazily against the current composition readback revision during hit testing.
Bounded visuals are stored in fixed-size child-order buckets. Each bucket has its own dynamic AABB tree, and queries walk buckets from highest child order to lowest child order. Before a bucket is queried, it refreshes only the child entries in that bucket whose readback revision changed.
Candidates are sorted only inside the current bucket, which keeps hit-test order correct without requiring a global candidate sort.
Visuals that cannot safely use subtree-bounds optimization are kept as unbounded entries in their z-order bucket, so custom hit-test visuals and similar cases preserve existing hit-test semantics.
Checklist
Breaking changes
None.
Obsoletions / Deprecations
None.