feat: add particle static CPU fast path
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-09-23 08:22:24 +09:00
parent 98e1d2d977
commit 4e3d27e1f3
18 changed files with 1247 additions and 38 deletions

View File

@@ -221,6 +221,12 @@ void AnnotationManager::updateData() {
}
}
bool AnnotationManager::hasPendingChanges() {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN(false);
std::scoped_lock lock(mutex);
return dirty;
}
void AnnotationManager::addTile(AnnotationTile& tile) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
std::scoped_lock lock(mutex);

View File

@@ -42,6 +42,7 @@ public:
void onStyleLoaded();
void updateData();
bool hasPendingChanges();
void addTile(AnnotationTile&);
void removeTile(AnnotationTile&);

View File

@@ -1102,10 +1102,7 @@ void RenderRasterParticleLayer::evaluate(const PropertyEvaluationParameters&) {
}
bool RenderRasterParticleLayer::needsRepaint() const {
const auto& properties = static_cast<const style::ParticleRenderLayerProperties&>(*evaluatedProperties);
if (!properties.active || (formalSource && (!sourceField || !sourceNextField)) ||
baseImpl->visibility == style::VisibilityType::None)
return false;
if (!isParticleAnimationActive()) return false;
const bool repaint = !formalSource ||
(sourceUsesV4Projection
? sourceV4Cadence && sourceV4Cadence->cadenceDue(std::chrono::steady_clock::now())
@@ -1114,6 +1111,14 @@ bool RenderRasterParticleLayer::needsRepaint() const {
return repaint;
}
bool RenderRasterParticleLayer::isParticleAnimationActive() const {
const auto& properties = static_cast<const style::ParticleRenderLayerProperties&>(*evaluatedProperties);
if (!properties.active || (formalSource && (!sourceField || !sourceNextField)) ||
baseImpl->visibility == style::VisibilityType::None)
return false;
return true;
}
bool RenderRasterParticleLayer::needsHiddenLayerSourceCleanup(const RenderSource& renderSource) const {
if (!formalSource || !gpuResources || renderSource.baseImpl->type != style::SourceType::RasterArray) return false;
const auto& source = static_cast<const RenderRasterArraySource&>(renderSource);

View File

@@ -27,6 +27,8 @@ private:
bool hasTransition() const override { return false; }
bool hasCrossfade() const override { return false; }
bool needsRepaint() const override;
bool isParticleAnimationLayer() const override { return true; }
bool isParticleAnimationActive() const override;
bool startsRetainedDynamicRange() const override { return true; }
void prepare(const LayerPrepareParameters&) override;
void markLayerRenderable(bool, UniqueChangeRequestVec&) override;

View File

@@ -0,0 +1,135 @@
#pragma once
#include <cstdint>
#include <memory>
namespace mbgl {
enum class ParticleFastPathMissReason : uint8_t {
None,
NoPreparedTree,
RetainedDisabled,
CacheInvalid,
RevisionMismatch,
ContentMutation,
CameraChanged,
ViewportChanged,
UpdateParametersChanged,
StyleNotLoaded,
BoundaryChanged,
TopologyChanged,
StaticUnstable,
PlacementOrFade,
PendingChanges,
NonParticleTransition,
NonParticleRepaint,
FadingTiles,
NoParticleRepaint,
StillImageRequest,
UnsupportedMode,
DebugOptions,
UnsupportedScene,
AnnotationMutation,
};
inline const char* particleFastPathMissReasonText(ParticleFastPathMissReason reason) noexcept {
switch (reason) {
case ParticleFastPathMissReason::None: return "none";
case ParticleFastPathMissReason::NoPreparedTree: return "no-prepared-tree";
case ParticleFastPathMissReason::RetainedDisabled: return "retained-disabled";
case ParticleFastPathMissReason::CacheInvalid: return "cache-invalid";
case ParticleFastPathMissReason::RevisionMismatch: return "revision-mismatch";
case ParticleFastPathMissReason::ContentMutation: return "content-mutation";
case ParticleFastPathMissReason::CameraChanged: return "camera-changed";
case ParticleFastPathMissReason::ViewportChanged: return "viewport-changed";
case ParticleFastPathMissReason::UpdateParametersChanged: return "update-parameters-changed";
case ParticleFastPathMissReason::StyleNotLoaded: return "style-not-loaded";
case ParticleFastPathMissReason::BoundaryChanged: return "boundary-changed";
case ParticleFastPathMissReason::TopologyChanged: return "topology-changed";
case ParticleFastPathMissReason::StaticUnstable: return "static-unstable";
case ParticleFastPathMissReason::PlacementOrFade: return "placement-or-fade";
case ParticleFastPathMissReason::PendingChanges: return "pending-changes";
case ParticleFastPathMissReason::NonParticleTransition: return "non-particle-transition";
case ParticleFastPathMissReason::NonParticleRepaint: return "non-particle-repaint";
case ParticleFastPathMissReason::FadingTiles: return "fading-tiles";
case ParticleFastPathMissReason::NoParticleRepaint: return "no-particle-repaint";
case ParticleFastPathMissReason::StillImageRequest: return "still-image-request";
case ParticleFastPathMissReason::UnsupportedMode: return "unsupported-mode";
case ParticleFastPathMissReason::DebugOptions: return "debug-options";
case ParticleFastPathMissReason::UnsupportedScene: return "unsupported-scene";
case ParticleFastPathMissReason::AnnotationMutation: return "annotation-mutation";
}
return "unknown";
}
struct ParticleFastPathGateInput {
bool hasPreparedTree = false;
bool retainedEnabled = false;
bool cacheValid = false;
bool revisionMatches = false;
bool contentRevisionMatches = false;
bool cameraMatches = false;
bool viewportMatches = false;
bool updateParametersMatch = false;
bool styleLoaded = false;
bool boundaryMatches = false;
bool topologyMatches = false;
bool staticStable = false;
bool hasPlacementTransition = false;
bool hasPendingChanges = false;
bool hasNonParticleTransition = false;
bool hasNonParticleRepaint = false;
bool hasFadingTiles = false;
bool particleNeedsRepaint = false;
bool stillImageRequest = false;
bool continuousMode = false;
bool debugDisabled = false;
bool supportedScene = false;
bool annotationMutation = false;
};
struct ParticleFastPathDecision {
bool candidate = false;
ParticleFastPathMissReason reason = ParticleFastPathMissReason::None;
};
inline ParticleFastPathDecision evaluateParticleFastPath(const ParticleFastPathGateInput& input) noexcept {
if (!input.hasPreparedTree) return {false, ParticleFastPathMissReason::NoPreparedTree};
if (!input.retainedEnabled) return {false, ParticleFastPathMissReason::RetainedDisabled};
if (!input.cacheValid) return {false, ParticleFastPathMissReason::CacheInvalid};
if (!input.revisionMatches) return {false, ParticleFastPathMissReason::RevisionMismatch};
if (!input.contentRevisionMatches) return {false, ParticleFastPathMissReason::ContentMutation};
if (!input.viewportMatches) return {false, ParticleFastPathMissReason::ViewportChanged};
if (!input.cameraMatches) return {false, ParticleFastPathMissReason::CameraChanged};
if (!input.updateParametersMatch) return {false, ParticleFastPathMissReason::UpdateParametersChanged};
if (!input.styleLoaded) return {false, ParticleFastPathMissReason::StyleNotLoaded};
if (!input.boundaryMatches) return {false, ParticleFastPathMissReason::BoundaryChanged};
if (!input.topologyMatches) return {false, ParticleFastPathMissReason::TopologyChanged};
if (!input.staticStable) return {false, ParticleFastPathMissReason::StaticUnstable};
if (input.hasPlacementTransition) return {false, ParticleFastPathMissReason::PlacementOrFade};
if (input.hasPendingChanges) return {false, ParticleFastPathMissReason::PendingChanges};
if (input.hasNonParticleTransition) return {false, ParticleFastPathMissReason::NonParticleTransition};
if (input.hasNonParticleRepaint) return {false, ParticleFastPathMissReason::NonParticleRepaint};
if (input.hasFadingTiles) return {false, ParticleFastPathMissReason::FadingTiles};
if (!input.particleNeedsRepaint) return {false, ParticleFastPathMissReason::NoParticleRepaint};
if (input.stillImageRequest) return {false, ParticleFastPathMissReason::StillImageRequest};
if (!input.continuousMode) return {false, ParticleFastPathMissReason::UnsupportedMode};
if (!input.debugDisabled) return {false, ParticleFastPathMissReason::DebugOptions};
if (!input.supportedScene) return {false, ParticleFastPathMissReason::UnsupportedScene};
if (input.annotationMutation) return {false, ParticleFastPathMissReason::AnnotationMutation};
return {true, ParticleFastPathMissReason::None};
}
template <class T>
class ParticleFastPathPreparedTree {
public:
T* get() const noexcept { return tree.get(); }
bool empty() const noexcept { return !tree; }
void replace(std::unique_ptr<T> next) { tree = std::move(next); }
void reset() { tree.reset(); }
private:
std::unique_ptr<T> tree;
};
} // namespace mbgl

View File

@@ -109,6 +109,12 @@ public:
// Returns true when an active animated layer needs another MapLibre frame.
virtual bool needsRepaint() const { return false; }
// Renderer-internal classification used by the retained-underlay CPU fast
// path. It is intentionally narrower than startsRetainedDynamicRange():
// WeatherColor starts the dynamic range but is not a Particle animation.
virtual bool isParticleAnimationLayer() const { return false; }
virtual bool isParticleAnimationActive() const { return false; }
// Renderer-internal boundary for retained static-underlay rendering. This is
// deliberately not exposed through the style or Host APIs.
virtual bool startsRetainedDynamicRange() const { return false; }

View File

@@ -929,6 +929,27 @@ bool RenderOrchestrator::retainedUnderlayHasUnsupportedLayer(int32_t boundary) c
return false;
}
ParticleFastPathDynamicState RenderOrchestrator::particleFastPathDynamicState(TimePoint timePoint) const {
ParticleFastPathDynamicState result;
result.blockingTransition = renderLight.hasTransition();
for (const auto& entry : renderLayers) {
const auto& layer = *entry.second;
result.blockingTransition = result.blockingTransition || layer.hasTransition();
if (layer.isLayerRenderable() && layer.isParticleAnimationActive()) result.particleNeedsRepaint = true;
if (layer.needsRepaint()) {
if (!layer.isParticleAnimationLayer()) {
result.nonParticleNeedsRepaint = true;
}
}
}
result.placementTransition = placementController.hasTransitions(timePoint);
for (const auto& entry : renderSources) {
result.fadingTiles = result.fadingTiles || entry.second->hasFadingTiles();
}
result.pendingChanges = !pendingChanges.empty();
return result;
}
bool RenderOrchestrator::isLoaded() const {
MLN_TRACE_FUNC();
@@ -1090,6 +1111,33 @@ void RenderOrchestrator::updateLayers(gfx::ShaderRegistry& shaders,
addChanges(changes);
}
void RenderOrchestrator::updateParticleLayers(gfx::ShaderRegistry& shaders,
gfx::Context& context,
const TransformState& state,
const std::shared_ptr<UpdateParameters>& updateParameters,
const RenderTree& renderTree) {
MLN_TRACE_FUNC();
const auto& items = renderTree.getLayerRenderItemMap();
std::vector<std::unique_ptr<ChangeRequest>> changes;
changes.reserve(8);
for (const auto& item : items) {
auto& renderLayer = item.layer.get();
if (!renderLayer.isParticleAnimationLayer()) continue;
#if MLN_RENDER_BACKEND_OPENGL
if (androidGoldfishMitigationEnabled) {
renderLayer.removeAllDrawables();
}
#endif
try {
renderLayer.update(shaders, context, state, updateParameters, renderTree, changes);
} catch (...) {
observer->onRenderError(std::current_exception());
}
}
addChanges(changes);
}
void RenderOrchestrator::processChanges() {
auto localChanges = std::move(pendingChanges);
for (auto& change : localChanges) {

View File

@@ -54,6 +54,15 @@ class LayerProperties;
using ImmutableLayer = Immutable<style::Layer::Impl>;
struct ParticleFastPathDynamicState {
bool particleNeedsRepaint = false;
bool nonParticleNeedsRepaint = false;
bool blockingTransition = false;
bool placementTransition = false;
bool fadingTiles = false;
bool pendingChanges = false;
};
class RenderOrchestrator final : public GlyphManagerObserver, public ImageManagerObserver, public RenderSourceObserver {
public:
RenderOrchestrator(bool backgroundLayerAsColor_,
@@ -113,6 +122,7 @@ public:
bool retainedUnderlayUsesPlacement(int32_t boundary) const;
bool retainedUnderlayIsStable(TimePoint, int32_t boundary) const;
bool retainedUnderlayHasUnsupportedLayer(int32_t boundary) const;
ParticleFastPathDynamicState particleFastPathDynamicState(TimePoint) const;
uint64_t getRetainedUnderlayContentRevision() const noexcept {
return retainedUnderlayContentRevision.load(std::memory_order_acquire);
}
@@ -218,6 +228,11 @@ public:
const TransformState&,
const std::shared_ptr<UpdateParameters>&,
const RenderTree&);
void updateParticleLayers(gfx::ShaderRegistry&,
gfx::Context&,
const TransformState&,
const std::shared_ptr<UpdateParameters>&,
const RenderTree&);
void processChanges();

View File

@@ -99,6 +99,17 @@ public:
// Parameters
const RenderTreeParameters& getParameters() const { return *parameters; }
// Rebase the cached prepared tree for a Particle-only frame. All semantic
// inputs are checked by Renderer before this is called; only frame-local
// time/repaint bookkeeping is allowed to change here.
void prepareParticleFastFrame(TimePoint timePoint) {
parameters->timePoint = timePoint;
parameters->needsRepaint = true;
parameters->placementChanged = false;
parameters->symbolFadeChange = 0.0f;
startTime = util::MonotonicTimer::now().count();
}
double getElapsedTime() const { return util::MonotonicTimer::now().count() - startTime; }
protected:

View File

@@ -24,6 +24,7 @@ Renderer::~Renderer() {
}
void Renderer::markContextLost() {
impl->clearPreparedRenderTree();
impl->orchestrator.markContextLost();
}
@@ -39,12 +40,21 @@ void Renderer::render(const std::shared_ptr<UpdateParameters>& updateParameters)
const bool styleChanged = impl->styleLoaded && !updateParameters->styleLoaded;
impl->styleLoaded = updateParameters->styleLoaded;
if (!impl->dynamicTextureAtlas || styleChanged) {
if (styleChanged) impl->clearPreparedRenderTree();
auto& context = impl->backend.getContext();
impl->dynamicTextureAtlas = std::make_unique<gfx::DynamicTextureAtlas>(context);
}
if (impl->tryRenderParticleFastFrame(updateParameters)) return;
// A full createRenderTree may replace RenderLayer/RenderSource instances.
// Drop the old prepared tree first so its reference-based render items can
// never outlive the orchestrator objects they describe.
impl->clearPreparedRenderTree();
if (auto renderTree = impl->orchestrator.createRenderTree(updateParameters, impl->dynamicTextureAtlas)) {
renderTree->prepare();
impl->render(*renderTree, updateParameters);
impl->render(*renderTree, updateParameters, false);
impl->commitPreparedRenderTree(std::move(renderTree), updateParameters);
} else {
impl->clearPreparedRenderTree();
}
}
@@ -162,11 +172,13 @@ bool Renderer::getTileCacheEnabled() const {
void Renderer::reduceMemoryUse() {
gfx::BackendScope guard{impl->backend};
impl->clearPreparedRenderTree();
impl->reduceMemoryUse();
impl->orchestrator.reduceMemoryUse();
}
void Renderer::clearData() {
impl->clearPreparedRenderTree();
impl->orchestrator.clearData();
}

View File

@@ -1,6 +1,7 @@
#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>
@@ -58,7 +59,7 @@ RendererObserver& nullObserver() {
#ifndef NDEBUG
constexpr uint64_t RetainedCounterLogIntervalFrames = 120;
void logRetainedRendererCounters(const RetainedUnderlayCache& cache) {
void logRetainedRendererCounters(const RetainedUnderlayCache& cache, ParticleFastPathMissReason fastPathReason) {
const auto detailed = particle::rendererDebugCounterSnapshot();
const auto retained = cache.getDiagnostics();
if (detailed.fullRendererFrame == 0 ||
@@ -79,7 +80,11 @@ void logRetainedRendererCounters(const RetainedUnderlayCache& cache) {
<< " buildRevision=" << cache.getBuildRevision()
<< " staticDraw=" << detailed.retainedUnderlayStaticGroupDraw
<< " dynamicDraw=" << detailed.retainedDynamicGroupDraw
<< " particleAdvance=" << detailed.particleSimulationAdvance;
<< " particleAdvance=" << detailed.particleSimulationAdvance
<< " fastHit=" << detailed.particleFastFrameHit
<< " fastMiss=" << detailed.particleFastFrameMiss
<< " particleFull=" << detailed.particleFullFrame
<< " fastReason=" << particleFastPathMissReasonText(fastPathReason);
Log::Info(Event::Render, message.str());
}
#endif
@@ -130,9 +135,153 @@ void Renderer::Impl::synchronizeRetainedUnderlayState(
retainedUnderlayCache->synchronizeRuntimeState(state);
}
void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<UpdateParameters>& updateParameters) {
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 currentBoundary = orchestrator.getRetainedDynamicBoundary();
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 revisionMatches =
retainedUnderlayCache->getStaticRevision() == retainedUnderlayRevisionTracker.current() &&
retainedUnderlayCache->getBuildRevision() == retainedUnderlayRevisionTracker.current() &&
particlePreparedStaticRevision == retainedUnderlayRevisionTracker.current();
const bool boundaryMatches = particlePreparedBoundary == currentBoundary;
const bool topologyMatches = particlePreparedTopologyRevision == retainedUnderlayTopologyTracker.current();
const bool contentRevisionMatches =
particlePreparedContentRevision == orchestrator.getRetainedUnderlayContentRevision();
const bool staticStable =
currentBoundary && orchestrator.retainedUnderlayIsStable(updateParameters->timePoint, *currentBoundary);
const ParticleFastPathGateInput input{
.hasPreparedTree = !particlePreparedTree.empty(),
.retainedEnabled = retained.enabled,
.cacheValid = retainedUnderlayCache->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,
.supportedScene = particlePreparedSceneSupported,
.annotationMutation = annotationMutation,
};
const auto decision = evaluateParticleFastPath(input);
particleFastPathLastReason = 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) {
particlePreparedSignature = makeParticleFastPathUpdateSignature(*updateParameters);
particlePreparedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
particlePreparedTopologyRevision = retainedUnderlayTopologyTracker.current();
particlePreparedStaticRevision = retainedUnderlayRevisionTracker.current();
particlePreparedBoundary = orchestrator.getRetainedDynamicBoundary();
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();
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);
}
auto& context = backend.getContext();
context.setObserver(this);
@@ -273,7 +422,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
// - 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)
@@ -295,8 +444,13 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
// - LAYER GROUP UPDATE ------------------------------------------------------------------------
// Updates all layer groups and process changes
if (staticData && staticData->shaders) {
orchestrator.updateLayers(
*staticData->shaders, context, renderTreeParameters.transformParams.state, updateParameters, renderTree);
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();
@@ -314,7 +468,8 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
});
retainedKey = RetainedUnderlayKey{size, *retainedBoundary, retainedCameraMatrix};
}
const auto retainedTopologyRevision = retainedUnderlayTopologyTracker.observe(retainedTopology);
const auto retainedTopologyRevision = particleFastFrame ? retainedUnderlayTopologyTracker.current()
: retainedUnderlayTopologyTracker.observe(retainedTopology);
const auto retainedStaticGroupCount = retainedTopology.staticPrefix.size();
bool legacyBelowBoundary = false;
if (retainedBoundary) {
@@ -359,6 +514,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
renderTreeParameters.debugOptions == MapDebugOptions::NoDebug &&
retainedStaticStable && retainedPlacementStable &&
!legacyBelowBoundary && !retainedUnsupportedStaticLayer;
if (!particleFastFrame) particlePreparedSceneSupported = retainedSupported;
const char* retainedFallbackReason = !retainedBoundary ? "no-boundary"
: retainedStaticGroupCount == 0 ? "empty-static-prefix"
: !renderTreeParameters.loaded ? "style-not-loaded"
@@ -394,29 +550,44 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
const auto debugGroup = uploadPass->createDebugGroup("layerGroup-upload");
#endif
// Update the debug layer groups
orchestrator.updateDebugLayerGroups(renderTree, parameters);
// 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;
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) {
layerGroup.runTweakers(renderTree, parameters);
parameters.currentLayer++;
});
parameters.currentLayer = 0;
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) {
layerGroup.runTweakers(renderTree, parameters);
parameters.currentLayer++;
});
if (particleFastFrame && retainedBoundary) {
orchestrator.visitLayerGroupsFrom(*retainedBoundary, [&](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
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
if (particleFastFrame && retainedBoundary) {
orchestrator.visitLayerGroupsFrom(
*retainedBoundary, [&](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
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
if (!particleFastFrame) {
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
}
if (useRetainedUnderlay) retainedUnderlayCache->upload(*uploadPass);
}
@@ -594,6 +765,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
};
const auto drawableDebugOverlays = [&] {
if (particleFastFrame) return;
// Renders debug overlays.
{
const auto debugGroup(parameters.renderPass->createDebugGroup("debug"));
@@ -619,7 +791,14 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
drawableDebugOverlays();
// Give the layers a chance to do cleanup
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.postRender(orchestrator, parameters); });
if (particleFastFrame && retainedBoundary) {
orchestrator.visitLayerGroupsFrom(*retainedBoundary, [&](LayerGroupBase& layerGroup, size_t) {
layerGroup.postRender(orchestrator, parameters);
});
} else {
orchestrator.visitLayerGroups(
[&](LayerGroupBase& layerGroup) { layerGroup.postRender(orchestrator, parameters); });
}
context.unbindGlobalUniformBuffers(*parameters.renderPass);
// Ends the RenderPass
@@ -662,7 +841,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
}
#ifndef NDEBUG
logRetainedRendererCounters(*retainedUnderlayCache);
logRetainedRendererCounters(*retainedUnderlayCache, particleFastPathLastReason);
#endif
frameCount += 1;

View File

@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/renderer/render_orchestrator.hpp>
#include <mbgl/renderer/particle_fast_path.hpp>
#include <mbgl/gfx/context_observer.hpp>
#if MLN_RENDER_BACKEND_METAL
@@ -9,6 +10,7 @@
#endif // MLN_RENDER_BACKEND_METAL
#include <memory>
#include <optional>
#include <string>
namespace mbgl {
@@ -42,8 +44,11 @@ private:
void setObserver(RendererObserver*);
void render(const RenderTree&, const std::shared_ptr<UpdateParameters>&);
void render(const RenderTree&, const std::shared_ptr<UpdateParameters>&, bool particleFastFrame);
void synchronizeRetainedUnderlayState(const std::shared_ptr<RetainedUnderlayRuntimeState>&);
bool tryRenderParticleFastFrame(const std::shared_ptr<UpdateParameters>&);
void commitPreparedRenderTree(std::unique_ptr<RenderTree>, const std::shared_ptr<UpdateParameters>&);
void clearPreparedRenderTree();
void reduceMemoryUse();
@@ -59,9 +64,53 @@ private:
std::unique_ptr<RetainedUnderlayCache> retainedUnderlayCache;
RetainedUnderlayRevisionTracker retainedUnderlayRevisionTracker;
RetainedUnderlayTopologyTracker retainedUnderlayTopologyTracker;
ParticleFastPathPreparedTree<RenderTree> particlePreparedTree;
gfx::DynamicTextureAtlasPtr dynamicTextureAtlas;
bool styleLoaded = false;
struct ParticleFastPathUpdateSignature {
Size viewport;
mat4 cameraMatrix{};
float pixelRatio = 1.0f;
MapMode mode = MapMode::Continuous;
MapDebugOptions debugOptions = MapDebugOptions::NoDebug;
bool styleLoaded = false;
bool spriteLoaded = false;
bool transformChanging = false;
bool gestureInProgress = false;
const void* light = nullptr;
const void* images = nullptr;
const void* sources = nullptr;
const void* layers = nullptr;
const void* fontFaces = nullptr;
const void* fileSource = nullptr;
std::string glyphURL;
std::optional<Duration> transitionDuration;
std::optional<Duration> transitionDelay;
bool placementTransitions = true;
uint8_t prefetchZoomDelta = 0;
bool crossSourceCollisions = false;
bool fastPFOREnabled = false;
double tileLodMinRadius = 0;
double tileLodScale = 0;
double tileLodPitchThreshold = 0;
double tileLodZoomShift = 0;
TileLodMode tileLodMode = TileLodMode::Default;
};
ParticleFastPathUpdateSignature makeParticleFastPathUpdateSignature(const UpdateParameters&) const;
static bool particleFastPathNonCameraInputsMatch(const ParticleFastPathUpdateSignature&,
const ParticleFastPathUpdateSignature&);
std::optional<ParticleFastPathUpdateSignature> particlePreparedSignature;
uint64_t particlePreparedContentRevision = 0;
uint64_t particlePreparedTopologyRevision = 0;
uint64_t particlePreparedStaticRevision = 0;
std::optional<int32_t> particlePreparedBoundary;
Size particlePreparedBackendSize;
bool particlePreparedSceneSupported = false;
ParticleFastPathMissReason particleFastPathLastReason = ParticleFastPathMissReason::NoPreparedTree;
enum class RenderState {
Never,
Partial,