Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Refactor tile coverage to support level of detail #16335

Merged
merged 1 commit into from
Apr 6, 2020

Conversation

mpulkki-mapbox
Copy link
Contributor

@mpulkki-mapbox mpulkki-mapbox commented Mar 23, 2020

This pull request implements a more versatile logic for finding visible tiles on the screen. Intersection tests have been extended to 3D space by using bounding volumes for tiles and camera instead of projecting everything to zero elevation plane. Tile pyramid is traversed depth-first while preserving distances to tiles allowing an easy support for a level-of-detail. Even though the distance based lod is supported by this implementation, this PR has no visible effect for the user. Level of detail logic is deactive when pitch is <= 60 degrees.

The native implementation follows the javascript one (mapbox/mapbox-gl-js#8975) with few exceptions:

  1. Certain edge cases (false positives) were found in the tests. These cases (<1%) are culled by more precise intersection tests before reporting a tile to be visible.
  2. Few hotspots were found using a profiler so the order of intersection tests were slightly modified for a better performance.

Performance-wise the new implementation is few orders of magnitude slower than the original one. In a bigger picture the difference is not noticeable. I could see an increase of 5-15% time spent in TilePyramid::Update. Perhaps it's worth of discussion if the new tileCover should be a separate function and optional unless explicitly activated?

TODO

  • Investigate north orientation of map

@mpulkki-mapbox mpulkki-mapbox marked this pull request as ready for review March 24, 2020 09:20

const double numTiles = std::pow(2.0, z);
const double worldSize = Projection::worldSize(state.getScale());
const uint8_t minZoom = state.getPitch() <= (60.0 / 180.0) * M_PI ? z : 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line controls if tiles from different zoom levels can be returned. Level-of-detail functionality is disabled when pitch is <= 60 degrees.

// It is possible run only edge cases that were not covered in intersects()
IntersectionResult intersectsPrecise(const AABB& aabb, bool edgeCasesOnly = false) const;

inline const std::array<vec3, 8>& getPoints() const { return points; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: inline is not needed

IntersectionResult intersectsPrecise(const AABB& aabb, bool edgeCasesOnly = false) const;

inline const std::array<vec3, 8>& getPoints() const { return points; }
inline const std::array<vec4, 6>& getPlanes() const { return planes; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


std::array<vec4, 6> frustumPlanes;

for (int i = 0; i < (int)frustumPlanePointIndices.size(); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ++i
why not std::size_t

@pozdnyakov
Copy link
Contributor

Looks great, but let @astojilj to take a look before merge

@astojilj
Copy link
Contributor

Performance-wise the new implementation is few orders of magnitude slower than the original one. In a bigger picture the difference is not noticeable. I could see an increase of 5-15% time spent in TilePyramid::Update. Perhaps it's worth of discussion if the new tileCover should be a separate function and optional unless explicitly activated?

In case that this was measured using some benchmark, how much is "tile cover update" budget per frame affected by this on example mobile platform?

@mpulkki-mapbox mpulkki-mapbox force-pushed the mpulkki_tilecover_lod branch 2 times, most recently from f0dc2a8 to 41a50ac Compare March 30, 2020 09:17
if (result == IntersectionResult::Separate) return result;
}

const std::array<vec3, 4> aabbPoints = {vec3{aabb.min[0], aabb.min[1], 0.0},
Copy link
Contributor

@astojilj astojilj Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could alloc and copy here get removed, and add methods to access min and max also for dim 2?

EDIT: For AABB we store min & max vectors and in addition, for every AABB we construct aabbPoints. Might help performance if allocate one only (whichever fits algorithm better) and reduce copies...

Copy link
Contributor Author

@mpulkki-mapbox mpulkki-mapbox Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these arrays are stack allocated so the only cost should be variable initialization.

Z-dimension is redundant as all tiles are on the xy-plane and leaving z out reduces the number of required intersection tests. This could be updated if we ever have a need for elevation.

Corner points of the aabb could be pre-generated but it would be premature optimization in this case. Intersection tests are performed once per aabb and moving the generation of points to constructor wouldn't remove any logic. Frustum::intersects also requires a w component whereas Frustum::intersectsPrecise does not.

@astojilj
Copy link
Contributor

LGTM after documenting performance impact on one frame.

@mpulkki-mapbox mpulkki-mapbox force-pushed the mpulkki_tilecover_lod branch 2 times, most recently from 8dbc84d to aab6608 Compare April 3, 2020 10:02
@mpulkki-mapbox
Copy link
Contributor Author

Performance-wise the new implementation is few orders of magnitude slower than the original one. In a bigger picture the difference is not noticeable. I could see an increase of 5-15% time spent in TilePyramid::Update. Perhaps it's worth of discussion if the new tileCover should be a separate function and optional unless explicitly activated?

In case that this was measured using some benchmark, how much is "tile cover update" budget per frame affected by this on example mobile platform?

I profiled the code on my quite fast laptop in the following location: [INFO] {mbgl-glfw}[General]: Exit location: --lat="60.169900" --lon="24.943500" --zoom="16.064100" --bearing "43.857600". I chose few test runs describing the average performance of both new and original tileCover implementation. These numbers are not exact but they give a rough idea of the performance impact.

original:
vtune_orig

new:
vtune_new

Tests were done by rendering a map 60fps for 10 seconds using "streets-v11" style. Time spent in TilePyramid::update increased ~10%. tileCover is also called from render_background_layer.cpp, but the profiler could not catch that. Frame budget used by TilePyramid::update during a 10s test run increases from ~2.7% to ~3.0% based on these observations. Impact on the overall tile budget is quite small.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants