Always use GL_LINEAR interpolation for icon rendering
Nobody has claimed this yet.
- #8738 by @kkaefer — closed without merging
- Dominant language
- TypeScript
- Stars
- 12.4k
- Forks
- 2.4k
- PR merge metrics
- No merged PRs in 30d
Description
Our goal is to render icons as crisp and sharp as possible. Since our icons are raster based, the best case is doing a 1:1 blit where we transfer the pixels directly, without any kind of interpolation, from the icon atlas texture. To that extent, we are disabling texture interpolation (by using `GL_NEAREST`) in situations where the map is not in the process of being rotated or zoomed, it's not tilted, and only for icons that are not rotated, transformed, or resized in any way. If all of those conditions are met, our map looks like this:

However, when one of these isn't met, we resort to linear interpolation, which makes them look like this:

Note how some icons, like the Metro signs, are a bit blurry. To make this easier to see, here's a GIF that swaps both back and forth:

However, there are a lot of caveats with our current approach:
- We've hit lots of rounding errors where nearest-neighbor texture lookups produced weird results
- We can't render pixel-grid aligned icons with interpolated icons in the same draw call. This e.g. happens when using data-driven styling for `icon-scale` or `icon-rotate` ([a user hit this problem](https://github.com/mapbox/mapbox-gl-native/issues/15537)), or when the icon layer is tilted
- We can't render pixel-grid aligned icons when they're rotated by right angles (90°, 180° 270°)
- The switch between linearly-interpolated and nearest-neighbor texture lookups when the user starts or stops rotating or zooming is quite jarring:

I think we should remove our use of `GL_NEAREST` and always use linear interpolation. To keep icons crips, we could ensure that icons that can be rendered crisply use texture coordinates that are aligned to the texture's pixel grid. This would allow us to mix pixel-grid aligned and scaled icons within the same draw call, as well as "fade" between pixel-grid alignment and linearly interpolated icons when rotating or zooming.
I've done a few experiments in a local playground, and I believe the way to go is to adjust the texture coordinate in the _vertex shader_ and shift it by the fractional value that the vertex is offset from an integer screen coordinate. This requires a border of 1 pixel around all of our icons to account for the linear interpolation. We already have a 1 pixel padding in our rendering code.
/cc @ansis
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the icon rendering code, icon atlas texture handling, and the vertex shader described in the issue. Verify how texture coordinates are adjusted for fractional vertex offsets and how the existing one-pixel padding is used. Done means always using linear interpolation while keeping eligible icons crisp and allowing aligned and scaled icons in the same draw call.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- computer-graphics
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100