diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 032b4558ce8..aa138df6628 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -339,6 +339,10 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { } else { std::sort(sortedTiles.begin(), sortedTiles.end(), [](const auto& a, const auto& b) { return a.get().id < b.get().id; }); + // Don't render non-symbol layers for tiles that we're only holding on to for symbol fading + sortedTiles.erase(std::remove_if(sortedTiles.begin(), sortedTiles.end(), + [](const auto& tile) { return tile.get().tile.holdForFade(); }), + sortedTiles.end()); } std::vector> sortedTilesForInsertion; @@ -389,6 +393,8 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { } placement->setRecent(parameters.timePoint); + + updateFadingTiles(); } else { placement->setStale(); } @@ -754,10 +760,27 @@ bool Renderer::Impl::hasTransitions(TimePoint timePoint) const { if (placement->hasTransitions(timePoint)) { return true; } + + if (fadingTiles) { + return true; + } return false; } +void Renderer::Impl::updateFadingTiles() { + fadingTiles = false; + for (auto& source : renderSources) { + for (auto& renderTile : source.second->getRenderTiles()) { + Tile& tile = renderTile.get().tile; + if (tile.holdForFade()) { + fadingTiles = true; + tile.performedFadePlacement(); + } + } + } +} + bool Renderer::Impl::isLoaded() const { for (const auto& entry: renderSources) { if (!entry.second->isLoaded()) { diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp index 32e2dc99f20..4f8139791c6 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -74,6 +74,8 @@ class Renderer::Impl : public GlyphManagerObserver, void onTileChanged(RenderSource&, const OverscaledTileID&) override; void onTileError(RenderSource&, const OverscaledTileID&, std::exception_ptr) override; + void updateFadingTiles(); + friend class Renderer; RendererBackend& backend; @@ -113,6 +115,7 @@ class Renderer::Impl : public GlyphManagerObserver, std::unique_ptr placement; bool contextLost = false; + bool fadingTiles = false; }; } // namespace mbgl diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index fae16c9ce62..c1566d12a57 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -148,9 +148,17 @@ void TilePyramid::update(const std::vector>& layer } return tiles.emplace(tileID, std::move(tile)).first->second.get(); }; + + std::map previouslyRenderedTiles; + for (auto& renderTile : renderTiles) { + previouslyRenderedTiles[renderTile.id] = &renderTile.tile; + } + auto renderTileFn = [&](const UnwrappedTileID& tileID, Tile& tile) { renderTiles.emplace_back(tileID, tile); rendered.emplace(tileID); + previouslyRenderedTiles.erase(tileID); // Still rendering this tile, no need for special fading logic. + tile.markRenderedIdeal(); }; renderTiles.clear(); @@ -162,6 +170,18 @@ void TilePyramid::update(const std::vector>& layer algorithm::updateRenderables(getTileFn, createTileFn, retainTileFn, renderTileFn, idealTiles, zoomRange, tileZoom); + + for (auto previouslyRenderedTile : previouslyRenderedTiles) { + Tile& tile = *previouslyRenderedTile.second; + tile.markRenderedPreviously(); + if (tile.holdForFade()) { + // Since it was rendered in the last frame, we know we have it + // Don't mark the tile "Required" to avoid triggering a new network request + retainTileFn(tile, TileNecessity::Optional); + renderTiles.emplace_back(previouslyRenderedTile.first, tile); + rendered.emplace(previouslyRenderedTile.first); + } + } if (type != SourceType::Annotations) { size_t conservativeCacheSize = diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 85f6385ce66..9284e213c29 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -46,7 +46,6 @@ void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatri std::unordered_set seenCrossTileIDs; for (RenderTile& renderTile : symbolLayer.renderTiles) { - if (!renderTile.tile.isRenderable()) { continue; } @@ -78,7 +77,7 @@ void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatri state, pixelsToTileUnits); - placeLayerBucket(symbolBucket, posMatrix, textLabelPlaneMatrix, iconLabelPlaneMatrix, scale, textPixelRatio, showCollisionBoxes, seenCrossTileIDs); + placeLayerBucket(symbolBucket, posMatrix, textLabelPlaneMatrix, iconLabelPlaneMatrix, scale, textPixelRatio, showCollisionBoxes, seenCrossTileIDs, renderTile.tile.holdForFade()); } } @@ -90,7 +89,8 @@ void Placement::placeLayerBucket( const float scale, const float textPixelRatio, const bool showCollisionBoxes, - std::unordered_set& seenCrossTileIDs) { + std::unordered_set& seenCrossTileIDs, + const bool holdingForFade) { auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom()); auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom()); @@ -101,6 +101,13 @@ void Placement::placeLayerBucket( for (auto& symbolInstance : bucket.symbolInstances) { if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) { + if (holdingForFade) { + // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't + // know yet if we have a duplicate in a parent tile that _should_ be placed. + placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false)); + continue; + } + bool placeText = false; bool placeIcon = false; bool offscreen = true; @@ -152,6 +159,12 @@ void Placement::placeLayerBucket( assert(symbolInstance.crossTileID != 0); + if (placements.find(symbolInstance.crossTileID) != placements.end()) { + // If there's a previous placement with this ID, it comes from a tile that's fading out + // Erase it so that the placement result from the non-fading tile supersedes it + placements.erase(symbolInstance.crossTileID); + } + placements.emplace(symbolInstance.crossTileID, JointPlacement(placeText, placeIcon, offscreen)); seenCrossTileIDs.insert(symbolInstance.crossTileID); } diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index c6d1f259a5c..bcc20f15a4a 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -70,7 +70,8 @@ class Placement { const float scale, const float pixelRatio, const bool showCollisionBoxes, - std::unordered_set& seenCrossTileIDs); + std::unordered_set& seenCrossTileIDs, + const bool holdingForFade); void updateBucketOpacities(SymbolBucket&, std::set&); diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 66778fa2b72..701e7cf2d6b 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -294,4 +294,25 @@ void GeometryTile::resetCrossTileIDs() { } } +bool GeometryTile::holdForFade() const { + return mode == MapMode::Continuous && + (fadeState == FadeState::NeedsFirstPlacement || fadeState == FadeState::NeedsSecondPlacement); +} + +void GeometryTile::markRenderedIdeal() { + fadeState = FadeState::Loaded; +} +void GeometryTile::markRenderedPreviously() { + if (fadeState == FadeState::Loaded) { + fadeState = FadeState::NeedsFirstPlacement; + } +} +void GeometryTile::performedFadePlacement() { + if (fadeState == FadeState::NeedsFirstPlacement) { + fadeState = FadeState::NeedsSecondPlacement; + } else if (fadeState == FadeState::NeedsSecondPlacement) { + fadeState = FadeState::CanRemove; + } +} + } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index 442d84244e3..6f871819a46 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -97,6 +97,11 @@ class GeometryTile : public Tile, public GlyphRequestor, ImageRequestor { void resetCrossTileIDs() override; + bool holdForFade() const override; + void markRenderedIdeal() override; + void markRenderedPreviously() override; + void performedFadePlacement() override; + protected: const GeometryTileData* getData() { return data.get(); @@ -130,7 +135,15 @@ class GeometryTile : public Tile, public GlyphRequestor, ImageRequestor { const MapMode mode; bool showCollisionBoxes; + + enum class FadeState { + Loaded, + NeedsFirstPlacement, + NeedsSecondPlacement, + CanRemove + }; + FadeState fadeState = FadeState::Loaded; public: optional glyphAtlasTexture; optional iconAtlasTexture; diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index b4b50b93ee0..1186b741115 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -95,6 +95,20 @@ class Tile : private util::noncopyable { return loaded && !pending; } + // "holdForFade" is used to keep tiles in the render tree after they're no longer + // ideal tiles in order to allow symbols to fade out + virtual bool holdForFade() const { + return false; + } + // Set whenever this tile is used as an ideal tile + virtual void markRenderedIdeal() {} + // Set when the tile is removed from the ideal render set but may still be held for fading + virtual void markRenderedPreviously() {} + // Placement operation performed while this tile is fading + // We hold onto a tile for two placements: fading starts with the first placement + // and will have time to finish by the second placement. + virtual void performedFadePlacement() {} + virtual void resetCrossTileIDs() {}; void dumpDebugLogs() const;