diff --git a/CHANGELOG.md b/CHANGELOG.md index 39558e2c589..fbb51ec5441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,12 @@ ### 🏁 Performance improvements +- [core] Cross tile index performance ([#16127](https://github.com/mapbox/mapbox-gl-native/pull/16127)) + + For overscaled tiles, index only the symbols inside the viewport. + + Find matches only among the buckets that have the same leader Id. + - [core] Calculate GeoJSON tile geometries in a background thread ([#15953](https://github.com/mapbox/mapbox-gl-native/pull/15953)) Call `mapbox::geojsonvt::GeoJSONVT::getTile()` in a background thread, so that the rendering thread is not blocked. diff --git a/include/mbgl/renderer/renderer.hpp b/include/mbgl/renderer/renderer.hpp index 7a1ddde1c13..91f2b6146ce 100644 --- a/include/mbgl/renderer/renderer.hpp +++ b/include/mbgl/renderer/renderer.hpp @@ -31,7 +31,7 @@ class Renderer { void setObserver(RendererObserver*); - void render(const UpdateParameters&); + void render(const std::shared_ptr&); // Feature queries std::vector queryRenderedFeatures(const ScreenLineString&, const RenderedQueryOptions& options = {}) const; diff --git a/platform/android/src/map_renderer.cpp b/platform/android/src/map_renderer.cpp index 0c0e907f149..f5336e3dd88 100644 --- a/platform/android/src/map_renderer.cpp +++ b/platform/android/src/map_renderer.cpp @@ -143,7 +143,7 @@ void MapRenderer::render(JNIEnv&) { framebufferSizeChanged = false; } - renderer->render(*params); + renderer->render(params); // Deliver the snapshot if requested if (snapshotCallback) { diff --git a/platform/darwin/src/MGLRendererFrontend.h b/platform/darwin/src/MGLRendererFrontend.h index 1358f5fafa7..e2b8260fd15 100644 --- a/platform/darwin/src/MGLRendererFrontend.h +++ b/platform/darwin/src/MGLRendererFrontend.h @@ -54,7 +54,7 @@ class MGLRenderFrontend : public mbgl::RendererFrontend // Copy the shared pointer here so that the parameters aren't destroyed while `render(...)` is // still using them. auto updateParameters_ = updateParameters; - renderer->render(*updateParameters_); + renderer->render(updateParameters_); } mbgl::Renderer* getRenderer() { diff --git a/platform/default/src/mbgl/gfx/headless_frontend.cpp b/platform/default/src/mbgl/gfx/headless_frontend.cpp index 5235b2f4084..996006bfe26 100644 --- a/platform/default/src/mbgl/gfx/headless_frontend.cpp +++ b/platform/default/src/mbgl/gfx/headless_frontend.cpp @@ -39,7 +39,7 @@ HeadlessFrontend::HeadlessFrontend(Size size_, // Copy the shared pointer here so that the parameters aren't destroyed while `render(...)` is // still using them. auto updateParameters_ = updateParameters; - renderer->render(*updateParameters_); + renderer->render(updateParameters_); auto endTime = mbgl::util::MonotonicTimer::now(); frameTime = (endTime - startTime).count(); diff --git a/platform/glfw/glfw_renderer_frontend.cpp b/platform/glfw/glfw_renderer_frontend.cpp index b8478a49f89..46f13099015 100644 --- a/platform/glfw/glfw_renderer_frontend.cpp +++ b/platform/glfw/glfw_renderer_frontend.cpp @@ -38,7 +38,7 @@ void GLFWRendererFrontend::render() { // Copy the shared pointer here so that the parameters aren't destroyed while `render(...)` is // still using them. auto updateParameters_ = updateParameters; - renderer->render(*updateParameters_); + renderer->render(updateParameters_); } mbgl::Renderer* GLFWRendererFrontend::getRenderer() { diff --git a/platform/qt/src/qmapboxgl_map_renderer.cpp b/platform/qt/src/qmapboxgl_map_renderer.cpp index 84234703237..7c7f5ee1511 100644 --- a/platform/qt/src/qmapboxgl_map_renderer.cpp +++ b/platform/qt/src/qmapboxgl_map_renderer.cpp @@ -84,7 +84,7 @@ void QMapboxGLMapRenderer::render() // The OpenGL implementation automatically enables the OpenGL context for us. mbgl::gfx::BackendScope scope(m_backend, mbgl::gfx::BackendScope::ScopeType::Implicit); - m_renderer->render(*params); + m_renderer->render(params); if (m_forceScheduler) { getScheduler()->processEvents(); diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 2a25c655aa8..3cd67125b49 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -119,6 +119,8 @@ class SymbolInstance { std::array variableTextOffset; bool singleLine; uint32_t crossTileID = 0; + + static constexpr uint32_t invalidCrossTileID() { return std::numeric_limits::max(); } }; } // namespace mbgl diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index edc7f3b9cf8..8597f3b5df8 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -210,6 +210,7 @@ class TransformState { void setLatLngZoom(const LatLng& latLng, double zoom); void constrain(double& scale, double& x, double& y) const; + const mat4& getProjectionMatrix() const; private: bool rotatedNorth() const; @@ -236,7 +237,7 @@ class TransformState { void updateMatricesIfNeeded() const; bool needsMatricesUpdate() const { return requestMatricesUpdate; } - const mat4& getProjectionMatrix() const; + const mat4& getCoordMatrix() const; const mat4& getInvertedMatrix() const; diff --git a/src/mbgl/renderer/bucket.hpp b/src/mbgl/renderer/bucket.hpp index 6da0f280e65..fc34f55e75f 100644 --- a/src/mbgl/renderer/bucket.hpp +++ b/src/mbgl/renderer/bucket.hpp @@ -56,7 +56,7 @@ class Bucket { // Returns a pair, the first element of which is a bucket cross-tile id // on success call; `0` otherwise. The second element is `true` if // the bucket was originally registered; `false` otherwise. - virtual std::pair registerAtCrossTileIndex(CrossTileSymbolLayerIndex&, const OverscaledTileID&, uint32_t&) { + virtual std::pair registerAtCrossTileIndex(CrossTileSymbolLayerIndex&, const RenderTile&) { return std::make_pair(0u, false); } // Places this bucket to the given placement. diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index 7efd81053ed..55002e7851e 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -1,6 +1,7 @@ +#include #include #include -#include +#include #include #include #include @@ -37,6 +38,7 @@ SymbolBucket::SymbolBucket(Immutable SymbolBucket::registerAtCrossTileIndex(CrossTileSymbolLayerIndex& index, const OverscaledTileID& tileID, uint32_t& maxCrossTileID) { - bool firstTimeAdded = index.addBucket(tileID, *this, maxCrossTileID); +std::pair SymbolBucket::registerAtCrossTileIndex(CrossTileSymbolLayerIndex& index, + const RenderTile& renderTile) { + bool firstTimeAdded = index.addBucket(renderTile.getOverscaledTileID(), renderTile.matrix, *this); return std::make_pair(bucketInstanceId, firstTimeAdded); } diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index 1d1aa13dd00..82d8c682aa4 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -81,7 +81,7 @@ class SymbolBucket final : public Bucket { void upload(gfx::UploadPass&) override; bool hasData() const override; - std::pair registerAtCrossTileIndex(CrossTileSymbolLayerIndex&, const OverscaledTileID&, uint32_t& maxCrossTileID) override; + std::pair registerAtCrossTileIndex(CrossTileSymbolLayerIndex&, const RenderTile&) override; void place(Placement&, const BucketPlacementParameters&, std::set&) override; void updateVertices( const Placement&, bool updateOpacities, const TransformState&, const RenderTile&, std::set&) override; @@ -114,6 +114,7 @@ class SymbolBucket final : public Bucket { // Set and used by placement. mutable bool justReloaded : 1; bool hasVariablePlacement : 1; + bool hasUninitializedSymbols : 1; std::vector symbolInstances; diff --git a/src/mbgl/renderer/render_orchestrator.cpp b/src/mbgl/renderer/render_orchestrator.cpp index 2462ee69866..c771bd100b9 100644 --- a/src/mbgl/renderer/render_orchestrator.cpp +++ b/src/mbgl/renderer/render_orchestrator.cpp @@ -135,51 +135,49 @@ void RenderOrchestrator::setObserver(RendererObserver* observer_) { observer = observer_ ? observer_ : &nullObserver(); } -std::unique_ptr RenderOrchestrator::createRenderTree(const UpdateParameters& updateParameters) { - const bool isMapModeContinuous = updateParameters.mode == MapMode::Continuous; +std::unique_ptr RenderOrchestrator::createRenderTree( + const std::shared_ptr& updateParameters) { + const bool isMapModeContinuous = updateParameters->mode == MapMode::Continuous; if (!isMapModeContinuous) { // Reset zoom history state. zoomHistory.first = true; } if (LayerManager::annotationsEnabled) { - updateParameters.annotationManager.updateData(); + updateParameters->annotationManager.updateData(); } - const bool zoomChanged = zoomHistory.update(updateParameters.transformState.getZoom(), updateParameters.timePoint); + const bool zoomChanged = + zoomHistory.update(updateParameters->transformState.getZoom(), updateParameters->timePoint); - const TransitionOptions transitionOptions = isMapModeContinuous ? updateParameters.transitionOptions : TransitionOptions(); + const TransitionOptions transitionOptions = + isMapModeContinuous ? updateParameters->transitionOptions : TransitionOptions(); - const TransitionParameters transitionParameters { - updateParameters.timePoint, - transitionOptions - }; + const TransitionParameters transitionParameters{updateParameters->timePoint, transitionOptions}; - const PropertyEvaluationParameters evaluationParameters { + const PropertyEvaluationParameters evaluationParameters{ zoomHistory, - updateParameters.timePoint, - transitionOptions.duration.value_or(isMapModeContinuous ? util::DEFAULT_TRANSITION_DURATION : Duration::zero()) - }; - - const TileParameters tileParameters { - updateParameters.pixelRatio, - updateParameters.debugOptions, - updateParameters.transformState, - updateParameters.fileSource, - updateParameters.mode, - updateParameters.annotationManager, - *imageManager, - *glyphManager, - updateParameters.prefetchZoomDelta - }; - - glyphManager->setURL(updateParameters.glyphURL); + updateParameters->timePoint, + transitionOptions.duration.value_or(isMapModeContinuous ? util::DEFAULT_TRANSITION_DURATION + : Duration::zero())}; + + const TileParameters tileParameters{updateParameters->pixelRatio, + updateParameters->debugOptions, + updateParameters->transformState, + updateParameters->fileSource, + updateParameters->mode, + updateParameters->annotationManager, + *imageManager, + *glyphManager, + updateParameters->prefetchZoomDelta}; + + glyphManager->setURL(updateParameters->glyphURL); // Update light. - const bool lightChanged = renderLight.impl != updateParameters.light; + const bool lightChanged = renderLight.impl != updateParameters->light; if (lightChanged) { - renderLight.impl = updateParameters.light; + renderLight.impl = updateParameters->light; renderLight.transition(transitionParameters); } @@ -187,9 +185,8 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar renderLight.evaluate(evaluationParameters); } - - const ImageDifference imageDiff = diffImages(imageImpls, updateParameters.images); - imageImpls = updateParameters.images; + const ImageDifference imageDiff = diffImages(imageImpls, updateParameters->images); + imageImpls = updateParameters->images; // Only trigger tile reparse for changed images. Changed images only need a relayout when they have a different size. bool hasImageDiff = !imageDiff.removed.empty(); @@ -214,10 +211,10 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar } imageManager->notifyIfMissingImageAdded(); - imageManager->setLoaded(updateParameters.spriteLoaded); + imageManager->setLoaded(updateParameters->spriteLoaded); - const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters.layers); - layerImpls = updateParameters.layers; + const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters->layers); + layerImpls = updateParameters->layers; const bool layersAddedOrRemoved = !layerDiff.added.empty() || !layerDiff.removed.empty(); // Remove render layers for removed layers. @@ -266,8 +263,8 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar } } - const SourceDifference sourceDiff = diffSources(sourceImpls, updateParameters.sources); - sourceImpls = updateParameters.sources; + const SourceDifference sourceDiff = diffSources(sourceImpls, updateParameters->sources); + sourceImpls = updateParameters->sources; // Remove render layers for removed sources. for (const auto& entry : sourceDiff.removed) { @@ -280,15 +277,14 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar renderSource->setObserver(this); renderSources.emplace(entry.first, std::move(renderSource)); } - transformState = updateParameters.transformState; + transformState = updateParameters->transformState; // Create parameters for the render tree. - auto renderTreeParameters = std::make_unique( - updateParameters.transformState, - updateParameters.mode, - updateParameters.debugOptions, - updateParameters.timePoint, - renderLight.getEvaluated()); + auto renderTreeParameters = std::make_unique(updateParameters->transformState, + updateParameters->mode, + updateParameters->debugOptions, + updateParameters->timePoint, + renderLight.getEvaluated()); std::set layerRenderItems; layersNeedPlacement.clear(); @@ -349,7 +345,7 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar filteredLayersForSource.clear(); } - renderTreeParameters->loaded = updateParameters.styleLoaded && isLoaded(); + renderTreeParameters->loaded = updateParameters->styleLoaded && isLoaded(); if (!isMapModeContinuous && !renderTreeParameters->loaded) { return nullptr; } @@ -357,14 +353,16 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar // Prepare. Update all matrices and generate data that we should upload to the GPU. for (const auto& entry : renderSources) { if (entry.second->isEnabled()) { - entry.second->prepare({renderTreeParameters->transformParams, updateParameters.debugOptions, *imageManager}); + entry.second->prepare( + {renderTreeParameters->transformParams, updateParameters->debugOptions, *imageManager}); } } auto opaquePassCutOffEstimation = layerRenderItems.size(); for (auto& renderItem : layerRenderItems) { RenderLayer& renderLayer = renderItem.layer; - renderLayer.prepare({renderItem.source, *imageManager, *patternAtlas, *lineAtlas, updateParameters.transformState}); + renderLayer.prepare( + {renderItem.source, *imageManager, *patternAtlas, *lineAtlas, updateParameters->transformState}); if (renderLayer.needsPlacement()) { layersNeedPlacement.emplace_back(renderLayer); } @@ -377,10 +375,11 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar } // Symbol placement. bool symbolBucketsChanged = false; + auto longitude = updateParameters->transformState.getLatLng().longitude(); if (isMapModeContinuous) { bool symbolBucketsAdded = false; for (auto it = layersNeedPlacement.crbegin(); it != layersNeedPlacement.crend(); ++it) { - auto result = crossTileSymbolIndex.addLayer(*it, updateParameters.transformState.getLatLng().longitude()); + auto result = crossTileSymbolIndex.addLayer(*it, longitude); symbolBucketsAdded = symbolBucketsAdded || (result & CrossTileSymbolIndex::AddLayerResult::BucketsAdded); symbolBucketsChanged = symbolBucketsChanged || (result != CrossTileSymbolIndex::AddLayerResult::NoChanges); } @@ -391,24 +390,20 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar optional maximumPlacementUpdatePeriod; if (symbolBucketsAdded) maximumPlacementUpdatePeriod = optional(Milliseconds(30)); renderTreeParameters->placementChanged = !placementController.placementIsRecent( - updateParameters.timePoint, updateParameters.transformState.getZoom(), maximumPlacementUpdatePeriod); + updateParameters->timePoint, updateParameters->transformState.getZoom(), maximumPlacementUpdatePeriod); symbolBucketsChanged |= renderTreeParameters->placementChanged; std::set usedSymbolLayers; if (renderTreeParameters->placementChanged) { - Mutable placement = makeMutable(updateParameters.transformState, - updateParameters.mode, - updateParameters.transitionOptions, - updateParameters.crossSourceCollisions, - placementController.getPlacement()); + Mutable placement = makeMutable(updateParameters, placementController.getPlacement()); for (auto it = layersNeedPlacement.crbegin(); it != layersNeedPlacement.crend(); ++it) { const RenderLayer& layer = *it; usedSymbolLayers.insert(layer.getID()); - placement->placeLayer(layer, renderTreeParameters->transformParams.projMatrix, updateParameters.debugOptions & MapDebugOptions::Collision); + placement->placeLayer(layer); } - placement->commit(updateParameters.timePoint, updateParameters.transformState.getZoom()); + placement->commit(); crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers); for (const auto& entry : renderSources) { entry.second->updateFadingTiles(); @@ -418,24 +413,19 @@ std::unique_ptr RenderOrchestrator::createRenderTree(const UpdatePar placementController.setPlacementStale(); } renderTreeParameters->symbolFadeChange = - placementController.getPlacement()->symbolFadeChange(updateParameters.timePoint); - renderTreeParameters->needsRepaint = hasTransitions(updateParameters.timePoint); + placementController.getPlacement()->symbolFadeChange(updateParameters->timePoint); + renderTreeParameters->needsRepaint = hasTransitions(updateParameters->timePoint); } else { crossTileSymbolIndex.reset(); renderTreeParameters->placementChanged = symbolBucketsChanged = !layersNeedPlacement.empty(); if (renderTreeParameters->placementChanged) { - Mutable placement = makeMutable(updateParameters.transformState, - updateParameters.mode, - updateParameters.transitionOptions, - updateParameters.crossSourceCollisions); + Mutable placement = makeMutable(updateParameters); for (auto it = layersNeedPlacement.crbegin(); it != layersNeedPlacement.crend(); ++it) { const RenderLayer& layer = *it; - crossTileSymbolIndex.addLayer(layer, updateParameters.transformState.getLatLng().longitude()); - placement->placeLayer(layer, - renderTreeParameters->transformParams.projMatrix, - updateParameters.debugOptions & MapDebugOptions::Collision); + crossTileSymbolIndex.addLayer(layer, longitude); + placement->placeLayer(layer); } - placement->commit(updateParameters.timePoint, updateParameters.transformState.getZoom()); + placement->commit(); placementController.setPlacement(std::move(placement)); } renderTreeParameters->symbolFadeChange = 1.0f; diff --git a/src/mbgl/renderer/render_orchestrator.hpp b/src/mbgl/renderer/render_orchestrator.hpp index c2b44c27928..49835387497 100644 --- a/src/mbgl/renderer/render_orchestrator.hpp +++ b/src/mbgl/renderer/render_orchestrator.hpp @@ -52,7 +52,7 @@ class RenderOrchestrator final : public GlyphManagerObserver, // TODO: Introduce RenderOrchestratorObserver. void setObserver(RendererObserver*); - std::unique_ptr createRenderTree(const UpdateParameters&); + std::unique_ptr createRenderTree(const std::shared_ptr&); std::vector queryRenderedFeatures(const ScreenLineString&, const RenderedQueryOptions&) const; std::vector querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const; diff --git a/src/mbgl/renderer/renderer.cpp b/src/mbgl/renderer/renderer.cpp index a74a21030e6..5afbbbd47ee 100644 --- a/src/mbgl/renderer/renderer.cpp +++ b/src/mbgl/renderer/renderer.cpp @@ -25,7 +25,8 @@ void Renderer::setObserver(RendererObserver* observer) { impl->orchestrator.setObserver(observer); } -void Renderer::render(const UpdateParameters& updateParameters) { +void Renderer::render(const std::shared_ptr& updateParameters) { + assert(updateParameters); if (auto renderTree = impl->orchestrator.createRenderTree(updateParameters)) { renderTree->prepare(); impl->render(*renderTree); diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp index 50567ab8954..e75bdf8ba9e 100644 --- a/src/mbgl/text/collision_index.cpp +++ b/src/mbgl/text/collision_index.cpp @@ -26,7 +26,7 @@ static const float viewportPaddingDefault = 100; // Viewport padding must be much larger for static tiles to avoid clipped labels. static const float viewportPaddingForStaticTiles = 1024; -CollisionIndex::CollisionIndex(const TransformState& transformState_, MapMode& mapMode) +CollisionIndex::CollisionIndex(const TransformState& transformState_, MapMode mapMode) : transformState(transformState_), viewportPadding(mapMode == MapMode::Tile ? viewportPaddingForStaticTiles : viewportPaddingDefault), collisionGrid(transformState.getSize().width + 2 * viewportPadding, diff --git a/src/mbgl/text/collision_index.hpp b/src/mbgl/text/collision_index.hpp index d1a77b5492a..b9f3e9d88a0 100644 --- a/src/mbgl/text/collision_index.hpp +++ b/src/mbgl/text/collision_index.hpp @@ -20,7 +20,7 @@ class CollisionIndex { public: using CollisionGrid = GridIndex; - explicit CollisionIndex(const TransformState&, MapMode&); + explicit CollisionIndex(const TransformState&, MapMode); bool featureIntersectsTileBorders(const CollisionFeature& feature, Point shift, const mat4& posMatrix, diff --git a/src/mbgl/text/cross_tile_symbol_index.cpp b/src/mbgl/text/cross_tile_symbol_index.cpp index 55ab2cc3c53..f806c652e1b 100644 --- a/src/mbgl/text/cross_tile_symbol_index.cpp +++ b/src/mbgl/text/cross_tile_symbol_index.cpp @@ -6,15 +6,20 @@ namespace mbgl { - -TileLayerIndex::TileLayerIndex(OverscaledTileID coord_, std::vector& symbolInstances, uint32_t bucketInstanceId_) - : coord(coord_), bucketInstanceId(bucketInstanceId_) { - for (SymbolInstance& symbolInstance : symbolInstances) { - indexedSymbolInstances[symbolInstance.key].emplace_back(symbolInstance.crossTileID, getScaledCoordinates(symbolInstance, coord)); - } +TileLayerIndex::TileLayerIndex(OverscaledTileID coord_, + std::vector& symbolInstances, + uint32_t bucketInstanceId_, + std::string bucketLeaderId_) + : coord(coord_), bucketInstanceId(bucketInstanceId_), bucketLeaderId(std::move(bucketLeaderId_)) { + for (SymbolInstance& symbolInstance : symbolInstances) { + if (symbolInstance.crossTileID == SymbolInstance::invalidCrossTileID()) continue; + indexedSymbolInstances[symbolInstance.key].emplace_back(symbolInstance.crossTileID, + getScaledCoordinates(symbolInstance, coord)); } +} -Point TileLayerIndex::getScaledCoordinates(SymbolInstance& symbolInstance, const OverscaledTileID& childTileCoord) { +Point TileLayerIndex::getScaledCoordinates(SymbolInstance& symbolInstance, + const OverscaledTileID& childTileCoord) const { // Round anchor positions to roughly 4 pixel grid const double roundingFactor = 512.0 / util::EXTENT / 2.0; const double scale = roundingFactor / std::pow(2, childTileCoord.canonical.z - coord.canonical.z); @@ -24,9 +29,14 @@ Point TileLayerIndex::getScaledCoordinates(SymbolInstance& symbolInstan }; } -void TileLayerIndex::findMatches(std::vector& symbolInstances, const OverscaledTileID& newCoord, std::set& zoomCrossTileIDs) { +void TileLayerIndex::findMatches(SymbolBucket& bucket, + const OverscaledTileID& newCoord, + std::set& zoomCrossTileIDs) const { + auto& symbolInstances = bucket.symbolInstances; float tolerance = coord.canonical.z < newCoord.canonical.z ? 1 : std::pow(2, coord.canonical.z - newCoord.canonical.z); + if (bucket.bucketLeaderID != bucketLeaderId) return; + for (auto& symbolInstance : symbolInstances) { if (symbolInstance.crossTileID) { // already has a match, skip @@ -41,7 +51,7 @@ void TileLayerIndex::findMatches(std::vector& symbolInstances, c auto scaledSymbolCoord = getScaledCoordinates(symbolInstance, newCoord); - for (IndexedSymbolInstance& thisTileSymbol: it->second) { + for (const IndexedSymbolInstance& thisTileSymbol : it->second) { // Return any symbol with the same keys whose coordinates are within 1 // grid unit. (with a 4px grid, this covers a 12px by 12px area) if (std::abs(thisTileSymbol.coord.x - scaledSymbolCoord.x) <= tolerance && @@ -58,7 +68,7 @@ void TileLayerIndex::findMatches(std::vector& symbolInstances, c } } -CrossTileSymbolLayerIndex::CrossTileSymbolLayerIndex() = default; +CrossTileSymbolLayerIndex::CrossTileSymbolLayerIndex(uint32_t& maxCrossTileID_) : maxCrossTileID(maxCrossTileID_) {} /* * Sometimes when a user pans across the antimeridian the longitude value gets wrapped. @@ -86,11 +96,29 @@ void CrossTileSymbolLayerIndex::handleWrapJump(float newLng) { lng = newLng; } -bool CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& tileID, SymbolBucket& bucket, uint32_t& maxCrossTileID) { - const auto& thisZoomIndexes = indexes[tileID.overscaledZ]; +namespace { + +bool isInVewport(const mat4& posMatrix, const Point& point) { + vec4 p = {{point.x, point.y, 0, 1}}; + matrix::transformMat4(p, p, posMatrix); + + // buffer covers area of the next zoom level (current zoom - 1 covered area). + constexpr float buffer = 1.0f; + constexpr float edge = 1.0f + buffer; + float x = p[0] / p[3]; + float y = p[1] / p[3]; + return (x > -edge && y > -edge && x < edge && y < edge); +} + +} // namespace + +bool CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& tileID, + const mat4& tileMatrix, + SymbolBucket& bucket) { + auto& thisZoomIndexes = indexes[tileID.overscaledZ]; auto previousIndex = thisZoomIndexes.find(tileID); if (previousIndex != thisZoomIndexes.end()) { - if (previousIndex->second.bucketInstanceId == bucket.bucketInstanceId) { + if (previousIndex->second.bucketInstanceId == bucket.bucketInstanceId && !bucket.hasUninitializedSymbols) { return false; } else { // We're replacing this bucket with an updated version @@ -101,24 +129,41 @@ bool CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& tileID, Symbol } } - for (auto& symbolInstance: bucket.symbolInstances) { - symbolInstance.crossTileID = 0; + bucket.hasUninitializedSymbols = false; + + if (tileID.overscaleFactor() > 1u) { + // For overscaled tiles the viewport might be showing only a small part of the tile, + // so we filter out the off-screen symbols to improve the performance. + for (auto& symbolInstance : bucket.symbolInstances) { + if (isInVewport(tileMatrix, symbolInstance.anchor.point)) { + symbolInstance.crossTileID = 0u; + } else { + symbolInstance.crossTileID = SymbolInstance::invalidCrossTileID(); + bucket.hasUninitializedSymbols = true; + } + } + } else { + for (auto& symbolInstance : bucket.symbolInstances) { + symbolInstance.crossTileID = 0u; + } } + auto& thisZoomUsedCrossTileIDs = usedCrossTileIDs[tileID.overscaledZ]; + for (auto& it : indexes) { auto zoom = it.first; - auto zoomIndexes = it.second; + const auto& zoomIndexes = it.second; if (zoom > tileID.overscaledZ) { for (auto& childIndex : zoomIndexes) { if (childIndex.second.coord.isChildOf(tileID)) { - childIndex.second.findMatches(bucket.symbolInstances, tileID, usedCrossTileIDs[tileID.overscaledZ]); + childIndex.second.findMatches(bucket, tileID, thisZoomUsedCrossTileIDs); } } } else { auto parentTileID = tileID.scaledTo(zoom); auto parentIndex = zoomIndexes.find(parentTileID); if (parentIndex != zoomIndexes.end()) { - parentIndex->second.findMatches(bucket.symbolInstances, tileID, usedCrossTileIDs[tileID.overscaledZ]); + parentIndex->second.findMatches(bucket, tileID, thisZoomUsedCrossTileIDs); } } } @@ -127,13 +172,15 @@ bool CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& tileID, Symbol if (!symbolInstance.crossTileID) { // symbol did not match any known symbol, assign a new id symbolInstance.crossTileID = ++maxCrossTileID; - usedCrossTileIDs[tileID.overscaledZ].insert(symbolInstance.crossTileID); + thisZoomUsedCrossTileIDs.insert(symbolInstance.crossTileID); } } - - indexes[tileID.overscaledZ].erase(tileID); - indexes[tileID.overscaledZ].emplace(tileID, TileLayerIndex(tileID, bucket.symbolInstances, bucket.bucketInstanceId)); + thisZoomIndexes.erase(tileID); + thisZoomIndexes.emplace( + std::piecewise_construct, + std::forward_as_tuple(tileID), + std::forward_as_tuple(tileID, bucket.symbolInstances, bucket.bucketInstanceId, bucket.bucketLeaderID)); return true; } @@ -164,7 +211,15 @@ bool CrossTileSymbolLayerIndex::removeStaleBuckets(const std::unordered_set AddLayerResult { - auto& layerIndex = layerIndexes[layer.getID()]; + auto found = layerIndexes.find(layer.getID()); + if (found == layerIndexes.end()) { + found = layerIndexes + .emplace(std::piecewise_construct, + std::forward_as_tuple(layer.getID()), + std::forward_as_tuple(maxCrossTileID)) + .first; + } + auto& layerIndex = found->second; AddLayerResult result = AddLayerResult::NoChanges; std::unordered_set currentBucketIDs; @@ -174,7 +229,7 @@ auto CrossTileSymbolIndex::addLayer(const RenderLayer& layer, float lng) -> AddL for (const auto& item : layer.getPlacementData()) { const RenderTile& renderTile = item.tile; Bucket& bucket = item.bucket; - auto pair = bucket.registerAtCrossTileIndex(layerIndex, renderTile.getOverscaledTileID(), maxCrossTileID); + auto pair = bucket.registerAtCrossTileIndex(layerIndex, renderTile); assert(pair.first != 0u); if (pair.second) result |= AddLayerResult::BucketsAdded; currentBucketIDs.insert(pair.first); diff --git a/src/mbgl/text/cross_tile_symbol_index.hpp b/src/mbgl/text/cross_tile_symbol_index.hpp index 4e32698b3ea..8dfdb8466f9 100644 --- a/src/mbgl/text/cross_tile_symbol_index.hpp +++ b/src/mbgl/text/cross_tile_symbol_index.hpp @@ -2,8 +2,9 @@ #include #include -#include #include +#include +#include #include #include @@ -31,20 +32,24 @@ class IndexedSymbolInstance { class TileLayerIndex { public: - TileLayerIndex(OverscaledTileID coord, std::vector&, uint32_t bucketInstanceId); + TileLayerIndex(OverscaledTileID coord, + std::vector&, + uint32_t bucketInstanceId, + std::string bucketLeaderId); + + Point getScaledCoordinates(SymbolInstance&, const OverscaledTileID&) const; + void findMatches(SymbolBucket&, const OverscaledTileID&, std::set&) const; - Point getScaledCoordinates(SymbolInstance&, const OverscaledTileID&); - void findMatches(std::vector&, const OverscaledTileID&, std::set&); - OverscaledTileID coord; uint32_t bucketInstanceId; + std::string bucketLeaderId; std::map> indexedSymbolInstances; }; class CrossTileSymbolLayerIndex { public: - CrossTileSymbolLayerIndex(); - bool addBucket(const OverscaledTileID&, SymbolBucket&, uint32_t& maxCrossTileID); + CrossTileSymbolLayerIndex(uint32_t& maxCrossTileID); + bool addBucket(const OverscaledTileID&, const mat4& tileMatrix, SymbolBucket&); bool removeStaleBuckets(const std::unordered_set& currentIDs); void handleWrapJump(float newLng); private: @@ -53,6 +58,7 @@ class CrossTileSymbolLayerIndex { std::map> indexes; std::map> usedCrossTileIDs; float lng = 0; + uint32_t& maxCrossTileID; }; class CrossTileSymbolIndex { diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index d6a78082fa4..32edf679e83 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -1,11 +1,12 @@ #include #include +#include +#include #include #include +#include #include -#include -#include #include #include @@ -60,8 +61,7 @@ const CollisionGroups::CollisionGroup& CollisionGroups::get(const std::string& s // PlacementController implemenation -PlacementController::PlacementController() - : placement(makeMutable(TransformState{}, MapMode::Static, style::TransitionOptions{}, true, nullopt)) {} +PlacementController::PlacementController() : placement(makeMutable()) {} void PlacementController::setPlacement(Immutable placement_) { placement = std::move(placement_); @@ -90,33 +90,30 @@ bool PlacementController::hasTransitions(TimePoint now) const { // Placement implementation -Placement::Placement(const TransformState& state_, - MapMode mapMode_, - style::TransitionOptions transitionOptions_, - const bool crossSourceCollisions, +Placement::Placement(std::shared_ptr updateParameters_, optional> prevPlacement_) - : collisionIndex(state_, mapMode_), - mapMode(mapMode_), - transitionOptions(std::move(transitionOptions_)), - placementZoom(state_.getZoom()), - collisionGroups(crossSourceCollisions), - prevPlacement(std::move(prevPlacement_)) { + : updateParameters(std::move(updateParameters_)), + collisionIndex(updateParameters->transformState, updateParameters->mode), + mapMode(updateParameters->mode), + transitionOptions(updateParameters->transitionOptions), + commitTime(updateParameters->timePoint), + placementZoom(updateParameters->transformState.getZoom()), + collisionGroups(updateParameters->crossSourceCollisions), + prevPlacement(std::move(prevPlacement_)), + showCollisionBoxes(updateParameters->debugOptions & MapDebugOptions::Collision) { assert(prevPlacement || mapMode != MapMode::Continuous); if (prevPlacement) { prevPlacement->get()->prevPlacement = nullopt; // Only hold on to one placement back } } -void Placement::placeLayer(const RenderLayer& layer, const mat4& projMatrix, bool showCollisionBoxes) { +Placement::Placement() : collisionIndex({}, MapMode::Static), collisionGroups(true) {} + +void Placement::placeLayer(const RenderLayer& layer) { std::set seenCrossTileIDs; for (const auto& item : layer.getPlacementData()) { Bucket& bucket = item.bucket; - BucketPlacementParameters params{ - item.tile, - projMatrix, - layer.baseImpl->source, - item.featureIndex, - showCollisionBoxes}; + BucketPlacementParameters params{item.tile, layer.baseImpl->source, item.featureIndex}; bucket.place(*this, params, seenCrossTileIDs); } } @@ -145,34 +142,37 @@ Point calculateVariableLayoutOffset(style::SymbolAnchorType anchor, void Placement::placeBucket(const SymbolBucket& bucket, const BucketPlacementParameters& params, std::set& seenCrossTileIDs) { + assert(updateParameters); const auto& layout = *bucket.layout; const auto& renderTile = params.tile; const auto& state = collisionIndex.getTransformState(); - const float pixelsToTileUnits = renderTile.id.pixelsToTileUnits(1, state.getZoom()); + const float pixelsToTileUnits = renderTile.id.pixelsToTileUnits(1, placementZoom); const OverscaledTileID& overscaledID = renderTile.getOverscaledTileID(); - const float scale = std::pow(2, state.getZoom() - overscaledID.overscaledZ); + const float scale = std::pow(2, placementZoom - overscaledID.overscaledZ); const float pixelRatio = (util::tileSize * overscaledID.overscaleFactor()) / util::EXTENT; - mat4 posMatrix; - state.matrixFor(posMatrix, renderTile.id); - matrix::multiply(posMatrix, params.projMatrix, posMatrix); + const bool rotateTextWithMap = layout.get() == style::AlignmentType::Map; + const bool pitchTextWithMap = layout.get() == style::AlignmentType::Map; + + const bool rotateIconWithMap = layout.get() == style::AlignmentType::Map; + const bool pitchIconWithMap = layout.get() == style::AlignmentType::Map; + + const mat4& posMatrix = renderTile.matrix; mat4 textLabelPlaneMatrix = - getLabelPlaneMatrix(posMatrix, - layout.get() == style::AlignmentType::Map, - layout.get() == style::AlignmentType::Map, - state, - pixelsToTileUnits); - - mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, - layout.get() == style::AlignmentType::Map, - layout.get() == style::AlignmentType::Map, - state, - pixelsToTileUnits); + getLabelPlaneMatrix(posMatrix, pitchTextWithMap, rotateTextWithMap, state, pixelsToTileUnits); + + mat4 iconLabelPlaneMatrix; + if (rotateTextWithMap == rotateIconWithMap && pitchTextWithMap == pitchIconWithMap) { + iconLabelPlaneMatrix = textLabelPlaneMatrix; + } else { + iconLabelPlaneMatrix = + getLabelPlaneMatrix(posMatrix, pitchIconWithMap, rotateIconWithMap, state, pixelsToTileUnits); + } const auto& collisionGroup = collisionGroups.get(params.sourceId); - auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom()); - auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom()); + auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(placementZoom); + auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(placementZoom); optional tileBorders; optional avoidEdges; @@ -202,16 +202,17 @@ void Placement::placeBucket(const SymbolBucket& bucket, const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !(bucket.hasIconData() || bucket.hasSdfIconData()) || layout.get()); const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || layout.get()); std::vector variableTextAnchors = layout.get(); - const bool rotateWithMap = layout.get() == style::AlignmentType::Map; - const bool pitchWithMap = layout.get() == style::AlignmentType::Map; + const bool hasIconTextFit = layout.get() != style::IconTextFitType::None; const bool zOrderByViewportY = layout.get() == style::SymbolZOrderType::ViewportY; std::vector textBoxes; std::vector iconBoxes; - auto placeSymbol = [&] (const SymbolInstance& symbolInstance) { - if (seenCrossTileIDs.count(symbolInstance.crossTileID) != 0u) return; + auto placeSymbol = [&](const SymbolInstance& symbolInstance) { + if (symbolInstance.crossTileID == SymbolInstance::invalidCrossTileID() || + seenCrossTileIDs.count(symbolInstance.crossTileID) != 0u) + return; if (renderTile.holdForFade()) { // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't @@ -275,9 +276,9 @@ void Placement::placeBucket(const SymbolBucket& bucket, placedSymbol, scale, fontSize, - layout.get(), - pitchWithMap, - params.showCollisionBoxes, + textAllowOverlap, + pitchTextWithMap, + showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes); @@ -354,8 +355,8 @@ void Placement::placeBucket(const SymbolBucket& bucket, height, symbolInstance.variableTextOffset, textBoxScale, - rotateWithMap, - pitchWithMap, + rotateTextWithMap, + pitchTextWithMap, state.getBearing()); textBoxes.clear(); @@ -374,27 +375,28 @@ void Placement::placeBucket(const SymbolBucket& bucket, scale, fontSize, allowOverlap, - pitchWithMap, - params.showCollisionBoxes, + pitchTextWithMap, + showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes); if (doVariableIconPlacement) { - auto placedIconFeature = collisionIndex.placeFeature(iconCollisionFeature, - shift, - posMatrix, - iconLabelPlaneMatrix, - pixelRatio, - placedSymbol, - scale, - fontSize, - iconAllowOverlap, - pitchWithMap, - params.showCollisionBoxes, - avoidEdges, - collisionGroup.second, - iconBoxes); + auto placedIconFeature = + collisionIndex.placeFeature(iconCollisionFeature, + shift, + posMatrix, + iconLabelPlaneMatrix, + pixelRatio, + placedSymbol, + scale, + fontSize, + iconAllowOverlap, + pitchTextWithMap, // TODO: shall it be pitchIconWithMap? + showCollisionBoxes, + avoidEdges, + collisionGroup.second, + iconBoxes); iconBoxes.clear(); if (!placedIconFeature.first) continue; } @@ -487,9 +489,9 @@ void Placement::placeBucket(const SymbolBucket& bucket, placedSymbol, scale, fontSize, - layout.get(), - pitchWithMap, - params.showCollisionBoxes, + iconAllowOverlap, + pitchTextWithMap, + showCollisionBoxes, avoidEdges, collisionGroup.second, iconBoxes); @@ -588,8 +590,8 @@ void Placement::placeBucket(const SymbolBucket& bucket, height, symbol.variableTextOffset, symbol.textBoxScale, - rotateWithMap, - pitchWithMap, + rotateTextWithMap, + pitchTextWithMap, state.getBearing()); } bool result = collisionIndex.featureIntersectsTileBorders( @@ -622,8 +624,7 @@ void Placement::placeBucket(const SymbolBucket& bucket, std::forward_as_tuple(bucket.bucketInstanceId, params.featureIndex, overscaledID)); } -void Placement::commit(TimePoint now, const double zoom) { - commitTime = now; +void Placement::commit() { if (!getPrevPlacement()) { assert(mapMode != MapMode::Continuous); fadeStartTime = commitTime; @@ -638,7 +639,7 @@ void Placement::commit(TimePoint now, const double zoom) { bool placementChanged = false; - prevZoomAdjustment = getPrevPlacement()->zoomAdjustment(zoom); + prevZoomAdjustment = getPrevPlacement()->zoomAdjustment(placementZoom); float increment = getPrevPlacement()->symbolFadeChange(commitTime); // add the opacities from the current placement, and copy their current values from the previous placement diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index 4dd9011da46..46d560b6735 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -12,6 +12,7 @@ namespace mbgl { class SymbolBucket; class SymbolInstance; +class UpdateParameters; enum class PlacedSymbolOrientation : bool; class OpacityState { @@ -88,10 +89,8 @@ class CollisionGroups { class BucketPlacementParameters { public: const RenderTile& tile; - const mat4& projMatrix; std::string sourceId; std::shared_ptr featureIndex; - bool showCollisionBoxes; }; class Placement; @@ -112,13 +111,11 @@ class PlacementController { class Placement { public: - Placement(const TransformState&, - MapMode, - style::TransitionOptions, - const bool crossSourceCollisions, - optional> prevPlacement = nullopt); - void placeLayer(const RenderLayer&, const mat4&, bool showCollisionBoxes); - void commit(TimePoint, const double zoom); + Placement(std::shared_ptr, optional> prevPlacement = nullopt); + Placement(); + + void placeLayer(const RenderLayer&); + void commit(); void updateLayerBuckets(const RenderLayer&, const TransformState&, bool updateOpacities) const; float symbolFadeChange(TimePoint now) const; bool hasTransitions(TimePoint now) const; @@ -131,6 +128,7 @@ class Placement { float zoomAdjustment(const float zoom) const; const RetainedQueryData& getQueryData(uint32_t bucketInstanceId) const; + private: friend SymbolBucket; void placeBucket(const SymbolBucket&, const BucketPlacementParameters&, std::set& seenCrossTileIDs); @@ -144,15 +142,16 @@ class Placement { void markUsedOrientation(SymbolBucket&, style::TextWritingModeType, const SymbolInstance&) const; const Placement* getPrevPlacement() const { return prevPlacement ? prevPlacement->get() : nullptr; } + std::shared_ptr updateParameters; CollisionIndex collisionIndex; - MapMode mapMode; + MapMode mapMode = MapMode::Static; style::TransitionOptions transitionOptions; TimePoint fadeStartTime; TimePoint commitTime; - float placementZoom; - float prevZoomAdjustment = 0; + float placementZoom = 0.0f; + float prevZoomAdjustment = 0.0f; std::unordered_map placements; std::unordered_map opacities; @@ -162,6 +161,7 @@ class Placement { std::unordered_map retainedQueryData; CollisionGroups collisionGroups; mutable optional> prevPlacement; + bool showCollisionBoxes = false; // Used for debug purposes. std::unordered_map> collisionCircles; diff --git a/test/text/cross_tile_symbol_index.test.cpp b/test/text/cross_tile_symbol_index.test.cpp index fe1375c66db..6119392fd23 100644 --- a/test/text/cross_tile_symbol_index.test.cpp +++ b/test/text/cross_tile_symbol_index.test.cpp @@ -1,6 +1,7 @@ -#include +#include #include #include +#include using namespace mbgl; @@ -36,7 +37,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { uint32_t maxCrossTileID = 0; uint32_t maxBucketInstanceId = 0; - CrossTileSymbolLayerIndex index; + CrossTileSymbolLayerIndex index(maxCrossTileID); Immutable layout = makeMutable(); @@ -62,7 +63,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { {}, false /*iconsInText*/}; mainBucket.bucketInstanceId = ++maxBucketInstanceId; - index.addBucket(mainID, mainBucket, maxCrossTileID); + index.addBucket(mainID, mat4{}, mainBucket); // Assigned new IDs ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 1u); @@ -89,7 +90,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { {}, false /*iconsInText*/}; childBucket.bucketInstanceId = ++maxBucketInstanceId; - index.addBucket(childID, childBucket, maxCrossTileID); + index.addBucket(childID, mat4{}, childBucket); // matched parent tile ASSERT_EQ(childBucket.symbolInstances.at(0).crossTileID, 1u); @@ -117,7 +118,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { {}, false /*iconsInText*/}; parentBucket.bucketInstanceId = ++maxBucketInstanceId; - index.addBucket(parentID, parentBucket, maxCrossTileID); + index.addBucket(parentID, mat4{}, parentBucket); // matched child tile ASSERT_EQ(parentBucket.symbolInstances.at(0).crossTileID, 1u); @@ -145,7 +146,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { {}, false /*iconsInText*/}; grandchildBucket.bucketInstanceId = ++maxBucketInstanceId; - index.addBucket(grandchildID, grandchildBucket, maxCrossTileID); + index.addBucket(grandchildID, mat4{}, grandchildBucket); // Matches the symbol in `mainBucket` ASSERT_EQ(grandchildBucket.symbolInstances.at(0).crossTileID, 1u); @@ -158,7 +159,7 @@ TEST(CrossTileSymbolLayerIndex, resetIDs) { uint32_t maxCrossTileID = 0; uint32_t maxBucketInstanceId = 0; - CrossTileSymbolLayerIndex index; + CrossTileSymbolLayerIndex index(maxCrossTileID); Immutable layout = makeMutable(); @@ -203,7 +204,7 @@ TEST(CrossTileSymbolLayerIndex, resetIDs) { childBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns a new id - index.addBucket(mainID, mainBucket, maxCrossTileID); + index.addBucket(mainID, mat4{}, mainBucket); ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 1u); // removes the tile @@ -211,18 +212,18 @@ TEST(CrossTileSymbolLayerIndex, resetIDs) { index.removeStaleBuckets(currentIDs); // assigns a new id - index.addBucket(childID, childBucket, maxCrossTileID); + index.addBucket(childID, mat4{}, childBucket); ASSERT_EQ(childBucket.symbolInstances.at(0).crossTileID, 2u); // overwrites the old id to match the already-added tile - index.addBucket(mainID, mainBucket, maxCrossTileID); + index.addBucket(mainID, mat4{}, mainBucket); ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 2u); } TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) { uint32_t maxCrossTileID = 0; uint32_t maxBucketInstanceId = 0; - CrossTileSymbolLayerIndex index; + CrossTileSymbolLayerIndex index(maxCrossTileID); Immutable layout = makeMutable(); @@ -270,12 +271,12 @@ TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) { childBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns new ids - index.addBucket(mainID, mainBucket, maxCrossTileID); + index.addBucket(mainID, mat4{}, mainBucket); ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 1u); ASSERT_EQ(mainBucket.symbolInstances.at(1).crossTileID, 2u); // copies parent ids without duplicate ids in this tile - index.addBucket(childID, childBucket, maxCrossTileID); + index.addBucket(childID, mat4{}, childBucket); ASSERT_EQ(childBucket.symbolInstances.at(0).crossTileID, 1u); // A' copies from A ASSERT_EQ(childBucket.symbolInstances.at(1).crossTileID, 2u); // B' copies from B ASSERT_EQ(childBucket.symbolInstances.at(2).crossTileID, 3u); // C' gets new ID @@ -284,7 +285,7 @@ TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) { TEST(CrossTileSymbolLayerIndex, bucketReplacement) { uint32_t maxCrossTileID = 0; uint32_t maxBucketInstanceId = 0; - CrossTileSymbolLayerIndex index; + CrossTileSymbolLayerIndex index(maxCrossTileID); Immutable layout = makeMutable(); @@ -331,14 +332,66 @@ TEST(CrossTileSymbolLayerIndex, bucketReplacement) { secondBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns new ids - index.addBucket(tileID, firstBucket, maxCrossTileID); + index.addBucket(tileID, mat4{}, firstBucket); ASSERT_EQ(firstBucket.symbolInstances.at(0).crossTileID, 1u); ASSERT_EQ(firstBucket.symbolInstances.at(1).crossTileID, 2u); // copies parent ids without duplicate ids in this tile - index.addBucket(tileID, secondBucket, maxCrossTileID); + index.addBucket(tileID, mat4{}, secondBucket); ASSERT_EQ(secondBucket.symbolInstances.at(0).crossTileID, 1u); // A' copies from A ASSERT_EQ(secondBucket.symbolInstances.at(1).crossTileID, 2u); // B' copies from B ASSERT_EQ(secondBucket.symbolInstances.at(2).crossTileID, 3u); // C' gets new ID } +namespace { + +void populatePosMatrix(mat4& posMatrix, const OverscaledTileID& tileId, double lat, double lon, double zoom) { + TransformState transformState; + transformState.setSize({512, 512}); + transformState.setLatLngZoom(LatLng(lat, lon), zoom); + transformState.matrixFor(posMatrix, tileId.toUnwrapped()); + matrix::multiply(posMatrix, transformState.getProjectionMatrix(), posMatrix); +} + +} // namespace + +TEST(CrossTileSymbolLayerIndex, offscreenSymbols) { + uint32_t maxCrossTileID = 0; + CrossTileSymbolLayerIndex index(maxCrossTileID); + + Immutable layout = + makeMutable(); + bool iconsNeedLinear = false; + bool sortFeaturesByY = false; + std::string bucketLeaderID = "test"; + + OverscaledTileID tileId(7, 0, 6, 18, 24); + std::vector mainInstances; + mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Washington")); + mainInstances.push_back(makeSymbolInstance(2000, 2000, u"Richmond")); + SymbolBucket symbolBucket{layout, + {}, + 16.0f, + 1.0f, + 0, + iconsNeedLinear, + sortFeaturesByY, + bucketLeaderID, + std::move(mainInstances), + 1.0f, + false, + {}, + false /*iconsInText*/}; + mat4 posMatrix; + populatePosMatrix(posMatrix, tileId, 60.0, 25.0, 7.0); + index.addBucket(tileId, posMatrix, symbolBucket); + + EXPECT_EQ(symbolBucket.symbolInstances.at(0).crossTileID, SymbolInstance::invalidCrossTileID()); + EXPECT_EQ(symbolBucket.symbolInstances.at(1).crossTileID, SymbolInstance::invalidCrossTileID()); + + populatePosMatrix(posMatrix, tileId, 39.0, -76.0, 7.0); + index.addBucket(tileId, posMatrix, symbolBucket); + + EXPECT_EQ(symbolBucket.symbolInstances.at(0).crossTileID, 1u); + EXPECT_EQ(symbolBucket.symbolInstances.at(1).crossTileID, 2u); +} \ No newline at end of file