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

@@ -1,4 +1,5 @@
#include <mbgl/renderer/retained_underlay_cache.hpp>
#include <mbgl/renderer/particle_fast_path.hpp>
#include <gtest/gtest.h>
@@ -31,6 +32,34 @@ RetainedUnderlayRevisionState revisionState(Size size = {640, 480}, int32_t boun
result.cameraMatrix = key(size, boundary).cameraMatrix;
return result;
}
ParticleFastPathGateInput fastPathInput() {
return {
.hasPreparedTree = true,
.retainedEnabled = true,
.cacheValid = true,
.revisionMatches = true,
.contentRevisionMatches = true,
.cameraMatches = true,
.viewportMatches = true,
.updateParametersMatch = true,
.styleLoaded = true,
.boundaryMatches = true,
.topologyMatches = true,
.staticStable = true,
.hasPlacementTransition = false,
.hasPendingChanges = false,
.hasNonParticleTransition = false,
.hasNonParticleRepaint = false,
.hasFadingTiles = false,
.particleNeedsRepaint = true,
.stillImageRequest = false,
.continuousMode = true,
.debugDisabled = true,
.supportedScene = true,
.annotationMutation = false,
};
}
} // namespace
TEST(RetainedDynamicBoundary, SelectsFirstCandidate) {
@@ -310,3 +339,125 @@ TEST(RetainedUnderlayCache, RuntimeControlDisablesAndReenablesMapScopedCache) {
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
EXPECT_TRUE(state->snapshot().enabled);
}
TEST(ParticleFastPath, ParticleOnlyStableRevisionIsCandidate) {
const auto decision = evaluateParticleFastPath(fastPathInput());
EXPECT_TRUE(decision.candidate);
EXPECT_EQ(decision.reason, ParticleFastPathMissReason::None);
}
TEST(ParticleFastPath, RevisionCameraAndViewportMismatchReject) {
auto input = fastPathInput();
input.revisionMatches = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::RevisionMismatch);
input = fastPathInput();
input.cameraMatches = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::CameraChanged);
input = fastPathInput();
input.viewportMatches = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::ViewportChanged);
}
TEST(ParticleFastPath, NonParticleTransitionAndRepaintReject) {
auto input = fastPathInput();
input.hasNonParticleTransition = true;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::NonParticleTransition);
input = fastPathInput();
input.hasNonParticleRepaint = true;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::NonParticleRepaint);
}
TEST(ParticleFastPath, SettledCurrentPlacementAllowsCandidateAfterPlacementFullFrame) {
// A full frame may have run placement and captured placementChanged=true.
// That previous-frame event is deliberately not a fast-path gate input;
// only the controller's current transition state is authoritative.
auto input = fastPathInput();
input.hasPlacementTransition = false;
EXPECT_TRUE(evaluateParticleFastPath(input).candidate);
}
TEST(ParticleFastPath, ActivePlacementFadeAndFadingTilesReject) {
auto input = fastPathInput();
input.hasPlacementTransition = true;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::PlacementOrFade);
input = fastPathInput();
input.hasFadingTiles = true;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::FadingTiles);
}
TEST(ParticleFastPath, CacheDisabledAndSemanticMutationReject) {
auto input = fastPathInput();
input.retainedEnabled = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::RetainedDisabled);
input = fastPathInput();
input.cacheValid = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::CacheInvalid);
input = fastPathInput();
input.contentRevisionMatches = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::ContentMutation);
input = fastPathInput();
input.updateParametersMatch = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::UpdateParametersChanged);
input = fastPathInput();
input.topologyMatches = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::TopologyChanged);
input = fastPathInput();
input.styleLoaded = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::StyleNotLoaded);
}
TEST(ParticleFastPath, PendingStillAndUnsupportedPathsReject) {
auto input = fastPathInput();
input.hasPendingChanges = true;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::PendingChanges);
input = fastPathInput();
input.stillImageRequest = true;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::StillImageRequest);
input = fastPathInput();
input.supportedScene = false;
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::UnsupportedScene);
}
TEST(ParticleFastPath, FullFrameCanRestabilizeCandidate) {
auto input = fastPathInput();
input.contentRevisionMatches = false;
EXPECT_FALSE(evaluateParticleFastPath(input).candidate);
input.contentRevisionMatches = true;
input.revisionMatches = true;
input.cacheValid = true;
input.staticStable = true;
EXPECT_TRUE(evaluateParticleFastPath(input).candidate);
}
TEST(ParticleFastPath, PreparedTreeReplacementOwnsLifetime) {
struct TrackedTree {
TrackedTree(int value_, int& destroyed_)
: value(value_), destroyed(destroyed_) {}
~TrackedTree() { ++destroyed; }
int value;
int& destroyed;
};
int destroyed = 0;
ParticleFastPathPreparedTree<TrackedTree> slot;
slot.replace(std::make_unique<TrackedTree>(1, destroyed));
EXPECT_EQ(slot.get()->value, 1);
slot.replace(std::make_unique<TrackedTree>(2, destroyed));
EXPECT_EQ(destroyed, 1);
EXPECT_EQ(slot.get()->value, 2);
slot.reset();
EXPECT_EQ(destroyed, 2);
EXPECT_TRUE(slot.empty());
}