-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
fix antimeridian flicker #6438
fix antimeridian flicker #6438
Conversation
75adcce
to
ff393e1
Compare
|
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've been looking at the new lru_cache
behavior and I don't think I understand how the changes are supposed to work. It doesn't seem like the behavior matches the comments. As far as I can tell SourceCache
is the only consumer so we may be able to get away with tweaking the behavior arbitrarily -- but if we're calling it LRUCache
it should have pretty generic LRU-like behavior.
How does LRUCache#get
work -- it pops data but doesn't affect the order
? How is the order
supposed to be managed in general now? It seems like order
could grow indefinitely if each add
isn't matched with a remove
/getAndRemove
(w/o any get
calls)?
Umm sorry ignore #6438 (comment), I'm not seeing the changes any more after a full reload, not sure what I had cached or if rollup just hadn't finished rebuilding when I loaded the page... 😳 Looks great! (well there's still the symbol flicker but overall it's a great improvement) |
The |
I think the sign is wrong here: From logging it looks like what happens is we wrap in the wrong direction the first time after changing zoom levels, ending up with two tiles, and after that it doesn't matter which direction you change the wrap until that part of the symbol index gets cleared out:
Changing to |
src/source/source_cache.js
Outdated
@@ -43,6 +43,7 @@ class SourceCache extends Evented { | |||
_sourceLoaded: boolean; | |||
_sourceErrored: boolean; | |||
_tiles: {[any]: Tile}; | |||
_prevLng: number; |
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.
number | void
src/source/source_cache.js
Outdated
// calculates this difference in wrap values so that we can match tiles | ||
// across frames where the longitude gets wrapped. | ||
const prevLng = this._prevLng === undefined ? lng : this._prevLng; | ||
const wrapDelta = Math.round((lng - prevLng) / 360); |
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.
Why is wrapDelta = Math.round((lng - prevLng) / 360))
correct? This is one of those formulas that I find superficially indistinguishable from, e.g., Math.round((lng - prevLng) / 180))
, Math.floor((lng - prevLng) / 360))
, Math.floor(lng - prevLng) % 360
, etc., at least until I get out some scrap paper and do a little scribbling. Is there any way to make this a bit more self-documenting?
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.
Would this be clearer?
const lngDifference = lng - prevLng;
const worldDifference = lngDifference / 360;
const wrapDelta = Math.round(worldDifference);
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.
👍 worldDifference
definitely helps, but it's still not obvious to me why wrapDelta
is 1 for 0.5 <= worldDifference < 1.5
rather than for, say, 1 <= worldDifference < 2
.
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.
Btw, I recently learned that in JS, Math.round(-1.5)
is -1, not -2 as you might expect from other languages.
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.
wrapDelta
is for 0.5 <= worldDifference < 1.5
because it is matching with the nearest frame and the sign of the distance to the nearest frame doesn't matter. If the map goes from lng: 270
to lng: 10
then the former z/x/y/1
tile is closer to the current area of the z/x/y/0
tile than the current z/x/y/1
tile.
Any ideas on how I could clarify that well with a code or comment change?
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.
Now that I'm seeing the expanded comment you added, together with these var names, I think it's pretty clear 🙇
src/source/source_cache.js
Outdated
@@ -392,6 +393,27 @@ class SourceCache extends Evented { | |||
this._cache.setMaxSize(maxSize); | |||
} | |||
|
|||
handleWrapJump(lng: number) { | |||
// Wrapping longitude values can cause a jump in `wrap` values. Tiles that | |||
// cover a similar area of the screen can have different wrap values. This |
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.
Tiles that cover a similar area of the screen can have different wrap values
Is this about the different wrap values of adjacent tiles, like (1,0,1) wrap=-1
and (0,0,1) wrap=0
, or about the different wrap values of the same tile in two successive frames, like (0,0,1) wrap=0
=> (0,0,1) wrap=-1
? The latter seems like the relevant thing here, but the sentence sounds more like it's referring to the former.
src/source/source_cache.js
Outdated
if (tile) { | ||
// set the tileID because the cached tile could have had a different wrap value |
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.
Attach comment to the line where tile.tileID
is being reassigned.
src/source/source_cache.js
Outdated
const tile = this._tiles[id]; | ||
this._setTileReloadTimer(id, tile); | ||
} | ||
} |
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'd be inclined to inline this since it's only used in handleWrapJump
@anandthakker 132bfd7 fixes things based on your review. Can you check if the comment/calculation changes are clearer now? @ChrisLoer thanks for catching the wrapDelta bug. Fixed in 913f891
6ffb4d8 fixes the bug of Are there any other changes I should make? |
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 think this is looking pretty good, and we probably want to get it in soon. I still have two things on my wish list:
- Rename LRUCache and change comments to make it clearly a subordinate of SourceCache's use case.
- Add unit tests for CrossTileSymbolIndex and SourceCache that exercise
handleWrapJump
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.
👍 concur w/ @ChrisLoer
@ChrisLoer ee32a0b turns LRUCache into TileCache. A careful review of this would be great. 4297d51 adds tests for |
Changes look good! A few questions:
|
Nope. ab2e59c
Deleted in ab2e59c. It used to be used by the unit tests but I had changed them to just use
It is number of tiles held by the cache. It's just not the number of unique tiles held by the cache. The number of unique tiles would be lower. This is what you were saying, right? I think this seems fine.
I think you're right that the way we use
If you have a really wide viewport it would be possible to fill the entire cache with just |
Yup, I'm sold, thanks for helping me walk through that reasoning. 🚀 This all looks good to me. |
Note that this should be re-targeted at |
When panning cross the antimeridian, the longitude value gets wrapped. This results in tileIDs getting assigned a different `wrap` value for tiles that cover roughly the same area on the screen. This pr calculates what this change in wrap values is and updates the state of both `SourceCache` and `CrossTileSymbolIndex` so that areas use the same tile and symbol state for the same screen areas even if they have a different `wrap` value. I think this is the long term fix for the CrossTileSymbolIndex. For SourceCache, it may be better to rework how tiles are retained so that you can actually use versions of tiles with a different wrap as tiles in the next frame.
LRUCache was only being used for tiles and recently we started adding some non-standard behaviour including storing multiple values for a single key. This pr: - replaces `number` key with `OverscaledTileID` so that we can enforce the type and enforce tile id wrapping - moves expiry logic into cache and gets rid of `cacheTimers` in `SourceCache`
ab2e59c
to
5882bfe
Compare
smartWrap this function in mapbox-gl source code ,can solve the problem, when renderCopies is true . // First, try shifting one world in either direction, and see if either is closer to the // Second, wrap toward the center until the new position is on screen, or we can't get return lngLat; |
fix #6315
This fixes several intertwined bugs. The first two commits fix adding and removing tiles by their wrapped tileIDs. This let's us reuse cached tiles for tiles with any
wrap
value.The last commit fixes two issues:
First, after a user pans across the antimeridian and the longitude gets wrapped (175, pan to 183 which wraps as -177), the same area of the screen gets covered by a tile with a different wrap value. The commit updates the CrossTileSymbolIndex state so that it uses the symbol values for the nearest version of the tile (what visually you would think is "the same tile").
Second, previously it wasn't possible to reuse a wrapped version of a tile in the next frame (without adding it to the cache). For example, if you had z0/x0/y0/w0 and you set the center to 360 it would load a new tile for z0/x0/y0/w1 instead of reusing it. The right fix here would be to make deep changes to sourcecache so that it can use wrapped tiles from retain. But this gets complicated, especially if you want to prioritize exact matches. I wasn't confident that I could write a bug-free implementation of that for this release (progress on that attempt: 6fc31ec, 7bc39da), so I went with a slightly easier fix: I do the same thing I do for symbols and just update all the tileIDs so that the data matches the nearest tile from the previous frame.
I still need to fix some tests, but otherwise this is ready for review.
Launch Checklist