Files
MapLibreParticleLab/include/mbgl/renderer/render_segment.hpp
tei 524aaf4af5
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
fix: localize retained segment source invalidation
2026-10-06 17:18:09 +09:00

272 lines
9.6 KiB
C++

#pragma once
#include <cstdint>
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
namespace mbgl {
enum class RenderSegmentStrategy : uint8_t { Retained, Continuous };
inline const char* renderSegmentStrategyText(RenderSegmentStrategy strategy) noexcept {
return strategy == RenderSegmentStrategy::Retained ? "RETAINED" : "CONTINUOUS";
}
struct RenderSegmentDefinition {
RenderSegmentStrategy strategy = RenderSegmentStrategy::Continuous;
std::optional<std::string> startLayerId;
bool operator==(const RenderSegmentDefinition&) const = default;
};
struct RenderSegmentPlan {
uint32_t schemaVersion = 1;
std::vector<RenderSegmentDefinition> segments;
bool operator==(const RenderSegmentPlan&) const = default;
};
struct StyleLayerDiagnostics {
int32_t layerIndex = -1;
std::string layerId;
std::string layerType;
bool visible = false;
std::optional<std::string> sourceId;
std::optional<std::string> sourceLayer;
bool operator==(const StyleLayerDiagnostics&) const = default;
};
// The style layer list is the single dependency source for diagnostics and rendering.
struct RenderSegmentDependencies {
std::vector<std::string> sourceIds;
std::vector<std::string> globalPrerequisites;
bool unknown = false;
};
inline RenderSegmentDependencies renderSegmentDependencies(const std::vector<StyleLayerDiagnostics>& layers,
int32_t begin,
int32_t endExclusive) {
RenderSegmentDependencies result;
if (begin < 0 || endExclusive < begin || endExclusive > static_cast<int32_t>(layers.size())) {
result.unknown = true;
return result;
}
std::set<std::string> sources;
std::set<std::string> prerequisites;
for (int32_t i = begin; i < endExclusive; ++i) {
const auto& layer = layers[static_cast<std::size_t>(i)];
if (layer.sourceId) sources.insert(*layer.sourceId);
if (layer.layerType == "symbol") prerequisites.insert("symbol/glyph/image/placement");
if (layer.layerType == "custom" || layer.layerType == "custom-drawable") result.unknown = true;
if (layer.layerType != "background" && layer.layerType != "custom" &&
layer.layerType != "custom-drawable" && !layer.sourceId) result.unknown = true;
}
result.sourceIds.assign(sources.begin(), sources.end());
result.globalPrerequisites.assign(prerequisites.begin(), prerequisites.end());
return result;
}
class RenderSegmentRevisionBook {
public:
void advanceGlobal() noexcept { ++globalEpoch; }
void advanceSource(const std::string& sourceId) { sourceRevisions[sourceId] = ++sequence; }
std::optional<uint64_t> revisionFor(const RenderSegmentDependencies& dependencies) const {
if (dependencies.unknown) return std::nullopt;
uint64_t revision = globalEpoch;
for (const auto& id : dependencies.sourceIds) {
const auto found = sourceRevisions.find(id);
if (found == sourceRevisions.end()) return std::nullopt;
for (const unsigned char ch : id) revision = (revision ^ ch) * 1099511628211ULL;
revision = (revision ^ found->second) * 1099511628211ULL;
}
return revision;
}
private:
uint64_t globalEpoch = 1;
uint64_t sequence = 1;
// Retain tombstones: remove + recreate of the same ID always receives a newer identity.
std::unordered_map<std::string, uint64_t> sourceRevisions;
};
struct RenderSegmentSourceStatus { bool loaded = false; bool fading = false; };
struct RenderSegmentSourceAssessment {
bool ready = false;
bool stable = false;
const char* reason = "unknown-dependency";
};
template <typename Lookup>
RenderSegmentSourceAssessment assessRenderSegmentSources(const RenderSegmentDependencies& dependencies,
Lookup lookup) {
RenderSegmentSourceAssessment result;
if (dependencies.unknown) return result;
for (const auto& id : dependencies.sourceIds) {
const std::optional<RenderSegmentSourceStatus> source = lookup(id);
if (!source) { result.reason = "missing-source"; return result; }
if (!source->loaded) { result.reason = "source-not-loaded"; return result; }
if (source->fading) { result.ready = true; result.reason = "source-fading"; return result; }
}
result.ready = true;
result.stable = true;
result.reason = "none";
return result;
}
struct RenderSourceReadinessDiagnostics {
std::string sourceId;
std::string sourceType;
bool enabled = false;
bool isLoaded = false;
bool tileBacked = false;
std::optional<uint32_t> tileCount;
std::optional<uint32_t> incompleteTileCount;
std::optional<uint32_t> loadedTileCount;
bool hasFadingTiles = false;
uint64_t lastSourceMutationSequence = 0;
uint64_t lastTileChangedSequence = 0;
uint64_t lastTileActionSequence = 0;
std::string lastTileAction{"none"};
std::string lastMutationReason{"none"};
bool operator==(const RenderSourceReadinessDiagnostics&) const = default;
};
struct RenderMutationDiagnostics {
uint64_t sequence = 0;
uint64_t contentRevision = 0;
std::string eventType{"none"};
std::string reason{"none"};
std::optional<std::string> sourceId;
std::vector<uint32_t> affectedSegmentIndices;
std::string dependencyScope{"none"};
bool operator==(const RenderMutationDiagnostics&) const = default;
};
struct RendererReadinessDiagnostics {
bool styleLoaded = false;
bool imageManagerLoaded = false;
bool renderOrchestratorLoaded = false;
bool spriteLoaded = false;
bool hasFadingTiles = false;
uint64_t contentRevision = 0;
std::vector<RenderSourceReadinessDiagnostics> sources;
RenderMutationDiagnostics lastMutation;
bool operator==(const RendererReadinessDiagnostics&) const = default;
};
struct RenderSegmentCacheDiagnostics {
std::string cacheState{"disabled"};
uint64_t contentRevision = 0;
uint64_t build = 0;
uint64_t reuse = 0;
uint64_t invalidation = 0;
uint64_t fallbackCount = 0;
std::string finalDecision{"disabled"};
std::string lastFallbackReason{"none"};
bool sourceReady = false;
bool sourceStable = false;
bool supported = false;
uint64_t sourceRevision = 0;
std::string lastInvalidationReason{"none"};
uint64_t textureCreate = 0;
uint64_t compositeCreate = 0;
uint64_t resourceRelease = 0;
};
struct RenderSegmentDiagnosticsEntry {
uint32_t segmentIndex = 0;
RenderSegmentStrategy strategy = RenderSegmentStrategy::Continuous;
std::optional<std::string> configuredStartLayerId;
int32_t beginIndex = -1;
int32_t endExclusive = -1;
std::optional<std::string> resolvedStartLayerId;
std::optional<std::string> resolvedEndLayerId;
std::vector<StyleLayerDiagnostics> layers;
std::vector<std::string> dependentSourceIds;
std::vector<std::string> unknownDependencies;
std::optional<RenderSegmentCacheDiagnostics> cache;
};
struct RenderSegmentDiagnostics {
uint32_t schemaVersion = 1;
bool enabled = true;
bool configured = false;
bool valid = false;
std::string configSource{"unconfigured/legacy"};
uint64_t configRevision = 0;
uint64_t topologyRevision = 0;
uint32_t styleLayerCount = 0;
uint32_t segmentCount = 0;
uint32_t retainedSegmentCount = 0;
uint32_t continuousSegmentCount = 0;
bool coverageComplete = false;
bool fallback = false;
std::string lastReason{"not-configured"};
bool fastPathEligible = false;
bool fastPathActive = false;
std::string fastPathFallbackReason{"no-prepared-tree"};
uint64_t fastPathHit = 0;
uint64_t fastPathMiss = 0;
uint64_t fullCpuFrame = 0;
bool styleLoaded = false;
bool imageManagerLoaded = false;
bool renderOrchestratorLoaded = false;
bool spriteLoaded = false;
bool hasFadingTiles = false;
uint64_t contentRevision = 0;
std::vector<RenderSourceReadinessDiagnostics> sources;
RenderMutationDiagnostics lastMutation;
std::vector<StyleLayerDiagnostics> styleLayers;
std::vector<RenderSegmentDiagnosticsEntry> segments;
};
struct ResolvedRenderSegment {
uint32_t segmentIndex = 0;
RenderSegmentStrategy strategy = RenderSegmentStrategy::Continuous;
std::optional<std::string> configuredStartLayerId;
int32_t beginIndex = 0;
int32_t endExclusive = 0;
bool operator==(const ResolvedRenderSegment&) const = default;
};
struct ResolvedRenderSegmentPlan {
bool configured = false;
bool valid = false;
std::string configSource{"unconfigured/legacy"};
uint64_t configRevision = 0;
std::string reason{"not-configured"};
int32_t layerCount = 0;
std::vector<ResolvedRenderSegment> segments;
bool operator==(const ResolvedRenderSegmentPlan&) const = default;
};
// This is the main-buffer traversal used by the renderer. A failed retained range
// is drawn live exactly once; neighboring cached ranges remain composites.
template <typename Cached, typename Composite, typename Live>
void visitRenderSegmentExecution(const ResolvedRenderSegmentPlan& plan,
Cached cached,
Composite composite,
Live live) {
if (!plan.valid) {
live(0, plan.layerCount);
return;
}
for (const auto& segment : plan.segments) {
if (segment.strategy == RenderSegmentStrategy::Retained && cached(segment.segmentIndex)) {
composite(segment.segmentIndex);
} else {
live(segment.beginIndex, segment.endExclusive);
}
}
}
} // namespace mbgl