From a1f67804854a934a77970728651328dfbfa1604f Mon Sep 17 00:00:00 2001 From: alasram Date: Wed, 9 Oct 2024 15:30:45 -0700 Subject: [PATCH] Add Tile LOD controls --- benchmark/util/tilecover.benchmark.cpp | 2 +- include/mbgl/map/map.hpp | 32 +++++++ .../src/cpp/native_map_view.cpp | 40 ++++++++ .../src/cpp/native_map_view.hpp | 16 ++++ .../maplibre/android/maps/MapLibreMap.java | 95 +++++++++++++++++++ .../org/maplibre/android/maps/NativeMap.java | 16 ++++ .../maplibre/android/maps/NativeMapView.java | 88 +++++++++++++++++ platform/glfw/glfw_view.cpp | 82 +++++++++++++++- src/mbgl/map/map.cpp | 32 +++++++ src/mbgl/map/map_impl.cpp | 6 +- src/mbgl/map/map_impl.hpp | 7 ++ .../layers/render_background_layer.cpp | 16 +++- src/mbgl/renderer/paint_parameters.cpp | 10 +- src/mbgl/renderer/paint_parameters.hpp | 9 +- src/mbgl/renderer/render_orchestrator.cpp | 6 +- src/mbgl/renderer/renderer_impl.cpp | 11 ++- .../renderer/sources/render_image_source.cpp | 4 +- src/mbgl/renderer/tile_parameters.hpp | 5 + src/mbgl/renderer/tile_pyramid.cpp | 15 ++- src/mbgl/renderer/update_parameters.hpp | 6 ++ src/mbgl/util/tile_cover.cpp | 31 +++--- src/mbgl/util/tile_cover.hpp | 13 ++- test/util/tile_cover.test.cpp | 22 ++--- 23 files changed, 518 insertions(+), 46 deletions(-) diff --git a/benchmark/util/tilecover.benchmark.cpp b/benchmark/util/tilecover.benchmark.cpp index b3c63c61e81..95dc7b065b6 100644 --- a/benchmark/util/tilecover.benchmark.cpp +++ b/benchmark/util/tilecover.benchmark.cpp @@ -26,7 +26,7 @@ static void TileCoverPitchedViewport(benchmark::State& state) { std::size_t length = 0; while (state.KeepRunning()) { - auto tiles = util::tileCover(transform.getState(), 8); + auto tiles = util::tileCover({transform.getState()}, 8); length += tiles.size(); } (void)length; diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 914560c9203..442479ee174 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -152,6 +152,38 @@ class Map : private util::noncopyable { void setFreeCameraOptions(const FreeCameraOptions& camera); FreeCameraOptions getFreeCameraOptions() const; + // Tile LOD controls + // + /// The number of map tile requests can be reduced by using a lower level + /// of details (Lower zoom level) away from the camera. + /// This can improve performance, particularly when the camera pitch is high. + /// The LOD calculation uses a heuristic based on the distance to the camera + /// view point. The heuristic behavior is controlled with 3 parameters: + /// - `TileLodMinRadius` is a radius around the view point in unit of tiles + /// in which the fine grained zoom level tiles are always used + /// - `TileLodScale` is a scale factor for the distance to the camera view + /// point. A value larger than 1 increases the distance to the camera view + /// point in which case the LOD is reduced + /// - `TileLodPitchThreshold` is the pitch angle in radians above which LOD + /// calculation is performed. + /// LOD calculation is always performed if `TileLodPitchThreshold` is zero. + /// LOD calculation is never performed if `TileLodPitchThreshold` is pi. + /// - `TileLodZoomShift` shifts the the Zoom level used for LOD calculation + /// A value of zero (default) does not change the Zoom level + /// A positive value increases the Zoom level and a negative value decreases + /// the Zoom level + /// A negative values typically improves performance but reduces quality. + /// For instance, a value of -1 reduces the zoom level by 1 and this + /// reduces the number of tiles by a factor of 4 for the same camera view. + void setTileLodMinRadius(double radius); + double getTileLodMinRadius() const; + void setTileLodScale(double scale); + double getTileLodScale() const; + void setTileLodPitchThreshold(double threshold); + double getTileLodPitchThreshold() const; + void setTileLodZoomShift(double shift); + double getTileLodZoomShift() const; + protected: class Impl; const std::unique_ptr impl; diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index 082443b98ef..e26dea7e2af 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -1220,6 +1220,38 @@ jni::jboolean NativeMapView::getTileCacheEnabled(JNIEnv&) { return jni::jboolean(rendererFrontend->getTileCacheEnabled()); } +void NativeMapView::setTileLodMinRadius(JNIEnv&, jni::jdouble radius) { + map->setTileLodMinRadius(radius); +} + +jni::jdouble NativeMapView::getTileLodMinRadius(JNIEnv&) { + return jni::jdouble(map->getTileLodMinRadius()); +} + +void NativeMapView::setTileLodScale(JNIEnv&, jni::jdouble scale) { + map->setTileLodScale(scale); +} + +jni::jdouble NativeMapView::getTileLodScale(JNIEnv&) { + return jni::jdouble(map->getTileLodScale()); +} + +void NativeMapView::setTileLodPitchThreshold(JNIEnv&, jni::jdouble threshold) { + map->setTileLodPitchThreshold(threshold); +} + +jni::jdouble NativeMapView::getTileLodPitchThreshold(JNIEnv&) { + return jni::jdouble(map->getTileLodPitchThreshold()); +} + +void NativeMapView::setTileLodZoomShift(JNIEnv&, jni::jdouble shift) { + map->setTileLodZoomShift(shift); +} + +jni::jdouble NativeMapView::getTileLodZoomShift(JNIEnv&) { + return jni::jdouble(map->getTileLodZoomShift()); +} + mbgl::Map& NativeMapView::getMap() { return *map; } @@ -1339,6 +1371,14 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getPrefetchZoomDelta, "nativeGetPrefetchZoomDelta"), METHOD(&NativeMapView::setTileCacheEnabled, "nativeSetTileCacheEnabled"), METHOD(&NativeMapView::getTileCacheEnabled, "nativeGetTileCacheEnabled"), + METHOD(&NativeMapView::setTileLodMinRadius, "nativeSetTileLodMinRadius"), + METHOD(&NativeMapView::getTileLodMinRadius, "nativeGetTileLodMinRadius"), + METHOD(&NativeMapView::setTileLodScale, "nativeSetTileLodScale"), + METHOD(&NativeMapView::getTileLodScale, "nativeGetTileLodScale"), + METHOD(&NativeMapView::setTileLodPitchThreshold, "nativeSetTileLodPitchThreshold"), + METHOD(&NativeMapView::getTileLodPitchThreshold, "nativeGetTileLodPitchThreshold"), + METHOD(&NativeMapView::setTileLodZoomShift, "nativeSetTileLodZoomShift"), + METHOD(&NativeMapView::getTileLodZoomShift, "nativeGetTileLodZoomShift"), METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint")); } diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp index 7ec2a4dd359..3c136040344 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp @@ -295,6 +295,22 @@ class NativeMapView : public MapObserver { jni::jboolean getTileCacheEnabled(JNIEnv&); + void setTileLodMinRadius(JNIEnv&, jni::jdouble); + + jni::jdouble getTileLodMinRadius(JNIEnv&); + + void setTileLodScale(JNIEnv&, jni::jdouble); + + jni::jdouble getTileLodScale(JNIEnv&); + + void setTileLodPitchThreshold(JNIEnv&, jni::jdouble); + + jni::jdouble getTileLodPitchThreshold(JNIEnv&); + + void setTileLodZoomShift(JNIEnv&, jni::jdouble); + + jni::jdouble getTileLodZoomShift(JNIEnv&); + mbgl::Map& getMap(); void triggerRepaint(JNIEnv&); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java index d870a76a335..bc778e71f8f 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java @@ -344,6 +344,101 @@ public boolean getTileCacheEnabled() { return nativeMapView.getTileCacheEnabled(); } + /** + * Camera based tile level of detail controls + * + * @param radius minimum radius around the view point in unit of tiles in which the fine + * grained zoom level tiles are always used when performing LOD + * radius must be greater than 1 (At least 1 fine detailed tile is present) + * A smaller radius value may improve performance at the cost of quality (tiles away from + * camera use lower Zoom levels) + */ + public void setTileLodMinRadius(@FloatRange(from = 1, fromInclusive = true) double radius) { + nativeMapView.setTileLodMinRadius(radius); + } + + /** + * Camera based tile level of detail controls + * + * @return minimum radius around the view point in unit of tiles in which the fine grained + * zoom level tiles are always used when performing LOD + * @see MapLibreMap#setTileLodMinRadius(double) + */ + public double getTileLodMinRadius() { + return nativeMapView.getTileLodMinRadius(); + } + + /** + * Camera based tile level of detail controls + * + * @param scale factor for the distance to the camera view point + * A value larger than 1 increases the distance to the camera view point reducing LOD + * Larger values may improve performance at the cost of quality (tiles away from camera + * use lower Zoom levels) + */ + public void setTileLodScale(@FloatRange(from = 0, fromInclusive = false) double scale) { + nativeMapView.setTileLodScale(scale); + } + + /** + * Camera based tile level of detail controls + * + * @return scale factor for the distance to the camera view point + * @see MapLibreMap#setTileLodScale(double) + */ + public double getTileLodScale() { + return nativeMapView.getTileLodScale(); + } + + /** + * Camera based tile level of detail controls + * + * @param threshold pitch angle in radians above which LOD calculation is performed + * A smaller radius value may improve performance at the cost of quality + */ + public void setTileLodPitchThreshold(@FloatRange(from = 0, to = Math.PI) double threshold) { + nativeMapView.setTileLodPitchThreshold(threshold); + } + + /** + * Camera based tile level of detail controls + * + * @return pitch angle threshold in radians above which LOD calculation is performed + * @see MapLibreMap#setTileLodPitchThreshold(double) + */ + public double getTileLodPitchThreshold() { + return nativeMapView.getTileLodPitchThreshold(); + } + + /** + * Camera based tile level of detail controls + * + * @param shift shift applied to the Zoom level during LOD calculation + * A negative value shifts the Zoom level to a coarser level reducing quality but + * improving performance + * A positive value shifts the Zoom level to a finer level increasing details but + * negatively affecting performance + * A value of zero (default) does not apply any shift to the Zoom level + * It is not recommended to change the default value unless performance is critical + * and the loss of quality is acceptable. A value of -1 reduces the number of + * displayed tiles by a factor of 4 on average + * It is recommended to first configure the pixelRatio before adjusting + * TileLodZoomShift. {@link MapLibreMapOptions#pixelRatio(float)} + */ + public void setTileLodZoomShift(double shift) { + nativeMapView.setTileLodZoomShift(shift); + } + + /** + * Camera based tile level of detail controls + * + * @return shift applied to the Zoom level during LOD calculation + * @see MapLibreMap#setTileLodZoomShift(double) + */ + public double getTileLodZoomShift() { + return nativeMapView.getTileLodZoomShift(); + } + // // MinZoom // diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java index 21ee9397732..5c8782f1dba 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java @@ -237,6 +237,22 @@ List queryRenderedFeatures(@NonNull RectF coordinates, boolean getTileCacheEnabled(); + void setTileLodMinRadius(double radius); + + double getTileLodMinRadius(); + + void setTileLodScale(double scale); + + double getTileLodScale(); + + void setTileLodPitchThreshold(double threshold); + + double getTileLodPitchThreshold(); + + void setTileLodZoomShift(double shift); + + double getTileLodZoomShift(); + void setGestureInProgress(boolean inProgress); float getPixelRatio(); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java index 09797905eb4..edc2efdfa97 100755 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java @@ -823,6 +823,70 @@ public boolean getTileCacheEnabled() { } return nativeGetTileCacheEnabled(); } + + @Override + public void setTileLodMinRadius(double radius) { + if (checkState("setTileLodMinRadius")) { + return; + } + nativeSetTileLodMinRadius(radius); + } + + @Override + public double getTileLodMinRadius() { + if (checkState("getTileLodMinRadius")) { + return 0; + } + return nativeGetTileLodMinRadius(); + } + + @Override + public void setTileLodScale(double scale) { + if (checkState("setTileLodScale")) { + return; + } + nativeSetTileLodScale(scale); + } + + @Override + public double getTileLodScale() { + if (checkState("getTileLodScale")) { + return 0; + } + return nativeGetTileLodScale(); + } + + @Override + public void setTileLodPitchThreshold(double threshold) { + if (checkState("setTileLodPitchThreshold")) { + return; + } + nativeSetTileLodPitchThreshold(threshold); + } + + @Override + public double getTileLodPitchThreshold() { + if (checkState("getTileLodPitchThreshold")) { + return 0; + } + return nativeGetTileLodPitchThreshold(); + } + + @Override + public void setTileLodZoomShift(double shift) { + if (checkState("setTileLodZoomShift")) { + return; + } + nativeSetTileLodZoomShift(shift); + } + + @Override + public double getTileLodZoomShift() { + if (checkState("getTileLodZoomShift")) { + return 0; + } + return nativeGetTileLodZoomShift(); + } // Runtime style Api @Override @@ -1594,6 +1658,30 @@ private native Feature[] nativeQueryRenderedFeaturesForBox(float left, float top @Keep private native int nativeGetPrefetchZoomDelta(); + @Keep + private native void nativeSetTileLodMinRadius(double radius); + + @Keep + private native double nativeGetTileLodMinRadius(); + + @Keep + private native void nativeSetTileLodScale(double scale); + + @Keep + private native double nativeGetTileLodScale(); + + @Keep + private native void nativeSetTileLodPitchThreshold(double threshold); + + @Keep + private native double nativeGetTileLodPitchThreshold(); + + @Keep + private native void nativeSetTileLodZoomShift(double shift); + + @Keep + private native double nativeGetTileLodZoomShift(); + @Override public long getNativePtr() { return nativePtr; diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp index febb52d3ffe..fe277e8d699 100644 --- a/platform/glfw/glfw_view.cpp +++ b/platform/glfw/glfw_view.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,6 @@ using namespace std::numbers; #ifdef ENABLE_LOCATION_INDICATOR - namespace { const std::string mbglPuckAssetsPath{MAPBOX_PUCK_ASSETS_PATH}; @@ -79,7 +79,7 @@ std::array toArray(const mbgl::LatLng &crd) { return {crd.latitude(), crd.longitude(), 0}; } } // namespace -#endif +#endif // ENABLE_LOCATION_INDICATOR class SnapshotObserver final : public mbgl::MapSnapshotterObserver { public: @@ -95,6 +95,72 @@ class SnapshotObserver final : public mbgl::MapSnapshotterObserver { }; namespace { + +enum class TileLodMode { + Default, // Default Tile LOD parameters + NoLod, // Disable LOD + Reduced, // Reduce LOD away from camera + Aggressive, // Aggressively reduce LOD away from camera at the detriment of quality +}; + +constexpr TileLodMode nextTileLodMode(TileLodMode current) { + switch (current) { + case TileLodMode::Default: + return TileLodMode::NoLod; + case TileLodMode::NoLod: + return TileLodMode::Reduced; + case TileLodMode::Reduced: + return TileLodMode::Aggressive; + case TileLodMode::Aggressive: + return TileLodMode::Default; + default: + return TileLodMode::Default; + } +} + +void cycleTileLodMode(mbgl::Map &map) { + // TileLodMode::Default parameters + static const auto defaultRadius = map.getTileLodMinRadius(); + static const auto defaultScale = map.getTileLodScale(); + static const auto defaultPitchThreshold = map.getTileLodPitchThreshold(); + + static TileLodMode mode = TileLodMode::Default; + mode = nextTileLodMode(mode); + + switch (mode) { + case TileLodMode::Default: + map.setTileLodMinRadius(defaultRadius); + map.setTileLodScale(defaultScale); + map.setTileLodPitchThreshold(defaultPitchThreshold); + break; + case TileLodMode::NoLod: + // When LOD is off we set a maximum PitchThreshold + map.setTileLodMinRadius(std::numbers::pi); + break; + case TileLodMode::Reduced: + map.setTileLodMinRadius(2); + map.setTileLodScale(1.5); + map.setTileLodPitchThreshold(std::numbers::pi / 4); + break; + case TileLodMode::Aggressive: + map.setTileLodMinRadius(1); + map.setTileLodScale(2); + map.setTileLodPitchThreshold(0); + break; + } + map.triggerRepaint(); +} + +void tileLodZoomShift(mbgl::Map &map, bool positive) { + constexpr auto tileLodZoomShiftStep = 0.25; + auto shift = positive ? tileLodZoomShiftStep : -tileLodZoomShiftStep; + shift = map.getTileLodZoomShift() + shift; + shift = mbgl::util::clamp(shift, -2.5, 2.5); + mbgl::Log::Info(mbgl::Event::OpenGL, "Zoom shift: " + std::to_string(shift)); + map.setTileLodZoomShift(shift); + map.triggerRepaint(); +} + void addFillExtrusionLayer(mbgl::style::Style &style, bool visible) { MLN_TRACE_FUNC(); @@ -282,6 +348,9 @@ GLFWView::GLFWView(bool fullscreen_, printf("- Press `F1` to generate a render test for the current view\n"); printf("\n"); printf("- Press `Tab` to cycle through the map debug options\n"); + printf("- Press `V` to cycle through Tile LOD modes\n"); + printf("- Press `F7` to lower the zoom level without changing the camera\n"); + printf("- Press `F8` to higher the zoom level without changing the camera\n"); printf("- Press `Esc` to quit\n"); printf("\n"); printf( @@ -557,6 +626,15 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, view->freeCameraDemoStartTime = mbgl::Clock::now(); view->invalidate(); } break; + case GLFW_KEY_V: { + cycleTileLodMode(*view->map); + } break; + case GLFW_KEY_F7: { + tileLodZoomShift(*view->map, false); + } break; + case GLFW_KEY_F8: { + tileLodZoomShift(*view->map, true); + } break; } } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 9bad545b184..8903bb1b71c 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -527,4 +527,36 @@ FreeCameraOptions Map::getFreeCameraOptions() const { return impl->transform.getFreeCameraOptions(); } +void Map::setTileLodMinRadius(double radius) { + impl->tileLodMinRadius = radius; +} + +double Map::getTileLodMinRadius() const { + return impl->tileLodMinRadius; +} + +void Map::setTileLodScale(double scale) { + impl->tileLodScale = scale; +} + +double Map::getTileLodScale() const { + return impl->tileLodScale; +} + +void Map::setTileLodPitchThreshold(double threshold) { + impl->tileLodPitchThreshold = threshold; +} + +double Map::getTileLodPitchThreshold() const { + return impl->tileLodPitchThreshold; +} + +void Map::setTileLodZoomShift(double shift) { + impl->tileLodZoomShift = shift; +} + +double Map::getTileLodZoomShift() const { + return impl->tileLodZoomShift; +} + } // namespace mbgl diff --git a/src/mbgl/map/map_impl.cpp b/src/mbgl/map/map_impl.cpp index 6b5a6454f18..aeb7b78dfc3 100644 --- a/src/mbgl/map/map_impl.cpp +++ b/src/mbgl/map/map_impl.cpp @@ -99,7 +99,11 @@ void Map::Impl::onUpdate() { fileSource, prefetchZoomDelta, bool(stillImageRequest), - crossSourceCollisions}; + crossSourceCollisions, + tileLodMinRadius, + tileLodScale, + tileLodPitchThreshold, + tileLodZoomShift}; rendererFrontend.update(std::make_shared(std::move(params))); } diff --git a/src/mbgl/map/map_impl.hpp b/src/mbgl/map/map_impl.hpp index d093b30f227..83aca45f8c7 100644 --- a/src/mbgl/map/map_impl.hpp +++ b/src/mbgl/map/map_impl.hpp @@ -14,6 +14,8 @@ #include #include +#include + namespace mbgl { class FileSource; @@ -90,6 +92,11 @@ class Map::Impl final : public style::Observer, public RendererObserver { bool loading = false; bool rendererFullyLoaded; std::unique_ptr stillImageRequest; + + double tileLodMinRadius = 3; + double tileLodScale = 1; + double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; + double tileLodZoomShift = 0; }; // Forward declaration of this method is required for the MapProjection class diff --git a/src/mbgl/renderer/layers/render_background_layer.cpp b/src/mbgl/renderer/layers/render_background_layer.cpp index c84e23bb50c..820e9c559c6 100644 --- a/src/mbgl/renderer/layers/render_background_layer.cpp +++ b/src/mbgl/renderer/layers/render_background_layer.cpp @@ -129,6 +129,9 @@ void RenderBackgroundLayer::render(PaintParameters& parameters) { segments = RenderStaticData::tileTriangleSegments(); } + util::TileCoverParameters tileCoverParameters = { + parameters.state, parameters.tileLodMinRadius, parameters.tileLodScale, parameters.tileLodPitchThreshold}; + const auto& evaluated = static_cast(*evaluatedProperties).evaluated; const auto& crossfade = static_cast(*evaluatedProperties).crossfade; if (!evaluated.get().to.empty()) { @@ -140,7 +143,7 @@ void RenderBackgroundLayer::render(PaintParameters& parameters) { if (!imagePosA || !imagePosB) return; uint32_t i = 0; - for (const auto& tileID : util::tileCover(parameters.state, parameters.state.getIntegerZoom())) { + for (const auto& tileID : util::tileCover(tileCoverParameters, parameters.state.getIntegerZoom())) { const UnwrappedTileID unwrappedTileID = tileID.toUnwrapped(); draw(*backgroundPatternProgram, BackgroundPatternProgram::layoutUniformValues(parameters.matrixForTile(unwrappedTileID), @@ -166,7 +169,7 @@ void RenderBackgroundLayer::render(PaintParameters& parameters) { return; } uint32_t i = 0; - for (const auto& tileID : util::tileCover(parameters.state, parameters.state.getIntegerZoom())) { + for (const auto& tileID : util::tileCover(tileCoverParameters, parameters.state.getIntegerZoom())) { draw(*backgroundProgram, BackgroundProgram::LayoutUniformValues{ uniforms::matrix::Value(parameters.matrixForTile(tileID.toUnwrapped())), @@ -216,11 +219,16 @@ static constexpr std::string_view BackgroundPatternShaderName = "BackgroundPatte void RenderBackgroundLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, const TransformState& state, - const std::shared_ptr&, + const std::shared_ptr& updateParameters, [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] UniqueChangeRequestVec& changes) { + assert(updateParameters); const auto zoom = state.getIntegerZoom(); - const auto tileCover = util::tileCover(state, zoom); + const auto tileCover = util::tileCover({state, + updateParameters->tileLodMinRadius, + updateParameters->tileLodScale, + updateParameters->tileLodPitchThreshold}, + zoom); // renderTiles is always empty, we use tileCover instead if (tileCover.empty()) { diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index e4957ff65b0..a8dd6c513a8 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -51,7 +51,10 @@ PaintParameters::PaintParameters(gfx::Context& context_, RenderStaticData& staticData_, LineAtlas& lineAtlas_, PatternAtlas& patternAtlas_, - uint64_t frameCount_) + uint64_t frameCount_, + double tileLodMinRadius_, + double tileLodScale_, + double tileLodPitchThreshold_) : context(context_), backend(backend_), encoder(context.createCommandEncoder()), @@ -71,7 +74,10 @@ PaintParameters::PaintParameters(gfx::Context& context_, programs(staticData_.programs), #endif shaders(*staticData_.shaders), - frameCount(frameCount_) { + frameCount(frameCount_), + tileLodMinRadius(tileLodMinRadius_), + tileLodScale(tileLodScale_), + tileLodPitchThreshold(tileLodPitchThreshold_) { pixelsToGLUnits = {{2.0f / state.getSize().width, -2.0f / state.getSize().height}}; if (state.getViewportMode() == ViewportMode::FlippedY) { diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 6bb5a13fcde..6385ec0c709 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -58,7 +58,10 @@ class PaintParameters { RenderStaticData&, LineAtlas&, PatternAtlas&, - uint64_t frameCount); + uint64_t frameCount, + double tileLodMinRadius, + double tileLodScale, + double tileLodPitchThreshold); ~PaintParameters(); gfx::Context& context; @@ -134,6 +137,10 @@ class PaintParameters { static constexpr float depthEpsilon = 1.0f / (1 << 12); #endif static constexpr int maxStencilValue = 255; + + double tileLodMinRadius; + double tileLodScale; + double tileLodPitchThreshold; }; } // namespace mbgl diff --git a/src/mbgl/renderer/render_orchestrator.cpp b/src/mbgl/renderer/render_orchestrator.cpp index c9762831a1b..21bb4eb9776 100644 --- a/src/mbgl/renderer/render_orchestrator.cpp +++ b/src/mbgl/renderer/render_orchestrator.cpp @@ -197,7 +197,11 @@ std::unique_ptr RenderOrchestrator::createRenderTree( imageManager, glyphManager, updateParameters->prefetchZoomDelta, - threadPool}; + threadPool, + updateParameters->tileLodMinRadius, + updateParameters->tileLodScale, + updateParameters->tileLodPitchThreshold, + updateParameters->tileLodZoomShift}; glyphManager->setURL(updateParameters->glyphURL); diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 5ccac52261b..a71ecf282f0 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -90,12 +91,13 @@ void Renderer::Impl::setObserver(RendererObserver* observer_) { observer = observer_ ? observer_ : &nullObserver(); } -void Renderer::Impl::render(const RenderTree& renderTree, - [[maybe_unused]] const std::shared_ptr& updateParameters) { +void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr& updateParameters) { MLN_TRACE_FUNC(); auto& context = backend.getContext(); context.setObserver(this); + assert(updateParameters); + #if MLN_RENDER_BACKEND_METAL if constexpr (EnableMetalCapture) { const auto& mtlBackend = static_cast(backend); @@ -200,7 +202,10 @@ void Renderer::Impl::render(const RenderTree& renderTree, *staticData, renderTree.getLineAtlas(), renderTree.getPatternAtlas(), - frameCount}; + frameCount, + updateParameters->tileLodMinRadius, + updateParameters->tileLodScale, + updateParameters->tileLodPitchThreshold}; parameters.symbolFadeChange = renderTreeParameters.symbolFadeChange; parameters.opaquePassCutoff = renderTreeParameters.opaquePassCutOff; diff --git a/src/mbgl/renderer/sources/render_image_source.cpp b/src/mbgl/renderer/sources/render_image_source.cpp index 76c9e71dde3..6f17c6bf3db 100644 --- a/src/mbgl/renderer/sources/render_image_source.cpp +++ b/src/mbgl/renderer/sources/render_image_source.cpp @@ -182,7 +182,9 @@ void RenderImageSource::update(Immutable baseImpl_, bool hasVisibleTile = false; // Add additional wrapped tile ids if neccessary - auto idealTiles = util::tileCover(transformState, static_cast(transformState.getZoom())); + auto idealTiles = util::tileCover( + {transformState, parameters.tileLodMinRadius, parameters.tileLodScale, parameters.tileLodPitchThreshold}, + static_cast(transformState.getZoom())); for (auto tile : idealTiles) { if (tile.wrap != 0 && tileCover[0].canonical.isChildOf(tile.canonical)) { tileIds.emplace_back(tile.wrap, tileCover[0].canonical); diff --git a/src/mbgl/renderer/tile_parameters.hpp b/src/mbgl/renderer/tile_parameters.hpp index 75c1c152f4f..d6615f8a6d1 100644 --- a/src/mbgl/renderer/tile_parameters.hpp +++ b/src/mbgl/renderer/tile_parameters.hpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -27,6 +28,10 @@ class TileParameters { std::shared_ptr glyphManager; const uint8_t prefetchZoomDelta; TaggedScheduler threadPool; + double tileLodMinRadius = 3; + double tileLodScale = 1; + double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; + double tileLodZoomShift = 0; }; } // namespace mbgl diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 4533e6f7549..6a265833259 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -88,9 +88,13 @@ void TilePyramid::update(const std::vector>& l handleWrapJump(static_cast(parameters.transformState.getLatLng().longitude())); + // Optionally shift the zoom level + double zoom = util::clamp( + parameters.transformState.getZoom() + parameters.tileLodZoomShift, zoomRange.min, zoomRange.max); + const auto type = sourceImpl.type; // Determine the overzooming/underzooming amounts and required tiles. - int32_t overscaledZoom = util::coveringZoomLevel(parameters.transformState.getZoom(), type, tileSize); + int32_t overscaledZoom = util::coveringZoomLevel(zoom, type, tileSize); int32_t tileZoom = overscaledZoom; int32_t panZoom = zoomRange.max; @@ -102,6 +106,11 @@ void TilePyramid::update(const std::vector>& l std::vector idealTiles; std::vector panTiles; + util::TileCoverParameters tileCoverParameters = {parameters.transformState, + parameters.tileLodMinRadius, + parameters.tileLodScale, + parameters.tileLodPitchThreshold}; + if (overscaledZoom >= zoomRange.min) { int32_t idealZoom = std::min(zoomRange.max, overscaledZoom); @@ -122,11 +131,11 @@ void TilePyramid::update(const std::vector>& l } if (panZoom < idealZoom) { - panTiles = util::tileCover(parameters.transformState, panZoom); + panTiles = util::tileCover(tileCoverParameters, panZoom); } } - idealTiles = util::tileCover(parameters.transformState, idealZoom, tileZoom); + idealTiles = util::tileCover(tileCoverParameters, idealZoom, tileZoom); if (parameters.mode == MapMode::Tile && type != SourceType::Raster && type != SourceType::RasterDEM && idealTiles.size() > 1) { mbgl::Log::Warning(mbgl::Event::General, diff --git a/src/mbgl/renderer/update_parameters.hpp b/src/mbgl/renderer/update_parameters.hpp index a16a2801f0b..d2b162c29ee 100644 --- a/src/mbgl/renderer/update_parameters.hpp +++ b/src/mbgl/renderer/update_parameters.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -44,6 +45,11 @@ class UpdateParameters { const bool stillImageRequest; const bool crossSourceCollisions; + + double tileLodMinRadius = 3; + double tileLodScale = 1; + double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; + double tileLodZoomShift = 0; }; } // namespace mbgl diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index 1a3055788bf..d4f2e4eab4f 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -9,7 +8,6 @@ #include #include -#include using namespace std::numbers; @@ -157,7 +155,7 @@ int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) no } } -std::vector tileCover(const TransformState& state, +std::vector tileCover(const TileCoverParameters& state, uint8_t z, const std::optional& overscaledZ) { struct Node { @@ -173,23 +171,26 @@ std::vector tileCover(const TransformState& state, double sqrDist; }; + const auto& transform = state.transformState; 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) * pi ? z : 0; + const double worldSize = Projection::worldSize(transform.getScale()); + const uint8_t minZoom = transform.getPitch() <= state.tileLodPitchThreshold ? z : 0; const uint8_t maxZoom = z; - const uint8_t overscaledZoom = overscaledZ.value_or(z); - const bool flippedY = state.getViewportMode() == ViewportMode::FlippedY; + const uint8_t overscaledZoom = std::max(overscaledZ.value_or(z), z); + const bool flippedY = transform.getViewportMode() == ViewportMode::FlippedY; - const auto centerPoint = - TileCoordinate::fromScreenCoordinate(state, z, {state.getSize().width / 2.0, state.getSize().height / 2.0}).p; + const auto centerPoint = TileCoordinate::fromScreenCoordinate( + transform, z, {transform.getSize().width / 2.0, transform.getSize().height / 2.0}) + .p; const vec3 centerCoord = {{centerPoint.x, centerPoint.y, 0.0}}; - const Frustum frustum = Frustum::fromInvProjMatrix(state.getInvProjectionMatrix(), worldSize, z, flippedY); + const Frustum frustum = Frustum::fromInvProjMatrix(transform.getInvProjectionMatrix(), worldSize, z, flippedY); // There should always be a certain number of maximum zoom level tiles // surrounding the center location - const double radiusOfMaxLvlLodInTiles = 3; + assert(state.tileLodMinRadius >= 1); + const double radiusOfMaxLvlLodInTiles = std::max(1.0, state.tileLodMinRadius); const auto newRootTile = [&](int16_t wrap) -> Node { return {AABB({{wrap * numTiles, 0.0, 0.0}}, {{(wrap + 1) * numTiles, numTiles, 0.0}}), @@ -235,12 +236,14 @@ std::vector tileCover(const TransformState& state, // there's always a certain number of maxLevel tiles next to the map // center. Using the fact that a parent node in quadtree is twice the // size of its children (per dimension) we can define distance - // thresholds for each relative level: f(k) = offset + 2 + 4 + 8 + 16 + - // ... + 2^k. This is the same as "offset+2^(k+1)-2" + // thresholds for each relative level: + // f(k) = offset + 2 + 4 + 8 + 16 + ... + 2^k + // This is the same as: + // f(k) = offset + 2^(k+1)-2 const double distToSplit = radiusOfMaxLvlLodInTiles + (1 << (maxZoom - node.zoom)) - 2; // Have we reached the target depth or is the tile too far away to be any split further? - if (node.zoom == maxZoom || (*longestDim > distToSplit && node.zoom >= minZoom)) { + if (node.zoom == maxZoom || (*longestDim * state.tileLodScale > distToSplit && node.zoom >= minZoom)) { // Perform precise intersection test between the frustum and aabb. // This will cull < 1% false positives missed by the original test if (node.fullyVisible || frustum.intersectsPrecise(node.aabb, true) != IntersectionResult::Separate) { diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp index 972fdc5ed84..a935b2d3a4c 100644 --- a/src/mbgl/util/tile_cover.hpp +++ b/src/mbgl/util/tile_cover.hpp @@ -1,16 +1,18 @@ #pragma once +#include #include #include #include +#include #include #include +#include #include namespace mbgl { -class TransformState; class LatLngBounds; namespace util { @@ -31,9 +33,16 @@ class TileCover { std::unique_ptr impl; }; +struct TileCoverParameters { + TransformState transformState; + double tileLodMinRadius = 3; + double tileLodScale = 1; + double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; +}; + int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize) noexcept; -std::vector tileCover(const TransformState&, +std::vector tileCover(const TileCoverParameters& state, uint8_t z, const std::optional& overscaledZ = std::nullopt); std::vector tileCover(const LatLngBounds&, uint8_t z); diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index 535029d1d4e..a84119d2da3 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -44,7 +44,7 @@ TEST(TileCover, Pitch) { .withPitch(40.0)); EXPECT_EQ((std::vector{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}), - util::tileCover(transform.getState(), 2)); + util::tileCover({transform.getState()}, 2)); } TEST(TileCover, PitchIssue15442) { @@ -62,7 +62,7 @@ TEST(TileCover, PitchIssue15442) { EXPECT_EQ((std::vector{ {2, 3, 1}, {2, 2, 1}, {2, 3, 0}, {2, 2, 0}, {2, 1, {2, 0, 0}}, {2, 1, {2, 1, 0}}}), - util::tileCover(transform.getState(), 2)); + util::tileCover({transform.getState()}, 2)); } TEST(TileCover, PitchOverAllowedByContentInsets) { @@ -80,7 +80,7 @@ TEST(TileCover, PitchOverAllowedByContentInsets) { EXPECT_EQ( (std::vector{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}, {3, 4, 2}, {3, 5, 3}, {3, 5, 2}}), - util::tileCover(transform.getState(), 3)); + util::tileCover({transform.getState()}, 3)); } TEST(TileCover, PitchWithLargerResultSet) { @@ -96,7 +96,7 @@ TEST(TileCover, PitchWithLargerResultSet) { .withBearing(-142.2630000003529176) .withPitch(60.0)); - auto cover = util::tileCover(transform.getState(), 5); + auto cover = util::tileCover({transform.getState()}, 5); // Returned vector has above 100 elements, we check first 16 as there is a // plan to return lower LOD for distant tiles. EXPECT_EQ((std::vector{{5, 15, 16}, @@ -148,7 +148,7 @@ TEST(TileCover, CoordinatesAreUnwrapped) { EXPECT_EQ( (std::vector{{1, 0, {1, 1, 0}}, {1, 1, {1, 0, 0}}, {1, 0, {1, 1, 1}}, {1, 1, {1, 0, 1}}}), - util::tileCover(transform.getState(), 1)); + util::tileCover({transform.getState()}, 1)); } TEST(TileCover, DifferentOverscaledZ) { @@ -165,7 +165,7 @@ TEST(TileCover, DifferentOverscaledZ) { EXPECT_EQ( (std::vector{{3, 0, {2, 1, 1}}, {3, 0, {2, 2, 1}}, {3, 0, {2, 1, 2}}, {3, 0, {2, 2, 2}}}), - util::tileCover(transform.getState(), 2, 3)); + util::tileCover({transform.getState()}, 2, 3)); } TEST(TileCover, DifferentOverscaledZWithPitch) { @@ -186,7 +186,7 @@ TEST(TileCover, DifferentOverscaledZWithPitch) { {5, 0, {3, 3, 1}}, {5, 0, {3, 4, 1}}, {5, 0, {3, 2, 1}}}), - util::tileCover(transform.getState(), 3, 5)); + util::tileCover({transform.getState()}, 3, 5)); } TEST(TileCover, DifferentOverscaledZWrapped) { @@ -201,7 +201,7 @@ TEST(TileCover, DifferentOverscaledZWrapped) { EXPECT_EQ( (std::vector{{2, 0, {1, 1, 0}}, {2, 1, {1, 0, 0}}, {2, 0, {1, 1, 1}}, {2, 1, {1, 0, 1}}}), - util::tileCover(transform.getState(), 1, 2)); + util::tileCover({transform.getState()}, 1, 2)); } TEST(TileCover, FlippedY) { @@ -216,7 +216,7 @@ TEST(TileCover, FlippedY) { .withZoom(1.0)); EXPECT_EQ((std::vector{{1, 0, 0}, {1, 1, 0}, {1, 0, 1}, {1, 1, 1}}), - util::tileCover(transform.getState(), 1)); + util::tileCover({transform.getState()}, 1)); } TEST(TileCover, FlippedYPitch) { @@ -233,7 +233,7 @@ TEST(TileCover, FlippedYPitch) { .withPitch(40.0)); EXPECT_EQ((std::vector{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}), - util::tileCover(transform.getState(), 2)); + util::tileCover({transform.getState()}, 2)); } TEST(TileCover, FlippedYHelsinki) { @@ -248,7 +248,7 @@ TEST(TileCover, FlippedYHelsinki) { .withZoom(11.447425)); EXPECT_EQ((std::vector{{11, 1165, 592}, {11, 1166, 592}, {11, 1165, 593}, {11, 1166, 593}}), - util::tileCover(transform.getState(), 11)); + util::tileCover({transform.getState()}, 11)); } TEST(TileCoverStream, Arctic) {