Skip to content
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

Raster tiles are much lower detail when terrain is enabled #3983

Open
olsen232 opened this issue Apr 12, 2024 · 12 comments
Open

Raster tiles are much lower detail when terrain is enabled #3983

olsen232 opened this issue Apr 12, 2024 · 12 comments

Comments

@olsen232
Copy link
Contributor

olsen232 commented Apr 12, 2024

(This may not be a bug - if that's the case, I am just looking for info about how to control this behaviour).

Adding terrain to a map viewer causes it to load raster tiles at lower zoom, that is, load the lower detail rasters with a lower "z" value. Also, the zoom drops away much quicker when terrain is enabled, so that one end of the screen is noticeably lower detail than the other even for a reasonably small camera pitch, and worse for extreme pitches. Maps without terrain, by contrast, do a good job of maintaining the illusion that all tiles change zoom at once.

maplibre-gl-js version: 4.1.2

browser: Brave

Steps to Trigger Behavior

  1. Set up a map with raster tiles. Turn on map.showTileBoundaries
  2. Compare it with the same map but also with 3d terrain enabled.
  3. Observe different tile zoom levels in each case.

Link to Demonstration

Map with terrain disabled:
https://jsbin.com/mesowukici/1/edit?html,output
Screenshot 2024-04-12 at 2 19 50 PM

Identical map with terrain enabled:
https://jsbin.com/rigisawuze/1/edit?html,output
Screenshot 2024-04-12 at 2 20 01 PM

Expected Behavior

Adding terrain shouldn't affect raster tile zoom - maybe?
This may be by design for performance reasons. If so, do you know if it can be tweaked?

Actual Behavior

Adding terrain has a large and obvious impact on raster tile zoom.

@HarelM
Copy link
Member

HarelM commented Apr 12, 2024

Yes, this was by design, I don't remember exactly why, probably related to render to texture performance.
I'm not familiar with a way to tweak it.
Cc: @prozessor13

@olsen232
Copy link
Contributor Author

FYI I found the most visible difference between the two is in this line in transform.ts:
const refPoint = options.terrain ? cameraPoint : centerPoint;

When that's switched to always be centerPoint, as in this draft PR, the raster tiles are much higher detail.

I'm suspicious of whether this is a performance change - there's no comments, so I can only guess, but it doesn't look anything like "if terrain is on then reduce detail by x%" - instead it is "if terrain is on then use a different center-point for the following calculation". That could still be for performance reasons but it's not obvious to me that it is

@HarelM
Copy link
Member

HarelM commented Apr 15, 2024

I do see this change as part of the terrain 3D change here:

From what I remember in the discussions around the terrain, this change is towards performance, to allow loading less details for the terrain mesh, making the terrain smoother.

I don't know why center point is the reference point.
I hope @prozessor13 remembers.

@wiesehahn
Copy link

I have the same problem. It makes the vizualization with terrain unusable in my case.

without terrain:
grafik

with terrain:
grafik

@UberMouse
Copy link

Any updates on this @prozessor13 ?

@notnotzero
Copy link

I have also encountered this issue; we are unable to use high-resolution tiles for our applications.

@HarelM
Copy link
Member

HarelM commented May 2, 2024

I think @msbarry is looking into improving that by splitting the logic of which tiles to fetch according to the source cache and underlying source as part of the contour source implementation.

@msbarry
Copy link
Contributor

msbarry commented May 2, 2024

I am looking into some weird behavior in deciding which tiles to load - but it should only affect crosstalk when a raster-dem source is shared between hillshading and terrain. This issue seems to be on any source when terrain is enabled. This issue might be related to this code block which is the only thing in source_cache that affects other sources when terrain is present:

if (terrain) {
const idealRasterTileIDs: {[_: string]: OverscaledTileID} = {};
const missingTileIDs: {[_: string]: OverscaledTileID} = {};
for (const tileID of idealTileIDs) {
if (this._tiles[tileID.key].hasData())
idealRasterTileIDs[tileID.key] = tileID;
else
missingTileIDs[tileID.key] = tileID;
}
// search for a complete set of children for each missing tile
for (const key in missingTileIDs) {
const children = missingTileIDs[key].children(this._source.maxzoom);
if (this._tiles[children[0].key] && this._tiles[children[1].key] && this._tiles[children[2].key] && this._tiles[children[3].key]) {
idealRasterTileIDs[children[0].key] = retain[children[0].key] = children[0];
idealRasterTileIDs[children[1].key] = retain[children[1].key] = children[1];
idealRasterTileIDs[children[2].key] = retain[children[2].key] = children[2];
idealRasterTileIDs[children[3].key] = retain[children[3].key] = children[3];
delete missingTileIDs[key];
}
}
// search for parent for each missing tile
for (const key in missingTileIDs) {
const parent = this.findLoadedParent(missingTileIDs[key], this._source.minzoom);
if (parent) {
idealRasterTileIDs[parent.tileID.key] = retain[parent.tileID.key] = parent.tileID;
// remove idealTiles which would be rendered twice
for (const key in idealRasterTileIDs) {
if (idealRasterTileIDs[key].isChildOf(parent.tileID)) delete idealRasterTileIDs[key];
}
}
}
// cover all tiles which are not needed
for (const key in this._tiles) {
if (!idealRasterTileIDs[key]) this._coveredTiles[key] = true;
}
}

@olsen232
Copy link
Contributor Author

olsen232 commented May 2, 2024

If I can save some time - the PR I linked #3994 fixes this behaviour, by making the tiles loading logic when terrain is enabled more similar to when the logic when it is disabled. Since the logic is currently switched from one behaviour to the other depending on whether terrain is enabled or not, and switching the logic back fixes it, I don't think we need to investigate any crosstalk between layers etc (or if so, that's a separate issue).

Anyway, all this to say that there is already a fix. Of course, if that entire piece of code is being rewritten anyway, that's fine too.

This is basically blocked on anyone (@prozessor13?) being able to explain what the purpose for the two different logics is (possibly performance?), or alternatively waiting until someone signs off on switching it so that it always uses the apparently superior logic, even if we don't understand what the purpose was previously.

@prozessor13
Copy link
Collaborator

I am looking into some weird behavior in deciding which tiles to load - but it should only affect crosstalk when a raster-dem source is shared between hillshading and terrain. This issue seems to be on any source when terrain is enabled. This issue might be related to this code block which is the only thing in source_cache that affects other sources when terrain is present:

if (terrain) {
const idealRasterTileIDs: {[_: string]: OverscaledTileID} = {};
const missingTileIDs: {[_: string]: OverscaledTileID} = {};
for (const tileID of idealTileIDs) {
if (this._tiles[tileID.key].hasData())
idealRasterTileIDs[tileID.key] = tileID;
else
missingTileIDs[tileID.key] = tileID;
}
// search for a complete set of children for each missing tile
for (const key in missingTileIDs) {
const children = missingTileIDs[key].children(this._source.maxzoom);
if (this._tiles[children[0].key] && this._tiles[children[1].key] && this._tiles[children[2].key] && this._tiles[children[3].key]) {
idealRasterTileIDs[children[0].key] = retain[children[0].key] = children[0];
idealRasterTileIDs[children[1].key] = retain[children[1].key] = children[1];
idealRasterTileIDs[children[2].key] = retain[children[2].key] = children[2];
idealRasterTileIDs[children[3].key] = retain[children[3].key] = children[3];
delete missingTileIDs[key];
}
}
// search for parent for each missing tile
for (const key in missingTileIDs) {
const parent = this.findLoadedParent(missingTileIDs[key], this._source.minzoom);
if (parent) {
idealRasterTileIDs[parent.tileID.key] = retain[parent.tileID.key] = parent.tileID;
// remove idealTiles which would be rendered twice
for (const key in idealRasterTileIDs) {
if (idealRasterTileIDs[key].isChildOf(parent.tileID)) delete idealRasterTileIDs[key];
}
}
}
// cover all tiles which are not needed
for (const key in this._tiles) {
if (!idealRasterTileIDs[key]) this._coveredTiles[key] = true;
}
}

Because tiles in terrain renders to a texture, and because the textures are cached, this logic is needed to not render raster tiles of different zoomlevels (because of race-conditions during tile-loading) into on render-to-texture tile.

@prozessor13
Copy link
Collaborator

Yes, it was some kind of performance decision, but either it is too agressive, or there is a bug for rastertiles. The Problem is the following scenario:

You are in heavy terrain, and you tilt the camera from Pos A to Pos B than you are behind a mountain. The distance to the centerPoint decreases radically and you have to render tiles in very high zoomlevel, but you still have a very wide view behind the mountain, because of camera altitude. So because there is no visibility check of the tiles behind the mountain a ton of tiles will be loaded. I cannot remember on all the details, but i think changing this is an issue to test very well.

IMG_3948

@HarelM
Copy link
Member

HarelM commented May 3, 2024

@prozessor13 thanks for the info!
I think the best way forward would be to create a test that recreates this scenario and see which tiles are loaded in each case.
It might be that improving the logic of which tiles to fetch can solve this issue as well, IDK...

UberMouse added a commit to koordinates/maplibre-gl-js that referenced this issue May 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants