#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { using namespace style; static TileObserver nullObserver; TilePyramid::TilePyramid() : observer(&nullObserver) { } TilePyramid::~TilePyramid() = default; bool TilePyramid::isLoaded() const { for (const auto& pair : tiles) { if (!pair.second->isComplete()) { return false; } } return true; } Tile* TilePyramid::getTile(const OverscaledTileID& tileID) { auto it = tiles.find(tileID); return it == tiles.end() ? cache.get(tileID) : it->second.get(); } const Tile* TilePyramid::getRenderedTile(const UnwrappedTileID& tileID) const { auto it = renderedTiles.find(tileID); return it != renderedTiles.end() ? &it->second.get() : nullptr; } void TilePyramid::update(const std::vector>& layers, const bool needsRendering, const bool needsRelayout, const TileParameters& parameters, const style::Source::Impl& sourceImpl, const uint16_t tileSize, const Range zoomRange, optional bounds, std::function(const OverscaledTileID&)> createTile) { // If we need a relayout, abandon any cached tiles; they're now stale. if (needsRelayout) { cache.clear(); } // If we're not going to render anything, move our existing tiles into // the cache (if they're not stale) or abandon them, and return. if (!needsRendering) { if (!needsRelayout) { for (auto& entry : tiles) { // These tiles are invisible, we set optional necessity // for them and thus suppress network requests on // tiles expiration (see `OnlineFileRequest`). entry.second->setNecessity(TileNecessity::Optional); cache.add(entry.first, std::move(entry.second)); } } tiles.clear(); renderedTiles.clear(); return; } handleWrapJump(parameters.transformState.getLatLng().longitude()); 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 tileZoom = overscaledZoom; int32_t panZoom = zoomRange.max; const optional& sourcePrefetchZoomDelta = sourceImpl.getPrefetchZoomDelta(); const optional& maxParentTileOverscaleFactor = sourceImpl.getMaxOverscaleFactorForParentTiles(); const Duration minimumUpdateInterval = sourceImpl.getMinimumTileUpdateInterval(); const bool isVolatile = sourceImpl.isVolatile(); std::vector idealTiles; std::vector panTiles; if (overscaledZoom >= zoomRange.min) { int32_t idealZoom = std::min(zoomRange.max, overscaledZoom); // Make sure we're not reparsing overzoomed raster tiles. if (type == SourceType::Raster) { tileZoom = idealZoom; } // Only attempt prefetching in continuous mode. if (parameters.mode == MapMode::Continuous && type != style::SourceType::GeoJSON && type != style::SourceType::Annotations) { // Request lower zoom level tiles (if configured to do so) in an attempt // to show something on the screen faster at the cost of a little of bandwidth. const uint8_t prefetchZoomDelta = sourcePrefetchZoomDelta ? *sourcePrefetchZoomDelta : parameters.prefetchZoomDelta; if (prefetchZoomDelta) { panZoom = std::max(tileZoom - prefetchZoomDelta, zoomRange.min); } if (panZoom < idealZoom) { panTiles = util::tileCover(parameters.transformState, panZoom); } } idealTiles = util::tileCover(parameters.transformState, idealZoom, tileZoom); if (parameters.mode == MapMode::Tile && type != SourceType::Raster && type != SourceType::RasterDEM && idealTiles.size() > 1) { mbgl::Log::Warning(mbgl::Event::General, "Provided camera options returned %zu tiles, only %s is taken in Tile mode.", idealTiles.size(), util::toString(idealTiles[0]).c_str()); idealTiles = {idealTiles[0]}; } } // Stores a list of all the tiles that we're definitely going to retain. There are two // kinds of tiles we need: the ideal tiles determined by the tile cover. They may not yet be in // use because they're still loading. In addition to that, we also need to retain all tiles that // we're actively using, e.g. as a replacement for tile that aren't loaded yet. std::set retain; auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void { if (retain.emplace(tile.id).second) { tile.setUpdateParameters({minimumUpdateInterval, isVolatile}); tile.setNecessity(necessity); } if (needsRelayout) { tile.setLayers(layers); } }; auto getTileFn = [&](const OverscaledTileID& tileID) -> Tile* { auto it = tiles.find(tileID); return it == tiles.end() ? nullptr : it->second.get(); }; // The min and max zoom for TileRange are based on the updateRenderables algorithm. // Tiles are created at the ideal tile zoom or at lower zoom levels. Child // tiles are used from the cache, but not created. optional tileRange = {}; if (bounds) { tileRange = util::TileRange::fromLatLngBounds( *bounds, zoomRange.min, std::min(tileZoom, static_cast(zoomRange.max))); } auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* { if (tileRange && !tileRange->contains(tileID.canonical)) { return nullptr; } std::unique_ptr tile = cache.pop(tileID); if (!tile) { tile = createTile(tileID); if (!tile) return nullptr; } tile->setObserver(observer); tile->setLayers(layers); return tiles.emplace(tileID, std::move(tile)).first->second.get(); }; auto previouslyRenderedTiles = std::move(renderedTiles); auto renderTileFn = [&](const UnwrappedTileID& tileID, Tile& tile) { addRenderTile(tileID, tile); previouslyRenderedTiles.erase(tileID); // Still rendering this tile, no need for special fading logic. tile.markRenderedIdeal(); }; renderedTiles.clear(); if (!panTiles.empty()) { algorithm::updateRenderables( getTileFn, createTileFn, retainTileFn, [](const UnwrappedTileID&, Tile&) {}, panTiles, zoomRange, maxParentTileOverscaleFactor); } algorithm::updateRenderables( getTileFn, createTileFn, retainTileFn, renderTileFn, idealTiles, zoomRange, maxParentTileOverscaleFactor); 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); addRenderTile(previouslyRenderedTile.first, tile); } } if (type != SourceType::Annotations) { size_t conservativeCacheSize = std::max(static_cast(parameters.transformState.getSize().width) / tileSize, 1.0f) * std::max(static_cast(parameters.transformState.getSize().height) / tileSize, 1.0f) * (parameters.transformState.getMaxZoom() - parameters.transformState.getMinZoom() + 1) * 0.5; cache.setSize(conservativeCacheSize); } // Remove stale tiles. This goes through the (sorted!) tiles map and retain set in lockstep // and removes items from tiles that don't have the corresponding key in the retain set. { auto tilesIt = tiles.begin(); auto retainIt = retain.begin(); while (tilesIt != tiles.end()) { if (retainIt == retain.end() || tilesIt->first < *retainIt) { if (!needsRelayout) { tilesIt->second->setNecessity(TileNecessity::Optional); cache.add(tilesIt->first, std::move(tilesIt->second)); } tiles.erase(tilesIt++); } else { if (!(*retainIt < tilesIt->first)) { ++tilesIt; } ++retainIt; } } } for (auto& pair : tiles) { pair.second->setShowCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision); } // Initialize renderable tiles and update the contained layer render data. for (auto& entry : renderedTiles) { Tile& tile = entry.second; assert(tile.isRenderable()); tile.usedByRenderedLayers = false; const bool holdForFade = tile.holdForFade(); for (const auto& layerProperties : layers) { const auto* typeInfo = layerProperties->baseImpl->getTypeInfo(); if (holdForFade && typeInfo->fadingTiles == LayerTypeInfo::FadingTiles::NotRequired) { continue; } tile.usedByRenderedLayers |= tile.layerPropertiesUpdated(layerProperties); } } } void TilePyramid::handleWrapJump(float lng) { // On top of the regular z/x/y values, TileIDs have a `wrap` value that specify // which cppy of the world the tile belongs to. For example, at `lng: 10` you // might render z/x/y/0 while at `lng: 370` you would render z/x/y/1. // // When lng values get wrapped (going from `lng: 370` to `long: 10`) you expect // to see the same thing on the screen (370 degrees and 10 degrees is the same // place in the world) but all the TileIDs will have different wrap values. // // In order to make this transition seamless, we calculate the rounded difference of // "worlds" between the last frame and the current frame. If the map panned by // a world, then we can assign all the tiles new TileIDs with updated wrap values. // For example, assign z/x/y/1 a new id: z/x/y/0. It is the same tile, just rendered // in a different position. // // This enables us to reuse the tiles at more ideal locations and prevent flickering. const float lngDifference = lng - prevLng; const float worldDifference = lngDifference / 360; const int wrapDelta = std::round(worldDifference); prevLng = lng; if (wrapDelta) { std::map> newTiles; std::map> newRenderTiles; for (auto& tile : tiles) { auto newID = tile.second->id.unwrapTo(tile.second->id.wrap + wrapDelta); tile.second->id = newID; newTiles.emplace(newID, std::move(tile.second)); } tiles = std::move(newTiles); for (auto& tile : renderedTiles) { UnwrappedTileID newID = tile.first.unwrapTo(tile.first.wrap + wrapDelta); newRenderTiles.emplace(newID, tile.second); } renderedTiles = std::move(newRenderTiles); } } std::unordered_map> TilePyramid::queryRenderedFeatures( const ScreenLineString& geometry, const TransformState& transformState, const std::unordered_map& layers, const RenderedQueryOptions& options, const mat4& projMatrix, const SourceFeatureState& featureState) const { std::unordered_map> result; if (renderedTiles.empty() || geometry.empty()) { return result; } LineString queryGeometry; queryGeometry.reserve(geometry.size()); for (const auto& p : geometry) { queryGeometry.push_back(TileCoordinate::fromScreenCoordinate( transformState, 0, { p.x, transformState.getSize().height - p.y }).p); } mapbox::geometry::box box = mapbox::geometry::envelope(queryGeometry); auto cmp = [](const UnwrappedTileID& a, const UnwrappedTileID& b) { return std::tie(a.canonical.z, a.canonical.y, a.wrap, a.canonical.x) < std::tie(b.canonical.z, b.canonical.y, b.wrap, b.canonical.x); }; std::map, decltype(cmp)> sortedTiles{renderedTiles.begin(), renderedTiles.end(), cmp}; auto maxPitchScaleFactor = transformState.maxPitchScaleFactor(); for (const auto& entry : sortedTiles) { const UnwrappedTileID& id = entry.first; Tile& tile = entry.second; const float scale = transformState.getScale() / (1 << id.canonical.z); // equivalent to std::pow(2, transformState.getZoom() - id.canonical.z); auto queryPadding = maxPitchScaleFactor * tile.getQueryPadding(layers) * util::EXTENT / util::tileSize / scale; GeometryCoordinate tileSpaceBoundsMin = TileCoordinate::toGeometryCoordinate(id, box.min); if (tileSpaceBoundsMin.x - queryPadding >= util::EXTENT || tileSpaceBoundsMin.y - queryPadding >= util::EXTENT) { continue; } GeometryCoordinate tileSpaceBoundsMax = TileCoordinate::toGeometryCoordinate(id, box.max); if (tileSpaceBoundsMax.x + queryPadding < 0 || tileSpaceBoundsMax.y + queryPadding < 0) { continue; } GeometryCoordinates tileSpaceQueryGeometry; tileSpaceQueryGeometry.reserve(queryGeometry.size()); for (const auto& c : queryGeometry) { tileSpaceQueryGeometry.push_back(TileCoordinate::toGeometryCoordinate(id, c)); } tile.queryRenderedFeatures(result, tileSpaceQueryGeometry, transformState, layers, options, projMatrix, featureState); } return result; } std::vector TilePyramid::querySourceFeatures(const SourceQueryOptions& options) const { std::vector result; for (const auto& pair : tiles) { pair.second->querySourceFeatures(result, options); } return result; } void TilePyramid::setCacheSize(size_t size) { cache.setSize(size); } void TilePyramid::reduceMemoryUse() { cache.clear(); } void TilePyramid::setObserver(TileObserver* observer_) { observer = observer_; } void TilePyramid::dumpDebugLogs() const { for (const auto& pair : tiles) { pair.second->dumpDebugLogs(); } } void TilePyramid::clearAll() { fadingTiles = false; tiles.clear(); renderedTiles.clear(); cache.clear(); } void TilePyramid::addRenderTile(const UnwrappedTileID& tileID, Tile& tile) { assert(tile.isRenderable()); renderedTiles.emplace(tileID, tile); } void TilePyramid::updateFadingTiles() { fadingTiles = false; for (auto& entry : renderedTiles) { Tile& tile = entry.second; if (tile.holdForFade()) { fadingTiles = true; tile.performedFadePlacement(); } } } } // namespace mbgl