Files
MapLibreParticleLab/src/mbgl/renderer/renderer_impl.cpp

982 lines
49 KiB
C++

#include <mbgl/renderer/renderer_impl.hpp>
#include <mbgl/geometry/line_atlas.hpp>
#include <mbgl/annotation/annotation_manager.hpp>
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gfx/context.hpp>
#include <mbgl/gfx/cull_face_mode.hpp>
#include <mbgl/gfx/render_pass.hpp>
#include <mbgl/gfx/renderer_backend.hpp>
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/gfx/upload_pass.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/renderer/pattern_atlas.hpp>
#include <mbgl/renderer/renderer_observer.hpp>
#include <mbgl/renderer/render_static_data.hpp>
#include <mbgl/renderer/render_tree.hpp>
#include <mbgl/renderer/update_parameters.hpp>
#include <mbgl/shaders/program_parameters.hpp>
#include <mbgl/util/convert.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/instrumentation.hpp>
#ifndef NDEBUG
#include <sstream>
#endif
#include <mbgl/gfx/drawable_tweaker.hpp>
#include <mbgl/renderer/layer_tweaker.hpp>
#include <mbgl/renderer/render_target.hpp>
#include <mbgl/renderer/retained_underlay_cache.hpp>
#include <mbgl/renderer/continuous_layer_range.hpp>
#include <mbgl/particle/renderer_debug_counters.hpp>
#if MLN_RENDER_BACKEND_METAL
#include <mbgl/mtl/renderer_backend.hpp>
#include <Metal/MTLCaptureManager.hpp>
#include <Metal/MTLCaptureScope.hpp>
/// 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 <mbgl/gl/defines.hpp>
#include <mbgl/gl/drawable_gl.hpp>
#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<std::string>& localFontFamily_)
: orchestrator(!backend_.contextIsShared(), backend_.getThreadPool(), localFontFamily_),
backend(backend_),
observer(&nullObserver()),
pixelRatio(pixelRatio_),
retainedUnderlayCache(std::make_unique<RetainedUnderlayCache>(RetainedRangeRole::Bottom)),
retainedTopCache(std::make_unique<RetainedUnderlayCache>(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<RetainedUnderlayRuntimeState>& 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>& 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<int32_t> currentBoundary =
(ranges.explicitValid || !ranges.explicitConfigured) ? std::optional<int32_t>(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<int32_t>(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> renderTree,
const std::shared_ptr<UpdateParameters>& 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<int32_t>(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>& 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<mtl::RendererBackend&>(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 = "<none>";
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<RenderStaticData>(std::make_unique<gfx::ShaderRegistry>());
// 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<int32_t>(frustumOffset.left() * pixelRatio),
#if MLN_RENDER_BACKEND_OPENGL
.y = static_cast<int32_t>(frustumOffset.bottom() * pixelRatio),
#else
.y = static_cast<int32_t>(frustumOffset.top() * pixelRatio),
#endif
.width = size.width - static_cast<uint32_t>((frustumOffset.left() + frustumOffset.right()) * pixelRatio),
.height = size.height - static_cast<uint32_t>((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<int32_t> retainedBoundary = retainedMode
? std::optional<int32_t>(ranges.continuousBegin) : std::nullopt;
std::optional<RetainedUnderlayKey> bottomKey;
std::optional<RetainedUnderlayKey> topKey;
RetainedUnderlayTopologySnapshot retainedTopology{
.boundary = retainedBoundary,
.staticPrefix = {},
.continuousEndExclusive = retainedMode ? std::optional<int32_t>(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<uint8_t>(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<uint8_t>(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<int32_t>(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<int32_t>(ranges.continuousEndExclusive)
: std::nullopt,
.rangeConfigRevision = ranges.configRevision,
.pixelRatio = updateParameters->pixelRatio,
.contentRevision = retainedContentRevision,
.topologyRevision = retainedTopologyRevision,
.mapMode = static_cast<uint8_t>(updateParameters->mode),
.debugOptions = static_cast<uint32_t>(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<uint32_t>(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<float>(atlasSize.width), static_cast<float>(atlasSize.height)},
.units_to_pixels = {1.0f / parameters.pixelsToGLUnits[0], 1.0f / parameters.pixelsToGLUnits[1]},
.world_size = {static_cast<float>(worldSize.width), static_cast<float>(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<float>(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<gfx::RenderbufferPixelType::Depth>(
// 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<uint32_t>(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<uint32_t>(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<uint32_t>(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> 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<uint32_t>(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<uint32_t>(orchestrator.numLayerGroups() - 1 - ordinal);
layerGroup.render(orchestrator, parameters);
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedDynamicGroupDraw);
});
} else {
parameters.currentLayer = static_cast<uint32_t>(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<int32_t>(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<int32_t>(layerItem.index) >= ranges.continuousBegin &&
static_cast<int32_t>(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