fix(step097): complete A2 clock and GPU field correctness
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-09-26 15:17:43 +09:00
parent 5bb1a0e635
commit f49ffa68a1
26 changed files with 610 additions and 86 deletions

View File

@@ -1,8 +1,10 @@
#include <mbgl/particle/ingress/qbin_v2_decoder.hpp>
#include <mbgl/particle/field/field_projection_materialization.hpp>
#include <mbgl/particle/field/offline_fixture_particle_adapter.hpp>
#include <mbgl/particle/renderer_debug_counters.hpp>
#include <mbgl/style/sources/raster_array_source.hpp>
#include <mbgl/style/sources/raster_array_source_impl.hpp>
#include <mbgl/style/source_observer.hpp>
#include <gtest/gtest.h>
@@ -332,6 +334,147 @@ TEST(Source, RasterArrayV4HostApiResolvesIndependentBindingsFromOneForecastClock
EXPECT_TRUE(source.setForecastPlaybackSpeed(2.0f));
}
TEST(Source, RasterArrayV4ProductionClockTicksOnceForBothBindingsAndResetsOnCommands) {
style::RasterArraySource source{"v4-clock"};
submit(source, "wind", "wind", Base, 1);
submit(source, "wind", "wind", Base + Hour, 2);
source.setColorBinding({"wind", {1, 1, particle::FieldComponent::UEast},
particle::FieldValueClass::VelocityComponent, 1});
source.setParticleBinding({particle::ParticleBindingMode::VectorComponents,
"wind", {1, 1, particle::FieldComponent::UEast},
{2, 1, particle::FieldComponent::VNorth}, {}, 1});
ASSERT_TRUE(source.setRequestedForecastTime(Base + 1000));
source.setForecastPlaybackState(particle::ForecastPlaybackState::Playing);
const auto origin = std::chrono::steady_clock::now();
EXPECT_FALSE(source.tickForecastClock(origin));
EXPECT_TRUE(source.tickForecastClock(origin + std::chrono::milliseconds(100)));
EXPECT_FALSE(source.tickForecastClock(origin + std::chrono::milliseconds(100)));
const auto color = source.resolveColorProjection();
const auto particle = source.resolveParticleProjection();
ASSERT_TRUE(color.accepted());
ASSERT_TRUE(particle.accepted());
EXPECT_EQ(color.projection->sampleState.requestedForecastTimeMs, Base + 1100);
EXPECT_EQ(particle.projection->sampleState.requestedForecastTimeMs, Base + 1100);
source.setForecastPlaybackState(particle::ForecastPlaybackState::Paused);
EXPECT_FALSE(source.tickForecastClock(origin + std::chrono::seconds(3)));
EXPECT_EQ(source.resolveParticleProjection().projection->sampleState.requestedForecastTimeMs, Base + 1100);
ASSERT_TRUE(source.setForecastPlaybackSpeed(2.0f));
source.setForecastPlaybackState(particle::ForecastPlaybackState::Playing);
EXPECT_FALSE(source.tickForecastClock(origin + std::chrono::seconds(4)));
EXPECT_TRUE(source.tickForecastClock(origin + std::chrono::milliseconds(4100)));
EXPECT_EQ(source.resolveParticleProjection().projection->sampleState.requestedForecastTimeMs, Base + 1300);
ASSERT_TRUE(source.setRequestedForecastTime(Base + 2000));
EXPECT_FALSE(source.tickForecastClock(origin + std::chrono::seconds(5)));
EXPECT_EQ(source.resolveParticleProjection().projection->sampleState.requestedForecastTimeMs, Base + 2000);
source.clearAllEncodedFields();
EXPECT_FALSE(source.tickForecastClock(origin + std::chrono::seconds(6)));
EXPECT_FALSE(source.resolveParticleProjection().accepted());
}
TEST(Source, RasterArrayV4ClockSourceNotificationReentryDoesNotAdvanceTwice) {
style::RasterArraySource source{"v4-reentry"};
submit(source, "wind", "wind", Base, 1);
submit(source, "wind", "wind", Base + Hour, 2);
source.setColorBinding({"wind", {1, 1, particle::FieldComponent::UEast},
particle::FieldValueClass::VelocityComponent, 1});
source.setParticleBinding({particle::ParticleBindingMode::VectorComponents,
"wind", {1, 1, particle::FieldComponent::UEast},
{2, 1, particle::FieldComponent::VNorth}, {}, 1});
ASSERT_TRUE(source.setRequestedForecastTime(Base + 1000));
source.setForecastPlaybackState(particle::ForecastPlaybackState::Playing);
const auto origin = std::chrono::steady_clock::now();
EXPECT_FALSE(source.tickForecastClock(origin));
struct ReentryObserver final : style::SourceObserver {
style::RasterArraySource& source;
std::chrono::steady_clock::time_point nestedTime;
int notifications = 0;
bool nestedAdvanced = false;
ReentryObserver(style::RasterArraySource& src, std::chrono::steady_clock::time_point time)
: source(src), nestedTime(time) {}
void onSourceChanged(style::Source&) override {
++notifications;
nestedAdvanced = source.tickForecastClock(nestedTime) || nestedAdvanced;
}
} observer(source, origin + std::chrono::milliseconds(200));
source.setObserver(&observer);
ASSERT_TRUE(source.tickForecastClock(origin + std::chrono::milliseconds(100)));
EXPECT_GE(observer.notifications, 1);
EXPECT_FALSE(observer.nestedAdvanced);
EXPECT_EQ(source.resolveParticleProjection().projection->sampleState.requestedForecastTimeMs, Base + 1100);
ASSERT_TRUE(source.tickForecastClock(origin + std::chrono::milliseconds(200)));
EXPECT_FALSE(observer.nestedAdvanced);
EXPECT_EQ(source.resolveParticleProjection().projection->sampleState.requestedForecastTimeMs, Base + 1200);
source.setObserver(nullptr);
}
TEST(Source, RasterArrayV4MixTicksReuseImmutableMaterializationsUntilEndpointChanges) {
style::RasterArraySource source{"v4-materialization-cache"};
submit(source, "wind", "wind", Base, 1);
submit(source, "wind", "wind", Base + Hour, 2);
source.setColorBinding({"wind", {1, 1, particle::FieldComponent::UEast},
particle::FieldValueClass::VelocityComponent, 1});
source.setParticleBinding({particle::ParticleBindingMode::VectorComponents,
"wind", {1, 1, particle::FieldComponent::UEast},
{2, 1, particle::FieldComponent::VNorth}, {}, 1});
ASSERT_TRUE(source.setRequestedForecastTime(Base + 1000));
const auto beforeTicks = particle::rendererDebugCounterSnapshot();
const auto initialColor = source.impl().v4RendererState.color;
const auto initialParticle = source.impl().v4RendererState.particle;
ASSERT_TRUE(initialColor);
ASSERT_TRUE(initialParticle);
ASSERT_TRUE(initialColor->t0);
ASSERT_TRUE(initialColor->t1);
ASSERT_TRUE(initialParticle->t0);
ASSERT_TRUE(initialParticle->t1);
source.setForecastPlaybackState(particle::ForecastPlaybackState::Playing);
const auto origin = std::chrono::steady_clock::now();
EXPECT_FALSE(source.tickForecastClock(origin));
for (uint64_t tick = 1; tick <= 50; ++tick)
EXPECT_TRUE(source.tickForecastClock(origin + std::chrono::milliseconds(tick * 16)));
const auto afterTicks = particle::rendererDebugCounterSnapshot();
const auto mixedColor = source.impl().v4RendererState.color;
const auto mixedParticle = source.impl().v4RendererState.particle;
ASSERT_TRUE(mixedColor);
ASSERT_TRUE(mixedParticle);
EXPECT_EQ(afterTicks.v4ProjectionMaterialize, beforeTicks.v4ProjectionMaterialize);
EXPECT_EQ(afterTicks.fieldTextureUpload, beforeTicks.fieldTextureUpload);
EXPECT_EQ(afterTicks.gpuResourceCreate, beforeTicks.gpuResourceCreate);
EXPECT_EQ(afterTicks.gpuResourceRelease, beforeTicks.gpuResourceRelease);
EXPECT_EQ(mixedColor->t0, initialColor->t0);
EXPECT_EQ(mixedColor->t1, initialColor->t1);
EXPECT_EQ(mixedParticle->t0, initialParticle->t0);
EXPECT_EQ(mixedParticle->t1, initialParticle->t1);
EXPECT_EQ(mixedParticle->sampleState.requestedForecastTimeMs, Base + 1800);
submit(source, "wind", "wind", Base + 2 * Hour, 3);
ASSERT_TRUE(source.setRequestedForecastTime(Base + Hour + 1000));
const auto afterEndpointChange = particle::rendererDebugCounterSnapshot();
const auto advancedParticle = source.impl().v4RendererState.particle;
ASSERT_TRUE(advancedParticle);
EXPECT_EQ(afterEndpointChange.v4ProjectionMaterialize, afterTicks.v4ProjectionMaterialize + 2);
EXPECT_NE(advancedParticle->t0, initialParticle->t0);
EXPECT_NE(advancedParticle->t1, initialParticle->t1);
const auto beforeRevision = afterEndpointChange.v4ProjectionMaterialize;
EXPECT_EQ(source.submitEncodedField("wind", metadata("wind", 4), windContract(), qbin(Base + Hour)),
style::RasterArrayEncodedFieldSubmitResult::AcceptedReplacedRevision);
ASSERT_TRUE(source.setRequestedForecastTime(Base + Hour + 1000));
const auto afterRevision = particle::rendererDebugCounterSnapshot();
const auto revisedParticle = source.impl().v4RendererState.particle;
ASSERT_TRUE(revisedParticle);
EXPECT_EQ(afterRevision.v4ProjectionMaterialize, beforeRevision + 2);
EXPECT_NE(revisedParticle->t0, advancedParticle->t0);
EXPECT_NE(revisedParticle->t1, advancedParticle->t1);
source.clearAllEncodedFields();
EXPECT_FALSE(source.impl().v4RendererState.color);
EXPECT_FALSE(source.impl().v4RendererState.particle);
}
TEST(Source, RasterArrayV4HostApiClearRetainsBindingsAndNeverTouchesV3) {
style::RasterArraySource source{"v4"};
const auto v3T0 = particle::OfflineFixtureParticleAdapter::load("uniform-east", 71);