-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Quadtree performance #12907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Quadtree performance #12907
Conversation
Thank you for the pull request, @mzschwartz5! ✅ We can confirm we have a CLA on file for you. |
function childTileAtPosition(tile, positionCartographic) { | ||
const center = Rectangle.center(tile.rectangle, centerScratch); | ||
const x = positionCartographic.longitude >= center.longitude ? 1 : 0; | ||
const y = positionCartographic.latitude < center.latitude ? 1 : 0; | ||
|
||
switch (y * 2 + x) { | ||
case 0: | ||
return tile.northwestChild; | ||
case 1: | ||
return tile.northeastChild; | ||
case 2: | ||
return tile.southwestChild; | ||
default: | ||
return tile.southeastChild; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two comments / questions on this:
- Is this reliable? I don't think it works if the tile crosses the IDL, so is that a bad assumption to make here?
- Instead of a switch statement, we could define an array out of this function that contains the names of the children and index into that. But that means if the variable name changes, so must the array. I wasn't sure if that was a good tradeoff for a minimal speed boost (if there's any), but input is welcome.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a short look at this.
The performance difference is huge. In the test with the labels, I did not see any functional difference (as in "regression"). So that looks fine for me.
Beyond that:
It can be difficult to separate "the old code" and "the change itself" during a review. For example, I'm easy to distract with things like QuadtreePrimitive.updateHeights
, which seems to aim at an indentation depth highscore with " "
, has that 11500
repeated three times (Mariana trench?), or defines that let i;
that is used 150 lines later.
Trying to ignore all this, and looking at the code changes:
I won't claim to have fully understood what has been going on there to begin with. For example: The tile now stores the _addedCustomData
and _removedCustomData
which previously had been passed to updateCustomData
. The _updateCustomData
function is called in visitTile
and selectTilesForRendering
, and I haven't zoomed beyond that. I assume that these arrays are stored for reversing the direction of the dispatching.
A bit more abstractly: It looks like the whole "custom data" functionality is just dispatching these callbacks into a tree: "Starting at the root, here's a custom data - add it to the root, and to the child it falls in" - in pseudocode:
Tile.addCallback(callback, position) {
this.callbacks.push(callback);
const child = childFor(position);
child.addCallback(callback, position);
}
recursively going down the tree.
There's probably a reason why it isn't done like that. It may be related to that _loadQueueTimeSlice
, which seems to be a mechanism for saying: "We don't care whether the positions are wrong or flickering, as long as the computation doesn't take too long".
Bottom lines:
I don't see anything that could cause issues or regressions, and it looks like an overall improvement.
A detail is that there was that _lastTileIndex
, which is now replaced by _customDataIterator
, but the latter is not declared in the constructor.
Description
Background:
customData
in Quadtree-speak. (Maybe the intention was to stay general, but that's all the custom data has been used for since its introduction ~10 years ago).Current state:
The way callbacks are handled in the quadtree is very inefficient:
customData
structure. Removing callbacks requires iterating through the array for each removed callback at each level of the tree.Because
Scene
re-registers a height-changed callback whenever the camera moves, the tree currently gets reevaluated in this inefficient manner every single frame (that the camera moves).State after this PR:
Fixes all the above issues by changing the direction of data flow to (parent --> child), rather than (child <-- parent). Adding / removing callbacks no longer re-evaluates the whole tree, and is now independent of the number of items in the tree. The
customData
member is now aSet
, so removal of callbacks is constant-time.Issue number and link
#12719 (this is really a sub-issue of that)
Testing plan
Sandcastle.
In current release, with 1k labels and 100 characters each, panning the camera around just halts the application entirely. Because each frame the quadtree re-evaluates the label's 100k callbacks (1k callbacks once #12905 is merged).
Performance comparison with 100 labels at 20 characters each, just panning the camera around:
Before changes: (see how the time is dominated by

selectTilesForRendering
, basically all inupdateCustomData
(at various stack levels))After changes (

selectTilesForRendering
is now much smaller -updateCustomData
is basically non-existent now)__
Test with vertical exaggeration
There's definitely a bug here, but it seems to pre-date my changes to the Quadtree. The updates to vertical exaggeration do not consistently propagate to billboards / labels.
Author checklist
CONTRIBUTORS.md
CHANGES.md
with a short summary of my change