feat: add particle static CPU fast path
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
void onStyleLoaded();
|
||||
|
||||
void updateData();
|
||||
bool hasPendingChanges();
|
||||
|
||||
void addTile(AnnotationTile&);
|
||||
void removeTile(AnnotationTile&);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
135
src/mbgl/renderer/particle_fast_path.hpp
Normal file
135
src/mbgl/renderer/particle_fast_path.hpp
Normal 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
|
||||
@@ -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; }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user