[core] Symbol cross-fading.
Hold onto tiles after they've been removed from the render tree long enough to run a fade animation on their symbols.
This commit is contained in:
@@ -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<std::reference_wrapper<RenderTile>> 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()) {
|
||||
|
||||
@@ -74,6 +74,8 @@ private:
|
||||
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 @@ private:
|
||||
std::unique_ptr<Placement> placement;
|
||||
|
||||
bool contextLost = false;
|
||||
bool fadingTiles = false;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -148,9 +148,17 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
|
||||
}
|
||||
return tiles.emplace(tileID, std::move(tile)).first->second.get();
|
||||
};
|
||||
|
||||
std::map<UnwrappedTileID, Tile*> 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<Immutable<style::Layer::Impl>>& 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 =
|
||||
|
||||
@@ -46,7 +46,6 @@ void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatri
|
||||
std::unordered_set<uint32_t> 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<uint32_t>& seenCrossTileIDs) {
|
||||
std::unordered_set<uint32_t>& 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);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ private:
|
||||
const float scale,
|
||||
const float pixelRatio,
|
||||
const bool showCollisionBoxes,
|
||||
std::unordered_set<uint32_t>& seenCrossTileIDs);
|
||||
std::unordered_set<uint32_t>& seenCrossTileIDs,
|
||||
const bool holdingForFade);
|
||||
|
||||
void updateBucketOpacities(SymbolBucket&, std::set<uint32_t>&);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -97,6 +97,11 @@ public:
|
||||
|
||||
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 @@ private:
|
||||
const MapMode mode;
|
||||
|
||||
bool showCollisionBoxes;
|
||||
|
||||
enum class FadeState {
|
||||
Loaded,
|
||||
NeedsFirstPlacement,
|
||||
NeedsSecondPlacement,
|
||||
CanRemove
|
||||
};
|
||||
|
||||
FadeState fadeState = FadeState::Loaded;
|
||||
public:
|
||||
optional<gl::Texture> glyphAtlasTexture;
|
||||
optional<gl::Texture> iconAtlasTexture;
|
||||
|
||||
@@ -95,6 +95,20 @@ public:
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user