summaryrefslogtreecommitdiff
path: root/src/mbgl/renderer/renderer_impl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/renderer/renderer_impl.cpp')
-rw-r--r--src/mbgl/renderer/renderer_impl.cpp815
1 files changed, 815 insertions, 0 deletions
diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp
new file mode 100644
index 0000000000..4bed0e251b
--- /dev/null
+++ b/src/mbgl/renderer/renderer_impl.cpp
@@ -0,0 +1,815 @@
+#include <mbgl/annotation/annotation_manager.hpp>
+#include <mbgl/renderer/renderer_impl.hpp>
+#include <mbgl/renderer/renderer_backend.hpp>
+#include <mbgl/renderer/renderer_observer.hpp>
+#include <mbgl/renderer/render_source.hpp>
+#include <mbgl/renderer/render_layer.hpp>
+#include <mbgl/renderer/render_static_data.hpp>
+#include <mbgl/renderer/update_parameters.hpp>
+#include <mbgl/renderer/paint_parameters.hpp>
+#include <mbgl/renderer/transition_parameters.hpp>
+#include <mbgl/renderer/property_evaluation_parameters.hpp>
+#include <mbgl/renderer/tile_parameters.hpp>
+#include <mbgl/renderer/render_tile.hpp>
+#include <mbgl/renderer/layers/render_background_layer.hpp>
+#include <mbgl/renderer/layers/render_custom_layer.hpp>
+#include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp>
+#include <mbgl/renderer/style_diff.hpp>
+#include <mbgl/renderer/query.hpp>
+#include <mbgl/renderer/backend_scope.hpp>
+#include <mbgl/renderer/image_manager.hpp>
+#include <mbgl/gl/debugging.hpp>
+#include <mbgl/geometry/line_atlas.hpp>
+#include <mbgl/style/source_impl.hpp>
+#include <mbgl/style/transition_options.hpp>
+#include <mbgl/text/glyph_manager.hpp>
+#include <mbgl/tile/tile.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/logging.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+static RendererObserver& nullObserver() {
+ static RendererObserver observer;
+ return observer;
+}
+
+Renderer::Impl::Impl(RendererBackend& backend_,
+ float pixelRatio_,
+ FileSource& fileSource_,
+ Scheduler& scheduler_,
+ GLContextMode contextMode_,
+ const optional<std::string> programCacheDir_,
+ const optional<std::string> localFontFamily_)
+ : backend(backend_)
+ , scheduler(scheduler_)
+ , fileSource(fileSource_)
+ , observer(&nullObserver())
+ , contextMode(contextMode_)
+ , pixelRatio(pixelRatio_)
+ , programCacheDir(programCacheDir_)
+ , glyphManager(std::make_unique<GlyphManager>(fileSource, std::make_unique<LocalGlyphRasterizer>(localFontFamily_)))
+ , imageManager(std::make_unique<ImageManager>())
+ , lineAtlas(std::make_unique<LineAtlas>(Size{ 256, 512 }))
+ , imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>())
+ , sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>())
+ , layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>())
+ , renderLight(makeMutable<Light::Impl>())
+ , placement(std::make_unique<Placement>(TransformState{}, MapMode::Static)) {
+ glyphManager->setObserver(this);
+}
+
+Renderer::Impl::~Impl() {
+ assert(BackendScope::exists());
+
+ if (contextLost) {
+ // Signal all RenderCustomLayers that the context was lost
+ // before cleaning up
+ for (const auto& entry : renderLayers) {
+ RenderLayer& layer = *entry.second;
+ if (layer.is<RenderCustomLayer>()) {
+ layer.as<RenderCustomLayer>()->markContextDestroyed();
+ }
+ }
+ }
+};
+
+void Renderer::Impl::setObserver(RendererObserver* observer_) {
+ observer = observer_ ? observer_ : &nullObserver();
+}
+
+void Renderer::Impl::render(const UpdateParameters& updateParameters) {
+ if (updateParameters.mode != MapMode::Continuous) {
+ // Don't load/render anyting in still mode until explicitly requested.
+ if (!updateParameters.stillImageRequest) {
+ return;
+ }
+
+ // Reset zoom history state.
+ zoomHistory.first = true;
+ }
+
+ assert(BackendScope::exists());
+
+ updateParameters.annotationManager.updateData();
+
+ const bool zoomChanged = zoomHistory.update(updateParameters.transformState.getZoom(), updateParameters.timePoint);
+
+ const TransitionParameters transitionParameters {
+ updateParameters.timePoint,
+ updateParameters.mode == MapMode::Continuous ? updateParameters.transitionOptions : TransitionOptions()
+ };
+
+ const PropertyEvaluationParameters evaluationParameters {
+ zoomHistory,
+ updateParameters.timePoint,
+ updateParameters.mode == MapMode::Continuous ? util::DEFAULT_TRANSITION_DURATION : Duration::zero()
+ };
+
+ const TileParameters tileParameters {
+ updateParameters.pixelRatio,
+ updateParameters.debugOptions,
+ updateParameters.transformState,
+ scheduler,
+ 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;
+
+ // 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) {
+ imageManager->updateImage(entry.second.after);
+ }
+
+ 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, RenderLayer::create(entry.second));
+ }
+
+ // Update render layers for changed layers.
+ for (const auto& entry : layerDiff.changed) {
+ renderLayers.at(entry.first)->setImpl(entry.second.after);
+ }
+
+ // Update layers for class and zoom changes.
+ for (const auto& entry : renderLayers) {
+ RenderLayer& layer = *entry.second;
+ const bool layerAdded = layerDiff.added.count(entry.first);
+ const bool layerChanged = layerDiff.changed.count(entry.first);
+
+ if (layerAdded || layerChanged) {
+ layer.transition(transitionParameters);
+ }
+
+ if (layerAdded || layerChanged || zoomChanged || layer.hasTransition()) {
+ 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 = RenderSource::create(entry.second);
+ renderSource->setObserver(this);
+ renderSources.emplace(entry.first, std::move(renderSource));
+ }
+
+ const bool hasImageDiff = !(imageDiff.added.empty() && imageDiff.removed.empty() && imageDiff.changed.empty());
+
+ // Update all sources.
+ for (const auto& source : *sourceImpls) {
+ std::vector<Immutable<Layer::Impl>> filteredLayers;
+ bool needsRendering = false;
+ bool needsRelayout = false;
+
+ for (const auto& layer : *layerImpls) {
+ if (layer->type == LayerType::Background ||
+ layer->type == LayerType::Custom ||
+ layer->source != source->id) {
+ continue;
+ }
+
+ if (!needsRendering && getRenderLayer(layer->id)->needsRendering(zoomHistory.lastZoom)) {
+ needsRendering = true;
+ }
+
+ if (!needsRelayout && (hasImageDiff || hasLayoutDifference(layerDiff, layer->id))) {
+ needsRelayout = true;
+ }
+
+ filteredLayers.push_back(layer);
+ }
+
+ renderSources.at(source->id)->update(source,
+ filteredLayers,
+ needsRendering,
+ needsRelayout,
+ tileParameters);
+ }
+
+ transformState = updateParameters.transformState;
+
+ if (!staticData) {
+ staticData = std::make_unique<RenderStaticData>(backend.getContext(), pixelRatio, programCacheDir);
+ }
+
+ PaintParameters parameters {
+ backend.getContext(),
+ pixelRatio,
+ contextMode,
+ backend,
+ updateParameters,
+ renderLight.getEvaluated(),
+ *staticData,
+ *imageManager,
+ *lineAtlas
+ };
+
+ bool loaded = updateParameters.styleLoaded && isLoaded();
+ if (updateParameters.mode != MapMode::Continuous && !loaded) {
+ return;
+ }
+
+ if (renderState == RenderState::Never) {
+ observer->onWillStartRenderingMap();
+ }
+
+ observer->onWillStartRenderingFrame();
+
+ backend.updateAssumedState();
+
+ if (parameters.contextMode == GLContextMode::Shared) {
+ parameters.context.setDirtyState();
+ }
+
+ Color backgroundColor;
+
+ class RenderItem {
+ public:
+ RenderLayer& layer;
+ RenderSource* source;
+ };
+
+ std::vector<RenderItem> order;
+
+ for (auto& layerImpl : *layerImpls) {
+ RenderLayer* layer = getRenderLayer(layerImpl->id);
+ assert(layer);
+
+ if (!parameters.staticData.has3D && layer->is<RenderFillExtrusionLayer>()) {
+ parameters.staticData.has3D = true;
+ }
+
+ if (!layer->needsRendering(zoomHistory.lastZoom)) {
+ continue;
+ }
+
+ if (const RenderBackgroundLayer* background = layer->as<RenderBackgroundLayer>()) {
+ const BackgroundPaintProperties::PossiblyEvaluated& paint = background->evaluated;
+ if (parameters.contextMode == GLContextMode::Unique
+ && layerImpl.get() == layerImpls->at(0).get()
+ && paint.get<BackgroundPattern>().from.empty()) {
+ // This is a solid background. We can use glClear().
+ backgroundColor = paint.get<BackgroundColor>() * paint.get<BackgroundOpacity>();
+ } else {
+ // This is a textured background, or not the bottommost layer. We need to render it with a quad.
+ order.emplace_back(RenderItem { *layer, nullptr });
+ }
+ continue;
+ }
+
+ if (layer->is<RenderCustomLayer>()) {
+ order.emplace_back(RenderItem { *layer, nullptr });
+ continue;
+ }
+
+ RenderSource* source = getRenderSource(layer->baseImpl->source);
+ if (!source) {
+ Log::Warning(Event::Render, "can't find source for layer '%s'", layer->getID().c_str());
+ continue;
+ }
+
+ const bool symbolLayer = layer->is<RenderSymbolLayer>();
+
+ auto sortedTiles = source->getRenderTiles();
+ if (symbolLayer) {
+ // Sort symbol tiles in opposite y position, so tiles with overlapping symbols are drawn
+ // on top of each other, with lower symbols being drawn on top of higher symbols.
+ std::sort(sortedTiles.begin(), sortedTiles.end(), [&](const RenderTile& a, const RenderTile& b) {
+ Point<float> pa(a.id.canonical.x, a.id.canonical.y);
+ Point<float> pb(b.id.canonical.x, b.id.canonical.y);
+
+ auto par = util::rotate(pa, parameters.state.getAngle());
+ auto pbr = util::rotate(pb, parameters.state.getAngle());
+
+ return std::tie(b.id.canonical.z, par.y, par.x) < std::tie(a.id.canonical.z, pbr.y, pbr.x);
+ });
+ } 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;
+ for (auto& sortedTile : sortedTiles) {
+ auto& tile = sortedTile.get();
+ if (!tile.tile.isRenderable()) {
+ continue;
+ }
+
+ auto bucket = tile.tile.getBucket(*layer->baseImpl);
+ if (bucket) {
+ sortedTilesForInsertion.emplace_back(tile);
+ tile.used = true;
+
+ // We only need clipping when we're _not_ drawing a symbol layer.
+ if (!symbolLayer) {
+ tile.needsClipping = true;
+ }
+ }
+ }
+ layer->setRenderTiles(std::move(sortedTilesForInsertion));
+ order.emplace_back(RenderItem { *layer, source });
+ }
+
+ bool symbolBucketsChanged = false;
+ if (parameters.mapMode != MapMode::Continuous) {
+ // TODO: Think about right way for symbol index to handle still rendering
+ crossTileSymbolIndex.reset();
+ }
+ for (auto it = order.rbegin(); it != order.rend(); ++it) {
+ if (it->layer.is<RenderSymbolLayer>()) {
+ if (crossTileSymbolIndex.addLayer(*it->layer.as<RenderSymbolLayer>())) symbolBucketsChanged = true;
+ }
+ }
+
+ bool placementChanged = false;
+ if (!placement->stillRecent(parameters.timePoint)) {
+ auto newPlacement = std::make_unique<Placement>(parameters.state, parameters.mapMode);
+ for (auto it = order.rbegin(); it != order.rend(); ++it) {
+ if (it->layer.is<RenderSymbolLayer>()) {
+ newPlacement->placeLayer(*it->layer.as<RenderSymbolLayer>(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision);
+ }
+ }
+
+ placementChanged = newPlacement->commit(*placement, parameters.timePoint);
+ if (placementChanged || symbolBucketsChanged) {
+ placement = std::move(newPlacement);
+ }
+
+ placement->setRecent(parameters.timePoint);
+
+ updateFadingTiles();
+ } else {
+ placement->setStale();
+ }
+
+ parameters.symbolFadeChange = placement->symbolFadeChange(parameters.timePoint);
+
+ if (placementChanged || symbolBucketsChanged) {
+ for (auto it = order.rbegin(); it != order.rend(); ++it) {
+ if (it->layer.is<RenderSymbolLayer>()) {
+ placement->updateLayerOpacities(*it->layer.as<RenderSymbolLayer>());
+ }
+ }
+ }
+
+ // - UPLOAD PASS -------------------------------------------------------------------------------
+ // Uploads all required buffers and images before we do any actual rendering.
+ {
+ MBGL_DEBUG_GROUP(parameters.context, "upload");
+
+ parameters.imageManager.upload(parameters.context, 0);
+ parameters.lineAtlas.upload(parameters.context, 0);
+
+ // 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.getFramebufferSize();
+
+ MBGL_DEBUG_GROUP(parameters.context, "3d");
+ parameters.pass = RenderPass::Pass3D;
+
+ if (!parameters.staticData.depthRenderbuffer ||
+ parameters.staticData.depthRenderbuffer->size != parameters.staticData.backendSize) {
+ parameters.staticData.depthRenderbuffer =
+ parameters.context.createRenderbuffer<gl::RenderbufferType::DepthComponent>(parameters.staticData.backendSize);
+ }
+ parameters.staticData.depthRenderbuffer->shouldClear(true);
+
+ uint32_t i = static_cast<uint32_t>(order.size()) - 1;
+ for (auto it = order.begin(); it != order.end(); ++it, --i) {
+ parameters.currentLayer = i;
+ if (it->layer.hasRenderPass(parameters.pass)) {
+ MBGL_DEBUG_GROUP(parameters.context, it->layer.getID());
+ it->layer.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.
+ {
+ using namespace gl::value;
+
+ MBGL_DEBUG_GROUP(parameters.context, "clear");
+ parameters.backend.bind();
+ if (parameters.debugOptions & MapDebugOptions::Overdraw) {
+ parameters.context.clear(Color::black(), ClearDepth::Default, ClearStencil::Default);
+ } else if (parameters.contextMode == GLContextMode::Shared) {
+ parameters.context.clear({}, ClearDepth::Default, ClearStencil::Default);
+ } else {
+ parameters.context.clear(backgroundColor, ClearDepth::Default, ClearStencil::Default);
+ }
+ }
+
+ // - CLIPPING MASKS ----------------------------------------------------------------------------
+ // Draws the clipping masks to the stencil buffer.
+ {
+ MBGL_DEBUG_GROUP(parameters.context, "clipping masks");
+
+ static const style::FillPaintProperties::PossiblyEvaluated properties {};
+ static const FillProgram::PaintPropertyBinders paintAttibuteData(properties, 0);
+
+ for (const auto& clipID : parameters.clipIDGenerator.getClipIDs()) {
+ parameters.staticData.programs.fill.get(properties).draw(
+ parameters.context,
+ gl::Triangles(),
+ gl::DepthMode::disabled(),
+ gl::StencilMode {
+ gl::StencilMode::Always(),
+ static_cast<int32_t>(clipID.second.reference.to_ulong()),
+ 0b11111111,
+ gl::StencilMode::Keep,
+ gl::StencilMode::Keep,
+ gl::StencilMode::Replace
+ },
+ gl::ColorMode::disabled(),
+ FillProgram::UniformValues {
+ uniforms::u_matrix::Value{ parameters.matrixForTile(clipID.first) },
+ uniforms::u_world::Value{ parameters.context.viewport.getCurrentValue().size },
+ },
+ parameters.staticData.tileVertexBuffer,
+ parameters.staticData.quadTriangleIndexBuffer,
+ parameters.staticData.tileTriangleSegments,
+ paintAttibuteData,
+ properties,
+ parameters.state.getZoom(),
+ "clipping"
+ );
+ }
+ }
+
+#if not MBGL_USE_GLES2 and not defined(NDEBUG)
+ // Render tile clip boundaries, using stencil buffer to calculate fill color.
+ if (parameters.debugOptions & MapDebugOptions::StencilClip) {
+ parameters.context.setStencilMode(gl::StencilMode::disabled());
+ parameters.context.setDepthMode(gl::DepthMode::disabled());
+ parameters.context.setColorMode(gl::ColorMode::unblended());
+ parameters.context.program = 0;
+
+ // Reset the value in case someone else changed it, or it's dirty.
+ parameters.context.pixelTransferStencil = gl::value::PixelTransferStencil::Default;
+
+ // Read the stencil buffer
+ const auto viewport = parameters.context.viewport.getCurrentValue();
+ auto image = parameters.context.readFramebuffer<AlphaImage, gl::TextureFormat::Stencil>(viewport.size, false);
+
+ // Scale the Stencil buffer to cover the entire color space.
+ auto it = image.data.get();
+ auto end = it + viewport.size.width * viewport.size.height;
+ const auto factor = 255.0f / *std::max_element(it, end);
+ for (; it != end; ++it) {
+ *it *= factor;
+ }
+
+ parameters.context.pixelZoom = { 1, 1 };
+ parameters.context.rasterPos = { -1, -1, 0, 1 };
+ parameters.context.drawPixels(image);
+
+ return;
+ }
+#endif
+
+ // Actually render the layers
+
+ parameters.depthRangeSize = 1 - (order.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;
+ MBGL_DEBUG_GROUP(parameters.context, "opaque");
+
+ uint32_t i = 0;
+ for (auto it = order.rbegin(); it != order.rend(); ++it, ++i) {
+ parameters.currentLayer = i;
+ if (it->layer.hasRenderPass(parameters.pass)) {
+ MBGL_DEBUG_GROUP(parameters.context, it->layer.getID());
+ it->layer.render(parameters, it->source);
+ }
+ }
+ }
+
+ // - TRANSLUCENT PASS --------------------------------------------------------------------------
+ // Make a second pass, rendering translucent objects. This time, we render bottom-to-top.
+ {
+ parameters.pass = RenderPass::Translucent;
+ MBGL_DEBUG_GROUP(parameters.context, "translucent");
+
+ uint32_t i = static_cast<uint32_t>(order.size()) - 1;
+ for (auto it = order.begin(); it != order.end(); ++it, --i) {
+ parameters.currentLayer = i;
+ if (it->layer.hasRenderPass(parameters.pass)) {
+ MBGL_DEBUG_GROUP(parameters.context, it->layer.getID());
+ it->layer.render(parameters, it->source);
+ }
+ }
+ }
+
+ // - DEBUG PASS --------------------------------------------------------------------------------
+ // Renders debug overlays.
+ {
+ MBGL_DEBUG_GROUP(parameters.context, "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 MBGL_USE_GLES2 and not defined(NDEBUG)
+ // Render the depth buffer.
+ if (parameters.debugOptions & MapDebugOptions::DepthBuffer) {
+ parameters.context.setStencilMode(gl::StencilMode::disabled());
+ parameters.context.setDepthMode(gl::DepthMode::disabled());
+ parameters.context.setColorMode(gl::ColorMode::unblended());
+ parameters.context.program = 0;
+
+ // Scales the values in the depth buffer so that they cover the entire grayscale range. This
+ // makes it easier to spot tiny differences.
+ const float base = 1.0f / (1.0f - parameters.depthRangeSize);
+ parameters.context.pixelTransferDepth = { base, 1.0f - base };
+
+ // Read the stencil buffer
+ auto viewport = parameters.context.viewport.getCurrentValue();
+ auto image = parameters.context.readFramebuffer<AlphaImage, gl::TextureFormat::Depth>(viewport.size, false);
+
+ parameters.context.pixelZoom = { 1, 1 };
+ parameters.context.rasterPos = { -1, -1, 0, 1 };
+ parameters.context.drawPixels(image);
+ }
+#endif
+
+ // TODO: Find a better way to unbind VAOs after we're done with them without introducing
+ // unnecessary bind(0)/bind(N) sequences.
+ {
+ MBGL_DEBUG_GROUP(parameters.context, "cleanup");
+
+ parameters.context.activeTextureUnit = 1;
+ parameters.context.texture[1] = 0;
+ parameters.context.activeTextureUnit = 0;
+ parameters.context.texture[0] = 0;
+
+ parameters.context.bindVertexArray = 0;
+ }
+
+ 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();
+ }
+
+ // Cleanup only after signaling completion
+ parameters.context.performCleanup();
+}
+
+std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options) const {
+ std::vector<const RenderLayer*> 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);
+}
+
+std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options, const std::vector<const RenderLayer*>& layers) const {
+ std::unordered_set<std::string> sourceIDs;
+ for (const RenderLayer* layer : layers) {
+ sourceIDs.emplace(layer->baseImpl->source);
+ }
+
+ std::unordered_map<std::string, std::vector<Feature>> resultsByLayer;
+ for (const auto& sourceID : sourceIDs) {
+ if (RenderSource* renderSource = getRenderSource(sourceID)) {
+ auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options, placement->getCollisionIndex());
+ std::move(sourceResults.begin(), sourceResults.end(), std::inserter(resultsByLayer, resultsByLayer.begin()));
+ }
+ }
+
+ std::vector<Feature> result;
+
+ if (resultsByLayer.empty()) {
+ return result;
+ }
+
+ // Combine all results based on the style layer order.
+ 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<Feature> Renderer::Impl::queryShapeAnnotations(const ScreenLineString& geometry) const {
+ std::vector<const RenderLayer*> 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<Feature> Renderer::Impl::querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options) const {
+ const RenderSource* source = getRenderSource(sourceID);
+ if (!source) return {};
+
+ return source->querySourceFeatures(options);
+}
+
+void Renderer::Impl::onLowMemory() {
+ assert(BackendScope::exists());
+ backend.getContext().performCleanup();
+ for (const auto& entry : renderSources) {
+ entry.second->onLowMemory();
+ }
+ observer->onInvalidate();
+}
+
+void Renderer::Impl::dumDebugLogs() {
+ for (const auto& entry : renderSources) {
+ entry.second->dumpDebugLogs();
+ }
+
+ 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->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();
+}
+
+} // namespace mbgl