#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NDEBUG #include #endif #include #include #include #include #include #include #if MLN_RENDER_BACKEND_METAL #include #include #include /// Enable programmatic Metal frame captures for specific frame numbers. /// Requires iOS 13 constexpr auto EnableMetalCapture = 0; constexpr auto CaptureFrameStart = 0; // frames are 0-based constexpr auto CaptureFrameCount = 1; #elif MLN_RENDER_BACKEND_OPENGL #include #include #endif // !MLN_RENDER_BACKEND_METAL namespace mbgl { using namespace style; namespace { ContinuousRenderRanges activeRenderRanges(const RenderOrchestrator& orchestrator) { return selectContinuousRenderRanges(orchestrator.getContinuousLayerRange(), orchestrator.getRetainedDynamicBoundary(), orchestrator.getOrderedLayerCount()); } RendererObserver& nullObserver() { static RendererObserver observer; return observer; } #ifndef NDEBUG constexpr uint64_t RetainedCounterLogIntervalFrames = 120; void logRetainedRendererCounters(const RetainedUnderlayCache& cache, ParticleFastPathMissReason fastPathReason) { const auto detailed = particle::rendererDebugCounterSnapshot(); const auto retained = cache.getDiagnostics(); if (detailed.fullRendererFrame == 0 || detailed.fullRendererFrame % RetainedCounterLogIntervalFrames != 0) { return; } std::ostringstream message; message << "ParticleRetained:" << " frame=" << detailed.fullRendererFrame << " enabled=" << (retained.enabled ? "true" : "false") << " build=" << retained.build << " reuse=" << retained.reuse << " fallback=" << retained.fallback << " invalidation=" << retained.invalidation << " reason=" << retained.lastReason << " staticRevision=" << cache.getStaticRevision() << " buildRevision=" << cache.getBuildRevision() << " staticDraw=" << detailed.retainedUnderlayStaticGroupDraw << " dynamicDraw=" << detailed.retainedDynamicGroupDraw << " particleAdvance=" << detailed.particleSimulationAdvance << " fastHit=" << detailed.particleFastFrameHit << " fastMiss=" << detailed.particleFastFrameMiss << " particleFull=" << detailed.particleFullFrame << " fastReason=" << particleFastPathMissReasonText(fastPathReason); Log::Info(Event::Render, message.str()); } #endif } // namespace Renderer::Impl::Impl(gfx::RendererBackend& backend_, float pixelRatio_, const std::optional& localFontFamily_) : orchestrator(!backend_.contextIsShared(), backend_.getThreadPool(), localFontFamily_), backend(backend_), observer(&nullObserver()), pixelRatio(pixelRatio_), retainedUnderlayCache(std::make_unique(RetainedRangeRole::Bottom)), retainedTopCache(std::make_unique(RetainedRangeRole::Top)) {} Renderer::Impl::~Impl() { assert(gfx::BackendScope::exists()); }; void Renderer::Impl::onPreCompileShader(shaders::BuiltIn shaderID, gfx::Backend::Type type, const std::string& additionalDefines) { observer->onPreCompileShader(shaderID, type, additionalDefines); } void Renderer::Impl::onPostCompileShader(shaders::BuiltIn shaderID, gfx::Backend::Type type, const std::string& additionalDefines) { observer->onPostCompileShader(shaderID, type, additionalDefines); } void Renderer::Impl::onShaderCompileFailed(shaders::BuiltIn shaderID, gfx::Backend::Type type, const std::string& additionalDefines) { observer->onShaderCompileFailed(shaderID, type, additionalDefines); } void Renderer::Impl::onRenderError(std::exception_ptr error) { observer->onRenderError(error); } void Renderer::Impl::setObserver(RendererObserver* observer_) { observer = observer_ ? observer_ : &nullObserver(); } void Renderer::Impl::synchronizeRetainedUnderlayState( const std::shared_ptr& state) { retainedUnderlayCache->synchronizeRuntimeState(state); retainedTopCache->synchronizeRuntimeState(state); } Renderer::Impl::ParticleFastPathUpdateSignature Renderer::Impl::makeParticleFastPathUpdateSignature( const UpdateParameters& parameters) const { ParticleFastPathUpdateSignature result; result.viewport = parameters.transformState.getSize(); parameters.transformState.getProjMatrix(result.cameraMatrix); result.pixelRatio = parameters.pixelRatio; result.mode = parameters.mode; result.debugOptions = parameters.debugOptions; result.styleLoaded = parameters.styleLoaded; result.spriteLoaded = parameters.spriteLoaded; result.transformChanging = parameters.transformState.isChanging(); result.gestureInProgress = parameters.transformState.isGestureInProgress(); result.light = parameters.light.get(); result.images = parameters.images.get(); result.sources = parameters.sources.get(); result.layers = parameters.layers.get(); result.fontFaces = parameters.fontFaces.get(); result.fileSource = parameters.fileSource.get(); result.glyphURL = parameters.glyphURL; result.transitionDuration = parameters.transitionOptions.duration; result.transitionDelay = parameters.transitionOptions.delay; result.placementTransitions = parameters.transitionOptions.enablePlacementTransitions; result.prefetchZoomDelta = parameters.prefetchZoomDelta; result.crossSourceCollisions = parameters.crossSourceCollisions; result.fastPFOREnabled = parameters.fastPFOREnabled; result.tileLodMinRadius = parameters.tileLodMinRadius; result.tileLodScale = parameters.tileLodScale; result.tileLodPitchThreshold = parameters.tileLodPitchThreshold; result.tileLodZoomShift = parameters.tileLodZoomShift; result.tileLodMode = parameters.tileLodMode; return result; } bool Renderer::Impl::particleFastPathNonCameraInputsMatch(const ParticleFastPathUpdateSignature& lhs, const ParticleFastPathUpdateSignature& rhs) { return lhs.pixelRatio == rhs.pixelRatio && lhs.mode == rhs.mode && lhs.debugOptions == rhs.debugOptions && lhs.styleLoaded == rhs.styleLoaded && lhs.spriteLoaded == rhs.spriteLoaded && lhs.transformChanging == rhs.transformChanging && lhs.gestureInProgress == rhs.gestureInProgress && lhs.light == rhs.light && lhs.images == rhs.images && lhs.sources == rhs.sources && lhs.layers == rhs.layers && lhs.fontFaces == rhs.fontFaces && lhs.fileSource == rhs.fileSource && lhs.glyphURL == rhs.glyphURL && lhs.transitionDuration == rhs.transitionDuration && lhs.transitionDelay == rhs.transitionDelay && lhs.placementTransitions == rhs.placementTransitions && lhs.prefetchZoomDelta == rhs.prefetchZoomDelta && lhs.crossSourceCollisions == rhs.crossSourceCollisions && lhs.fastPFOREnabled == rhs.fastPFOREnabled && lhs.tileLodMinRadius == rhs.tileLodMinRadius && lhs.tileLodScale == rhs.tileLodScale && lhs.tileLodPitchThreshold == rhs.tileLodPitchThreshold && lhs.tileLodZoomShift == rhs.tileLodZoomShift && lhs.tileLodMode == rhs.tileLodMode; } bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr& updateParameters) { const auto signature = makeParticleFastPathUpdateSignature(*updateParameters); const auto dynamicState = orchestrator.particleFastPathDynamicState(updateParameters->timePoint); bool annotationMutation = false; if (auto guard = updateParameters->annotationManager.lock(); updateParameters->annotationManager) { annotationMutation = updateParameters->annotationManager->hasPendingChanges(); } const auto retained = retainedUnderlayCache->getDiagnostics(); const auto ranges = activeRenderRanges(orchestrator); const auto liveRangeConfig = updateParameters->retainedUnderlayState ? updateParameters->retainedUnderlayState->continuousLayerRangeConfig() : ContinuousLayerRangeConfig{}; const std::optional currentBoundary = (ranges.explicitValid || !ranges.explicitConfigured) ? std::optional(ranges.continuousBegin) : std::nullopt; const bool hasSignature = particlePreparedSignature.has_value(); const bool viewportMatches = hasSignature && particlePreparedSignature->viewport == signature.viewport && particlePreparedBackendSize == backend.getDefaultRenderable().getSize(); const bool cameraMatches = hasSignature && particlePreparedSignature->cameraMatrix == signature.cameraMatrix; const bool updateParametersMatch = hasSignature && particleFastPathNonCameraInputsMatch(*particlePreparedSignature, signature); const bool bottomRevisionMatches = !ranges.bottomPresent() || (retainedUnderlayCache->getStaticRevision() == retainedUnderlayRevisionTracker.current() && retainedUnderlayCache->getBuildRevision() == retainedUnderlayRevisionTracker.current()); const bool topRevisionMatches = !ranges.topPresent() || (retainedTopCache->getStaticRevision() == retainedUnderlayRevisionTracker.current() && retainedTopCache->getBuildRevision() == retainedUnderlayRevisionTracker.current()); const bool revisionMatches = bottomRevisionMatches && topRevisionMatches && particlePreparedStaticRevision == retainedUnderlayRevisionTracker.current(); const bool boundaryMatches = particlePreparedBoundary == currentBoundary && particlePreparedContinuousEnd == std::optional(ranges.continuousEndExclusive) && particlePreparedRangeConfigRevision == ranges.configRevision && liveRangeConfig.revision == ranges.configRevision && liveRangeConfig.configured == ranges.explicitConfigured && particlePreparedBottomPresent == ranges.bottomPresent() && particlePreparedTopPresent == ranges.topPresent(); const bool topologyMatches = particlePreparedTopologyRevision == retainedUnderlayTopologyTracker.current(); const bool contentRevisionMatches = particlePreparedContentRevision == orchestrator.getRetainedUnderlayContentRevision(); const bool staticStable = (ranges.explicitValid || (!ranges.explicitConfigured && ranges.bottomPresent())) && (!ranges.bottomPresent() || orchestrator.retainedRangeIsStable( updateParameters->timePoint, 0, ranges.continuousBegin)) && (!ranges.topPresent() || orchestrator.retainedRangeIsStable( updateParameters->timePoint, ranges.continuousEndExclusive, ranges.layerCount)); const ParticleFastPathGateInput input{ .hasPreparedTree = !particlePreparedTree.empty(), .retainedEnabled = retained.enabled, .cacheValid = (!ranges.bottomPresent() || retainedUnderlayCache->isValid()) && (!ranges.topPresent() || retainedTopCache->isValid()), .revisionMatches = revisionMatches, .contentRevisionMatches = contentRevisionMatches, .cameraMatches = cameraMatches, .viewportMatches = viewportMatches, .updateParametersMatch = updateParametersMatch, .styleLoaded = updateParameters->styleLoaded, .boundaryMatches = boundaryMatches, .topologyMatches = topologyMatches, .staticStable = staticStable, // placementChanged/symbolFadeChange belong to the previous prepared // full frame. The current controller transition is the authoritative // semantic state: while active we fail closed, and once settled the // old full-frame snapshot must not permanently block the fast path. .hasPlacementTransition = dynamicState.placementTransition, .hasPendingChanges = dynamicState.pendingChanges, .hasNonParticleTransition = dynamicState.blockingTransition, .hasNonParticleRepaint = dynamicState.nonParticleNeedsRepaint, .hasFadingTiles = dynamicState.fadingTiles, .particleNeedsRepaint = dynamicState.particleNeedsRepaint, .stillImageRequest = updateParameters->stillImageRequest, .continuousMode = updateParameters->mode == MapMode::Continuous, .debugDisabled = updateParameters->debugOptions == MapDebugOptions::NoDebug, .unsupportedContinuousLayer = particlePreparedContinuousUnsupported, .supportedScene = particlePreparedSceneSupported, .annotationMutation = annotationMutation, }; const auto decision = evaluateParticleFastPath(input); particleFastPathLastReason = decision.reason; retainedUnderlayCache->recordFastPathDecision(decision.candidate, decision.reason); if (!decision.candidate) { particle::incrementRendererDebugCounter(particle::RendererDebugCounter::ParticleFastFrameMiss); return false; } particle::incrementRendererDebugCounter(particle::RendererDebugCounter::ParticleFastFrameHit); particlePreparedTree.get()->prepareParticleFastFrame(updateParameters->timePoint); render(*particlePreparedTree.get(), updateParameters, true); return true; } void Renderer::Impl::commitPreparedRenderTree(std::unique_ptr renderTree, const std::shared_ptr& updateParameters) { const auto ranges = activeRenderRanges(orchestrator); particlePreparedSignature = makeParticleFastPathUpdateSignature(*updateParameters); particlePreparedContentRevision = orchestrator.getRetainedUnderlayContentRevision(); particlePreparedTopologyRevision = retainedUnderlayTopologyTracker.current(); particlePreparedStaticRevision = retainedUnderlayRevisionTracker.current(); particlePreparedBoundary = (ranges.explicitValid || !ranges.explicitConfigured) ? std::optional(ranges.continuousBegin) : std::nullopt; particlePreparedContinuousEnd = ranges.continuousEndExclusive; particlePreparedRangeConfigRevision = ranges.configRevision; particlePreparedBottomPresent = ranges.bottomPresent(); particlePreparedTopPresent = ranges.topPresent(); particlePreparedBackendSize = backend.getDefaultRenderable().getSize(); particlePreparedTree.replace(std::move(renderTree)); } void Renderer::Impl::clearPreparedRenderTree() { particlePreparedTree.reset(); particlePreparedSignature.reset(); particlePreparedContentRevision = 0; particlePreparedTopologyRevision = 0; particlePreparedStaticRevision = 0; particlePreparedBoundary.reset(); particlePreparedContinuousEnd.reset(); particlePreparedRangeConfigRevision = 0; particlePreparedBottomPresent = false; particlePreparedTopPresent = false; particlePreparedContinuousUnsupported = false; particlePreparedBackendSize = {}; particlePreparedSceneSupported = false; } void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr& updateParameters, bool particleFastFrame) { MLN_TRACE_FUNC(); particle::incrementRendererDebugCounter(particle::RendererDebugCounter::FullRendererFrame); if (!particleFastFrame) { particle::incrementRendererDebugCounter(particle::RendererDebugCounter::ParticleFullFrame); retainedUnderlayCache->recordFullCpuFrame(); } auto& context = backend.getContext(); context.setObserver(this); assert(updateParameters); #if MLN_RENDER_BACKEND_METAL #if MLN_CREATE_AUTORELEASEPOOL NS::SharedPtr pool = NS::TransferPtr(NS::AutoreleasePool::alloc()->init()); #endif if constexpr (EnableMetalCapture) { const auto& mtlBackend = static_cast(backend); const auto& mtlDevice = mtlBackend.getDevice(); if (!commandCaptureScope) { if (const auto& cmdQueue = mtlBackend.getCommandQueue()) { if (const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager())) { // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if ((commandCaptureScope = NS::TransferPtr(captureManager->newCaptureScope(cmdQueue.get())))) { const auto label = "Renderer::Impl frame=" + util::toString(frameCount); commandCaptureScope->setLabel(NS::String::string(label.c_str(), NS::UTF8StringEncoding)); captureManager->setDefaultCaptureScope(commandCaptureScope.get()); } } } } // "When you capture a frame programmatically, you can capture Metal commands that span multiple // frames by using a custom capture scope. For example, by calling begin() at the start of frame // 1 and end() after frame 3, the trace will contain command data from all the buffers that were // committed in the three frames." // https://developer.apple.com/documentation/metal/debugging_tools/capturing_gpu_command_data_programmatically if constexpr (0 < CaptureFrameStart && 0 < CaptureFrameCount) { if (commandCaptureScope) { const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager()); if (frameCount == CaptureFrameStart) { constexpr auto captureDest = MTL::CaptureDestination::CaptureDestinationDeveloperTools; if (captureManager && !captureManager->isCapturing() && captureManager->supportsDestination(captureDest)) { if (auto captureDesc = NS::TransferPtr(MTL::CaptureDescriptor::alloc()->init())) { captureDesc->setCaptureObject(mtlDevice.get()); captureDesc->setDestination(captureDest); NS::Error* errorPtr = nullptr; if (captureManager->startCapture(captureDesc.get(), &errorPtr)) { Log::Warning(Event::Render, "Capture Started"); } else { std::string errStr = ""; if (auto error = NS::TransferPtr(errorPtr)) { if (auto str = error->localizedDescription()) { if (auto cstr = str->utf8String()) { errStr = cstr; } } } Log::Warning(Event::Render, "Capture Failed: " + errStr); } } } } } } if (commandCaptureScope) { commandCaptureScope->beginScope(); const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager()); if (captureManager->isCapturing()) { Log::Info(Event::Render, "Capturing frame " + util::toString(frameCount)); } } } #endif // MLN_RENDER_BACKEND_METAL // Blocks execution until the renderable is available. backend.getDefaultRenderable().wait(); context.beginFrame(); if (!staticData) { staticData = std::make_unique(std::make_unique()); // Initialize shaders for drawables const auto programParameters = ProgramParameters{pixelRatio, false}; backend.initShaders(*staticData->shaders, programParameters); // Notify post-shader registration observer->onRegisterShaders(*staticData->shaders); } const auto& renderTreeParameters = renderTree.getParameters(); staticData->has3D = renderTreeParameters.has3D; staticData->backendSize = backend.getDefaultRenderable().getSize(); if (renderState == RenderState::Never) { observer->onWillStartRenderingMap(); } observer->onWillStartRenderingFrame(); const TransformState& state = renderTreeParameters.transformParams.state; const Size& size = staticData->backendSize; const EdgeInsets& frustumOffset = state.getFrustumOffset(); const gfx::ScissorRect scissorRect = { .x = static_cast(frustumOffset.left() * pixelRatio), #if MLN_RENDER_BACKEND_OPENGL .y = static_cast(frustumOffset.bottom() * pixelRatio), #else .y = static_cast(frustumOffset.top() * pixelRatio), #endif .width = size.width - static_cast((frustumOffset.left() + frustumOffset.right()) * pixelRatio), .height = size.height - static_cast((frustumOffset.top() + frustumOffset.bottom()) * pixelRatio), }; PaintParameters parameters{ context, pixelRatio, backend, renderTreeParameters.light, renderTreeParameters.mapMode, renderTreeParameters.debugOptions, renderTreeParameters.timePoint, renderTreeParameters.transformParams, *staticData, renderTree.getLineAtlas(), renderTree.getPatternAtlas(), frameCount, updateParameters->tileLodMinRadius, updateParameters->tileLodScale, updateParameters->tileLodPitchThreshold, updateParameters->tileLodMode, scissorRect, }; parameters.symbolFadeChange = renderTreeParameters.symbolFadeChange; parameters.opaquePassCutoff = renderTreeParameters.opaquePassCutOff; const auto& sourceRenderItems = renderTree.getSourceRenderItems(); const auto& layerRenderItems = renderTree.getLayerRenderItemMap(); // - UPLOAD PASS ------------------------------------------------------------------------------- // Uploads all required buffers and images before we do any actual rendering. if (!particleFastFrame) { const auto uploadPass = parameters.encoder->createUploadPass("upload", parameters.backend.getDefaultRenderable()); #if !defined(NDEBUG) const auto debugGroup = uploadPass->createDebugGroup("upload"); #endif // Update all clipping IDs + upload buckets. for (const RenderItem& item : sourceRenderItems) { item.upload(*uploadPass); } for (const RenderItem& item : layerRenderItems) { item.upload(*uploadPass); } staticData->upload(*uploadPass); renderTree.getLineAtlas().upload(*uploadPass); renderTree.getPatternAtlas().upload(*uploadPass); } // - LAYER GROUP UPDATE ------------------------------------------------------------------------ // Updates all layer groups and process changes if (staticData && staticData->shaders) { if (particleFastFrame) { orchestrator.updateParticleLayers( *staticData->shaders, context, renderTreeParameters.transformParams.state, updateParameters, renderTree); } else { orchestrator.updateLayers( *staticData->shaders, context, renderTreeParameters.transformParams.state, updateParameters, renderTree); } } orchestrator.processChanges(); const auto ranges = activeRenderRanges(orchestrator); const bool rangeMode = ranges.explicitValid; const bool retainedMode = rangeMode || (!ranges.explicitConfigured && ranges.bottomPresent()); const std::optional retainedBoundary = retainedMode ? std::optional(ranges.continuousBegin) : std::nullopt; std::optional bottomKey; std::optional topKey; RetainedUnderlayTopologySnapshot retainedTopology{ .boundary = retainedBoundary, .staticPrefix = {}, .continuousEndExclusive = retainedMode ? std::optional(ranges.continuousEndExclusive) : std::nullopt, .staticSuffix = {}}; mat4 retainedCameraMatrix; state.getProjMatrix(retainedCameraMatrix); if (retainedMode && ranges.bottomPresent()) { orchestrator.visitLayerGroupsInRange(0, ranges.continuousBegin, [&](LayerGroupBase& layerGroup, size_t) { retainedTopology.staticPrefix.push_back({.layerIndex = layerGroup.getLayerIndex(), .groupName = layerGroup.getName(), .groupType = static_cast(layerGroup.getType())}); }); bottomKey = RetainedUnderlayKey{size, 0, ranges.continuousBegin, retainedCameraMatrix}; } if (rangeMode && ranges.topPresent()) { orchestrator.visitLayerGroupsInRange( ranges.continuousEndExclusive, ranges.layerCount, [&](LayerGroupBase& layerGroup, size_t) { retainedTopology.staticSuffix.push_back({.layerIndex = layerGroup.getLayerIndex(), .groupName = layerGroup.getName(), .groupType = static_cast(layerGroup.getType())}); }); topKey = RetainedUnderlayKey{ size, ranges.continuousEndExclusive, ranges.layerCount, retainedCameraMatrix}; } const auto retainedTopologyRevision = particleFastFrame ? retainedUnderlayTopologyTracker.current() : retainedUnderlayTopologyTracker.observe(retainedTopology); const auto bottomGroupCount = retainedTopology.staticPrefix.size(); bool legacyInRetainedRange = false; if (retainedMode) { for (const auto& item : layerRenderItems) { const auto index = static_cast(item.index); if ((index < ranges.continuousBegin || (rangeMode && index >= ranges.continuousEndExclusive)) && item.layer.get().usesLegacyRendering()) { legacyInRetainedRange = true; break; } } } const bool retainedStaticUsesPlacement = retainedMode && ((ranges.bottomPresent() && orchestrator.retainedRangeUsesPlacement(0, ranges.continuousBegin)) || (ranges.topPresent() && orchestrator.retainedRangeUsesPlacement( ranges.continuousEndExclusive, ranges.layerCount))); const bool retainedStaticStable = retainedMode && (!ranges.bottomPresent() || orchestrator.retainedRangeIsStable( renderTreeParameters.timePoint, 0, ranges.continuousBegin)) && (!ranges.topPresent() || orchestrator.retainedRangeIsStable( renderTreeParameters.timePoint, ranges.continuousEndExclusive, ranges.layerCount)); const bool retainedPlacementStable = !retainedStaticUsesPlacement || (!renderTreeParameters.placementChanged && renderTreeParameters.symbolFadeChange == 0.0f); const bool retainedUnsupportedStaticLayer = retainedMode && ((ranges.bottomPresent() && orchestrator.retainedRangeHasUnsupportedLayer(0, ranges.continuousBegin)) || (ranges.topPresent() && orchestrator.retainedRangeHasUnsupportedLayer( ranges.continuousEndExclusive, ranges.layerCount))); const auto retainedContentRevision = orchestrator.getRetainedUnderlayContentRevision(); const auto retainedContentReason = orchestrator.getRetainedUnderlayContentReason(); const RetainedUnderlayRevisionState retainedRevisionState{ .viewport = size, .cameraMatrix = retainedCameraMatrix, .boundary = retainedBoundary, .continuousEndExclusive = retainedMode ? std::optional(ranges.continuousEndExclusive) : std::nullopt, .rangeConfigRevision = ranges.configRevision, .pixelRatio = updateParameters->pixelRatio, .contentRevision = retainedContentRevision, .topologyRevision = retainedTopologyRevision, .mapMode = static_cast(updateParameters->mode), .debugOptions = static_cast(renderTreeParameters.debugOptions), .styleLoaded = updateParameters->styleLoaded, .renderTreeLoaded = renderTreeParameters.loaded, .staticStable = retainedStaticStable, .placementStable = retainedPlacementStable, .has3D = renderTreeParameters.has3D, .unsupportedStaticLayer = retainedUnsupportedStaticLayer, }; const auto retainedStaticRevision = retainedUnderlayRevisionTracker.observe( retainedRevisionState, retainedUnderlayReasonText(retainedContentReason)); const bool retainedSupported = retainedMode && (rangeMode || !ranges.bottomPresent() || bottomGroupCount > 0) && renderTreeParameters.loaded && !renderTreeParameters.has3D && renderTreeParameters.debugOptions == MapDebugOptions::NoDebug && retainedStaticStable && retainedPlacementStable && !legacyInRetainedRange && !retainedUnsupportedStaticLayer; const bool fastContinuousSupported = rangeMode ? orchestrator.continuousRangeSupportsParticleFastPath( ranges.continuousBegin, ranges.continuousEndExclusive) : retainedSupported; if (!particleFastFrame) particlePreparedContinuousUnsupported = rangeMode && !fastContinuousSupported; retainedUnderlayCache->recordFastPathEligibility(retainedSupported && fastContinuousSupported); if (!particleFastFrame) particlePreparedSceneSupported = retainedSupported && fastContinuousSupported; const char* retainedFallbackReason = nullptr; if (!retainedMode) { retainedFallbackReason = "no-boundary"; } else if (!rangeMode && ranges.bottomPresent() && bottomGroupCount == 0) { retainedFallbackReason = "empty-static-prefix"; } else if (!renderTreeParameters.loaded) { retainedFallbackReason = "style-not-loaded"; } else if (renderTreeParameters.has3D) { retainedFallbackReason = "3d"; } else if (renderTreeParameters.debugOptions != MapDebugOptions::NoDebug) { retainedFallbackReason = "debug-options"; } else if (!retainedStaticStable) { retainedFallbackReason = "static-transition"; } else if (!retainedPlacementStable) { retainedFallbackReason = "static-placement"; } else if (legacyInRetainedRange) { retainedFallbackReason = "legacy-ordering"; } else if (retainedUnsupportedStaticLayer) { retainedFallbackReason = "unsupported"; } auto bottomDecision = retainedUnderlayCache->evaluate(bottomKey, retainedStaticRevision, retainedSupported, retainedFallbackReason, retainedUnderlayRevisionTracker.reason().c_str()); auto topDecision = retainedTopCache->evaluate(topKey, retainedStaticRevision, retainedSupported, retainedFallbackReason, retainedUnderlayRevisionTracker.reason().c_str()); const auto wantsResources = [](RetainedUnderlayDecision decision) { return decision == RetainedUnderlayDecision::Build || decision == RetainedUnderlayDecision::Reuse; }; bool resourcesReady = true; if (wantsResources(bottomDecision)) { resourcesReady = staticData->shaders && retainedUnderlayCache->prepareResources(context, *staticData->shaders, *bottomKey); } if (resourcesReady && wantsResources(topDecision)) { resourcesReady = staticData->shaders && retainedTopCache->prepareResources(context, *staticData->shaders, *topKey); } if (!resourcesReady) { if (wantsResources(bottomDecision)) retainedUnderlayCache->markBuildFailed("resource-or-compositor"); if (wantsResources(topDecision)) retainedTopCache->markBuildFailed("resource-or-compositor"); bottomDecision = topDecision = RetainedUnderlayDecision::Fallback; } const bool useRetainedRanges = retainedSupported && (!ranges.bottomPresent() || wantsResources(bottomDecision)) && (!ranges.topPresent() || wantsResources(topDecision)); // Upload layer groups { const auto uploadPass = parameters.encoder->createUploadPass("layerGroup-upload", parameters.backend.getDefaultRenderable()); #if !defined(NDEBUG) const auto debugGroup = uploadPass->createDebugGroup("layerGroup-upload"); #endif // Update the debug layer groups only on the full path. Debug rendering // is excluded by the fast-path gate. if (!particleFastFrame) orchestrator.updateDebugLayerGroups(renderTree, parameters); // Tweakers are run in the upload pass so they can set up uniforms. parameters.currentLayer = 0; if (particleFastFrame && retainedMode) { orchestrator.visitLayerGroupsInRange( ranges.continuousBegin, ranges.continuousEndExclusive, [&](LayerGroupBase& layerGroup, size_t ordinal) { parameters.currentLayer = static_cast(ordinal); layerGroup.runTweakers(renderTree, parameters); }); } else { orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.runTweakers(renderTree, parameters); parameters.currentLayer++; }); parameters.currentLayer = 0; orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.runTweakers(renderTree, parameters); parameters.currentLayer++; }); } // Give the layers a chance to upload if (particleFastFrame && retainedMode) { orchestrator.visitLayerGroupsInRange( ranges.continuousBegin, ranges.continuousEndExclusive, [&](LayerGroupBase& layerGroup, size_t) { layerGroup.upload(*uploadPass); }); } else { orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); }); } // Give the render targets a chance to upload orchestrator.visitRenderTargets([&](RenderTarget& renderTarget) { renderTarget.upload(*uploadPass); }); // Upload the Debug layer group if (!particleFastFrame) { orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); }); } if (useRetainedRanges && ranges.bottomPresent()) retainedUnderlayCache->upload(*uploadPass); if (useRetainedRanges && ranges.topPresent()) retainedTopCache->upload(*uploadPass); } const Size atlasSize = parameters.patternAtlas.getPixelSize(); const auto& worldSize = parameters.staticData.backendSize; const shaders::GlobalPaintParamsUBO globalPaintParamsUBO = { .pattern_atlas_texsize = {static_cast(atlasSize.width), static_cast(atlasSize.height)}, .units_to_pixels = {1.0f / parameters.pixelsToGLUnits[0], 1.0f / parameters.pixelsToGLUnits[1]}, .world_size = {static_cast(worldSize.width), static_cast(worldSize.height)}, .camera_to_center_distance = parameters.state.getCameraToCenterDistance(), .symbol_fade_change = parameters.symbolFadeChange, .aspect_ratio = parameters.state.getSize().aspectRatio(), .pixel_ratio = parameters.pixelRatio, .map_zoom = static_cast(parameters.state.getZoom()), .pad1 = 0, }; auto& globalUniforms = context.mutableGlobalUniformBuffers(); globalUniforms.createOrUpdate(shaders::idGlobalPaintParamsUBO, &globalPaintParamsUBO, context); // - 3D PASS // ------------------------------------------------------------------------------------- // Renders any 3D layers bottom-to-top to unique FBOs with texture // attachments, but share the same depth rbo between them. const auto common3DPass = [&] { if (parameters.staticData.has3D) { parameters.staticData.backendSize = parameters.backend.getDefaultRenderable().getSize(); const auto debugGroup(parameters.encoder->createDebugGroup("common-3d")); parameters.pass = RenderPass::Pass3D; #if MLN_RENDER_BACKEND_OPENGL parameters.updateStencilBufferAvailability(); #endif // TODO is this needed? // 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); } }; const auto drawable3DPass = [&] { const auto debugGroup(parameters.encoder->createDebugGroup("drawables-3d")); assert(parameters.pass == RenderPass::Pass3D); // draw layer groups, 3D pass parameters.currentLayer = static_cast(orchestrator.numLayerGroups()) - 1; orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.render(orchestrator, parameters); if (parameters.currentLayer > 0) { parameters.currentLayer--; } }); }; const auto drawableTargetsPass = [&] { // draw render targets orchestrator.visitRenderTargets( [&](RenderTarget& renderTarget) { renderTarget.render(orchestrator, renderTree, parameters); }); }; const auto buildRetainedRange = [&](RetainedUnderlayCache& cache, RetainedUnderlayDecision decision, int32_t begin, int32_t endExclusive) { if (!useRetainedRanges || decision != RetainedUnderlayDecision::Build || begin >= endExclusive) return; cache.beginBuild(parameters); context.bindGlobalUniformBuffers(*parameters.renderPass); parameters.depthRangeSize = 1 - (orchestrator.numLayerGroups() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; parameters.pass = RenderPass::Opaque; orchestrator.visitLayerGroupsInRangeReversed(begin, endExclusive, [&](LayerGroupBase& layerGroup, size_t ordinal) { parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); layerGroup.render(orchestrator, parameters); particle::incrementRendererDebugCounter( particle::RendererDebugCounter::RetainedUnderlayStaticGroupDraw); }); parameters.pass = RenderPass::Translucent; orchestrator.visitLayerGroupsInRange(begin, endExclusive, [&](LayerGroupBase& layerGroup, size_t ordinal) { parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); layerGroup.render(orchestrator, parameters); particle::incrementRendererDebugCounter( particle::RendererDebugCounter::RetainedUnderlayStaticGroupDraw); }); context.unbindGlobalUniformBuffers(*parameters.renderPass); cache.finishBuild(parameters); cache.markBuildSucceeded(); }; const auto commonClearPass = [&] { // - CLEAR // ------------------------------------------------------------------------------------- // Renders the backdrop of the OpenGL view. This also paints in areas where // we don't have any tiles whatsoever. { std::optional color; if (parameters.debugOptions & MapDebugOptions::Overdraw) { color = Color::black(); } else if (!backend.contextIsShared()) { color = renderTreeParameters.backgroundColor; } parameters.renderPass = parameters.encoder->createRenderPass( "main buffer", {.renderable = parameters.backend.getDefaultRenderable(), .clearColor = color, .clearDepth = 1.0f, .clearStencil = 0}); #if MLN_RENDER_BACKEND_OPENGL parameters.updateStencilBufferAvailability(); #endif } }; // Actually render the layers // Drawables const auto drawableOpaquePass = [&] { const auto debugGroup(parameters.renderPass->createDebugGroup("drawables-opaque")); parameters.pass = RenderPass::Opaque; parameters.depthRangeSize = 1 - (orchestrator.numLayerGroups() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; // draw layer groups, opaque pass if (useRetainedRanges && retainedMode) { orchestrator.visitLayerGroupsInRangeReversed( ranges.continuousBegin, ranges.continuousEndExclusive, [&](LayerGroupBase& layerGroup, size_t ordinal) { parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); layerGroup.render(orchestrator, parameters); particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedDynamicGroupDraw); }); } else { parameters.currentLayer = 0; orchestrator.visitLayerGroupsReversed([&](LayerGroupBase& layerGroup) { layerGroup.render(orchestrator, parameters); parameters.currentLayer++; }); } }; const auto drawableTranslucentPass = [&] { const auto debugGroup(parameters.renderPass->createDebugGroup("drawables-translucent")); parameters.pass = RenderPass::Translucent; parameters.depthRangeSize = 1 - (orchestrator.numLayerGroups() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; // draw layer groups, translucent pass if (useRetainedRanges && retainedMode) { orchestrator.visitLayerGroupsInRange( ranges.continuousBegin, ranges.continuousEndExclusive, [&](LayerGroupBase& layerGroup, size_t ordinal) { parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); layerGroup.render(orchestrator, parameters); particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedDynamicGroupDraw); }); } else { parameters.currentLayer = static_cast(orchestrator.numLayerGroups()) - 1; orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.render(orchestrator, parameters); if (parameters.currentLayer > 0) { parameters.currentLayer--; } }); } // Finally, render any legacy layers which have not been converted to drawables. // Note that they may be out of order, this is just a temporary fix for `RenderLocationIndicatorLayer` (#2216) parameters.depthRangeSize = 1 - (layerRenderItems.size() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; int32_t i = static_cast(layerRenderItems.size()) - 1; for (auto it = layerRenderItems.begin(); it != layerRenderItems.end() && i >= 0; ++it, --i) { parameters.currentLayer = i; const LayerRenderItem& layerItem = *it; const RenderItem& item = layerItem; const bool itemInLiveRange = !useRetainedRanges || !retainedMode || (static_cast(layerItem.index) >= ranges.continuousBegin && static_cast(layerItem.index) < ranges.continuousEndExclusive); if (itemInLiveRange && item.hasRenderPass(parameters.pass)) { item.render(parameters); } } }; const auto drawableDebugOverlays = [&] { if (particleFastFrame) return; // Renders debug overlays. { const auto debugGroup(parameters.renderPass->createDebugGroup("debug")); parameters.currentLayer = 0; orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.render(orchestrator, parameters); parameters.currentLayer++; }); } }; if (parameters.staticData.has3D) { common3DPass(); drawable3DPass(); } drawableTargetsPass(); buildRetainedRange(*retainedUnderlayCache, bottomDecision, 0, ranges.continuousBegin); buildRetainedRange(*retainedTopCache, topDecision, ranges.continuousEndExclusive, ranges.layerCount); commonClearPass(); context.bindGlobalUniformBuffers(*parameters.renderPass); if (useRetainedRanges && ranges.bottomPresent()) retainedUnderlayCache->composite(orchestrator, parameters); drawableOpaquePass(); drawableTranslucentPass(); if (useRetainedRanges && ranges.topPresent()) retainedTopCache->composite(orchestrator, parameters); drawableDebugOverlays(); // Give the layers a chance to do cleanup if (particleFastFrame && retainedMode) { orchestrator.visitLayerGroupsInRange( ranges.continuousBegin, ranges.continuousEndExclusive, [&](LayerGroupBase& layerGroup, size_t) { layerGroup.postRender(orchestrator, parameters); }); } else { orchestrator.visitLayerGroups( [&](LayerGroupBase& layerGroup) { layerGroup.postRender(orchestrator, parameters); }); } context.unbindGlobalUniformBuffers(*parameters.renderPass); // Ends the RenderPass parameters.renderPass.reset(); const auto startRendering = util::MonotonicTimer::now().count(); // present submits render commands parameters.encoder->present(parameters.backend.getDefaultRenderable()); context.renderingStats().renderingTime = util::MonotonicTimer::now().count() - startRendering; parameters.encoder.reset(); context.endFrame(); #if MLN_RENDER_BACKEND_METAL if constexpr (EnableMetalCapture) { if (commandCaptureScope) { commandCaptureScope->endScope(); const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager()); if (frameCount == CaptureFrameStart + CaptureFrameCount - 1 && captureManager->isCapturing()) { captureManager->stopCapture(); } } } #endif // MLN_RENDER_BACKEND_METAL context.renderingStats().encodingTime = renderTree.getElapsedTime() - context.renderingStats().renderingTime; observer->onDidFinishRenderingFrame( renderTreeParameters.loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial, renderTreeParameters.needsRepaint, renderTreeParameters.placementChanged, context.threadSafeCopyRenderingStats()); if (!renderTreeParameters.loaded) { renderState = RenderState::Partial; } else if (renderState != RenderState::Fully) { renderState = RenderState::Fully; observer->onDidFinishRenderingMap(); } #ifndef NDEBUG logRetainedRendererCounters(*retainedUnderlayCache, particleFastPathLastReason); #endif frameCount += 1; MLN_END_FRAME(); } void Renderer::Impl::reduceMemoryUse() { assert(gfx::BackendScope::exists()); backend.getContext().reduceMemoryUsage(); } } // namespace mbgl