390 lines
14 KiB
C++
390 lines
14 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
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<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;
|
|
}
|
|
|
|
// 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<StyleLayerDiagnostics>& layers,
|
|
int32_t begin,
|
|
int32_t endExclusive) {
|
|
if (begin < 0 || endExclusive < begin || endExclusive > static_cast<int32_t>(layers.size())) return true;
|
|
for (int32_t i = begin; i < endExclusive; ++i) {
|
|
if (layers[static_cast<std::size_t>(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<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;
|
|
bool placementCapable = false;
|
|
bool placementGatePassed = false;
|
|
std::string actualExecution{"not-rendered"};
|
|
std::optional<RenderSegmentCacheDiagnostics> cache;
|
|
};
|
|
|
|
// Platform-neutral, bounded diagnostic samples. No pointers to mutable renderer objects.
|
|
struct FastGateRetainedSample {
|
|
uint32_t segmentIndex = 0;
|
|
RenderSegmentCacheDiagnostics cache;
|
|
bool placementGatePassed = false;
|
|
std::string actualExecution;
|
|
};
|
|
|
|
struct FastGateCacheInputSample {
|
|
uint32_t segmentIndex = 0;
|
|
bool exists = false;
|
|
bool valid = false;
|
|
uint64_t buildRevision = 0;
|
|
uint64_t staticRevision = 0;
|
|
};
|
|
|
|
struct FastGatePhaseSample {
|
|
// Placement changed/fade come from the last completed render-tree snapshot
|
|
// in pre-fast. Fast tree flags do not imply a fresh Placement calculation.
|
|
// They are never substituted for the current controller gate #13.
|
|
bool hasPlacementSnapshot = false;
|
|
bool placementChanged = false;
|
|
float symbolFadeChange = 0;
|
|
bool placementTransitionActive = false;
|
|
bool retainedSamplesTruncated = false;
|
|
uint64_t contentRevision = 0;
|
|
uint64_t topologyRevision = 0;
|
|
uint64_t configRevision = 0;
|
|
uint64_t fullCpuFrame = 0;
|
|
std::optional<uint64_t> processSimulationAdvance;
|
|
std::vector<FastGateRetainedSample> retained;
|
|
};
|
|
|
|
struct FastGateFrameSample {
|
|
uint64_t sequence = 0;
|
|
uint64_t frame = 0;
|
|
int64_t updateTimeNs = 0;
|
|
uint32_t failMask = 0;
|
|
uint32_t knownMask = 0x00ffffff;
|
|
std::string firstFailure;
|
|
bool decisionConsistent = true;
|
|
bool candidate = false;
|
|
bool preFastPlacementTransition = false;
|
|
bool placementTransitionsEnabled = true;
|
|
bool completed = false;
|
|
std::string outcome{"pending"};
|
|
bool cacheInputsTruncated = false;
|
|
std::vector<FastGateCacheInputSample> cacheInputs;
|
|
FastGatePhaseSample pre;
|
|
FastGatePhaseSample post;
|
|
};
|
|
|
|
struct FastGateTraceDiagnostics {
|
|
uint32_t schemaVersion = 1;
|
|
uint64_t lifecycle = 0;
|
|
uint64_t attempts = 0;
|
|
uint64_t overwritten = 0;
|
|
uint64_t decisionMismatch = 0;
|
|
uint64_t fullRendered = 0;
|
|
uint64_t fastRendered = 0;
|
|
uint64_t noRender = 0;
|
|
std::array<uint64_t, 24> failureCounts{};
|
|
std::array<uint64_t, 24> firstFailureCounts{};
|
|
std::vector<FastGateFrameSample> frames;
|
|
};
|
|
|
|
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<RenderSourceReadinessDiagnostics> sources;
|
|
RenderMutationDiagnostics lastMutation;
|
|
std::vector<StyleLayerDiagnostics> styleLayers;
|
|
std::vector<RenderSegmentDiagnosticsEntry> segments;
|
|
std::optional<FastGateTraceDiagnostics> fastGateTrace;
|
|
};
|
|
|
|
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 mln
|