feat: add STEP-113 Android PBF fast gate diagnostics
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-10-11 09:11:27 +09:00
parent c6d13e1059
commit 92efa36f4c
14 changed files with 1368 additions and 5 deletions

View File

@@ -1362,3 +1362,138 @@ TEST(RenderSegmentPlan, InvalidJSONClearsRendererPlanAndValidPlanRecovers) {
ASSERT_TRUE(state.setRenderSegmentPlan(std::move(recovered)));
EXPECT_TRUE(resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), segmentLayerIds(), true).valid);
}
TEST(FastGateTrace, EveryGateBitMatchesProductionDecisionOrder) {
using Field = bool ParticleFastPathGateInput::*;
const std::array<Field, 24> fields{{
&ParticleFastPathGateInput::hasPreparedTree, &ParticleFastPathGateInput::retainedEnabled,
&ParticleFastPathGateInput::cacheValid, &ParticleFastPathGateInput::revisionMatches,
&ParticleFastPathGateInput::contentRevisionMatches, &ParticleFastPathGateInput::viewportMatches,
&ParticleFastPathGateInput::cameraMatches, &ParticleFastPathGateInput::updateParametersMatch,
&ParticleFastPathGateInput::styleLoaded, &ParticleFastPathGateInput::boundaryMatches,
&ParticleFastPathGateInput::topologyMatches, &ParticleFastPathGateInput::staticStable,
&ParticleFastPathGateInput::hasPlacementTransition, &ParticleFastPathGateInput::hasPendingChanges,
&ParticleFastPathGateInput::hasNonParticleTransition, &ParticleFastPathGateInput::hasNonParticleRepaint,
&ParticleFastPathGateInput::hasFadingTiles, &ParticleFastPathGateInput::particleNeedsRepaint,
&ParticleFastPathGateInput::stillImageRequest, &ParticleFastPathGateInput::continuousMode,
&ParticleFastPathGateInput::debugDisabled, &ParticleFastPathGateInput::unsupportedContinuousLayer,
&ParticleFastPathGateInput::supportedScene, &ParticleFastPathGateInput::annotationMutation,
}};
EXPECT_EQ(particleFastGateFailureMask(fastPathInput()), 0u);
auto allFail = fastPathInput();
for (std::size_t i = 0; i < fields.size(); ++i) {
auto input = fastPathInput();
input.*fields[i] = !(input.*fields[i]);
allFail.*fields[i] = input.*fields[i];
EXPECT_EQ(particleFastGateFailureMask(input), uint32_t{1} << i);
EXPECT_EQ(evaluateParticleFastPath(input).reason, particleFastGateReasons[i]);
EXPECT_FALSE(evaluateParticleFastPath(input).candidate);
}
EXPECT_EQ(particleFastGateFailureMask(allFail), 0x00ffffffu);
}
TEST(FastGateTrace, CacheMissRetainsLaterFailuresAndBoundedOrderedWindow) {
RetainedUnderlayRuntimeState state;
state.resetFastGateTrace();
auto input = fastPathInput();
input.cacheValid = false;
input.unsupportedContinuousLayer = true;
input.supportedScene = false;
const auto decision = evaluateParticleFastPath(input);
for (uint64_t i = 0; i < 137; ++i) {
state.recordFastGateAttempt(i, static_cast<int64_t>(i * 33333333), input, decision, true);
if (i == 0) state.completeFastGateAttempt("no-render-tree");
else {
state.recordFullCpuFrame();
state.completeFastGateAttempt("full-rendered");
}
}
EXPECT_FALSE(state.renderSegmentSnapshot().fastGateTrace); // No ring copy on internal render hot path.
const auto snapshot = state.renderSegmentSnapshot(true);
ASSERT_TRUE(snapshot.fastGateTrace);
const auto& t = *snapshot.fastGateTrace;
EXPECT_EQ(t.attempts, 137u);
EXPECT_EQ(t.overwritten, 17u);
EXPECT_EQ(t.decisionMismatch, 0u);
EXPECT_EQ(t.fullRendered, 136u);
EXPECT_EQ(t.noRender, 1u);
ASSERT_EQ(t.frames.size(), 120u);
EXPECT_EQ(t.frames.front().sequence, 18u);
EXPECT_EQ(t.frames.back().sequence, 137u);
EXPECT_EQ(t.frames.back().firstFailure, "cache-invalid");
EXPECT_EQ(t.frames.back().failMask, (1u << 2) | (1u << 21) | (1u << 22));
EXPECT_EQ(t.frames.back().pre.fullCpuFrame, 135u);
EXPECT_EQ(t.frames.back().post.fullCpuFrame, 136u);
EXPECT_EQ(t.failureCounts[2], 137u);
EXPECT_EQ(t.failureCounts[21], 137u);
EXPECT_EQ(t.failureCounts[22], 137u);
EXPECT_EQ(t.firstFailureCounts[2], 137u);
const auto json = renderSegmentDiagnosticsToJSON(snapshot);
rapidjson::Document document;
ASSERT_FALSE(document.Parse(json.c_str()).HasParseError());
const auto& last = document["fastGateTrace"]["frames"][119];
ASSERT_EQ(last["gateStatus"].Size(), 24u);
EXPECT_STREQ(last["gateStatus"][2].GetString(), "FAIL");
EXPECT_STREQ(last["gateStatus"][21].GetString(), "FAIL");
EXPECT_STREQ(last["gateStatus"][22].GetString(), "FAIL");
EXPECT_STREQ(last["pre"]["phase"].GetString(), "pre-fast");
EXPECT_STREQ(last["post"]["phase"].GetString(), "post-render");
state.resetFastGateTrace();
const auto reset = state.renderSegmentSnapshot(true);
ASSERT_TRUE(reset.fastGateTrace);
EXPECT_EQ(reset.fastGateTrace->lifecycle, t.lifecycle + 1);
EXPECT_TRUE(reset.fastGateTrace->frames.empty());
EXPECT_EQ(reset.fastGateTrace->attempts, 0u);
EXPECT_EQ(reset.fastGateTrace->failureCounts[2], 0u);
}
TEST(FastGateTrace, PendingAttemptAndInconsistentShadowAreExplicit) {
RetainedUnderlayRuntimeState state;
auto input = fastPathInput();
state.recordFastGateAttempt(8, 123, input, {false, ParticleFastPathMissReason::CacheInvalid}, false);
auto snapshot = state.renderSegmentSnapshot(true);
ASSERT_TRUE(snapshot.fastGateTrace);
ASSERT_EQ(snapshot.fastGateTrace->frames.size(), 1u);
EXPECT_EQ(snapshot.fastGateTrace->decisionMismatch, 1u);
EXPECT_FALSE(snapshot.fastGateTrace->frames[0].completed);
EXPECT_FALSE(snapshot.fastGateTrace->frames[0].placementTransitionsEnabled);
const auto json = renderSegmentDiagnosticsToJSON(snapshot);
rapidjson::Document document;
ASSERT_FALSE(document.Parse(json.c_str()).HasParseError());
EXPECT_TRUE(document["fastGateTrace"]["frames"][rapidjson::SizeType{0}]["post"].IsNull());
state.completeFastGateAttempt("fast-rendered");
state.completeFastGateAttempt("fast-rendered"); // Completion must be idempotent.
snapshot = state.renderSegmentSnapshot(true);
EXPECT_EQ(snapshot.fastGateTrace->fastRendered, 1u);
}
TEST(FastGateTrace, RetainedSamplesHaveDistinctPhasesAndExplicitLimit) {
RetainedUnderlayRuntimeState state;
RenderSegmentPlan configured;
std::vector<std::string> ids;
std::vector<StyleLayerDiagnostics> layers;
for (int32_t i = 0; i < 18; ++i) {
ids.push_back("r-" + std::to_string(i));
configured.segments.push_back({RenderSegmentStrategy::Retained,
i ? std::optional<std::string>(ids.back()) : std::nullopt});
layers.push_back({i, ids.back(), "background", true, std::nullopt, std::nullopt});
}
ASSERT_TRUE(state.setRenderSegmentPlan(configured));
const auto resolved = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), ids, true);
ASSERT_TRUE(resolved.valid);
state.recordRenderSegmentResolution(resolved, layers);
const auto input = fastPathInput();
state.recordFastGateAttempt(0, 0, input, evaluateParticleFastPath(input), true);
state.recordSegmentBuild(0);
state.recordSegmentExecution(0, true, true, true, 7, "build", nullptr);
state.completeFastGateAttempt("full-rendered");
const auto snapshot = state.renderSegmentSnapshot(true);
const auto& frame = snapshot.fastGateTrace->frames.front();
ASSERT_EQ(frame.pre.retained.size(), 16u);
ASSERT_EQ(frame.post.retained.size(), 16u);
EXPECT_TRUE(frame.pre.retainedSamplesTruncated);
EXPECT_TRUE(frame.post.retainedSamplesTruncated);
EXPECT_EQ(frame.pre.retained[0].cache.build, 0u);
EXPECT_EQ(frame.post.retained[0].cache.build, 1u);
EXPECT_EQ(frame.post.retained[0].cache.sourceRevision, 7u);
}