423 lines
16 KiB
C++
423 lines
16 KiB
C++
#include <mbgl/renderer/tile_pyramid.hpp>
|
|
#include <mbgl/renderer/paint_parameters.hpp>
|
|
#include <mbgl/renderer/render_source.hpp>
|
|
#include <mbgl/renderer/tile_parameters.hpp>
|
|
#include <mbgl/renderer/query.hpp>
|
|
#include <mbgl/map/transform.hpp>
|
|
#include <mbgl/math/clamp.hpp>
|
|
#include <mbgl/util/tile_cover.hpp>
|
|
#include <mbgl/util/tile_range.hpp>
|
|
#include <mbgl/util/enum.hpp>
|
|
#include <mbgl/util/logging.hpp>
|
|
|
|
#include <mbgl/algorithm/update_renderables.hpp>
|
|
|
|
#include <mapbox/geometry/envelope.hpp>
|
|
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
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<Immutable<style::LayerProperties>>& layers,
|
|
const bool needsRendering,
|
|
const bool needsRelayout,
|
|
const TileParameters& parameters,
|
|
const style::Source::Impl& sourceImpl,
|
|
const uint16_t tileSize,
|
|
const Range<uint8_t> zoomRange,
|
|
std::optional<LatLngBounds> bounds,
|
|
std::function<std::unique_ptr<Tile>(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(static_cast<float>(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 std::optional<uint8_t>& sourcePrefetchZoomDelta = sourceImpl.getPrefetchZoomDelta();
|
|
const std::optional<uint8_t>& maxParentTileOverscaleFactor = sourceImpl.getMaxOverscaleFactorForParentTiles();
|
|
const Duration minimumUpdateInterval = sourceImpl.getMinimumTileUpdateInterval();
|
|
const bool isVolatile = sourceImpl.isVolatile();
|
|
|
|
std::vector<OverscaledTileID> idealTiles;
|
|
std::vector<OverscaledTileID> panTiles;
|
|
|
|
if (overscaledZoom >= zoomRange.min) {
|
|
int32_t idealZoom = std::min<int32_t>(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<int32_t>(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 " + std::to_string(idealTiles.size()) +
|
|
" tiles, only " + util::toString(idealTiles[0]) + " is taken in Tile mode.");
|
|
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<OverscaledTileID> 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.
|
|
std::optional<util::TileRange> tileRange = std::nullopt;
|
|
if (bounds) {
|
|
tileRange = util::TileRange::fromLatLngBounds(
|
|
*bounds, zoomRange.min, std::min(tileZoom, static_cast<int32_t>(zoomRange.max)));
|
|
}
|
|
auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* {
|
|
if (tileRange && !tileRange->contains(tileID.canonical)) {
|
|
return nullptr;
|
|
}
|
|
std::unique_ptr<Tile> 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) {
|
|
auto conservativeCacheSize = static_cast<size_t>(
|
|
std::max(static_cast<double>(parameters.transformState.getSize().width) / tileSize, 1.0) *
|
|
std::max(static_cast<double>(parameters.transformState.getSize().height) / tileSize, 1.0) *
|
|
(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.f;
|
|
const auto wrapDelta = static_cast<int16_t>(std::round(worldDifference));
|
|
prevLng = lng;
|
|
|
|
if (wrapDelta) {
|
|
std::map<OverscaledTileID, std::unique_ptr<Tile>> newTiles;
|
|
std::map<UnwrappedTileID, std::reference_wrapper<Tile>> 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<std::string, std::vector<Feature>> TilePyramid::queryRenderedFeatures(
|
|
const ScreenLineString& geometry,
|
|
const TransformState& transformState,
|
|
const std::unordered_map<std::string, const RenderLayer*>& layers,
|
|
const RenderedQueryOptions& options,
|
|
const mat4& projMatrix,
|
|
const SourceFeatureState& featureState) const {
|
|
std::unordered_map<std::string, std::vector<Feature>> result;
|
|
if (renderedTiles.empty() || geometry.empty()) {
|
|
return result;
|
|
}
|
|
|
|
LineString<double> 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<double> 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<UnwrappedTileID, std::reference_wrapper<Tile>, 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 auto scale = static_cast<float>(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_D /
|
|
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<Feature> TilePyramid::querySourceFeatures(const SourceQueryOptions& options) const {
|
|
std::vector<Feature> 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
|