#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 mbgl { using namespace style; static RendererObserver& nullObserver() { static RendererObserver observer; return observer; } Renderer::Impl::Impl(gfx::RendererBackend& backend_, float pixelRatio_, Scheduler& scheduler_, const optional programCacheDir_, const optional localFontFamily_) : backend(backend_) , scheduler(scheduler_) , observer(&nullObserver()) , pixelRatio(pixelRatio_) , programCacheDir(std::move(programCacheDir_)) , localFontFamily(std::move(localFontFamily_)) , lineAtlas(std::make_unique(Size{ 256, 512 })) , imageImpls(makeMutable>>()) , sourceImpls(makeMutable>>()) , layerImpls(makeMutable>>()) , renderLight(makeMutable()) , placement(std::make_unique(TransformState{}, MapMode::Static, TransitionOptions{}, true)) {} Renderer::Impl::~Impl() { assert(gfx::BackendScope::exists()); 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(); } } }; void Renderer::Impl::setObserver(RendererObserver* observer_) { observer = observer_ ? observer_ : &nullObserver(); } void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (!glyphManager) { glyphManager = std::make_unique(updateParameters.fileSource, std::make_unique(localFontFamily)); glyphManager->setObserver(this); } if (!imageManager) { imageManager = std::make_unique(); imageManager->setObserver(this); } if (updateParameters.mode != MapMode::Continuous) { // Reset zoom history state. zoomHistory.first = true; } assert(gfx::BackendScope::exists()); if (LayerManager::annotationsEnabled) { updateParameters.annotationManager.updateData(); } const bool zoomChanged = zoomHistory.update(updateParameters.transformState.getZoom(), updateParameters.timePoint); const bool isMapModeContinuous = updateParameters.mode == MapMode::Continuous; const TransitionOptions transitionOptions = isMapModeContinuous ? updateParameters.transitionOptions : TransitionOptions(); const TransitionParameters transitionParameters { updateParameters.timePoint, transitionOptions }; const PropertyEvaluationParameters evaluationParameters { zoomHistory, updateParameters.timePoint, transitionOptions.duration.value_or(isMapModeContinuous ? util::DEFAULT_TRANSITION_DURATION : Duration::zero()) }; const TileParameters tileParameters { updateParameters.pixelRatio, updateParameters.debugOptions, updateParameters.transformState, scheduler, updateParameters.fileSource, updateParameters.mode, updateParameters.annotationManager, *imageManager, *glyphManager, updateParameters.prefetchZoomDelta }; glyphManager->setURL(updateParameters.glyphURL); // Update light. const bool lightChanged = renderLight.impl != updateParameters.light; if (lightChanged) { renderLight.impl = updateParameters.light; renderLight.transition(transitionParameters); } if (lightChanged || zoomChanged || renderLight.hasTransition()) { renderLight.evaluate(evaluationParameters); } const ImageDifference imageDiff = diffImages(imageImpls, updateParameters.images); 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); } // 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) { hasImageDiff = imageManager->updateImage(entry.second.after) || hasImageDiff; } imageManager->notifyIfMissingImageAdded(); imageManager->setLoaded(updateParameters.spriteLoaded); const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters.layers); layerImpls = updateParameters.layers; // Remove render layers for removed layers. for (const auto& entry : layerDiff.removed) { renderLayers.erase(entry.first); } // Create render layers for newly added layers. for (const auto& entry : layerDiff.added) { renderLayers.emplace(entry.first, LayerManager::get()->createRenderLayer(entry.second)); } // Update render layers for changed layers. for (const auto& entry : layerDiff.changed) { renderLayers.at(entry.first)->setImpl(entry.second.after); } if (!layerDiff.removed.empty() || !layerDiff.added.empty() || !layerDiff.changed.empty()) { glyphManager->evict(fontStacks(*updateParameters.layers)); } // Update layers for class and zoom changes. for (const auto& entry : renderLayers) { RenderLayer& layer = *entry.second; const bool layerAddedOrChanged = layerDiff.added.count(entry.first) || layerDiff.changed.count(entry.first); if (layerAddedOrChanged) { layer.transition(transitionParameters); layer.update(); } if (layerAddedOrChanged || zoomChanged || layer.hasTransition() || layer.hasCrossfade()) { layer.evaluate(evaluationParameters); } } const SourceDifference sourceDiff = diffSources(sourceImpls, updateParameters.sources); 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) { std::unique_ptr renderSource = RenderSource::create(entry.second); renderSource->setObserver(this); renderSources.emplace(entry.first, std::move(renderSource)); } transformState = updateParameters.transformState; if (!staticData) { staticData = std::make_unique(backend.getContext(), pixelRatio, programCacheDir); } Color backgroundColor; struct RenderItem { RenderItem(RenderLayer& layer_, RenderSource* source_, uint32_t index_) : layer(layer_), source(source_), index(index_) {} std::reference_wrapper layer; RenderSource* source; uint32_t index; bool operator<(const RenderItem& other) const { return index < other.index; } }; std::set renderItems; std::vector renderItemsWithSymbols; // Update all sources and initialize renderItems. staticData->has3D = false; for (const auto& sourceImpl : *sourceImpls) { RenderSource* source = renderSources.at(sourceImpl->id).get(); std::vector> filteredLayersForSource; filteredLayersForSource.reserve(layerImpls->size()); bool sourceNeedsRendering = false; bool sourceNeedsRelayout = false; uint32_t index = 0u; const auto begin = layerImpls->begin(); const auto end = layerImpls->end(); for (auto it = begin; it != end; ++it, ++index) { const Immutable& layerImpl = *it; RenderLayer* layer = getRenderLayer(layerImpl->id); const auto* layerInfo = layerImpl->getTypeInfo(); const bool layerNeedsRendering = layer->needsRendering(zoomHistory.lastZoom); staticData->has3D = (staticData->has3D || layerInfo->pass3d == LayerTypeInfo::Pass3D::Required); if (layerInfo->source != LayerTypeInfo::Source::NotRequired) { if (layerImpl->source == sourceImpl->id) { sourceNeedsRelayout = (sourceNeedsRelayout || hasImageDiff || hasLayoutDifference(layerDiff, layerImpl->id)); if (layerNeedsRendering) { sourceNeedsRendering = true; filteredLayersForSource.push_back(layerImpl); renderItems.emplace(*layer, source, index); } } continue; } // Handle layers without source. if (layerNeedsRendering && sourceImpl.get() == sourceImpls->at(0).get()) { if (!backend.contextIsShared() && layerImpl.get() == layerImpls->at(0).get()) { const auto& solidBackground = layer->getSolidBackground(); if (solidBackground) { backgroundColor = *solidBackground; continue; // This layer is shown with background color, and it shall not be added to render items. } } renderItems.emplace(*layer, nullptr, index); } } source->update(sourceImpl, filteredLayersForSource, sourceNeedsRendering, sourceNeedsRelayout, tileParameters); } bool loaded = updateParameters.styleLoaded && isLoaded(); if (updateParameters.mode != MapMode::Continuous && !loaded) { return; } if (renderState == RenderState::Never) { observer->onWillStartRenderingMap(); } observer->onWillStartRenderingFrame(); // Set render tiles to the render items. for (auto& renderItem : renderItems) { if (!renderItem.source) { continue; } RenderLayer& renderLayer = renderItem.layer; renderLayer.setRenderTiles(renderItem.source->getRenderTiles(), updateParameters.transformState); if (const RenderLayerSymbolInterface* symbolLayer = renderLayer.getSymbolInterface()) { renderItemsWithSymbols.push_back(symbolLayer); } } { if (updateParameters.mode != MapMode::Continuous) { // TODO: Think about right way for symbol index to handle still rendering crossTileSymbolIndex.reset(); } bool symbolBucketsChanged = false; const bool placementChanged = !placement->stillRecent(updateParameters.timePoint); std::set usedSymbolLayers; if (placementChanged) { placement = std::make_unique( updateParameters.transformState, updateParameters.mode, updateParameters.transitionOptions, updateParameters.crossSourceCollisions, std::move(placement)); } for (auto it = renderItemsWithSymbols.rbegin(); it != renderItemsWithSymbols.rend(); ++it) { const RenderLayerSymbolInterface *symbolLayer = *it; if (crossTileSymbolIndex.addLayer(*symbolLayer, updateParameters.transformState.getLatLng().longitude())) symbolBucketsChanged = true; if (placementChanged) { usedSymbolLayers.insert(symbolLayer->layerID()); mat4 projMatrix; updateParameters.transformState.getProjMatrix(projMatrix); placement->placeLayer(*symbolLayer, projMatrix, updateParameters.debugOptions & MapDebugOptions::Collision); } } if (placementChanged) { placement->commit(updateParameters.timePoint); crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers); updateFadingTiles(); } else { placement->setStale(); } if (placementChanged || symbolBucketsChanged) { for (auto it = renderItemsWithSymbols.rbegin(); it != renderItemsWithSymbols.rend(); ++it) { const RenderLayerSymbolInterface *symbolLayer = *it; placement->updateLayerOpacities(*symbolLayer); } } } PaintParameters parameters { backend.getContext(), pixelRatio, backend, updateParameters, renderLight.getEvaluated(), *staticData, *imageManager, *lineAtlas, placement->getVariableOffsets() }; parameters.symbolFadeChange = placement->symbolFadeChange(updateParameters.timePoint); // - UPLOAD PASS ------------------------------------------------------------------------------- // Uploads all required buffers and images before we do any actual rendering. { const auto debugGroup(parameters.encoder->createDebugGroup("upload")); parameters.imageManager.upload(parameters.context); parameters.lineAtlas.upload(parameters.context); // Update all clipping IDs + upload buckets. for (const auto& entry : renderSources) { if (entry.second->isEnabled()) { entry.second->startRender(parameters); } } } // - 3D PASS ------------------------------------------------------------------------------------- // Renders any 3D layers bottom-to-top to unique FBOs with texture attachments, but share the same // depth rbo between them. if (parameters.staticData.has3D) { parameters.staticData.backendSize = parameters.backend.getDefaultRenderable().getSize(); const auto debugGroup(parameters.encoder->createDebugGroup("3d")); parameters.pass = RenderPass::Pass3D; if (!parameters.staticData.depthRenderbuffer || parameters.staticData.depthRenderbuffer->getSize() != parameters.staticData.backendSize) { parameters.staticData.depthRenderbuffer = parameters.context.createRenderbuffer(parameters.staticData.backendSize); } parameters.staticData.depthRenderbuffer->setShouldClear(true); uint32_t i = static_cast(renderItems.size()) - 1; for (auto it = renderItems.begin(); it != renderItems.end(); ++it, --i) { parameters.currentLayer = i; RenderLayer& renderLayer = it->layer; if (renderLayer.hasRenderPass(parameters.pass)) { const auto layerDebugGroup(parameters.encoder->createDebugGroup(renderLayer.getID().c_str())); renderLayer.render(parameters, it->source); } } } // - CLEAR ------------------------------------------------------------------------------------- // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any // tiles whatsoever. { optional color; if (parameters.debugOptions & MapDebugOptions::Overdraw) { color = Color::black(); } else if (!backend.contextIsShared()) { color = backgroundColor; } parameters.renderPass = parameters.encoder->createRenderPass("main buffer", { parameters.backend.getDefaultRenderable(), color, 1, 0 }); } // - CLIPPING MASKS ---------------------------------------------------------------------------- // Draws the clipping masks to the stencil buffer. { const auto debugGroup(parameters.renderPass->createDebugGroup("clipping masks")); static const Properties<>::PossiblyEvaluated properties {}; static const ClippingMaskProgram::Binders paintAttributeData(properties, 0); for (const auto& clipID : parameters.clipIDGenerator.getClipIDs()) { auto& program = parameters.staticData.programs.clippingMask; program.draw( parameters.context, *parameters.renderPass, gfx::Triangles(), gfx::DepthMode::disabled(), gfx::StencilMode { gfx::StencilMode::Always(), static_cast(clipID.second.reference.to_ulong()), 0b11111111, gfx::StencilOpType::Keep, gfx::StencilOpType::Keep, gfx::StencilOpType::Replace }, gfx::ColorMode::disabled(), gfx::CullFaceMode::disabled(), parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.tileTriangleSegments, program.computeAllUniformValues( ClippingMaskProgram::LayoutUniformValues { uniforms::matrix::Value( parameters.matrixForTile(clipID.first) ), }, paintAttributeData, properties, parameters.state.getZoom() ), program.computeAllAttributeBindings( parameters.staticData.tileVertexBuffer, paintAttributeData, properties ), ClippingMaskProgram::TextureBindings{}, "clipping" ); } } #if not defined(NDEBUG) // Render tile clip boundaries, using stencil buffer to calculate fill color. if (parameters.debugOptions & MapDebugOptions::StencilClip) { parameters.context.visualizeStencilBuffer(); return; } #endif // Actually render the layers parameters.depthRangeSize = 1 - (renderItems.size() + 2) * parameters.numSublayers * parameters.depthEpsilon; // - OPAQUE PASS ------------------------------------------------------------------------------- // Render everything top-to-bottom by using reverse iterators. Render opaque objects first. { parameters.pass = RenderPass::Opaque; const auto debugGroup(parameters.renderPass->createDebugGroup("opaque")); uint32_t i = 0; for (auto it = renderItems.rbegin(); it != renderItems.rend(); ++it, ++i) { parameters.currentLayer = i; RenderLayer& renderLayer = it->layer; if (renderLayer.hasRenderPass(parameters.pass)) { const auto layerDebugGroup(parameters.renderPass->createDebugGroup(renderLayer.getID().c_str())); renderLayer.render(parameters, it->source); } } } // - TRANSLUCENT PASS -------------------------------------------------------------------------- // Make a second pass, rendering translucent objects. This time, we render bottom-to-top. { parameters.pass = RenderPass::Translucent; const auto debugGroup(parameters.renderPass->createDebugGroup("translucent")); uint32_t i = static_cast(renderItems.size()) - 1; for (auto it = renderItems.begin(); it != renderItems.end(); ++it, --i) { parameters.currentLayer = i; RenderLayer& renderLayer = it->layer; if (renderLayer.hasRenderPass(parameters.pass)) { const auto layerDebugGroup(parameters.renderPass->createDebugGroup(renderLayer.getID().c_str())); renderLayer.render(parameters, it->source); } } } // - DEBUG PASS -------------------------------------------------------------------------------- // Renders debug overlays. { const auto debugGroup(parameters.renderPass->createDebugGroup("debug")); // Finalize the rendering, e.g. by calling debug render calls per tile. // This guarantees that we have at least one function per tile called. // When only rendering layers via the stylesheet, it's possible that we don't // ever visit a tile during rendering. for (const auto& entry : renderSources) { if (entry.second->isEnabled()) { entry.second->finishRender(parameters); } } } #if not defined(NDEBUG) // Render the depth buffer. if (parameters.debugOptions & MapDebugOptions::DepthBuffer) { parameters.context.visualizeDepthBuffer(parameters.depthRangeSize); } #endif observer->onDidFinishRenderingFrame( loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial, updateParameters.mode == MapMode::Continuous && hasTransitions(parameters.timePoint) ); if (!loaded) { renderState = RenderState::Partial; } else if (renderState != RenderState::Fully) { renderState = RenderState::Fully; observer->onDidFinishRenderingMap(); } // CommandEncoder destructor submits render commands. } std::vector Renderer::Impl::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options) const { std::vector layers; if (options.layerIDs) { for (const auto& layerID : *options.layerIDs) { if (const RenderLayer* layer = getRenderLayer(layerID)) { layers.emplace_back(layer); } } } else { for (const auto& entry : renderLayers) { layers.emplace_back(entry.second.get()); } } return queryRenderedFeatures(geometry, options, layers); } void Renderer::Impl::queryRenderedSymbols(std::unordered_map>& resultsByLayer, const ScreenLineString& geometry, const std::vector& layers, const RenderedQueryOptions& options) const { auto renderedSymbols = placement->getCollisionIndex().queryRenderedSymbols(geometry); std::vector> bucketQueryData; for (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::sort(bucketQueryData.begin(), bucketQueryData.end(), [](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, layers, queryData.tileID, queryData.featureSortOrder); for (auto layer : bucketSymbols) { auto& resultFeatures = resultsByLayer[layer.first]; std::move(layer.second.begin(), layer.second.end(), std::inserter(resultFeatures, resultFeatures.end())); } } } std::vector Renderer::Impl::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options, const std::vector& layers) const { std::unordered_set sourceIDs; for (const RenderLayer* layer : layers) { sourceIDs.emplace(layer->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, layers, options, projMatrix); std::move(sourceResults.begin(), sourceResults.end(), std::inserter(resultsByLayer, resultsByLayer.begin())); } } queryRenderedSymbols(resultsByLayer, geometry, layers, options); std::vector result; if (resultsByLayer.empty()) { return result; } // Combine all results based on the style layer renderItems. for (const auto& layerImpl : *layerImpls) { const RenderLayer* layer = getRenderLayer(layerImpl->id); if (!layer->needsRendering(zoomHistory.lastZoom)) { continue; } auto it = resultsByLayer.find(layer->baseImpl->id); if (it != resultsByLayer.end()) { std::move(it->second.begin(), it->second.end(), std::back_inserter(result)); } } return result; } std::vector Renderer::Impl::queryShapeAnnotations(const ScreenLineString& geometry) const { assert(LayerManager::annotationsEnabled); std::vector 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_back(layer); } } } return queryRenderedFeatures(geometry, options, shapeAnnotationLayers); } std::vector Renderer::Impl::querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options) const { const RenderSource* source = getRenderSource(sourceID); if (!source) return {}; return source->querySourceFeatures(options); } FeatureExtensionValue Renderer::Impl::queryFeatureExtensions(const std::string& sourceID, const Feature& feature, const std::string& extension, const std::string& extensionField, const optional>& args) const { if (RenderSource* renderSource = getRenderSource(sourceID)) { return renderSource->queryFeatureExtensions(feature, extension, extensionField, args); } return {}; } void Renderer::Impl::reduceMemoryUse() { assert(gfx::BackendScope::exists()); for (const auto& entry : renderSources) { entry.second->reduceMemoryUse(); } backend.getContext().performCleanup(); observer->onInvalidate(); } void Renderer::Impl::dumDebugLogs() { for (const auto& entry : renderSources) { entry.second->dumpDebugLogs(); } if (imageManager) { imageManager->dumpDebugLogs(); } } RenderLayer* Renderer::Impl::getRenderLayer(const std::string& id) { auto it = renderLayers.find(id); return it != renderLayers.end() ? it->second.get() : nullptr; } const RenderLayer* Renderer::Impl::getRenderLayer(const std::string& id) const { auto it = renderLayers.find(id); return it != renderLayers.end() ? it->second.get() : nullptr; } RenderSource* Renderer::Impl::getRenderSource(const std::string& id) const { auto it = renderSources.find(id); return it != renderSources.end() ? it->second.get() : nullptr; } bool Renderer::Impl::hasTransitions(TimePoint timePoint) const { if (renderLight.hasTransition()) { return true; } for (const auto& entry : renderLayers) { if (entry.second->hasTransition()) { return true; } } 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()) { return false; } } if (!imageManager || !imageManager->isLoaded()) { return false; } return true; } void Renderer::Impl::onGlyphsError(const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) { Log::Error(Event::Style, "Failed to load glyph range %d-%d for font stack %s: %s", glyphRange.first, glyphRange.second, fontStackToString(fontStack).c_str(), util::toString(error).c_str()); observer->onResourceError(error); } void Renderer::Impl::onTileError(RenderSource& source, const OverscaledTileID& tileID, std::exception_ptr error) { Log::Error(Event::Style, "Failed to load tile %s for source %s: %s", util::toString(tileID).c_str(), source.baseImpl->id.c_str(), util::toString(error).c_str()); observer->onResourceError(error); } void Renderer::Impl::onTileChanged(RenderSource&, const OverscaledTileID&) { observer->onInvalidate(); } void Renderer::Impl::onStyleImageMissing(const std::string& id, std::function done) { observer->onStyleImageMissing(id, std::move(done)); } } // namespace mbgl