fix: preserve canonical color semantics after QBIN decode
This commit is contained in:
@@ -5,16 +5,23 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
namespace mbgl::particle {
|
namespace mbgl::particle {
|
||||||
|
|
||||||
// Renderer-neutral canonical snapshots. QBIN stays confined to ingress and
|
// Renderer-neutral canonical snapshots. QBIN stays confined to ingress and
|
||||||
// field Core; renderers receive only the existing canonical field shape.
|
// field Core; renderers receive only the existing canonical field shape.
|
||||||
|
enum class MaterializedColorValueSource : uint8_t {
|
||||||
|
Scalar,
|
||||||
|
VectorMagnitude,
|
||||||
|
};
|
||||||
|
|
||||||
struct MaterializedColorProjection {
|
struct MaterializedColorProjection {
|
||||||
std::shared_ptr<const ParticleFieldSnapshot> t0;
|
std::shared_ptr<const ParticleFieldSnapshot> t0;
|
||||||
std::shared_ptr<const ParticleFieldSnapshot> t1;
|
std::shared_ptr<const ParticleFieldSnapshot> t1;
|
||||||
ColorProjectionContentIdentity contentIdentity;
|
ColorProjectionContentIdentity contentIdentity;
|
||||||
FieldSampleState sampleState;
|
FieldSampleState sampleState;
|
||||||
|
MaterializedColorValueSource valueSource = MaterializedColorValueSource::Scalar;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MaterializedParticleProjection {
|
struct MaterializedParticleProjection {
|
||||||
|
|||||||
@@ -138,23 +138,76 @@ bool vectorSnapshot(const DecodedFieldFrame& frame, const ParticleBinding& bindi
|
|||||||
|
|
||||||
std::optional<MaterializedColorProjection> materializeColorProjection(const ColorProjection& projection) {
|
std::optional<MaterializedColorProjection> materializeColorProjection(const ColorProjection& projection) {
|
||||||
if (!projection.t0Frame || !projection.t1Frame || !compatible(*projection.t0Frame, *projection.t1Frame)) return {};
|
if (!projection.t0Frame || !projection.t1Frame || !compatible(*projection.t0Frame, *projection.t1Frame)) return {};
|
||||||
const auto* p0 = findPlane(*projection.t0Frame, projection.contentIdentity.binding.plane);
|
|
||||||
const auto* p1 = findPlane(*projection.t1Frame, projection.contentIdentity.binding.plane);
|
const auto& binding = projection.contentIdentity.binding;
|
||||||
|
if (binding.expectedValueClass == FieldValueClass::VelocityComponent) {
|
||||||
|
const auto findComponent = [&](const DecodedFieldFrame& frame, const FieldComponent component) {
|
||||||
|
const DecodedFieldPlane* result = nullptr;
|
||||||
|
for (const auto& plane : frame.planes) {
|
||||||
|
if (plane.valueClass != FieldValueClass::VelocityComponent || plane.unitID != binding.expectedUnitID ||
|
||||||
|
plane.key.groupID != binding.plane.groupID || plane.key.component != component)
|
||||||
|
continue;
|
||||||
|
if (result) return static_cast<const DecodedFieldPlane*>(nullptr);
|
||||||
|
result = &plane;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto* u0 = findComponent(*projection.t0Frame, FieldComponent::UEast);
|
||||||
|
const auto* v0 = findComponent(*projection.t0Frame, FieldComponent::VNorth);
|
||||||
|
const auto* u1 = findComponent(*projection.t1Frame, FieldComponent::UEast);
|
||||||
|
const auto* v1 = findComponent(*projection.t1Frame, FieldComponent::VNorth);
|
||||||
|
if (!u0 || !v0 || !u1 || !v1 || !u0->isInterpolationCompatibleWith(*u1) ||
|
||||||
|
!v0->isInterpolationCompatibleWith(*v1))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
ParticleBinding vectorBinding{
|
||||||
|
ParticleBindingMode::VectorComponents,
|
||||||
|
binding.fieldKey,
|
||||||
|
u0->key,
|
||||||
|
v0->key,
|
||||||
|
{},
|
||||||
|
binding.expectedUnitID,
|
||||||
|
};
|
||||||
|
auto t0 = std::make_shared<ParticleFieldSnapshot>(
|
||||||
|
baseSnapshot(*projection.t0Frame, u0->contentIdentity + "|" + v0->contentIdentity));
|
||||||
|
auto t1 = std::make_shared<ParticleFieldSnapshot>(
|
||||||
|
baseSnapshot(*projection.t1Frame, u1->contentIdentity + "|" + v1->contentIdentity));
|
||||||
|
if (!vectorSnapshot(*projection.t0Frame, vectorBinding, *t0) ||
|
||||||
|
!vectorSnapshot(*projection.t1Frame, vectorBinding, *t1))
|
||||||
|
return {};
|
||||||
|
return MaterializedColorProjection{std::move(t0),
|
||||||
|
std::move(t1),
|
||||||
|
projection.contentIdentity,
|
||||||
|
projection.sampleState,
|
||||||
|
MaterializedColorValueSource::VectorMagnitude};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* p0 = findPlane(*projection.t0Frame, binding.plane);
|
||||||
|
const auto* p1 = findPlane(*projection.t1Frame, binding.plane);
|
||||||
if (!p0 || !p1) return {};
|
if (!p0 || !p1) return {};
|
||||||
auto t0 = std::make_shared<ParticleFieldSnapshot>(baseSnapshot(*projection.t0Frame, projection.contentIdentity.t0Plane));
|
auto t0 = std::make_shared<ParticleFieldSnapshot>(
|
||||||
auto t1 = std::make_shared<ParticleFieldSnapshot>(baseSnapshot(*projection.t1Frame, projection.contentIdentity.t1Plane));
|
baseSnapshot(*projection.t0Frame, projection.contentIdentity.t0Plane));
|
||||||
if (!scalarSnapshot(*projection.t0Frame, *p0, *t0) || !scalarSnapshot(*projection.t1Frame, *p1, *t1))
|
auto t1 = std::make_shared<ParticleFieldSnapshot>(
|
||||||
return {};
|
baseSnapshot(*projection.t1Frame, projection.contentIdentity.t1Plane));
|
||||||
return MaterializedColorProjection{std::move(t0), std::move(t1), projection.contentIdentity, projection.sampleState};
|
if (!scalarSnapshot(*projection.t0Frame, *p0, *t0) || !scalarSnapshot(*projection.t1Frame, *p1, *t1)) return {};
|
||||||
|
return MaterializedColorProjection{std::move(t0),
|
||||||
|
std::move(t1),
|
||||||
|
projection.contentIdentity,
|
||||||
|
projection.sampleState,
|
||||||
|
MaterializedColorValueSource::Scalar};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<MaterializedParticleProjection> materializeParticleProjection(const ParticleProjection& projection) {
|
std::optional<MaterializedParticleProjection> materializeParticleProjection(const ParticleProjection& projection) {
|
||||||
if (!projection.t0Frame || !projection.t1Frame || !compatible(*projection.t0Frame, *projection.t1Frame)) return {};
|
if (!projection.t0Frame || !projection.t1Frame || !compatible(*projection.t0Frame, *projection.t1Frame)) return {};
|
||||||
auto t0 = std::make_shared<ParticleFieldSnapshot>(baseSnapshot(*projection.t0Frame, projection.contentIdentity.t0FirstPlane));
|
auto t0 = std::make_shared<ParticleFieldSnapshot>(
|
||||||
auto t1 = std::make_shared<ParticleFieldSnapshot>(baseSnapshot(*projection.t1Frame, projection.contentIdentity.t1FirstPlane));
|
baseSnapshot(*projection.t0Frame, projection.contentIdentity.t0FirstPlane));
|
||||||
|
auto t1 = std::make_shared<ParticleFieldSnapshot>(
|
||||||
|
baseSnapshot(*projection.t1Frame, projection.contentIdentity.t1FirstPlane));
|
||||||
if (!vectorSnapshot(*projection.t0Frame, projection.contentIdentity.binding, *t0) ||
|
if (!vectorSnapshot(*projection.t0Frame, projection.contentIdentity.binding, *t0) ||
|
||||||
!vectorSnapshot(*projection.t1Frame, projection.contentIdentity.binding, *t1))
|
!vectorSnapshot(*projection.t1Frame, projection.contentIdentity.binding, *t1))
|
||||||
return {};
|
return {};
|
||||||
return MaterializedParticleProjection{std::move(t0), std::move(t1), projection.contentIdentity, projection.sampleState};
|
return MaterializedParticleProjection{
|
||||||
|
std::move(t0), std::move(t1), projection.contentIdentity, projection.sampleState};
|
||||||
}
|
}
|
||||||
} // namespace mbgl::particle
|
} // namespace mbgl::particle
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ float scale(const particle::ParticleFieldSnapshot& field, style::FieldValueSourc
|
|||||||
float result = 1.0f;
|
float result = 1.0f;
|
||||||
for (size_t index = 0; index < field.valid.size(); ++index)
|
for (size_t index = 0; index < field.valid.size(); ++index)
|
||||||
if (field.valid[index]) {
|
if (field.valid[index]) {
|
||||||
if (valueSource == style::FieldValueSource::Scalar) {
|
if (valueSource == style::FieldValueSource::Scalar) {
|
||||||
result = std::max(result, std::abs(field.scalar[index]));
|
result = std::max(result, std::abs(field.scalar[index]));
|
||||||
} else {
|
} else {
|
||||||
result = std::max(result, std::abs(field.vector[index * 2]));
|
result = std::max(result, std::abs(field.vector[index * 2]));
|
||||||
result = std::max(result, std::abs(field.vector[index * 2 + 1]));
|
result = std::max(result, std::abs(field.vector[index * 2 + 1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,17 +62,17 @@ std::shared_ptr<PremultipliedImage> image(const particle::ParticleFieldSnapshot&
|
|||||||
auto result = std::make_shared<PremultipliedImage>(Size{field.width, field.height});
|
auto result = std::make_shared<PremultipliedImage>(Size{field.width, field.height});
|
||||||
for (uint32_t y = 0; y < field.height; ++y)
|
for (uint32_t y = 0; y < field.height; ++y)
|
||||||
for (uint32_t x = 0; x < field.width; ++x) {
|
for (uint32_t x = 0; x < field.width; ++x) {
|
||||||
const auto cell = static_cast<size_t>(y) * field.width + x;
|
const auto cell = static_cast<size_t>(y) * field.width + x;
|
||||||
const auto pixel = (static_cast<size_t>(y) * field.width + x) * PremultipliedImage::channels;
|
const auto pixel = (static_cast<size_t>(y) * field.width + x) * PremultipliedImage::channels;
|
||||||
const float first = valueSource == style::FieldValueSource::Scalar ? field.scalar[cell]
|
const float first = valueSource == style::FieldValueSource::Scalar ? field.scalar[cell]
|
||||||
: field.vector[cell * 2];
|
: field.vector[cell * 2];
|
||||||
const float second = valueSource == style::FieldValueSource::Scalar ? 0.0f : field.vector[cell * 2 + 1];
|
const float second = valueSource == style::FieldValueSource::Scalar ? 0.0f : field.vector[cell * 2 + 1];
|
||||||
result->data[pixel] = static_cast<uint8_t>(std::clamp(first / fieldScale * .5f + .5f, 0.0f, 1.0f) * 255.0f);
|
result->data[pixel] = static_cast<uint8_t>(std::clamp(first / fieldScale * .5f + .5f, 0.0f, 1.0f) * 255.0f);
|
||||||
result->data[pixel + 1] = static_cast<uint8_t>(std::clamp(second / fieldScale * .5f + .5f, 0.0f, 1.0f) *
|
result->data[pixel + 1] = static_cast<uint8_t>(std::clamp(second / fieldScale * .5f + .5f, 0.0f, 1.0f) *
|
||||||
255.0f);
|
255.0f);
|
||||||
result->data[pixel + 2] = 0;
|
result->data[pixel + 2] = 0;
|
||||||
result->data[pixel + 3] = field.valid[y * field.width + x] ? 255 : 0;
|
result->data[pixel + 3] = field.valid[y * field.width + x] ? 255 : 0;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,8 @@ struct Resources {
|
|||||||
class Tweaker final : public gfx::DrawableTweaker {
|
class Tweaker final : public gfx::DrawableTweaker {
|
||||||
public:
|
public:
|
||||||
Tweaker(std::shared_ptr<Resources> resources_, int16_t wrap_)
|
Tweaker(std::shared_ptr<Resources> resources_, int16_t wrap_)
|
||||||
: resources(std::move(resources_)), wrap(wrap_) {}
|
: resources(std::move(resources_)),
|
||||||
|
wrap(wrap_) {}
|
||||||
void init(gfx::Drawable&) override {}
|
void init(gfx::Drawable&) override {}
|
||||||
void execute(gfx::Drawable& drawable, PaintParameters& parameters) override {
|
void execute(gfx::Drawable& drawable, PaintParameters& parameters) override {
|
||||||
drawable.setTexture(resources->texture0, shaders::idParticleGpuValidationPreviousTexture);
|
drawable.setTexture(resources->texture0, shaders::idParticleGpuValidationPreviousTexture);
|
||||||
@@ -154,8 +155,8 @@ bool uploadField(gfx::Context& context,
|
|||||||
if (!outputTexture) return false;
|
if (!outputTexture) return false;
|
||||||
outputTexture->setImage(outputImage);
|
outputTexture->setImage(outputImage);
|
||||||
outputTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear,
|
outputTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear,
|
||||||
.wrapU = gfx::TextureWrapType::Clamp,
|
.wrapU = gfx::TextureWrapType::Clamp,
|
||||||
.wrapV = gfx::TextureWrapType::Clamp});
|
.wrapV = gfx::TextureWrapType::Clamp});
|
||||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::FieldTextureUpload);
|
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::FieldTextureUpload);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -199,6 +200,7 @@ void RenderWeatherColorLayer::prepare(const LayerPrepareParameters& parameters)
|
|||||||
if (!parameters.source || parameters.source->baseImpl->type != style::SourceType::RasterArray) return;
|
if (!parameters.source || parameters.source->baseImpl->type != style::SourceType::RasterArray) return;
|
||||||
const auto& source = static_cast<const RenderRasterArraySource&>(*parameters.source);
|
const auto& source = static_cast<const RenderRasterArraySource&>(*parameters.source);
|
||||||
sourceUsesV4Projection = false;
|
sourceUsesV4Projection = false;
|
||||||
|
sourceProjectionUsesVectorMagnitude = false;
|
||||||
sourceProjectionMix = 0.0f;
|
sourceProjectionMix = 0.0f;
|
||||||
if (source.impl().v4RendererState.color) {
|
if (source.impl().v4RendererState.color) {
|
||||||
const auto& projection = *source.impl().v4RendererState.color;
|
const auto& projection = *source.impl().v4RendererState.color;
|
||||||
@@ -207,6 +209,8 @@ void RenderWeatherColorLayer::prepare(const LayerPrepareParameters& parameters)
|
|||||||
sourceProjectionMix = projection.sampleState.mix;
|
sourceProjectionMix = projection.sampleState.mix;
|
||||||
sourceTimeline.reset();
|
sourceTimeline.reset();
|
||||||
sourceUsesV4Projection = true;
|
sourceUsesV4Projection = true;
|
||||||
|
sourceProjectionUsesVectorMagnitude = projection.valueSource ==
|
||||||
|
particle::MaterializedColorValueSource::VectorMagnitude;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sourceTimeline = source.impl().timeline;
|
sourceTimeline = source.impl().timeline;
|
||||||
@@ -242,10 +246,11 @@ void RenderWeatherColorLayer::update(gfx::ShaderRegistry& shaders,
|
|||||||
resources.reset();
|
resources.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// V4 ColorBinding has already selected and materialized its scalar plane;
|
// Encoded ingress is decoder-only. After materialization, WeatherColor
|
||||||
// it must not be reinterpreted through the legacy style value source.
|
// consumes the same canonical scalar/vector semantics as the legacy path.
|
||||||
const auto valueSource = sourceUsesV4Projection
|
const auto valueSource = sourceUsesV4Projection
|
||||||
? style::FieldValueSource::Scalar
|
? (sourceProjectionUsesVectorMagnitude ? style::FieldValueSource::VectorMagnitude
|
||||||
|
: style::FieldValueSource::Scalar)
|
||||||
: static_cast<const style::WeatherColorLayer::Impl&>(*baseImpl).valueSource;
|
: static_cast<const style::WeatherColorLayer::Impl&>(*baseImpl).valueSource;
|
||||||
if (!supportsValueSource(*sourceField, valueSource) || !supportsValueSource(*sourceNextField, valueSource)) {
|
if (!supportsValueSource(*sourceField, valueSource) || !supportsValueSource(*sourceNextField, valueSource)) {
|
||||||
if (layerGroup) static_cast<LayerGroup&>(*layerGroup).clearDrawables();
|
if (layerGroup) static_cast<LayerGroup&>(*layerGroup).clearDrawables();
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ private:
|
|||||||
std::shared_ptr<style::RasterArrayTimelineRuntime> sourceTimeline;
|
std::shared_ptr<style::RasterArrayTimelineRuntime> sourceTimeline;
|
||||||
float sourceProjectionMix = 0.0f;
|
float sourceProjectionMix = 0.0f;
|
||||||
bool sourceUsesV4Projection = false;
|
bool sourceUsesV4Projection = false;
|
||||||
|
bool sourceProjectionUsesVectorMagnitude = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -39,21 +39,21 @@ RasterArrayEncodedFieldSubmitResult mapEncodedFieldStoreResult(const particle::F
|
|||||||
switch (result) {
|
switch (result) {
|
||||||
case particle::FieldSeriesSubmitResult::Inserted:
|
case particle::FieldSeriesSubmitResult::Inserted:
|
||||||
return RasterArrayEncodedFieldSubmitResult::AcceptedInserted;
|
return RasterArrayEncodedFieldSubmitResult::AcceptedInserted;
|
||||||
case particle::FieldSeriesSubmitResult::ReplacedRevision:
|
case particle::FieldSeriesSubmitResult::ReplacedRevision:
|
||||||
return RasterArrayEncodedFieldSubmitResult::AcceptedReplacedRevision;
|
return RasterArrayEncodedFieldSubmitResult::AcceptedReplacedRevision;
|
||||||
case particle::FieldSeriesSubmitResult::Duplicate:
|
case particle::FieldSeriesSubmitResult::Duplicate:
|
||||||
return RasterArrayEncodedFieldSubmitResult::Duplicate;
|
return RasterArrayEncodedFieldSubmitResult::Duplicate;
|
||||||
case particle::FieldSeriesSubmitResult::RejectedInvalidFieldKey:
|
case particle::FieldSeriesSubmitResult::RejectedInvalidFieldKey:
|
||||||
return RasterArrayEncodedFieldSubmitResult::RejectedInvalidFieldKey;
|
return RasterArrayEncodedFieldSubmitResult::RejectedInvalidFieldKey;
|
||||||
case particle::FieldSeriesSubmitResult::RejectedIdentityConflict:
|
case particle::FieldSeriesSubmitResult::RejectedIdentityConflict:
|
||||||
return RasterArrayEncodedFieldSubmitResult::RejectedIdentityConflict;
|
return RasterArrayEncodedFieldSubmitResult::RejectedIdentityConflict;
|
||||||
case particle::FieldSeriesSubmitResult::RejectedStaleRevision:
|
case particle::FieldSeriesSubmitResult::RejectedStaleRevision:
|
||||||
return RasterArrayEncodedFieldSubmitResult::RejectedStaleRevision;
|
return RasterArrayEncodedFieldSubmitResult::RejectedStaleRevision;
|
||||||
case particle::FieldSeriesSubmitResult::RejectedIncompatibleFrame:
|
case particle::FieldSeriesSubmitResult::RejectedIncompatibleFrame:
|
||||||
case particle::FieldSeriesSubmitResult::RejectedInvalidFrame:
|
case particle::FieldSeriesSubmitResult::RejectedInvalidFrame:
|
||||||
return RasterArrayEncodedFieldSubmitResult::RejectedIncompatible;
|
return RasterArrayEncodedFieldSubmitResult::RejectedIncompatible;
|
||||||
case particle::FieldSeriesSubmitResult::RejectedCapacity:
|
case particle::FieldSeriesSubmitResult::RejectedCapacity:
|
||||||
return RasterArrayEncodedFieldSubmitResult::RejectedCapacity;
|
return RasterArrayEncodedFieldSubmitResult::RejectedCapacity;
|
||||||
}
|
}
|
||||||
return RasterArrayEncodedFieldSubmitResult::RejectedIncompatible;
|
return RasterArrayEncodedFieldSubmitResult::RejectedIncompatible;
|
||||||
}
|
}
|
||||||
@@ -66,8 +66,8 @@ template <class Projection>
|
|||||||
bool sameProjection(const std::shared_ptr<const Projection>& a, const std::shared_ptr<const Projection>& b) {
|
bool sameProjection(const std::shared_ptr<const Projection>& a, const std::shared_ptr<const Projection>& b) {
|
||||||
if (static_cast<bool>(a) != static_cast<bool>(b)) return false;
|
if (static_cast<bool>(a) != static_cast<bool>(b)) return false;
|
||||||
return !a || (a->contentIdentity == b->contentIdentity && a->sampleState.mix == b->sampleState.mix &&
|
return !a || (a->contentIdentity == b->contentIdentity && a->sampleState.mix == b->sampleState.mix &&
|
||||||
a->sampleState.requestedForecastTimeMs == b->sampleState.requestedForecastTimeMs &&
|
a->sampleState.requestedForecastTimeMs == b->sampleState.requestedForecastTimeMs && a->t0 && b->t0 &&
|
||||||
a->t0 && b->t0 && a->t1 && b->t1 && sameSnapshot(*a->t0, *b->t0) && sameSnapshot(*a->t1, *b->t1));
|
a->t1 && b->t1 && sameSnapshot(*a->t0, *b->t0) && sameSnapshot(*a->t1, *b->t1));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -207,7 +207,7 @@ RasterArrayCadenceAdvance RasterArrayTimelineRuntime::advanceIfDue(const std::ch
|
|||||||
lastCadenceLog = now;
|
lastCadenceLog = now;
|
||||||
Log::Info(Event::Render,
|
Log::Info(Event::Render,
|
||||||
"WeatherCadence: targetFps=" + std::to_string(static_cast<uint8_t>(targetFps_)) +
|
"WeatherCadence: targetFps=" + std::to_string(static_cast<uint8_t>(targetFps_)) +
|
||||||
" weatherTicks=" + std::to_string(cadenceTickCount));
|
" weatherTicks=" + std::to_string(cadenceTickCount));
|
||||||
}
|
}
|
||||||
if (lastTick == std::chrono::steady_clock::time_point{}) {
|
if (lastTick == std::chrono::steady_clock::time_point{}) {
|
||||||
lastTick = now;
|
lastTick = now;
|
||||||
@@ -341,8 +341,8 @@ bool RasterArrayTimelineRuntime::seekTimestamp(const uint64_t timestamp) {
|
|||||||
const auto previousState = state;
|
const auto previousState = state;
|
||||||
const auto upper = std::upper_bound(
|
const auto upper = std::upper_bound(
|
||||||
entries.begin(), entries.end(), timestamp, [](uint64_t value, const Entry& entry) {
|
entries.begin(), entries.end(), timestamp, [](uint64_t value, const Entry& entry) {
|
||||||
return value < entry.timestamp;
|
return value < entry.timestamp;
|
||||||
});
|
});
|
||||||
if (upper == entries.begin()) return false;
|
if (upper == entries.begin()) return false;
|
||||||
if (upper == entries.end()) {
|
if (upper == entries.end()) {
|
||||||
t0 = entries.size() - 2;
|
t0 = entries.size() - 2;
|
||||||
@@ -353,7 +353,7 @@ bool RasterArrayTimelineRuntime::seekTimestamp(const uint64_t timestamp) {
|
|||||||
const auto duration = static_cast<float>(entries[t0 + 1].timestamp - entries[t0].timestamp);
|
const auto duration = static_cast<float>(entries[t0 + 1].timestamp - entries[t0].timestamp);
|
||||||
mix = duration > 0.0f ? static_cast<float>(timestamp - entries[t0].timestamp) / duration : 0.0f;
|
mix = duration > 0.0f ? static_cast<float>(timestamp - entries[t0].timestamp) / duration : 0.0f;
|
||||||
state = previousState == RasterArrayPlaybackState::Playing ? RasterArrayPlaybackState::Playing
|
state = previousState == RasterArrayPlaybackState::Playing ? RasterArrayPlaybackState::Playing
|
||||||
: RasterArrayPlaybackState::Paused;
|
: RasterArrayPlaybackState::Paused;
|
||||||
}
|
}
|
||||||
if (t0 != previousT0) ++windowRevision;
|
if (t0 != previousT0) ++windowRevision;
|
||||||
lastTick = std::chrono::steady_clock::now();
|
lastTick = std::chrono::steady_clock::now();
|
||||||
@@ -428,8 +428,7 @@ RasterArrayEncodedFieldSubmitResult RasterArraySource::submitEncodedField(
|
|||||||
// checks metadata, but validate it here to make the Host ordering explicit.
|
// checks metadata, but validate it here to make the Host ordering explicit.
|
||||||
if (!validEncodedFieldKey(fieldKey)) return RasterArrayEncodedFieldSubmitResult::RejectedInvalidFieldKey;
|
if (!validEncodedFieldKey(fieldKey)) return RasterArrayEncodedFieldSubmitResult::RejectedInvalidFieldKey;
|
||||||
if (!validRuntimeMetadata(runtimeMetadata)) return RasterArrayEncodedFieldSubmitResult::RejectedDecode;
|
if (!validRuntimeMetadata(runtimeMetadata)) return RasterArrayEncodedFieldSubmitResult::RejectedDecode;
|
||||||
auto ingress = particle::ingress::EncodedFieldIngress::decodeAndValidate(
|
auto ingress = particle::ingress::EncodedFieldIngress::decodeAndValidate(runtimeMetadata, &contract, rawQbinBytes);
|
||||||
runtimeMetadata, &contract, rawQbinBytes);
|
|
||||||
if (ingress.code == particle::ingress::EncodedFieldIngressCode::DecodeRejected)
|
if (ingress.code == particle::ingress::EncodedFieldIngressCode::DecodeRejected)
|
||||||
return RasterArrayEncodedFieldSubmitResult::RejectedDecode;
|
return RasterArrayEncodedFieldSubmitResult::RejectedDecode;
|
||||||
if (ingress.code != particle::ingress::EncodedFieldIngressCode::Accepted)
|
if (ingress.code != particle::ingress::EncodedFieldIngressCode::Accepted)
|
||||||
@@ -499,16 +498,19 @@ void RasterArraySource::refreshV4RendererState() {
|
|||||||
if (colorBinding) {
|
if (colorBinding) {
|
||||||
const auto resolved = resolveColorProjection();
|
const auto resolved = resolveColorProjection();
|
||||||
if (const auto materialized = resolved.accepted() ? particle::materializeColorProjection(*resolved.projection)
|
if (const auto materialized = resolved.accepted() ? particle::materializeColorProjection(*resolved.projection)
|
||||||
: std::nullopt)
|
: std::nullopt)
|
||||||
next.color = std::make_shared<const particle::MaterializedColorProjection>(std::move(*materialized));
|
next.color = std::make_shared<const particle::MaterializedColorProjection>(std::move(*materialized));
|
||||||
if (next.color) particle::incrementRendererDebugCounter(particle::RendererDebugCounter::V4ProjectionMaterialize);
|
if (next.color)
|
||||||
|
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::V4ProjectionMaterialize);
|
||||||
}
|
}
|
||||||
if (particleBinding) {
|
if (particleBinding) {
|
||||||
const auto resolved = resolveParticleProjection();
|
const auto resolved = resolveParticleProjection();
|
||||||
if (const auto materialized = resolved.accepted() ? particle::materializeParticleProjection(*resolved.projection)
|
if (const auto materialized = resolved.accepted()
|
||||||
: std::nullopt)
|
? particle::materializeParticleProjection(*resolved.projection)
|
||||||
|
: std::nullopt)
|
||||||
next.particle = std::make_shared<const particle::MaterializedParticleProjection>(std::move(*materialized));
|
next.particle = std::make_shared<const particle::MaterializedParticleProjection>(std::move(*materialized));
|
||||||
if (next.particle) particle::incrementRendererDebugCounter(particle::RendererDebugCounter::V4ProjectionMaterialize);
|
if (next.particle)
|
||||||
|
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::V4ProjectionMaterialize);
|
||||||
}
|
}
|
||||||
const auto& current = impl().v4RendererState;
|
const auto& current = impl().v4RendererState;
|
||||||
if (sameProjection(current.color, next.color) && sameProjection(current.particle, next.particle)) return;
|
if (sameProjection(current.color, next.color) && sameProjection(current.particle, next.particle)) return;
|
||||||
@@ -691,7 +693,7 @@ void RasterArraySource::tryLoad() {
|
|||||||
" dataVersion=" + std::to_string(loadedField.dataVersion));
|
" dataVersion=" + std::to_string(loadedField.dataVersion));
|
||||||
if (impl().nextField) {
|
if (impl().nextField) {
|
||||||
Log::Info(Event::Render,
|
Log::Info(Event::Render,
|
||||||
"ParticleField: source loaded next fieldId=" + impl().nextField->fieldID +
|
"ParticleField: source loaded next fieldId=" + impl().nextField->fieldID +
|
||||||
" generation=" + std::to_string(impl().nextField->generation) +
|
" generation=" + std::to_string(impl().nextField->generation) +
|
||||||
" dataVersion=" + std::to_string(impl().nextField->dataVersion));
|
" dataVersion=" + std::to_string(impl().nextField->dataVersion));
|
||||||
}
|
}
|
||||||
@@ -715,7 +717,7 @@ void RasterArraySource::tryLoadRuntimeField(const size_t index) {
|
|||||||
candidate.snapshot = particle::OfflineFixtureParticleAdapter::load(candidate.descriptor.fixtureID,
|
candidate.snapshot = particle::OfflineFixtureParticleAdapter::load(candidate.descriptor.fixtureID,
|
||||||
*candidate.metadata,
|
*candidate.metadata,
|
||||||
*candidate.fieldBytes,
|
*candidate.fieldBytes,
|
||||||
++nextRasterArrayFieldGeneration);
|
++nextRasterArrayFieldGeneration);
|
||||||
Log::Info(Event::Render,
|
Log::Info(Event::Render,
|
||||||
"ParticleField: runtime candidate ready fieldId=" + candidate.snapshot->fieldID +
|
"ParticleField: runtime candidate ready fieldId=" + candidate.snapshot->fieldID +
|
||||||
" dataVersion=" + std::to_string(candidate.snapshot->dataVersion));
|
" dataVersion=" + std::to_string(candidate.snapshot->dataVersion));
|
||||||
@@ -725,8 +727,8 @@ void RasterArraySource::tryLoadRuntimeField(const size_t index) {
|
|||||||
"ParticleField: event=ADAPTER_REJECTED sourceId=" + getID() +
|
"ParticleField: event=ADAPTER_REJECTED sourceId=" + getID() +
|
||||||
" candidateIndex=" + std::to_string(index) + " fieldId=" + candidate.descriptor.fixtureID +
|
" candidateIndex=" + std::to_string(index) + " fieldId=" + candidate.descriptor.fixtureID +
|
||||||
" generation=" + std::to_string(candidate.snapshot ? candidate.snapshot->generation : 0) +
|
" generation=" + std::to_string(candidate.snapshot ? candidate.snapshot->generation : 0) +
|
||||||
" validTime=" + std::to_string(candidate.snapshot ? candidate.snapshot->validTime : 0) +
|
" validTime=" + std::to_string(candidate.snapshot ? candidate.snapshot->validTime : 0) +
|
||||||
" error=" + error.what());
|
" error=" + error.what());
|
||||||
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(error.what())));
|
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(error.what())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,9 +749,9 @@ void RasterArraySource::tryLoadLocalField(const size_t index) {
|
|||||||
candidate.snapshot = std::move(*result.snapshot);
|
candidate.snapshot = std::move(*result.snapshot);
|
||||||
Log::Info(Event::Render,
|
Log::Info(Event::Render,
|
||||||
"ParticleField: event=PRODUCTION_FEED_LOAD fieldId=" + candidate.snapshot->fieldID +
|
"ParticleField: event=PRODUCTION_FEED_LOAD fieldId=" + candidate.snapshot->fieldID +
|
||||||
" generation=" + std::to_string(candidate.snapshot->generation) +
|
" generation=" + std::to_string(candidate.snapshot->generation) +
|
||||||
" validTime=" + std::to_string(candidate.snapshot->validTime) +
|
" validTime=" + std::to_string(candidate.snapshot->validTime) +
|
||||||
" payloadBytes=" + std::to_string(candidate.fieldBytes->size()));
|
" payloadBytes=" + std::to_string(candidate.fieldBytes->size()));
|
||||||
tryInitializeTimeline();
|
tryInitializeTimeline();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -796,7 +798,7 @@ void RasterArraySource::tryInitializeTimeline() {
|
|||||||
auto field = *runtimeFields[index].snapshot;
|
auto field = *runtimeFields[index].snapshot;
|
||||||
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(std::move(field)),
|
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(std::move(field)),
|
||||||
runtimeFields[index].descriptor.timestamp ? runtimeFields[index].descriptor.timestamp
|
runtimeFields[index].descriptor.timestamp ? runtimeFields[index].descriptor.timestamp
|
||||||
: 80000 + static_cast<uint64_t>(index) * 40000});
|
: 80000 + static_cast<uint64_t>(index) * 40000});
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
timeline = std::make_shared<RasterArrayTimelineRuntime>(std::move(entries));
|
timeline = std::make_shared<RasterArrayTimelineRuntime>(std::move(entries));
|
||||||
@@ -1112,7 +1114,7 @@ bool RasterArraySource::seekRuntimeTimelineToTimestamp(const uint64_t timestamp)
|
|||||||
if (!accepted) {
|
if (!accepted) {
|
||||||
Log::Warning(Event::Render,
|
Log::Warning(Event::Render,
|
||||||
"WeatherRuntime: event=SEEK_TIMESTAMP_REJECTED reason=OUT_OF_RANGE sourceId=" + getID() +
|
"WeatherRuntime: event=SEEK_TIMESTAMP_REJECTED reason=OUT_OF_RANGE sourceId=" + getID() +
|
||||||
" timestamp=" + std::to_string(timestamp));
|
" timestamp=" + std::to_string(timestamp));
|
||||||
}
|
}
|
||||||
return accepted;
|
return accepted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ particle::ResolvedFieldProfileContract windContract() {
|
|||||||
result.profileVersion = 1;
|
result.profileVersion = 1;
|
||||||
result.requiredPlanes = {
|
result.requiredPlanes = {
|
||||||
{1, particle::FieldValueClass::VelocityComponent, 1, 1, particle::FieldComponent::UEast, true},
|
{1, particle::FieldValueClass::VelocityComponent, 1, 1, particle::FieldComponent::UEast, true},
|
||||||
{2, particle::FieldValueClass::VelocityComponent, 1, 1, particle::FieldComponent::VNorth, true},
|
{2, particle::FieldValueClass::VelocityComponent, 1, 1, particle::FieldComponent::VNorth, true},
|
||||||
{3, particle::FieldValueClass::Validity, 0, 0, particle::FieldComponent::None, true}};
|
{3, particle::FieldValueClass::Validity, 0, 0, particle::FieldComponent::None, true}};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
particle::ResolvedFieldProfileContract waveContract() {
|
particle::ResolvedFieldProfileContract waveContract() {
|
||||||
@@ -414,6 +414,7 @@ TEST(Source, RasterArrayV4RendererStateMaterializesBindingsAndMixWithoutTouching
|
|||||||
EXPECT_EQ(particles.t0->vector, (std::vector<float>{1, 5, 2, 6, 3, 7, 4, 8}));
|
EXPECT_EQ(particles.t0->vector, (std::vector<float>{1, 5, 2, 6, 3, 7, 4, 8}));
|
||||||
EXPECT_EQ(particles.t0->valid, (std::vector<uint8_t>{1, 1, 1, 1}));
|
EXPECT_EQ(particles.t0->valid, (std::vector<uint8_t>{1, 1, 1, 1}));
|
||||||
EXPECT_EQ(color.t0->scalar, (std::vector<float>{1, 2, 3, 4}));
|
EXPECT_EQ(color.t0->scalar, (std::vector<float>{1, 2, 3, 4}));
|
||||||
|
EXPECT_EQ(color.valueSource, particle::MaterializedColorValueSource::Scalar);
|
||||||
const auto particleIdentity = particles.contentIdentity;
|
const auto particleIdentity = particles.contentIdentity;
|
||||||
const auto retainedParticleT0 = particles.t0;
|
const auto retainedParticleT0 = particles.t0;
|
||||||
ASSERT_TRUE(source.setRequestedForecastTime(Base + Hour / 4));
|
ASSERT_TRUE(source.setRequestedForecastTime(Base + Hour / 4));
|
||||||
@@ -429,6 +430,46 @@ TEST(Source, RasterArrayV4RendererStateMaterializesBindingsAndMixWithoutTouching
|
|||||||
EXPECT_FALSE(source.impl().v4RendererState.particle);
|
EXPECT_FALSE(source.impl().v4RendererState.particle);
|
||||||
EXPECT_FALSE(source.impl().v4RendererState.color);
|
EXPECT_FALSE(source.impl().v4RendererState.color);
|
||||||
}
|
}
|
||||||
|
TEST(Source, RasterArrayV4VelocityColorReconstructsDecodedUVIndependentlyOfParticles) {
|
||||||
|
style::RasterArraySource source{"v4-vector-color"};
|
||||||
|
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});
|
||||||
|
ASSERT_TRUE(source.setRequestedForecastTime(Base + Hour / 2));
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.impl().v4RendererState.color);
|
||||||
|
EXPECT_FALSE(source.impl().v4RendererState.particle);
|
||||||
|
const auto& colorWithoutParticles = *source.impl().v4RendererState.color;
|
||||||
|
EXPECT_EQ(colorWithoutParticles.valueSource, particle::MaterializedColorValueSource::VectorMagnitude);
|
||||||
|
ASSERT_TRUE(colorWithoutParticles.t0);
|
||||||
|
ASSERT_TRUE(colorWithoutParticles.t1);
|
||||||
|
EXPECT_TRUE(colorWithoutParticles.t0->hasVector());
|
||||||
|
EXPECT_FALSE(colorWithoutParticles.t0->hasScalar());
|
||||||
|
EXPECT_EQ(colorWithoutParticles.t0->vector, (std::vector<float>{1, 5, 2, 6, 3, 7, 4, 8}));
|
||||||
|
EXPECT_EQ(colorWithoutParticles.t0->valid, (std::vector<uint8_t>{1, 1, 1, 1}));
|
||||||
|
EXPECT_FLOAT_EQ(colorWithoutParticles.sampleState.mix, 0.5f);
|
||||||
|
|
||||||
|
source.setParticleBinding({particle::ParticleBindingMode::VectorComponents,
|
||||||
|
"wind",
|
||||||
|
{1, 1, particle::FieldComponent::UEast},
|
||||||
|
{2, 1, particle::FieldComponent::VNorth},
|
||||||
|
{},
|
||||||
|
1});
|
||||||
|
ASSERT_TRUE(source.impl().v4RendererState.color);
|
||||||
|
ASSERT_TRUE(source.impl().v4RendererState.particle);
|
||||||
|
const auto& color = *source.impl().v4RendererState.color;
|
||||||
|
const auto& particles = *source.impl().v4RendererState.particle;
|
||||||
|
EXPECT_EQ(color.valueSource, particle::MaterializedColorValueSource::VectorMagnitude);
|
||||||
|
EXPECT_EQ(color.t0->vector, particles.t0->vector);
|
||||||
|
EXPECT_EQ(color.t1->vector, particles.t1->vector);
|
||||||
|
EXPECT_EQ(color.t0->valid, particles.t0->valid);
|
||||||
|
EXPECT_FALSE(source.impl().field);
|
||||||
|
EXPECT_FALSE(source.impl().nextField);
|
||||||
|
EXPECT_FALSE(source.impl().timeline);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Source, RasterArrayV4MaterializerUsesExplicitLittleEndianScaleOffsetAndStableRevision) {
|
TEST(Source, RasterArrayV4MaterializerUsesExplicitLittleEndianScaleOffsetAndStableRevision) {
|
||||||
const auto t0 = materializerFrame("scalar",
|
const auto t0 = materializerFrame("scalar",
|
||||||
Base,
|
Base,
|
||||||
@@ -457,8 +498,8 @@ TEST(Source, RasterArrayV4MaterializerUsesExplicitLittleEndianScaleOffsetAndStab
|
|||||||
particle::ColorBinding binding{
|
particle::ColorBinding binding{
|
||||||
"scalar", {99, 0, particle::FieldComponent::None}, particle::FieldValueClass::Scalar, 77};
|
"scalar", {99, 0, particle::FieldComponent::None}, particle::FieldValueClass::Scalar, 77};
|
||||||
particle::ColorProjection projection;
|
particle::ColorProjection projection;
|
||||||
projection.contentIdentity =
|
projection.contentIdentity = {
|
||||||
{binding, {"scalar", Base}, {"scalar", Base + Hour}, "plane-t0", "plane-t1", "validity-t0", "validity-t1"};
|
binding, {"scalar", Base}, {"scalar", Base + Hour}, "plane-t0", "plane-t1", "validity-t0", "validity-t1"};
|
||||||
projection.sampleState = {Base + Hour / 2, 0.5f};
|
projection.sampleState = {Base + Hour / 2, 0.5f};
|
||||||
projection.t0Frame = t0;
|
projection.t0Frame = t0;
|
||||||
projection.t1Frame = t1;
|
projection.t1Frame = t1;
|
||||||
|
|||||||
Reference in New Issue
Block a user