#pragma once #include #include #include #include #include #include namespace mln { 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 startLayerId; bool operator==(const RenderSegmentDefinition&) const = default; }; struct RenderSegmentPlan { uint32_t schemaVersion = 1; std::vector segments; bool operator==(const RenderSegmentPlan&) const = default; }; struct StyleLayerDiagnostics { int32_t layerIndex = -1; std::string layerId; std::string layerType; bool visible = false; std::optional sourceId; std::optional sourceLayer; bool operator==(const StyleLayerDiagnostics&) const = default; }; // The style layer list is the single dependency source for diagnostics and rendering. struct RenderSegmentDependencies { std::vector sourceIds; std::vector globalPrerequisites; bool unknown = false; }; inline RenderSegmentDependencies renderSegmentDependencies(const std::vector& layers, int32_t begin, int32_t endExclusive) { RenderSegmentDependencies result; if (begin < 0 || endExclusive < begin || endExclusive > static_cast(layers.size())) { result.unknown = true; return result; } std::set sources; std::set prerequisites; for (int32_t i = begin; i < endExclusive; ++i) { const auto& layer = layers[static_cast(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; } // Placement capability is a property of the style layer, not of this frame's // buckets or placementData. Invalid ranges fail closed. inline bool renderSegmentHasPlacementCapableLayer(const std::vector& layers, int32_t begin, int32_t endExclusive) { if (begin < 0 || endExclusive < begin || endExclusive > static_cast(layers.size())) return true; for (int32_t i = begin; i < endExclusive; ++i) { if (layers[static_cast(i)].layerType == "symbol") return true; } return false; } inline bool renderSegmentPlacementGatePassed(bool placementCapable, bool placementChanged, float symbolFadeChange, bool placementTransitionActive) noexcept { return !placementCapable || (!placementChanged && symbolFadeChange == 0.0f && !placementTransitionActive); } struct RenderSegmentGlobalLiveGates { bool retainedMode = false; bool has3D = false; bool debugOptions = false; bool continuousMode = false; bool spriteLoaded = false; bool globalResourcesReady = false; bool globalLightTransition = false; }; inline const char* renderSegmentGlobalLiveFallbackReason(const RenderSegmentGlobalLiveGates& gates) noexcept { if (!gates.retainedMode) return "no-boundary"; if (gates.has3D) return "3d"; if (gates.debugOptions) return "debug-options"; if (!gates.continuousMode) return "map-mode"; if (!gates.spriteLoaded) return "sprite-not-loaded"; if (!gates.globalResourcesReady) return "global-resource-not-ready"; if (gates.globalLightTransition) return "global-light-transition"; return "none"; } class RenderSegmentRevisionBook { public: void advanceGlobal() noexcept { ++globalEpoch; } void advanceSource(const std::string& sourceId) { sourceRevisions[sourceId] = ++sequence; } std::optional 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 sourceRevisions; }; struct RenderSegmentSourceStatus { bool loaded = false; bool fading = false; }; struct RenderSegmentSourceAssessment { bool ready = false; bool stable = false; const char* reason = "unknown-dependency"; }; template RenderSegmentSourceAssessment assessRenderSegmentSources(const RenderSegmentDependencies& dependencies, Lookup lookup) { RenderSegmentSourceAssessment result; if (dependencies.unknown) return result; for (const auto& id : dependencies.sourceIds) { const std::optional 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 tileCount; std::optional incompleteTileCount; std::optional 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 sourceId; std::vector 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 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 configuredStartLayerId; int32_t beginIndex = -1; int32_t endExclusive = -1; std::optional resolvedStartLayerId; std::optional resolvedEndLayerId; std::vector layers; std::vector dependentSourceIds; std::vector unknownDependencies; bool placementCapable = false; bool placementGatePassed = false; std::string actualExecution{"not-rendered"}; std::optional 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 placementChanged = false; float symbolFadeChange = 0.0f; bool placementTransitionActive = false; bool crossSourceCollisions = true; std::string mainBufferExecutionMode{"not-rendered"}; std::string globalLiveFallbackReason{"none"}; bool styleLoaded = false; bool imageManagerLoaded = false; bool renderOrchestratorLoaded = false; bool spriteLoaded = false; bool hasFadingTiles = false; uint64_t contentRevision = 0; std::vector sources; RenderMutationDiagnostics lastMutation; std::vector styleLayers; std::vector segments; }; struct ResolvedRenderSegment { uint32_t segmentIndex = 0; RenderSegmentStrategy strategy = RenderSegmentStrategy::Continuous; std::optional 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 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 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 mln