#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mln { using namespace style; LayerRenderItem::LayerRenderItem(RenderLayer& layer_, RenderSource* source_, uint32_t index_) : layer(layer_), source(source_), index(index_) {} bool LayerRenderItem::hasRenderPass(RenderPass pass) const { return layer.get().hasRenderPass(pass); } void LayerRenderItem::upload(gfx::UploadPass& pass) const { layer.get().upload(pass); } void LayerRenderItem::render(PaintParameters& parameters) const { layer.get().render(parameters); } const std::string& LayerRenderItem::getName() const { return layer.get().getID(); } void LayerRenderItem::updateDebugDrawables(DebugLayerGroupMap&, PaintParameters&) const {}; namespace { RendererObserver& nullObserver() { static RendererObserver observer; return observer; } const char* sourceTypeText(style::SourceType type) noexcept { switch (type) { case style::SourceType::Vector: return "vector"; case style::SourceType::Raster: return "raster"; case style::SourceType::RasterDEM: return "raster-dem"; case style::SourceType::GeoJSON: return "geojson"; case style::SourceType::Video: return "video"; case style::SourceType::Annotations: return "annotations"; case style::SourceType::Image: return "image"; case style::SourceType::CustomVector: return "customvector"; case style::SourceType::CustomMVTVector: return "unknown"; case style::SourceType::RasterArray: return "raster-array"; } return "unknown"; } const char* tileOperationText(TileOperation op) noexcept { switch (op) { case TileOperation::RequestedFromCache: return "RequestedFromCache"; case TileOperation::RequestedFromNetwork: return "RequestedFromNetwork"; case TileOperation::LoadFromNetwork: return "LoadFromNetwork"; case TileOperation::LoadFromCache: return "LoadFromCache"; case TileOperation::StartParse: return "StartParse"; case TileOperation::EndParse: return "EndParse"; case TileOperation::Error: return "Error"; case TileOperation::Cancelled: return "Cancelled"; case TileOperation::NullOp: return "NullOp"; } return "Unknown"; } const char* readinessEventType(RetainedUnderlayReason reason) noexcept { switch (reason) { case RetainedUnderlayReason::SourceMutation: return "SourceMutation"; case RetainedUnderlayReason::TileMutation: return "TileMutation"; case RetainedUnderlayReason::ImageMutation: return "ImageMutation"; case RetainedUnderlayReason::GlyphMutation: return "GlyphMutation"; case RetainedUnderlayReason::StyleMutation: return "StyleMutation"; case RetainedUnderlayReason::LayerMutation: case RetainedUnderlayReason::LayerGroupMutation: return "LayerMutation"; case RetainedUnderlayReason::ContextReset: return "ContextReset"; default: return "OtherMutation"; } } class RenderTreeImpl final : public RenderTree { public: RenderTreeImpl(std::unique_ptr parameters_, std::set layerRenderItems_, std::vector> sourceRenderItems_, LineAtlas& lineAtlas_, PatternAtlas& patternAtlas_, RenderLayerReferences layersNeedPlacement_, Immutable placement_, bool updateSymbolOpacities_, double startTime_) : RenderTree(std::move(parameters_), startTime_), layerRenderItems(std::move(layerRenderItems_)), sourceRenderItems(std::move(sourceRenderItems_)), lineAtlas(lineAtlas_), patternAtlas(patternAtlas_), layersNeedPlacement(std::move(layersNeedPlacement_)), placement(std::move(placement_)), updateSymbolOpacities(updateSymbolOpacities_) {} void prepare() override { MLN_TRACE_FUNC(); for (auto it = layersNeedPlacement.rbegin(); it != layersNeedPlacement.rend(); ++it) { placement->updateLayerBuckets(*it, parameters->transformParams.state, updateSymbolOpacities); } } const std::set& getLayerRenderItemMap() const noexcept override { return layerRenderItems; } RenderItems getLayerRenderItems() const override { return {layerRenderItems.begin(), layerRenderItems.end()}; } RenderItems getSourceRenderItems() const override { RenderItems result; result.reserve(sourceRenderItems.size()); for (const auto& item : sourceRenderItems) result.emplace_back(*item); return result; } LineAtlas& getLineAtlas() const override { return lineAtlas; } PatternAtlas& getPatternAtlas() const override { return patternAtlas; } std::set layerRenderItems; std::vector> sourceRenderItems; std::reference_wrapper lineAtlas; std::reference_wrapper patternAtlas; RenderLayerReferences layersNeedPlacement; Immutable placement; bool updateSymbolOpacities; }; } // namespace RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_, TaggedScheduler& threadPool_, const std::optional& localFontFamily_) : observer(&nullObserver()), glyphManager(std::make_unique(std::make_unique(localFontFamily_))), imageManager(ImageManager::create()), lineAtlas(std::make_unique()), patternAtlas(std::make_unique()), imageImpls(makeMutable>>()), sourceImpls(makeMutable>>()), layerImpls(makeMutable>>()), renderLight(makeMutable()), backgroundLayerAsColor(backgroundLayerAsColor_), threadPool(threadPool_) { glyphManager->setObserver(this); imageManager->setObserver(this); } RenderOrchestrator::~RenderOrchestrator() { MLN_TRACE_FUNC(); if (contextLost) { // Signal all RenderLayers that the context was lost // before cleaning up. At the moment, only CustomLayer is // interested whether rendering context is lost. However, it would be // beneficial for dynamically loaded or other custom built-in plugins. for (const auto& entry : renderLayers) { RenderLayer& layer = *entry.second; layer.markContextDestroyed(); } } // Finish work that can still reach the source observers before detaching them. threadPool.waitForEmpty(); for (auto& entry : renderSources) { entry.second->setObserver(nullptr); } glyphManager->setObserver(nullptr); imageManager->setObserver(nullptr); // Tile destructors can synchronously report Cancelled and enqueue further work. // Destroy sources while the readiness state and its mutex are still alive. renderSources.clear(); // Drain work submitted by source/tile destruction before the scheduler and // the remaining orchestrator state are torn down. threadPool.waitForEmpty(); } void RenderOrchestrator::recordReadinessMutation(const char* eventType, RetainedUnderlayReason reason, const std::optional& sourceId, const std::string& tileAction) noexcept { try { std::lock_guard lock(readinessDiagnosticsMutex); const auto sequence = ++readinessEventSequence; lastReadinessMutation.sequence = sequence; lastReadinessMutation.contentRevision = retainedUnderlayContentRevision.load(std::memory_order_acquire); lastReadinessMutation.eventType = eventType; lastReadinessMutation.reason = retainedUnderlayReasonText(reason); lastReadinessMutation.sourceId = sourceId; lastReadinessMutation.affectedSegmentIndices.clear(); lastReadinessMutation.dependencyScope = sourceId ? "unresolved-until-snapshot" : "shared/global/unknown"; if (sourceId) { auto& state = sourceReadinessEvents[*sourceId]; state.lastMutationReason = retainedUnderlayReasonText(reason); if (reason == RetainedUnderlayReason::SourceMutation) state.lastSourceMutationSequence = sequence; if (reason == RetainedUnderlayReason::TileMutation) state.lastTileChangedSequence = sequence; if (!tileAction.empty()) { state.lastTileActionSequence = sequence; state.lastTileAction = tileAction; } } } catch (...) { // Diagnostic recording is best-effort and must not escape into renderer mutation paths. } } void RenderOrchestrator::advanceRetainedSourceRevision(RetainedUnderlayReason reason, const std::string& sourceId) noexcept { if (sourceId.empty()) { std::lock_guard lock(readinessDiagnosticsMutex); retainedSegmentRevisions.advanceGlobal(); return; } try { std::lock_guard lock(readinessDiagnosticsMutex); retainedSegmentRevisions.advanceSource(sourceId); } catch (...) { // Correctness state must fail closed if a source revision cannot be recorded. std::lock_guard lock(readinessDiagnosticsMutex); retainedSegmentRevisions.advanceGlobal(); } recordReadinessMutation(readinessEventType(reason), reason, sourceId); } void RenderOrchestrator::advanceRetainedUnderlayContentRevision( RetainedUnderlayReason reason, const std::optional& sourceId) noexcept { retainedUnderlayContentReason.store(reason, std::memory_order_release); retainedUnderlayContentRevision.fetch_add(1, std::memory_order_acq_rel); if (sourceId && (reason == RetainedUnderlayReason::SourceMutation || reason == RetainedUnderlayReason::TileMutation)) { advanceRetainedSourceRevision(reason, *sourceId); return; } { std::lock_guard lock(readinessDiagnosticsMutex); retainedSegmentRevisions.advanceGlobal(); } recordReadinessMutation(readinessEventType(reason), reason, sourceId); } RendererReadinessDiagnostics RenderOrchestrator::buildReadinessDiagnostics(bool styleLoaded, bool spriteLoaded) const { RendererReadinessDiagnostics result; result.styleLoaded = styleLoaded; result.imageManagerLoaded = imageManager->isLoaded(); result.spriteLoaded = spriteLoaded; result.contentRevision = retainedUnderlayContentRevision.load(std::memory_order_acquire); std::unordered_map sourceEvents; { std::lock_guard lock(readinessDiagnosticsMutex); // Snapshot-only: do not prune or otherwise mutate diagnostic history from a query path. // Entries for sources no longer present are naturally ignored below because we enumerate // the current renderSources set and only look up matching ids. sourceEvents = sourceReadinessEvents; result.lastMutation = lastReadinessMutation; } result.renderOrchestratorLoaded = result.imageManagerLoaded; result.sources.reserve(renderSources.size()); for (const auto& [sourceId, renderSource] : renderSources) { const auto stats = renderSource->getReadinessDiagnostics(); RenderSourceReadinessDiagnostics source; source.sourceId = sourceId; source.sourceType = sourceTypeText(renderSource->baseImpl->type); source.enabled = renderSource->isEnabled(); source.isLoaded = renderSource->isLoaded(); source.tileBacked = stats.tileBacked; source.tileCount = stats.tileCount; source.incompleteTileCount = stats.incompleteTileCount; source.loadedTileCount = stats.loadedTileCount; source.hasFadingTiles = stats.hasFadingTiles; result.hasFadingTiles = result.hasFadingTiles || stats.hasFadingTiles; result.renderOrchestratorLoaded = result.renderOrchestratorLoaded && source.isLoaded; if (const auto it = sourceEvents.find(sourceId); it != sourceEvents.end()) { source.lastSourceMutationSequence = it->second.lastSourceMutationSequence; source.lastTileChangedSequence = it->second.lastTileChangedSequence; source.lastTileActionSequence = it->second.lastTileActionSequence; source.lastTileAction = it->second.lastTileAction; source.lastMutationReason = it->second.lastMutationReason; } result.sources.push_back(std::move(source)); } std::ranges::sort(result.sources, {}, &RenderSourceReadinessDiagnostics::sourceId); return result; } void RenderOrchestrator::markContextLost() { contextLost = true; advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ContextReset); } void RenderOrchestrator::setObserver(RendererObserver* observer_) { observer = observer_ ? observer_ : &nullObserver(); } std::unique_ptr RenderOrchestrator::createRenderTree( const std::shared_ptr& updateParameters, gfx::DynamicTextureAtlasPtr dynamicTextureAtlas) { MLN_TRACE_FUNC(); const auto startTime = util::MonotonicTimer::now().count(); const auto fontFacesIdentity = static_cast(updateParameters->fontFaces.get()); if (retainedInputStateInitialized && (retainedGlyphURL != updateParameters->glyphURL || retainedFontFacesIdentity != fontFacesIdentity)) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation); } if (retainedInputStateInitialized && retainedSpriteLoaded != updateParameters->spriteLoaded) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation); } retainedInputStateInitialized = true; retainedGlyphURL = updateParameters->glyphURL; retainedFontFacesIdentity = fontFacesIdentity; retainedSpriteLoaded = updateParameters->spriteLoaded; const bool isMapModeContinuous = updateParameters->mode == MapMode::Continuous; if (!isMapModeContinuous) { // Reset zoom history state. zoomHistory.first = true; } if (LayerManager::annotationsEnabled) { if (auto guard = updateParameters->annotationManager.lock(); updateParameters->annotationManager) { updateParameters->annotationManager->updateData(); } } const bool zoomChanged = zoomHistory.update(static_cast(updateParameters->transformState.getZoom()), updateParameters->timePoint); const TransitionOptions transitionOptions = isMapModeContinuous ? updateParameters->transitionOptions : TransitionOptions(); const TransitionParameters transitionParameters{.now = updateParameters->timePoint, .transition = transitionOptions}; const auto transitionDuration = transitionOptions.duration.value_or( isMapModeContinuous ? util::DEFAULT_TRANSITION_DURATION : Duration::zero()); PropertyEvaluationParameters evaluationParameters{zoomHistory, updateParameters->timePoint, transitionDuration}; evaluationParameters.zoomChanged = zoomChanged; TileParameters tileParameters{.pixelRatio = updateParameters->pixelRatio, .debugOptions = updateParameters->debugOptions, .transformState = updateParameters->transformState, .fileSource = updateParameters->fileSource, .mode = updateParameters->mode, .annotationManager = updateParameters->annotationManager, .imageManager = imageManager, .glyphManager = glyphManager, .prefetchZoomDelta = updateParameters->prefetchZoomDelta, .threadPool = threadPool, .tileLodMinRadius = updateParameters->tileLodMinRadius, .tileLodScale = updateParameters->tileLodScale, .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold, .tileLodZoomShift = updateParameters->tileLodZoomShift, .tileLodMode = updateParameters->tileLodMode, .dynamicTextureAtlas = dynamicTextureAtlas}; glyphManager->setURL(updateParameters->glyphURL); glyphManager->setFontFaces(updateParameters->fontFaces); // Update light. const bool lightChanged = renderLight.impl != updateParameters->light; if (lightChanged) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::StyleMutation); renderLight.impl = updateParameters->light; renderLight.transition(transitionParameters); } if (lightChanged || zoomChanged || renderLight.hasTransition()) { renderLight.evaluate(evaluationParameters); } const ImageDifference imageDiff = diffImages(imageImpls, updateParameters->images); if (!imageDiff.added.empty() || !imageDiff.removed.empty() || !imageDiff.changed.empty()) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation); } 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(); // Remove removed images from sprite atlas. for (const auto& entry : imageDiff.removed) { imageManager->removeImage(entry.first); patternAtlas->removePattern(entry.first); } // Add added images to sprite atlas. for (const auto& entry : imageDiff.added) { imageManager->addImage(entry.second); } // Update changed images. for (const auto& entry : imageDiff.changed) { if (imageManager->updateImage(entry.second.after)) { patternAtlas->removePattern(entry.first); hasImageDiff = true; } } imageManager->notifyIfMissingImageAdded(); imageManager->setLoaded(updateParameters->spriteLoaded); const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters->layers); // Layer mutations are tracked by per-segment immutable-Impl fingerprints below. Source, // tile, image, glyph and camera changes remain deliberately global invalidations. layerImpls = updateParameters->layers; const bool layersAddedOrRemoved = !layerDiff.added.empty() || !layerDiff.removed.empty(); std::vector> changes; // Remove render layers for removed layers. for (const auto& entry : layerDiff.removed) { MLN_TRACE_ZONE(remove layer); const auto hit = renderLayers.find(entry.first); if (hit != renderLayers.end()) { hit->second->layerRemoved(changes); renderLayers.erase(hit); } } // Create render layers for newly added layers. for (const auto& entry : layerDiff.added) { MLN_TRACE_ZONE(add layer); auto renderLayer = LayerManager::get()->createRenderLayer(entry.second); renderLayer->transition(transitionParameters); renderLayers.emplace(entry.first, std::move(renderLayer)); } // Update render layers for changed layers. for (const auto& entry : layerDiff.changed) { MLN_TRACE_ZONE(change layer); if (const auto& renderLayer = renderLayers.at(entry.first)) { const auto& newLayer = entry.second.after; renderLayer->layerChanged(transitionParameters, newLayer, changes); renderLayer->transition(transitionParameters, newLayer); } } if (layersAddedOrRemoved) { orderedLayers.clear(); orderedLayers.reserve(layerImpls->size()); [[maybe_unused]] int32_t layerIndex = 0; for (const auto& layerImpl : *layerImpls) { RenderLayer* layer = renderLayers.at(layerImpl->id).get(); assert(layer); orderedLayers.emplace_back(*layer); // We're mutating the list of ordered layers and must notify them of their new assigned indices layer->layerIndexChanged(layerIndex++, changes); } } assert(orderedLayers.size() == renderLayers.size()); if (layersAddedOrRemoved || !layerDiff.changed.empty()) { glyphManager->evict(fontStacks(*layerImpls)); } // Update layers for class and zoom changes. std::unordered_set constantsMaskChanged; for (RenderLayer& layer : orderedLayers) { MLN_TRACE_ZONE(update layer); const std::string& id = layer.getID(); const bool layerAddedOrChanged = layerDiff.added.contains(id) || layerDiff.changed.contains(id); evaluationParameters.layerChanged = layerAddedOrChanged; evaluationParameters.hasCrossfade = layer.hasCrossfade(); // Only re-evaluate on change of zoom if the style has some reference to it using Dependency = expression::Dependency; const bool zoomChangedAndMatters = zoomChanged && !layerAddedOrChanged && (layer.getStyleDependencies() & Dependency::Zoom); if (layerAddedOrChanged || zoomChangedAndMatters || evaluationParameters.hasCrossfade || layer.hasTransition()) { const auto previousMask = layer.evaluatedProperties->constantsMask(); layer.evaluate(evaluationParameters); if (previousMask != layer.evaluatedProperties->constantsMask()) { constantsMaskChanged.insert(id); } } } const SourceDifference sourceDiff = diffSources(sourceImpls, updateParameters->sources); if (!sourceDiff.added.empty() || !sourceDiff.removed.empty() || !sourceDiff.changed.empty()) { retainedUnderlayContentReason.store(RetainedUnderlayReason::SourceMutation, std::memory_order_release); retainedUnderlayContentRevision.fetch_add(1, std::memory_order_acq_rel); for (const auto& entry : sourceDiff.added) { advanceRetainedSourceRevision(RetainedUnderlayReason::SourceMutation, entry.first); } for (const auto& entry : sourceDiff.removed) { advanceRetainedSourceRevision(RetainedUnderlayReason::SourceMutation, entry.first); } for (const auto& entry : sourceDiff.changed) { advanceRetainedSourceRevision(RetainedUnderlayReason::SourceMutation, entry.first); } } sourceImpls = updateParameters->sources; // Remove render layers for removed sources. for (const auto& entry : sourceDiff.removed) { renderSources.erase(entry.first); } // Create render sources for newly added sources. for (const auto& entry : sourceDiff.added) { MLN_TRACE_ZONE(create source); std::unique_ptr renderSource = RenderSource::create(entry.second, threadPool); renderSource->setObserver(this); renderSource->setCacheEnabled(tileCacheEnabled); renderSource->setFastPFOREnabled(updateParameters->fastPFOREnabled); renderSources.emplace(entry.first, std::move(renderSource)); } transformState = updateParameters->transformState; const bool tiltedView = transformState.getPitch() != 0.0f; // Create parameters for the render tree. auto renderTreeParameters = std::make_unique(updateParameters->transformState, updateParameters->mode, updateParameters->debugOptions, updateParameters->timePoint, renderLight.getEvaluated()); std::set layerRenderItems; layersNeedPlacement.clear(); auto renderItemsEmplaceHint = layerRenderItems.begin(); // Reserve size for filteredLayersForSource if there are sources. if (!sourceImpls->empty()) { filteredLayersForSource.reserve(layerImpls->size()); } // Track which layers are flagged for rendering std::vector updateList(orderedLayers.size()); RetainedDynamicBoundaryTracker boundaryTracker; for (std::size_t index = 0; index < orderedLayers.size(); ++index) { boundaryTracker.consider(static_cast(index), orderedLayers[index].get().startsRetainedDynamicRange()); } retainedDynamicBoundary = boundaryTracker.value(); std::vector orderedLayerIds; orderedLayerIds.reserve(orderedLayers.size()); for (const auto& layer : orderedLayers) orderedLayerIds.push_back(layer.get().getID()); continuousLayerRange = resolveContinuousLayerRange( updateParameters->retainedUnderlayState ? updateParameters->retainedUnderlayState->continuousLayerRangeConfig() : ContinuousLayerRangeConfig{}, orderedLayerIds); if (continuousLayerRange.configured && !updateParameters->styleLoaded) { continuousLayerRange.valid = false; continuousLayerRange.startIndex = -1; continuousLayerRange.endIndex = -1; continuousLayerRange.reason = "style-not-ready"; } if (updateParameters->retainedUnderlayState) { updateParameters->retainedUnderlayState->recordRangeResolution( continuousLayerRange, continuousLayerRange.bottomPresent(), continuousLayerRange.topPresent(static_cast(orderedLayers.size()))); const auto segmentConfig = updateParameters->retainedUnderlayState->renderSegmentPlanConfig(); const bool segmentMetadataChanged = renderSegmentPlan.configRevision != segmentConfig.revision || renderSegmentPlan.layerCount != static_cast(orderedLayers.size()) || renderSegmentStyleReady != updateParameters->styleLoaded || layersAddedOrRemoved || !layerDiff.changed.empty(); if (segmentMetadataChanged) { renderSegmentPlan = resolveRenderSegmentPlan( segmentConfig, orderedLayerIds, updateParameters->styleLoaded, retainedDynamicBoundary); renderSegmentStyleReady = updateParameters->styleLoaded; std::vector styleLayerDiagnostics; styleLayerDiagnostics.reserve(orderedLayers.size()); for (std::size_t index = 0; index < orderedLayers.size(); ++index) { const auto& layer = orderedLayers[index].get(); const auto& impl = *layer.baseImpl; StyleLayerDiagnostics value; value.layerIndex = static_cast(index); value.layerId = layer.getID(); value.layerType = impl.getTypeInfo()->type; value.visible = impl.visibility != style::VisibilityType::None; if (!impl.source.empty()) value.sourceId = impl.source; if (!impl.sourceLayer.empty()) value.sourceLayer = impl.sourceLayer; styleLayerDiagnostics.push_back(std::move(value)); } retainedStyleLayers = styleLayerDiagnostics; updateParameters->retainedUnderlayState->recordRenderSegmentResolution( renderSegmentPlan, std::move(styleLayerDiagnostics)); } } else { renderSegmentPlan = {}; retainedStyleLayers.clear(); } // Update all sources and initialize renderItems. for (const auto& sourceImpl : *sourceImpls) { MLN_TRACE_ZONE(update source); MLN_ZONE_STR(sourceImpl->id); RenderSource* source = renderSources.at(sourceImpl->id).get(); bool sourceNeedsRendering = false; bool sourceNeedsRelayout = false; for (std::size_t index = 0; index < orderedLayers.size(); ++index) { RenderLayer& layer = orderedLayers[index]; const auto* layerInfo = layer.baseImpl->getTypeInfo(); const bool layerIsVisible = layer.baseImpl->visibility != style::VisibilityType::None; const bool zoomFitsLayer = layer.supportsZoom(zoomHistory.lastZoom); renderTreeParameters->has3D |= (layerInfo->pass3d == LayerTypeInfo::Pass3D::Required); if (layerInfo->source != LayerTypeInfo::Source::NotRequired) { if (layer.baseImpl->source == sourceImpl->id) { const std::string& layerId = layer.getID(); sourceNeedsRelayout = (sourceNeedsRelayout || hasImageDiff || constantsMaskChanged.contains(layerId) || hasLayoutDifference(layerDiff, layerId)); if (layerIsVisible) { filteredLayersForSource.push_back(layer.evaluatedProperties); if (zoomFitsLayer) { sourceNeedsRendering = true; renderItemsEmplaceHint = layerRenderItems.emplace_hint( renderItemsEmplaceHint, layer, source, static_cast(index)); updateList[index] = true; } } } continue; } // Handle layers without source. if (layerIsVisible && zoomFitsLayer && sourceImpl.get() == sourceImpls->at(0).get()) { if (backgroundLayerAsColor && layer.baseImpl == layerImpls->front()) { const auto& solidBackground = layer.getSolidBackground(); if (solidBackground) { renderTreeParameters->backgroundColor = *solidBackground; continue; // This layer is shown with background color, // and it shall not be added to render items. } } renderItemsEmplaceHint = layerRenderItems.emplace_hint( renderItemsEmplaceHint, layer, nullptr, static_cast(index)); updateList[index] = true; } } tileParameters.isUpdateSynchronous = sourceImpl->isUpdateSynchronous(); source->update(sourceImpl, filteredLayersForSource, sourceNeedsRendering, sourceNeedsRelayout, tileParameters); filteredLayersForSource.clear(); // Update all layers with their new renderability status, if it changed. for (size_t i = 0; i < updateList.size(); i++) { if (orderedLayers[i].get().isLayerRenderable() != updateList[i]) { orderedLayers[i].get().markLayerRenderable(updateList[i], changes); } } addChanges(changes); } // Readiness is sampled for diagnostics only. Preserve the renderer's formal load gate. renderTreeParameters->loaded = updateParameters->styleLoaded && isLoaded(); if (updateParameters->retainedUnderlayState) { try { auto readinessDiagnostics = buildReadinessDiagnostics(updateParameters->styleLoaded, updateParameters->spriteLoaded); updateParameters->retainedUnderlayState->recordRendererReadiness(std::move(readinessDiagnostics)); } catch (...) { // A failed diagnostic snapshot must not affect the render tree or its formal loaded gate. } } if (!isMapModeContinuous && !renderTreeParameters->loaded) { return nullptr; } // Prepare. Update all matrices and generate data that we should upload to the GPU. for (const auto& [name, renderSource] : renderSources) { MLN_TRACE_ZONE(prepare source); if (renderSource->isEnabled()) { renderSource->prepare({.transform = renderTreeParameters->transformParams, .debugOptions = updateParameters->debugOptions, .imageManager = *imageManager, .sourceName = name}); } } // Only weather layers that explicitly opt in may clean up after Host clear. // Ordinary hidden layers retain MapLibre's existing lifecycle. for (auto& entry : orderedLayers) { auto& layer = entry.get(); const auto source = renderSources.find(layer.baseImpl->source); if (!layer.isLayerRenderable() && source != renderSources.end() && layer.needsHiddenLayerSourceCleanup(*source->second)) { layer.prepare({.source = source->second.get(), .imageManager = *imageManager, .patternAtlas = *patternAtlas, .lineAtlas = *lineAtlas, .state = updateParameters->transformState}); } } auto opaquePassCutOffEstimation = layerRenderItems.size(); for (auto& renderItem : layerRenderItems) { RenderLayer& renderLayer = renderItem.layer; MLN_TRACE_ZONE(prepare layer); MLN_ZONE_STR(renderLayer.getID()); renderLayer.prepare({.source = renderItem.source, .imageManager = *imageManager, .patternAtlas = *patternAtlas, .lineAtlas = *lineAtlas, .state = updateParameters->transformState}); if (renderLayer.needsPlacement()) { layersNeedPlacement.emplace_back(renderLayer); } if (renderTreeParameters->opaquePassCutOff == 0) { --opaquePassCutOffEstimation; if (renderLayer.is3D()) { renderTreeParameters->opaquePassCutOff = uint32_t(opaquePassCutOffEstimation); } } } // Symbol placement. assert((updateParameters->mode == MapMode::Tile) || !placedSymbolDataCollected); bool symbolBucketsChanged = false; bool symbolBucketsAdded = false; std::set usedSymbolLayers; const auto longitude = static_cast(updateParameters->transformState.getLatLng().longitude()); for (auto it = layersNeedPlacement.crbegin(); it != layersNeedPlacement.crend(); ++it) { MLN_TRACE_ZONE(placement layer); RenderLayer& layer = *it; auto result = crossTileSymbolIndex.addLayer(layer, longitude); if (isMapModeContinuous) { usedSymbolLayers.insert(layer.getID()); symbolBucketsAdded = symbolBucketsAdded || (result & CrossTileSymbolIndex::AddLayerResult::BucketsAdded); symbolBucketsChanged = symbolBucketsChanged || (result != CrossTileSymbolIndex::AddLayerResult::NoChanges); } } if (isMapModeContinuous) { MLN_TRACE_ZONE(placement); std::optional placementUpdatePeriodOverride; if (symbolBucketsAdded && !tiltedView) { // If the view is not tilted, we want *the new* symbols to show up // faster, however simple setting `placementChanged` to `true` would // initiate placement too often as new buckets usually come from // several rendered tiles in a row within a short period of time. // Instead, we squeeze placement update period to coalesce buckets // updates from several tiles. On contrary, with the tilted view // it's more important to make placement rarely for performance // reasons and as the new symbols are normally "far away" and the // user is not that interested to see them ASAP. placementUpdatePeriodOverride = std::optional(Milliseconds(30)); } renderTreeParameters->placementChanged = !placementController.placementIsRecent( updateParameters->timePoint, static_cast(updateParameters->transformState.getZoom()), placementUpdatePeriodOverride); symbolBucketsChanged |= renderTreeParameters->placementChanged; if (renderTreeParameters->placementChanged) { Mutable placement = Placement::create(updateParameters, placementController.getPlacement()); placement->placeLayers(layersNeedPlacement); placementController.setPlacement(std::move(placement)); crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers); for (const auto& entry : renderSources) { entry.second->updateFadingTiles(); } } else { placementController.setPlacementStale(); } renderTreeParameters->symbolFadeChange = placementController.getPlacement()->symbolFadeChange( updateParameters->timePoint); renderTreeParameters->needsRepaint = hasTransitions(updateParameters->timePoint); } else { MLN_TRACE_ZONE(placement); renderTreeParameters->placementChanged = symbolBucketsChanged = !layersNeedPlacement.empty(); if (renderTreeParameters->placementChanged) { Mutable placement = Placement::create(updateParameters); placement->collectPlacedSymbolData(placedSymbolDataCollected); placement->placeLayers(layersNeedPlacement); placementController.setPlacement(std::move(placement)); } crossTileSymbolIndex.reset(); renderTreeParameters->symbolFadeChange = 1.0f; renderTreeParameters->needsRepaint = false; } if (!renderTreeParameters->needsRepaint && renderTreeParameters->loaded) { MLN_TRACE_ZONE(reduce); // Notify observer about unused images when map is fully loaded // and there are no ongoing transitions. imageManager->reduceMemoryUseIfCacheSizeExceedsLimit(); } std::vector> sourceRenderItems; for (const auto& entry : renderSources) { if (entry.second->isEnabled()) { sourceRenderItems.emplace_back(entry.second->createRenderItem()); } } return std::make_unique(std::move(renderTreeParameters), std::move(layerRenderItems), std::move(sourceRenderItems), *lineAtlas, *patternAtlas, std::move(layersNeedPlacement), placementController.getPlacement(), symbolBucketsChanged, startTime); } std::vector RenderOrchestrator::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options) const { MLN_TRACE_FUNC(); std::unordered_map layers; if (options.layerIDs) { for (const auto& layerID : *options.layerIDs) { if (const RenderLayer* layer = getRenderLayer(layerID)) { layers.emplace(layer->getID(), layer); } } } else { for (const auto& entry : renderLayers) { layers.emplace(entry.second->getID(), entry.second.get()); } } return queryRenderedFeatures(geometry, options, layers); } void RenderOrchestrator::queryRenderedSymbols(std::unordered_map>& resultsByLayer, const ScreenLineString& geometry, const std::unordered_map& layers, const RenderedQueryOptions& options) const { MLN_TRACE_FUNC(); const auto hasCrossTileIndex = [](const auto& pair) { return pair.second->baseImpl->getTypeInfo()->crossTileIndex == style::LayerTypeInfo::CrossTileIndex::Required; }; std::unordered_map crossTileSymbolIndexLayers; std::ranges::copy_if( layers, std::inserter(crossTileSymbolIndexLayers, crossTileSymbolIndexLayers.begin()), hasCrossTileIndex); if (crossTileSymbolIndexLayers.empty()) { return; } const Placement& placement = *placementController.getPlacement(); auto renderedSymbols = placement.getCollisionIndex().queryRenderedSymbols(geometry); std::vector> bucketQueryData; bucketQueryData.reserve(renderedSymbols.size()); for (const auto& entry : renderedSymbols) { bucketQueryData.emplace_back(placement.getQueryData(entry.first)); } // Although symbol query is global, symbol results are only sortable within // a bucket For a predictable global sort renderItems, we sort the buckets // based on their corresponding tile position std::ranges::sort(bucketQueryData, [](const RetainedQueryData& a, const RetainedQueryData& b) { return std::tie(a.tileID.canonical.z, a.tileID.canonical.y, a.tileID.wrap, a.tileID.canonical.x) < std::tie(b.tileID.canonical.z, b.tileID.canonical.y, b.tileID.wrap, b.tileID.canonical.x); }); for (auto wrappedQueryData : bucketQueryData) { auto& queryData = wrappedQueryData.get(); auto bucketSymbols = queryData.featureIndex->lookupSymbolFeatures(renderedSymbols[queryData.bucketInstanceId], options, crossTileSymbolIndexLayers, queryData.tileID, queryData.featureSortOrder); for (auto layer : bucketSymbols) { auto& resultFeatures = resultsByLayer[layer.first]; std::ranges::move(layer.second, std::inserter(resultFeatures, resultFeatures.end())); } } } std::vector RenderOrchestrator::queryRenderedFeatures( const ScreenLineString& geometry, const RenderedQueryOptions& options, const std::unordered_map& layers) const { MLN_TRACE_FUNC(); std::unordered_set sourceIDs; std::unordered_map filteredLayers; for (const auto& pair : layers) { if (!pair.second->needsRendering() || !pair.second->supportsZoom(zoomHistory.lastZoom)) { continue; } filteredLayers.emplace(pair); sourceIDs.emplace(pair.second->baseImpl->source); } mat4 projMatrix; transformState.getProjMatrix(projMatrix); std::unordered_map> resultsByLayer; for (const auto& sourceID : sourceIDs) { if (RenderSource* renderSource = getRenderSource(sourceID)) { auto sourceResults = renderSource->queryRenderedFeatures( geometry, transformState, filteredLayers, options, projMatrix); std::ranges::move(sourceResults, std::inserter(resultsByLayer, resultsByLayer.begin())); } } queryRenderedSymbols(resultsByLayer, geometry, filteredLayers, options); mln::DynamicFeatureIndex dynamicIndex; for (const auto& pair : filteredLayers) { const RenderLayer* layer = pair.second; layer->populateDynamicRenderFeatureIndex(dynamicIndex); } dynamicIndex.query(resultsByLayer, geometry, transformState); std::vector result; if (resultsByLayer.empty()) { return result; } // Combine all results based on the style layer renderItems. for (const auto& pair : filteredLayers) { auto it = resultsByLayer.find(pair.second->baseImpl->id); if (it != resultsByLayer.end()) { std::move(it->second.begin(), it->second.end(), std::back_inserter(result)); } } return result; } std::vector RenderOrchestrator::queryShapeAnnotations(const ScreenLineString& geometry) const { MLN_TRACE_FUNC(); assert(LayerManager::annotationsEnabled); std::unordered_map shapeAnnotationLayers; RenderedQueryOptions options; for (const auto& layerImpl : *layerImpls) { if (std::mismatch(layerImpl->id.begin(), layerImpl->id.end(), AnnotationManager::ShapeLayerID.begin(), AnnotationManager::ShapeLayerID.end()) .second == AnnotationManager::ShapeLayerID.end()) { if (const RenderLayer* layer = getRenderLayer(layerImpl->id)) { shapeAnnotationLayers.emplace(layer->getID(), layer); } } } return queryRenderedFeatures(geometry, options, shapeAnnotationLayers); } std::vector RenderOrchestrator::querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options) const { MLN_TRACE_FUNC(); const RenderSource* source = getRenderSource(sourceID); if (!source) return {}; return source->querySourceFeatures(options); } FeatureExtensionValue RenderOrchestrator::queryFeatureExtensions( const std::string& sourceID, const Feature& feature, const std::string& extension, const std::string& extensionField, const std::optional>& args) const { MLN_TRACE_FUNC(); if (RenderSource* renderSource = getRenderSource(sourceID)) { return renderSource->queryFeatureExtensions(feature, extension, extensionField, args); } return {}; } void RenderOrchestrator::setFeatureState(const std::string& sourceID, const std::optional& sourceLayerID, const std::string& featureID, const FeatureState& state) { MLN_TRACE_FUNC(); if (RenderSource* renderSource = getRenderSource(sourceID)) { renderSource->setFeatureState(sourceLayerID, featureID, state); advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::SourceMutation, sourceID); } } void RenderOrchestrator::getFeatureState(FeatureState& state, const std::string& sourceID, const std::optional& sourceLayerID, const std::string& featureID) const { MLN_TRACE_FUNC(); if (RenderSource* renderSource = getRenderSource(sourceID)) { renderSource->getFeatureState(state, sourceLayerID, featureID); } } void RenderOrchestrator::removeFeatureState(const std::string& sourceID, const std::optional& sourceLayerID, const std::optional& featureID, const std::optional& stateKey) { MLN_TRACE_FUNC(); if (RenderSource* renderSource = getRenderSource(sourceID)) { renderSource->removeFeatureState(sourceLayerID, featureID, stateKey); advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::SourceMutation, sourceID); } } void RenderOrchestrator::setTileCacheEnabled(bool enable) { tileCacheEnabled = enable; for (const auto& entry : renderSources) { entry.second->setCacheEnabled(enable); } } bool RenderOrchestrator::getTileCacheEnabled() const { return tileCacheEnabled; } void RenderOrchestrator::reduceMemoryUse() { MLN_TRACE_FUNC(); filteredLayersForSource.shrink_to_fit(); for (const auto& entry : renderSources) { entry.second->reduceMemoryUse(); } imageManager->reduceMemoryUse(); observer->onInvalidate(); } void RenderOrchestrator::dumpDebugLogs() { MLN_TRACE_FUNC(); for (const auto& entry : renderSources) { entry.second->dumpDebugLogs(); } imageManager->dumpDebugLogs(); } void RenderOrchestrator::collectPlacedSymbolData(bool enable) { placedSymbolDataCollected = enable; } const std::vector& RenderOrchestrator::getPlacedSymbolsData() const { return placementController.getPlacement()->getPlacedSymbolsData(); } RenderLayer* RenderOrchestrator::getRenderLayer(const std::string& id) { auto it = renderLayers.find(id); return it != renderLayers.end() ? it->second.get() : nullptr; } const RenderLayer* RenderOrchestrator::getRenderLayer(const std::string& id) const { auto it = renderLayers.find(id); return it != renderLayers.end() ? it->second.get() : nullptr; } RenderSource* RenderOrchestrator::getRenderSource(const std::string& id) const { auto it = renderSources.find(id); return it != renderSources.end() ? it->second.get() : nullptr; } bool RenderOrchestrator::hasTransitions(TimePoint timePoint) const { if (renderLight.hasTransition()) { return true; } for (const auto& entry : renderLayers) { if (entry.second->hasTransition() || entry.second->needsRepaint()) { return true; } } if (placementController.hasTransitions(timePoint)) { return true; } for (const auto& entry : renderSources) { if (entry.second->hasFadingTiles()) { return true; } } return false; } bool RenderOrchestrator::retainedUnderlayUsesPlacement(int32_t boundary) const { return retainedRangeUsesPlacement(0, boundary); } bool RenderOrchestrator::retainedRangeUsesPlacement(int32_t begin, int32_t endExclusive) const { const auto first = std::clamp(begin, 0, static_cast(orderedLayers.size())); const auto last = std::clamp(endExclusive, first, static_cast(orderedLayers.size())); for (int32_t index = first; index < last; ++index) { if (orderedLayers[index].get().needsPlacement()) return true; } return false; } bool RenderOrchestrator::retainedRangeHasPlacementCapableLayer(int32_t begin, int32_t endExclusive) const { if (begin < 0 || endExclusive < begin || endExclusive > static_cast(orderedLayers.size())) return true; if (renderSegmentHasPlacementCapableLayer(retainedStyleLayers, begin, endExclusive)) return true; for (int32_t index = begin; index < endExclusive; ++index) { if (orderedLayers[index].get().baseImpl->getTypeInfo()->crossTileIndex == style::LayerTypeInfo::CrossTileIndex::Required) return true; } return false; } void RenderOrchestrator::recordRenderError(std::exception_ptr error) { invalidateRetainedRenderResults(); observer->onRenderError(error); } void RenderOrchestrator::invalidateRetainedRenderResults() { ++renderErrorRevision; advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::LayerGroupMutation); } SymbolRenderResult RenderOrchestrator::retainedSymbolRenderResult( int32_t begin, int32_t endExclusive, float fadeChange, uint64_t frame) { SymbolRenderResult result; result.append(numLayerGroups()); // global depth range depends on all groups if (begin < 0 || endExclusive > static_cast(orderedLayers.size()) || begin > endExclusive) { result.complete = false; return result; } // Registry membership is checked after processChanges, including old groups // of hidden layers. Preserve global ordinals for depth and cross-segment order. for (int32_t index = begin; index < endExclusive; ++index) { auto& layer = orderedLayers[index].get(); const auto* info = layer.baseImpl->getTypeInfo(); const bool placementCapable = info->type == std::string_view("symbol") || info->crossTileIndex == style::LayerTypeInfo::CrossTileIndex::Required; if (!placementCapable) continue; if (info != style::SymbolLayer::Impl::staticTypeInfo()) { result.complete = false; continue; } auto& symbol = static_cast(layer); const auto registered = layerGroupsByLayerIndex.equal_range(index); std::size_t groups = 0; for (auto it = registered.first; it != registered.second; ++it) { ++groups; if (!symbol.isLayerRenderable() || it->second.get() != symbol.retainedResultGroup()) { result.complete = false; } } if (groups > 1 || (symbol.isLayerRenderable() && symbol.retainedResultGroup() && groups != 1)) { result.complete = false; } result.appendResult(symbol.retainedRenderResult(fadeChange, frame)); } auto it = layerGroupsByLayerIndex.lower_bound(begin); std::size_t ordinal = static_cast(std::distance(layerGroupsByLayerIndex.begin(), it)); for (; it != layerGroupsByLayerIndex.end() && it->first < endExclusive; ++it, ++ordinal) { if (!it->second) { result.complete = false; continue; } const auto& group = *it->second; result.append(ordinal); result.retain(it->second); result.append(group.getLayerIndex()); result.append(group.getDrawableCount()); result.append(group.getEnabled()); } result.complete = result.complete && pendingChanges.empty(); return result; } bool RenderOrchestrator::retainedPlacementTransitionActive(TimePoint timePoint) const { return placementController.hasActiveSymbolFade(timePoint); } bool RenderOrchestrator::retainedUnderlayIsStable(TimePoint timePoint, int32_t boundary) const { return retainedRangeIsStable(timePoint, 0, boundary); } bool RenderOrchestrator::retainedRangeIsStable(TimePoint timePoint, int32_t begin, int32_t endExclusive) const { if (renderLight.hasTransition()) return false; if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasActiveSymbolFade(timePoint)) return false; const auto first = std::clamp(begin, 0, static_cast(orderedLayers.size())); const auto last = std::clamp(endExclusive, first, static_cast(orderedLayers.size())); for (int32_t index = first; index < last; ++index) { const auto& layer = orderedLayers[index].get(); if (layer.hasTransition() || layer.needsRepaint()) return false; } for (const auto& entry : renderSources) { if (entry.second->hasFadingTiles()) return false; } return true; } RenderOrchestrator::RetainedSegmentSourceState RenderOrchestrator::retainedSegmentSourceState( int32_t begin, int32_t endExclusive) const { RetainedSegmentSourceState result; const auto dependencies = renderSegmentDependencies(retainedStyleLayers, begin, endExclusive); if (dependencies.unknown) return result; // The new upstream source has no retained-cache revision contract yet. // Keep its ordinary renderer active and fail closed only for cache reuse. for (const auto& sourceId : dependencies.sourceIds) { const auto source = renderSources.find(sourceId); if (source == renderSources.end()) { // A missing source also has no revision-book entry. Report the // concrete dependency failure before revisionFor collapses it to unknown. result.reason = "missing-source"; return result; } if (source->second->baseImpl->type == style::SourceType::CustomMVTVector) { return result; } } // The global epoch contains only changes whose effect cannot be scoped to one source. { std::lock_guard lock(readinessDiagnosticsMutex); const auto value = retainedSegmentRevisions.revisionFor(dependencies); if (!value) return result; result.revision = *value; } const auto assessment = assessRenderSegmentSources(dependencies, [&](const std::string& sourceId) -> std::optional { const auto source = renderSources.find(sourceId); if (source == renderSources.end()) return std::nullopt; return RenderSegmentSourceStatus{source->second->isLoaded(), source->second->hasFadingTiles()}; }); result.ready = assessment.ready; result.stable = assessment.stable; result.reason = assessment.reason; return result; } bool RenderOrchestrator::retainedSegmentIsStable(TimePoint timePoint, int32_t begin, int32_t endExclusive) const { if (renderLight.hasTransition()) return false; if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasActiveSymbolFade(timePoint)) return false; const auto first = std::clamp(begin, 0, static_cast(orderedLayers.size())); const auto last = std::clamp(endExclusive, first, static_cast(orderedLayers.size())); for (int32_t index = first; index < last; ++index) { const auto& layer = orderedLayers[index].get(); if (layer.hasTransition() || layer.needsRepaint()) return false; } return true; } bool RenderOrchestrator::retainedGlobalResourcesReady() const { return imageManager->isLoaded(); } bool RenderOrchestrator::retainedGlobalLightTransition() const { return renderLight.hasTransition(); } bool RenderOrchestrator::retainedUnderlayHasUnsupportedLayer(int32_t boundary) const { return retainedRangeHasUnsupportedLayer(0, boundary); } bool RenderOrchestrator::retainedRangeHasUnsupportedLayer(int32_t begin, int32_t endExclusive) const { const auto first = std::clamp(begin, 0, static_cast(orderedLayers.size())); const auto last = std::clamp(endExclusive, first, static_cast(orderedLayers.size())); for (int32_t index = first; index < last; ++index) { const std::string_view type = orderedLayers[index].get().baseImpl->getTypeInfo()->type; if (type == "custom" || type == "custom-drawable") return true; } return false; } bool RenderOrchestrator::continuousRangeSupportsParticleFastPath(int32_t begin, int32_t endExclusive) const { const auto first = std::clamp(begin, 0, static_cast(orderedLayers.size())); const auto last = std::clamp(endExclusive, first, static_cast(orderedLayers.size())); bool particle = false; for (int32_t index = first; index < last; ++index) { const auto& layer = orderedLayers[index].get(); if (!layer.startsRetainedDynamicRange()) return false; particle = particle || layer.isParticleAnimationLayer(); } return particle; } uint64_t RenderOrchestrator::retainedRangeContentFingerprint(int32_t begin, int32_t endExclusive) const { const auto first = std::clamp(begin, 0, static_cast(orderedLayers.size())); const auto last = std::clamp(endExclusive, first, static_cast(orderedLayers.size())); uint64_t value = 1469598103934665603ULL; for (int32_t index = first; index < last; ++index) { const auto& layer = orderedLayers[static_cast(index)].get(); const auto identity = reinterpret_cast(layer.baseImpl.get()); value ^= static_cast(identity); value *= 1099511628211ULL; value ^= static_cast(index); value *= 1099511628211ULL; } return value; } ParticleFastPathDynamicState RenderOrchestrator::particleFastPathDynamicState(TimePoint timePoint) const { ParticleFastPathDynamicState result; result.blockingTransition = renderLight.hasTransition(); for (const auto& entry : renderLayers) { const auto& layer = *entry.second; result.blockingTransition = result.blockingTransition || layer.hasTransition(); if (layer.isLayerRenderable() && layer.isParticleAnimationActive()) result.particleNeedsRepaint = true; if (layer.needsRepaint()) { if (!layer.isParticleAnimationLayer()) { result.nonParticleNeedsRepaint = true; } } } const bool controllerTransition = placementController.hasActiveSymbolFade(timePoint); result.placementTransition = controllerTransition && (retainedStyleLayers.size() != renderLayers.size() || particleFastPathHasPlacementTransition(retainedStyleLayers, controllerTransition)); for (const auto& entry : renderSources) { result.fadingTiles = result.fadingTiles || entry.second->hasFadingTiles(); } result.pendingChanges = !pendingChanges.empty(); return result; } bool RenderOrchestrator::isLoaded() const { MLN_TRACE_FUNC(); // do the simple boolean check before iterating over all the tiles in all the sources if (!imageManager->isLoaded()) { return false; } for (const auto& entry : renderSources) { if (!entry.second->isLoaded()) { return false; } } return true; } void RenderOrchestrator::clearData() { MLN_TRACE_FUNC(); if (!sourceImpls->empty()) sourceImpls = makeMutable>>(); if (!layerImpls->empty()) layerImpls = makeMutable>>(); if (!imageImpls->empty()) imageImpls = makeMutable>>(); UniqueChangeRequestVec changes; for (const auto& entry : renderLayers) { entry.second->layerRemoved(changes); } addChanges(changes); debugLayerGroups.clear(); renderSources.clear(); renderLayers.clear(); crossTileSymbolIndex.reset(); if (!lineAtlas->isEmpty()) lineAtlas = std::make_unique(); if (!patternAtlas->isEmpty()) patternAtlas = std::make_unique(); imageManager->clear(); glyphManager->evict(fontStacks(*layerImpls)); try { std::lock_guard lock(readinessDiagnosticsMutex); sourceReadinessEvents.clear(); lastReadinessMutation = {}; } catch (...) { // Keep renderer style cleanup independent from optional diagnostic history cleanup. } advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::StyleMutation); } void RenderOrchestrator::addChanges(UniqueChangeRequestVec& changes) { pendingChanges.insert( pendingChanges.end(), std::make_move_iterator(changes.begin()), std::make_move_iterator(changes.end())); changes.clear(); } void RenderOrchestrator::updateLayerIndex(LayerGroupBasePtr layerGroup, const int32_t newIndex) { MLN_TRACE_FUNC(); if (!layerGroup || layerGroup->getLayerIndex() == newIndex) { return; } const auto range = layerGroupsByLayerIndex.equal_range(layerGroup->getLayerIndex()); for (auto it = range.first; it != range.second; ++it) { if (it->second == layerGroup) { layerGroupsByLayerIndex.erase(it); layerGroup->updateLayerIndex(newIndex); layerGroupsByLayerIndex.insert(std::make_pair(newIndex, std::move(layerGroup))); return; } } // We're not tracking the layer, indicating that it's currently disabled, so update it directly. layerGroup->updateLayerIndex(newIndex); } bool RenderOrchestrator::addLayerGroup(LayerGroupBasePtr layerGroup) { MLN_TRACE_FUNC(); const auto index = layerGroup->getLayerIndex(); const auto range = layerGroupsByLayerIndex.equal_range(index); bool found = false; for (auto it = range.first; it != range.second; ++it) { if (it->second == layerGroup) { assert(layerGroup->getLayerIndex() == it->first); found = true; // not added break; } } if (found) { return false; // not added } else { layerGroupsByLayerIndex.insert(std::make_pair(index, std::move(layerGroup))); return true; // added } } bool RenderOrchestrator::removeLayerGroup(const LayerGroupBasePtr& layerGroup) { MLN_TRACE_FUNC(); if (!layerGroup) { return false; } const auto range = layerGroupsByLayerIndex.equal_range(layerGroup->getLayerIndex()); for (auto it = range.first; it != range.second; ++it) { if (it->second == layerGroup) { layerGroupsByLayerIndex.erase(it); return true; } } return false; } size_t RenderOrchestrator::numLayerGroups() const noexcept { return layerGroupsByLayerIndex.size(); } void RenderOrchestrator::updateLayers(gfx::ShaderRegistry& shaders, gfx::Context& context, const TransformState& state, const std::shared_ptr& updateParameters, const PaintParameters& paintParameters, const RenderTree& renderTree) { MLN_TRACE_FUNC(); const bool isMapModeContinuous = updateParameters->mode == MapMode::Continuous; const auto transitionOptions = isMapModeContinuous ? updateParameters->transitionOptions : style::TransitionOptions(); const auto defDuration = isMapModeContinuous ? util::DEFAULT_TRANSITION_DURATION : Duration::zero(); const PropertyEvaluationParameters evalParameters{ getZoomHistory(), updateParameters->timePoint, transitionOptions.duration.value_or(defDuration), }; const auto& items = renderTree.getLayerRenderItemMap(); std::vector> changes; changes.reserve(items.size() * 3); for (auto& entry : orderedLayers) { auto& layer = entry.get(); const auto source = renderSources.find(layer.baseImpl->source); if (!layer.isLayerRenderable() && source != renderSources.end() && layer.needsHiddenLayerSourceCleanup(*source->second)) { layer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes); } } for (const auto& item : items) { auto& renderLayer = item.layer.get(); #if MLN_RENDER_BACKEND_OPENGL // Android Emulator: Goldfish is *very* broken. This will prevent a crash // inside the GL translation layer at the cost of emulator performance. if (androidGoldfishMitigationEnabled) { renderLayer.removeAllDrawables(); } #endif try { renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes); } catch (...) { recordRenderError(std::current_exception()); } } addChanges(changes); } void RenderOrchestrator::updateParticleLayers(gfx::ShaderRegistry& shaders, gfx::Context& context, const TransformState& state, const std::shared_ptr& updateParameters, const PaintParameters& paintParameters, const RenderTree& renderTree) { MLN_TRACE_FUNC(); const auto& items = renderTree.getLayerRenderItemMap(); std::vector> changes; changes.reserve(8); for (const auto& item : items) { auto& renderLayer = item.layer.get(); if (!renderLayer.isParticleAnimationLayer()) continue; #if MLN_RENDER_BACKEND_OPENGL if (androidGoldfishMitigationEnabled) { renderLayer.removeAllDrawables(); } #endif try { renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes); } catch (...) { recordRenderError(std::current_exception()); } } addChanges(changes); } void RenderOrchestrator::processChanges() { auto localChanges = std::move(pendingChanges); for (auto& change : localChanges) { change->execute(*this); } } bool RenderOrchestrator::addRenderTarget(RenderTargetPtr renderTarget) { auto it = std::ranges::find(renderTargets, renderTarget); if (it == renderTargets.end()) { renderTargets.emplace_back(renderTarget); return true; } else { return false; } } bool RenderOrchestrator::removeRenderTarget(const RenderTargetPtr& renderTarget) { auto it = std::ranges::find(renderTargets, renderTarget); if (it != renderTargets.end()) { renderTargets.erase(it); return true; } else { return false; } } void RenderOrchestrator::updateDebugLayerGroups(const RenderTree& renderTree, PaintParameters& parameters) { for (const RenderItem& item : renderTree.getSourceRenderItems()) { item.updateDebugDrawables(debugLayerGroups, parameters); } } void RenderOrchestrator::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& range) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation); observer->onGlyphsLoaded(fontStack, range); } void RenderOrchestrator::onGlyphsError(const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) { MLN_TRACE_FUNC(); advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation); std::stringstream ss; ss << "Failed to load glyph range "; if (glyphRange.type == FontPBF) { ss << glyphRange.first << "-" << glyphRange.second; } else { ss << (int)glyphRange.type << "(font file)"; } ss << " for font stack " << fontStackToString(fontStack) << ":( " << util::toString(error) << ")"; auto errorDetail = ss.str(); Log::Error(Event::Style, errorDetail); observer->onResourceError(error); } void RenderOrchestrator::onGlyphsRequested(const FontStack& fontStack, const GlyphRange& range) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation); observer->onGlyphsRequested(fontStack, range); } void RenderOrchestrator::onTileError(RenderSource& source, const OverscaledTileID& tileID, std::exception_ptr error) { MLN_TRACE_FUNC(); advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id); Log::Error(Event::Style, "Failed to load tile " + util::toString(tileID) + " for source " + source.baseImpl->id + ": " + util::toString(error)); observer->onResourceError(error); } void RenderOrchestrator::onTileChanged(RenderSource& source, const OverscaledTileID&) { MLN_TRACE_FUNC(); advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id); observer->onInvalidate(); } void RenderOrchestrator::onTileAction(RenderSource&, TileOperation op, const OverscaledTileID& id, const std::string& sourceID) { try { std::lock_guard lock(readinessDiagnosticsMutex); const auto sequence = ++readinessEventSequence; auto& state = sourceReadinessEvents[sourceID]; state.lastTileActionSequence = sequence; state.lastTileAction = tileOperationText(op); } catch (...) { // Tile action observation is best-effort; always forward the original observer callback. } observer->onTileAction(op, id, sourceID); } void RenderOrchestrator::onSymbolError(RenderSource& source, const std::string& message) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id); observer->onSymbolError(message); } void RenderOrchestrator::onStyleImageMissing(const std::string& id, const std::function& done) { MLN_TRACE_FUNC(); advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation); observer->onStyleImageMissing(id, done); } void RenderOrchestrator::onRemoveUnusedStyleImages(const std::vector& unusedImageIDs) { MLN_TRACE_FUNC(); if (!unusedImageIDs.empty()) { advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation); } observer->onRemoveUnusedStyleImages(unusedImageIDs); } } // namespace mln