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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user