-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Annotation symbols are overdrawn if located on tile edges #6670
Comments
As previously mentioned in #3563, fixing this issue requires either removing stencil clip usage for symbols (option 1), or enforcing stencil clip for all symbol buckets (option 2). Option 1 is tricky because it contains some corner cases in which I have no proper fix in mind:
Option 2 involves enforcing stencil clip for all symbol buckets. I've already tried this and it produces erroneous rendering results such as the ones seen below (clipped symbols close to tile edges): |
@ansis though not specific to annotation symbols, this issue happens when both assertions below are true for symbol layers:
My understanding is that the overdraw happens because we consider a feature to be inside when its anchor point is located within the I've added a render test example here: symbol-opacity-edge/default In the render test above, I've added a source containing two GeoJSON points: one at
The actual result above is from GL JS. In GL native, we obtain the same results when we fix/remove the hacks that disables symbol stencil clip here, here and here. I have a proof-of-concept patch for GL native that re-enables symbol stencil clip for GL native: 782a477. |
Here's my understanding of the situation, and points where I have gaps in my understanding.
Gaps in my understanding:
|
@jfirebaugh |
I'm not sure I really understand those issues. #3582 seems to indicate that the purpose of |
The purpose of
|
Ansis comments about the extra quads are right. On #6832, I add a virtual padding that accounts for 1/8 of the tile size, for each direction. This can result in a performance impact during placement for a big amount of features (~100k). However, I don't see a large number of features like this to be the common use case.
Yes - e.g. issue #6670 is about the rendering artifact caused by unclipped features being overdrawn on tile edges.
Yes - IIRC this was my first proposed solution for #3563. However, that implies in features on tile edges in which tile is rightmost or bottommost being skipped, plus other issues mentioned below.
I believe both code paths and behavior should be the same for both modes - this is what I propose in #6832.
Besides the comments from @mikemorris - I believe we enable clipping there because we don't want symbols from other tiles polluting the requested tile in still mode.
Yes - rationale is that collision detection algorithm runs on a per-tile basis, relying on additional padding added to each tile for detecting collision with features on neighbor tiles. This gives us some guarantees that a colliding feature near one tile edge won't collide with another from a neighbor tile - thus no need for clipping. In theory, this is not an issue for non-colliding features (we disable collision when enabling one of the style properties mentioned above) - because we shouldn't need to worry about overdraw. However:
We then add buffers around each tile and then enable clipping to avoid overdraws.
We use the buffer for rendering the "other side" of the clipped feature - and we only throw out features outside of the tile boundaries if OK, this is a lengthy reply so here's a summary of my thoughts on this:
|
Fixing the original duplicate feature problem.
Sounds right to me. A point should only exist inside one tile.
I think this is the correct behavior. The rightmost edge of the world is the same space as the leftmost. The bottommost edge is a trickier question, but depending on the interpretation of the tile space and projection this edge maybe shouldn't exist. In practice I think excluding it won't cause any problems. Answers / comments about clippingThe first part would fix the original issue so this question is completely separate from that.
It would be good to benchmark. I think the 50% - 66% number came from counting the number of skipped labels but it would be good to get a difference a tile's total buffer size and processing time. A 1/8 buffer for annotations sounds good. We need a much bigger buffer for text labels though depending on their size and max width. Some of the text layers in the vector tiles have pretty large buffers.
I can't think of why this needs clipping. If it does, it's buggy.
In order to get y sorting to work correctly at tile boundaries we need to have the features in both tiles and then clip them when rendering. This is what -js does. We can't do this in -native yet because the annotation tiles only contain features strictly within them.
We're not going to throw them away. Annotations in continuous mode need to be clipped to tile boundaries to get y sorting to be correct. The annotations along borders need to be in both tiles. What I think about clippingI don't like the two paths. If we can get rid of the non-clipped one and the cost in size/time is reasonable I think this would be a worthwhile tradeoff. It might result in some visible clipping when rapidly zooming out but it would fix popping when panning. But I'm not sure how clipping everywhere would play with 3D. Labels in 3D will extend too far beyond tile edges for the clipping approach to work. I'm not sure if we can make the current tiled collision algorithm work in 3D either. Collision detection in 3D will need some global knowledge. I haven't thought much about what this would look like but I think we might need two separate paths here. |
The only way I could see getting rid of the unclipped mode is if we distinguished between two still rendering modes:
I gotta admit, even with all the above discussion, I'm not clear on the role of [Edit: basically, this means we can't "get rid of the non-clipped one", in the sense of being able to throw away features within the tile buffer no matter what. We can't ever do that unless we want to increase the cost of rendering a single still tile by ~9x.] |
If we get rid of unclipped mode we will never want to throw away features, so we wouldn't need to distinguish here.
We discard buffered features in unclipped mode. We can't discard them in clipped mode.
In most cases you want both to have the same value. I can't think of an example you would want them to be different. I think we should merge them into one property. There is an open spec issue about this: mapbox/mapbox-gl-style-spec#429 symbol-avoid-edges
These same limitations existed in Mapnik before it. CartoCSS had We can't predict line labels because they could be anywhere along the line. Any point labels placed after line labels are contaminated by the chaos in the line labels so we can't predict their position either. OK, why don't we just automatically set this for any point labels below a line label layer? My understanding (I might be wrong) is that there are cases where we want to risk the chaos in exchange for having cross-tile point labels that are placed after line labels. We might have a rare line label layer somewhere early on and we don't want that to affect all subsequent point layers. Another case where it might be used is if the style makes the text bigger than the buffer safely supports. I don't like this but my understanding is that this is a necessary part of mapnik-style tiled label placement. I've wondered if we could figure out a way to eliminate the chaos caused by lines. |
In practice, on -native
Check out https://github.com/mapbox/mapbox-gl-test-suite/blob/master/render-tests/symbol-avoid-edges/rotated-false/expected.png: I remember this specific render test being failing on my initial tests with clipping disabled (option 1). I also agree this clipping behavior looks wrong. If option 1 is still viable as long as we also add padding to annotation tiles to account for y-sorting, then a side effect is that the sorting is right up to the padding we add e.g. 1/8: |
Heads up I found a way of rendering sorted, non-colliding symbol annotations across tiles - we just need to order symbol render tiles in opposite Y order prior to rendering. Since this was the only reason for adding padding to symbol annotation tiles (#1673) - it seems we no longer need it. Sorting symbol render tiles is a cheap operation (certainly much cheaper than add padding to every render tiles) and also portable into -js code. IMO this makes getting rid of symbol clipping a valid option again. I'm going to update #6832 to reflect these findings. We still clip when in Still mode, but just because we need to update our render test results e.g. some render tests results have (expected!) clipped symbols that don't need to be. |
I don't think this works in all cases. Take a look at the image in the original issue (#1673). Some icons from the left tile need to be rendered on top of some icons from the right tile and some icons from the right tile need to be rendered on top of icons from the left tile. |
If we are fine with closer-to-the-top annotations sometimes being drawn over closer-to-the-bottom annotations then this is ok. I think the mobile team could better answer whether this is acceptable Also, I'm not sure if the closely packed grids are the worst case. I think the incorrect overlap could be more visible with just two annotations |
Fair enough @ansis 🙇♂️ Just worth noticing I'm not against adding padding - that is the best solution render-wise. I'm just curious whether we could use a faster (needs benchmark?) implementation that doesn't use padding and produces almost the same results.
Not sure if I follow, but the results I obtained with just two annotations looks OK with the sorted render tile approach: |
Can we settle a decision? I'm afraid #6832 is going to get bit-rotten soon. I'm a supporter for avoiding the extra padding given the results above, plus the fact we can avoid using clipping for symbols. If we proceed with this implementation decision, we'd need to backport these changes into GL JS before removing clip for Still mode, so we can update render test expectations that currently expects clipped symbols (which looks wrong IMO). ITOH, I won't argue if we decide to keep with the extra padding in favor of rendering results and parity with GL JS implementation. |
Once again: we will likely never be able to remove clipping in Still mode. We have to be able to render a single tile without loading the 8 surrounding tiles. That requires rendering all features within the tile buffers, but clipping the rendering. I don't see a way around this that won't kill our COGS for static tile rendering. @brunoabinader you seem very very keen on removing clipping in Still mode. How do you propose to get around this? |
The clipping in this test-case is a side effect of:
I don't think we can avoid it. In the real world, you'd be using |
Thank you @jfirebaugh - this helped me understand/record why we need to clip symbols in still mode. I'll update the comments in #6832 accordingly. Please reconsider what I've said here except removing symbol clipping in Still mode. FWIW this is exactly what is proposed in #6832 atm. |
Capturing chat with @brunoabinader + some Android folks, the solution proposed in #6670 (comment) (option 1 preserving current behavior for still mode), while not perfect, is an improvement over the current state of things and seems unlikely that we'll see it in real world situations. On the other hand, the alternative solution (option 2) is more expensive and will bring a performance hit. From a practical point of view, if I were to choose between a performance hit, or a better performing solution with rendering errors in unlikely scenarios, I'd say we go with the more performant approach (option 1). |
Fixed in #6832. |
Spin-off from the discussions around stencil clip usage from #3563.
Fixing the position of repeated features near tile edges caused a new issue to rise: opaque symbols are now overdrawn:
The text was updated successfully, but these errors were encountered: