feat: add generic render segment model
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -184,6 +184,8 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/retained_underlay_cache.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/retained_underlay_cache.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/retained_underlay_runtime_state.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/render_segment_plan.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/render_segment_json.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/change_request.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layer_group.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/tile_layer_group.cpp
|
||||
@@ -310,6 +312,7 @@ list(APPEND INCLUDE_FILES
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer_state.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/retained_underlay.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/render_segment.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/program_parameters.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/storage/database_file_source.hpp
|
||||
|
||||
@@ -293,6 +293,8 @@ MLN_CORE_SOURCE = [
|
||||
"src/mbgl/renderer/render_orchestrator.cpp",
|
||||
"src/mbgl/renderer/render_orchestrator.hpp",
|
||||
"src/mbgl/renderer/continuous_layer_range.hpp",
|
||||
"src/mbgl/renderer/render_segment_plan.hpp",
|
||||
"src/mbgl/renderer/render_segment_json.hpp",
|
||||
"src/mbgl/renderer/render_pass.hpp",
|
||||
"src/mbgl/renderer/render_source.cpp",
|
||||
"src/mbgl/renderer/render_source.hpp",
|
||||
@@ -752,6 +754,7 @@ MLN_CORE_HEADERS = [
|
||||
"include/mbgl/renderer/renderer_observer.hpp",
|
||||
"include/mbgl/renderer/renderer_state.hpp",
|
||||
"include/mbgl/renderer/retained_underlay.hpp",
|
||||
"include/mbgl/renderer/render_segment.hpp",
|
||||
"include/mbgl/shaders/program_parameters.hpp",
|
||||
"include/mbgl/storage/database_file_source.hpp",
|
||||
"include/mbgl/storage/file_source.hpp",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <mbgl/util/client_options.hpp>
|
||||
#include <mbgl/util/action_journal_options.hpp>
|
||||
#include <mbgl/renderer/retained_underlay.hpp>
|
||||
#include <mbgl/renderer/render_segment.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -59,6 +60,8 @@ public:
|
||||
/** Enables or disables the map-scoped retained static underlay optimization. */
|
||||
void setRetainedUnderlayEnabled(bool);
|
||||
bool setContinuousLayerRange(std::string startLayerId, std::string endLayerId);
|
||||
bool setRenderSegmentPlan(RenderSegmentPlan);
|
||||
RenderSegmentDiagnostics getRenderSegmentDiagnostics() const;
|
||||
bool isRetainedUnderlayEnabled() const;
|
||||
RetainedUnderlayDiagnostics getRetainedUnderlayDiagnostics() const;
|
||||
|
||||
|
||||
112
include/mbgl/renderer/render_segment.hpp
Normal file
112
include/mbgl/renderer/render_segment.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#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;
|
||||
};
|
||||
|
||||
struct RenderSegmentCacheDiagnostics {
|
||||
std::string cacheState{"disabled"};
|
||||
uint64_t contentRevision = 0;
|
||||
uint64_t build = 0;
|
||||
uint64_t reuse = 0;
|
||||
uint64_t invalidation = 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::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;
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
FieldRuntime,
|
||||
RendererRuntime,
|
||||
type RetainedUnderlayDiagnostics,
|
||||
type RenderSegmentDiagnostics,
|
||||
type RuntimeSnapshot,
|
||||
} from "@navsea/maplibre-particle-rn";
|
||||
import {
|
||||
@@ -67,6 +68,8 @@ export default function App() {
|
||||
const [snapshot, setSnapshot] = useState<RuntimeSnapshot>(initialSnapshot);
|
||||
const [retainedSnapshot, setRetainedSnapshot] =
|
||||
useState<RetainedUnderlayDiagnostics | null>(null);
|
||||
const [segmentSnapshot, setSegmentSnapshot] =
|
||||
useState<RenderSegmentDiagnostics | null>(null);
|
||||
const [mapStatus, setMapStatus] = useState("LOADING");
|
||||
const [weatherStatus, setWeatherStatus] = useState("WAITING");
|
||||
const [result, setResult] = useState("Waiting for map/style");
|
||||
@@ -91,6 +94,11 @@ export default function App() {
|
||||
const refreshRetained = useCallback(async (tag: number) => {
|
||||
setRetainedSnapshot(await RendererRuntime.getRetainedUnderlayDiagnostics(tag));
|
||||
}, []);
|
||||
const refreshSegments = useCallback(async (tag: number) => {
|
||||
const value = await RendererRuntime.getRenderSegmentDiagnostics(tag);
|
||||
setSegmentSnapshot(value);
|
||||
setResult(JSON.stringify(value, null, 2));
|
||||
}, []);
|
||||
const applyVisualToTag = useCallback(async (tag: number) => {
|
||||
const results = await Promise.all([
|
||||
FieldRuntime.setParticleCount(tag, Number(visual.count)),
|
||||
@@ -384,6 +392,28 @@ export default function App() {
|
||||
onPress={() => reactTag != null && void refreshRetained(reactTag)}
|
||||
/>
|
||||
</View>
|
||||
<Text selectable>
|
||||
Segments: {segmentSnapshot == null ? "not read" :
|
||||
`${segmentSnapshot.valid ? "VALID" : "INVALID"} source=${segmentSnapshot.configSource} count=${segmentSnapshot.segmentCount} topology=${segmentSnapshot.topologyRevision}`}
|
||||
</Text>
|
||||
<View style={styles.row}>
|
||||
<Button
|
||||
title="Apply C-R plan"
|
||||
disabled={reactTag == null}
|
||||
onPress={() => reactTag != null && void RendererRuntime.setRenderSegmentPlan(reactTag, {
|
||||
schemaVersion: 1,
|
||||
segments: [
|
||||
{ strategy: "CONTINUOUS" },
|
||||
{ strategy: "RETAINED", startLayerId: "darkmap-basemap" },
|
||||
],
|
||||
}).then(() => refreshSegments(reactTag))}
|
||||
/>
|
||||
<Button
|
||||
title="Fetch Segment Diagnostics"
|
||||
disabled={reactTag == null}
|
||||
onPress={() => reactTag != null && void refreshSegments(reactTag)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Text>QBIN Format</Text>
|
||||
<View style={styles.row}>
|
||||
|
||||
@@ -70,6 +70,12 @@ class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) :
|
||||
@ReactMethod override fun setContinuousLayerRange(reactTag: Double, startLayerId: String, endLayerId: String, promise: Promise) = withMap(reactTag, promise) { map ->
|
||||
promise.resolve(if (map.setContinuousLayerRange(startLayerId, endLayerId)) "ACCEPTED" else "REJECTED")
|
||||
}
|
||||
@ReactMethod override fun setRenderSegmentPlanJson(reactTag: Double, planJson: String, promise: Promise) = withMap(reactTag, promise) { map ->
|
||||
promise.resolve(if (map.setRenderSegmentPlan(planJson)) "ACCEPTED" else "REJECTED")
|
||||
}
|
||||
@ReactMethod override fun getRenderSegmentDiagnosticsJson(reactTag: Double, promise: Promise) = withMap(reactTag, promise) { map ->
|
||||
promise.resolve(map.renderSegmentDiagnostics)
|
||||
}
|
||||
@ReactMethod override fun getRetainedUnderlayDiagnostics(reactTag: Double, promise: Promise) = withMap(reactTag, promise) { map ->
|
||||
val value = map.retainedUnderlayDiagnostics
|
||||
promise.resolve(Arguments.createMap().apply {
|
||||
|
||||
@@ -169,6 +169,16 @@ RCT_EXPORT_MODULE(WeatherRuntimeBridge)
|
||||
resolve([map setContinuousLayerRangeFromLayerIdentifier:startLayerId toLayerIdentifier:endLayerId] ? @"ACCEPTED" : @"REJECTED");
|
||||
}];
|
||||
}
|
||||
- (void)setRenderSegmentPlanJson:(double)reactTag planJson:(NSString *)planJson resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
[self withMapForTag:reactTag requireRuntime:NO resolve:resolve reject:reject action:^(MLNMapView *map, MLNParticleFieldRuntime *runtime) {
|
||||
resolve([map setRenderSegmentPlanJSON:planJson] ? @"ACCEPTED" : @"REJECTED");
|
||||
}];
|
||||
}
|
||||
- (void)getRenderSegmentDiagnosticsJson:(double)reactTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
[self withMapForTag:reactTag requireRuntime:NO resolve:resolve reject:reject action:^(MLNMapView *map, MLNParticleFieldRuntime *runtime) {
|
||||
resolve(map.renderSegmentDiagnosticsJSON);
|
||||
}];
|
||||
}
|
||||
- (void)getRetainedUnderlayDiagnostics:(double)reactTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
[self withMapForTag:reactTag requireRuntime:NO resolve:resolve reject:reject action:^(MLNMapView *map, MLNParticleFieldRuntime *runtime) {
|
||||
MLNRetainedUnderlayDiagnostics *value = map.retainedUnderlayDiagnostics;
|
||||
|
||||
@@ -27,6 +27,8 @@ export interface Spec extends TurboModule {
|
||||
getState(reactTag: number): Promise<RuntimeSnapshot>;
|
||||
setRetainedUnderlayEnabled(reactTag: number, enabled: boolean): Promise<CommandResult>;
|
||||
setContinuousLayerRange(reactTag: number, startLayerId: string, endLayerId: string): Promise<CommandResult>;
|
||||
setRenderSegmentPlanJson(reactTag: number, planJson: string): Promise<CommandResult>;
|
||||
getRenderSegmentDiagnosticsJson(reactTag: number): Promise<string>;
|
||||
getRetainedUnderlayDiagnostics(reactTag: number): Promise<RetainedUnderlaySnapshot>;
|
||||
clearWindFields(reactTag: number): Promise<CommandResult>;
|
||||
submitWindField(reactTag: number, metadataJson: string, payloadFileUri: string): Promise<FieldSubmitResult>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getNativeFieldRuntimeBridge } from "./NativeWeatherRuntimeBridge";
|
||||
import type { CommandResult, RetainedUnderlayDiagnostics } from "./types";
|
||||
import type { CommandResult, RenderSegmentDiagnostics, RenderSegmentPlan, RetainedUnderlayDiagnostics } from "./types";
|
||||
|
||||
function nativeBridge() {
|
||||
const bridge = getNativeFieldRuntimeBridge();
|
||||
@@ -19,4 +19,9 @@ export const RendererRuntime = {
|
||||
},
|
||||
getRetainedUnderlayDiagnostics: (reactTag: number): Promise<RetainedUnderlayDiagnostics> =>
|
||||
nativeBridge().getRetainedUnderlayDiagnostics(reactTag),
|
||||
setRenderSegmentPlan: (reactTag: number, plan: RenderSegmentPlan): Promise<CommandResult> => {
|
||||
return nativeBridge().setRenderSegmentPlanJson(reactTag, JSON.stringify(plan));
|
||||
},
|
||||
getRenderSegmentDiagnostics: async (reactTag: number): Promise<RenderSegmentDiagnostics> =>
|
||||
JSON.parse(await nativeBridge().getRenderSegmentDiagnosticsJson(reactTag)) as RenderSegmentDiagnostics,
|
||||
} as const;
|
||||
|
||||
@@ -16,6 +16,13 @@ export type {
|
||||
ResolvedFieldProfileContractInput,
|
||||
RuntimeSnapshot,
|
||||
RetainedUnderlayDiagnostics,
|
||||
RenderSegmentDefinition,
|
||||
RenderSegmentDiagnostics,
|
||||
RenderSegmentLayerDiagnostics,
|
||||
RenderSegmentPlan,
|
||||
RenderSegmentStrategy,
|
||||
RetainedRenderSegmentDiagnostics,
|
||||
ContinuousRenderSegmentDiagnostics,
|
||||
RuntimeState,
|
||||
TargetFps,
|
||||
} from "./types";
|
||||
|
||||
@@ -62,3 +62,78 @@ export type RetainedUnderlayDiagnostics = Readonly<{
|
||||
fastPathMiss: number;
|
||||
fullCpuFrame: number;
|
||||
}>;
|
||||
export type RenderSegmentStrategy = "RETAINED" | "CONTINUOUS";
|
||||
export type RenderSegmentDefinition = Readonly<{
|
||||
strategy: RenderSegmentStrategy;
|
||||
startLayerId?: string;
|
||||
}>;
|
||||
export type RenderSegmentPlan = Readonly<{
|
||||
schemaVersion: 1;
|
||||
segments: readonly RenderSegmentDefinition[];
|
||||
}>;
|
||||
export type RenderSegmentLayerDiagnostics = Readonly<{
|
||||
layerIndex: number;
|
||||
layerId: string;
|
||||
layerType: string;
|
||||
visible: boolean;
|
||||
sourceId: string | null;
|
||||
sourceLayer: string | null;
|
||||
}>;
|
||||
export type RetainedRenderSegmentDiagnostics = Readonly<{
|
||||
segmentIndex: number;
|
||||
strategy: "RETAINED";
|
||||
configuredStartLayerId: string | null;
|
||||
beginIndex: number;
|
||||
endExclusive: number;
|
||||
resolvedStartLayerId: string | null;
|
||||
resolvedEndLayerId: string | null;
|
||||
layerCount: number;
|
||||
layers: readonly RenderSegmentLayerDiagnostics[];
|
||||
cache: Readonly<{
|
||||
cacheState: string;
|
||||
contentRevision: number;
|
||||
build: number;
|
||||
reuse: number;
|
||||
invalidation: number;
|
||||
lastInvalidationReason: string;
|
||||
textureCreate: number;
|
||||
compositeCreate: number;
|
||||
resourceRelease: number;
|
||||
}>;
|
||||
}>;
|
||||
export type ContinuousRenderSegmentDiagnostics = Readonly<{
|
||||
segmentIndex: number;
|
||||
strategy: "CONTINUOUS";
|
||||
configuredStartLayerId: string | null;
|
||||
beginIndex: number;
|
||||
endExclusive: number;
|
||||
resolvedStartLayerId: string | null;
|
||||
resolvedEndLayerId: string | null;
|
||||
layerCount: number;
|
||||
layers: readonly RenderSegmentLayerDiagnostics[];
|
||||
cache: null;
|
||||
}>;
|
||||
export type RenderSegmentDiagnostics = Readonly<{
|
||||
schemaVersion: 1;
|
||||
enabled: boolean;
|
||||
configured: boolean;
|
||||
valid: boolean;
|
||||
configSource: "explicit-render-segment-plan" | "continuous-range-compat" | "unconfigured/legacy";
|
||||
configRevision: number;
|
||||
topologyRevision: number;
|
||||
styleLayerCount: number;
|
||||
segmentCount: number;
|
||||
retainedSegmentCount: number;
|
||||
continuousSegmentCount: number;
|
||||
coverageComplete: boolean;
|
||||
fallback: boolean;
|
||||
lastReason: string;
|
||||
fastPathEligible: boolean;
|
||||
fastPathActive: boolean;
|
||||
fastPathFallbackReason: string;
|
||||
fastPathHit: number;
|
||||
fastPathMiss: number;
|
||||
fullCpuFrame: number;
|
||||
styleLayers: readonly RenderSegmentLayerDiagnostics[];
|
||||
segments: readonly (RetainedRenderSegmentDiagnostics | ContinuousRenderSegmentDiagnostics)[];
|
||||
}>;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <mbgl/style/image.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/renderer/query.hpp>
|
||||
#include <mbgl/renderer/render_segment_json.hpp>
|
||||
#include <mbgl/particle/field/field_profile_contract_json.hpp>
|
||||
#include <mbgl/particle/ingress/encoded_field_transport.hpp>
|
||||
|
||||
@@ -727,6 +728,19 @@ jni::jboolean NativeMapView::setContinuousLayerRange(JNIEnv& env,
|
||||
jni::Make<std::string>(env, endLayerId));
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setRenderSegmentPlan(JNIEnv& env, const jni::String& planJson) {
|
||||
const auto plan = parseRenderSegmentPlanJSON(jni::Make<std::string>(env, planJson));
|
||||
if (plan) return map->setRenderSegmentPlan(*plan);
|
||||
RenderSegmentPlan invalid;
|
||||
invalid.schemaVersion = 0;
|
||||
map->setRenderSegmentPlan(std::move(invalid));
|
||||
return false;
|
||||
}
|
||||
|
||||
jni::Local<jni::String> NativeMapView::getRenderSegmentDiagnostics(JNIEnv& env) {
|
||||
return jni::Make<jni::String>(env, renderSegmentDiagnosticsToJSON(map->getRenderSegmentDiagnostics()));
|
||||
}
|
||||
|
||||
jni::Local<jni::Array<jni::String>> NativeMapView::getRetainedUnderlayDiagnostics(JNIEnv& env) {
|
||||
const auto snapshot = map->getRetainedUnderlayDiagnostics();
|
||||
const std::array<std::string, 39> values = {
|
||||
@@ -1858,6 +1872,8 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::getDebug, "nativeGetDebug"),
|
||||
METHOD(&NativeMapView::setRetainedUnderlayEnabled, "nativeSetRetainedUnderlayEnabled"),
|
||||
METHOD(&NativeMapView::setContinuousLayerRange, "nativeSetContinuousLayerRange"),
|
||||
METHOD(&NativeMapView::setRenderSegmentPlan, "nativeSetRenderSegmentPlan"),
|
||||
METHOD(&NativeMapView::getRenderSegmentDiagnostics, "nativeGetRenderSegmentDiagnostics"),
|
||||
METHOD(&NativeMapView::getRetainedUnderlayDiagnostics, "nativeGetRetainedUnderlayDiagnostics"),
|
||||
METHOD(&NativeMapView::getActionJournalLogFiles, "nativeGetActionJournalLogFiles"),
|
||||
METHOD(&NativeMapView::getActionJournalLog, "nativeGetActionJournalLog"),
|
||||
|
||||
@@ -205,6 +205,10 @@ public:
|
||||
|
||||
jni::jboolean setContinuousLayerRange(JNIEnv&, const jni::String&, const jni::String&);
|
||||
|
||||
jni::jboolean setRenderSegmentPlan(JNIEnv&, const jni::String&);
|
||||
|
||||
jni::Local<jni::String> getRenderSegmentDiagnostics(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Array<jni::String>> getRetainedUnderlayDiagnostics(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Array<jni::String>> getActionJournalLogFiles(JNIEnv&);
|
||||
|
||||
@@ -121,6 +121,17 @@ public final class MapLibreMap {
|
||||
return nativeMapView.setContinuousLayerRange(startLayerId, endLayerId);
|
||||
}
|
||||
|
||||
/** Configures the renderer execution topology using schema-versioned JSON. */
|
||||
public boolean setRenderSegmentPlan(@NonNull String planJson) {
|
||||
return nativeMapView.setRenderSegmentPlan(planJson);
|
||||
}
|
||||
|
||||
/** Returns the pull-based Style-to-Segment-to-Layer diagnostics snapshot as JSON. */
|
||||
@NonNull
|
||||
public String getRenderSegmentDiagnostics() {
|
||||
return nativeMapView.getRenderSegmentDiagnostics();
|
||||
}
|
||||
|
||||
/** Returns whether the retained static underlay optimization is enabled for this map. */
|
||||
public boolean isRetainedUnderlayEnabled() {
|
||||
return nativeMapView.getRetainedUnderlayDiagnostics().enabled;
|
||||
|
||||
@@ -290,6 +290,10 @@ interface NativeMap {
|
||||
|
||||
boolean setContinuousLayerRange(String startLayerId, String endLayerId);
|
||||
|
||||
boolean setRenderSegmentPlan(String planJson);
|
||||
|
||||
String getRenderSegmentDiagnostics();
|
||||
|
||||
RetainedUnderlayDiagnostics getRetainedUnderlayDiagnostics();
|
||||
|
||||
String[] getActionJournalLogFiles();
|
||||
|
||||
@@ -667,6 +667,19 @@ final class NativeMapView implements NativeMap {
|
||||
return nativeSetContinuousLayerRange(startLayerId, endLayerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRenderSegmentPlan(@NonNull String planJson) {
|
||||
if (checkState("setRenderSegmentPlan")) return false;
|
||||
return nativeSetRenderSegmentPlan(planJson);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getRenderSegmentDiagnostics() {
|
||||
if (checkState("getRenderSegmentDiagnostics")) return "{\"schemaVersion\":1,\"valid\":false}";
|
||||
return nativeGetRenderSegmentDiagnostics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetainedUnderlayDiagnostics getRetainedUnderlayDiagnostics() {
|
||||
if (checkState("getRetainedUnderlayDiagnostics")) {
|
||||
@@ -1730,6 +1743,12 @@ final class NativeMapView implements NativeMap {
|
||||
@Keep
|
||||
private native boolean nativeSetContinuousLayerRange(String startLayerId, String endLayerId);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeSetRenderSegmentPlan(String planJson);
|
||||
|
||||
@Keep
|
||||
private native String nativeGetRenderSegmentDiagnostics();
|
||||
|
||||
@Keep
|
||||
private native String[] nativeGetRetainedUnderlayDiagnostics();
|
||||
|
||||
|
||||
@@ -2321,6 +2321,12 @@ of north, the map will automatically snap to exact north.
|
||||
- (BOOL)setContinuousLayerRangeFromLayerIdentifier:(NSString *)startLayerId
|
||||
toLayerIdentifier:(NSString *)endLayerId;
|
||||
|
||||
/** Configures the schema-versioned renderer segment plan transported as JSON. */
|
||||
- (BOOL)setRenderSegmentPlanJSON:(NSString *)planJSON;
|
||||
|
||||
/** Returns a pull-based Style-to-Segment-to-Layer diagnostics JSON snapshot. */
|
||||
@property (nonatomic, readonly) NSString *renderSegmentDiagnosticsJSON;
|
||||
|
||||
/** Returns a thread-safe retained-underlay diagnostics snapshot without enabling periodic events. */
|
||||
@property (nonatomic, readonly) MLNRetainedUnderlayDiagnostics *retainedUnderlayDiagnostics;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/layermanager/layer_manager.hpp>
|
||||
#include <mbgl/map/camera.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/renderer/render_segment_json.hpp>
|
||||
#include <mbgl/map/map_options.hpp>
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/math/wrap.hpp>
|
||||
@@ -3160,6 +3161,22 @@ static void *windowScreenContext = &windowScreenContext;
|
||||
return self.mbglMap.setContinuousLayerRange(startLayerId.UTF8String, endLayerId.UTF8String);
|
||||
}
|
||||
|
||||
- (BOOL)setRenderSegmentPlanJSON:(NSString *)planJSON {
|
||||
if (!_mbglMap) return NO;
|
||||
const auto plan = mbgl::parseRenderSegmentPlanJSON(planJSON.UTF8String);
|
||||
if (plan) return self.mbglMap.setRenderSegmentPlan(*plan);
|
||||
mbgl::RenderSegmentPlan invalid;
|
||||
invalid.schemaVersion = 0;
|
||||
self.mbglMap.setRenderSegmentPlan(std::move(invalid));
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)renderSegmentDiagnosticsJSON {
|
||||
if (!_mbglMap) return @"{\"schemaVersion\":1,\"valid\":false}";
|
||||
const auto json = mbgl::renderSegmentDiagnosticsToJSON(self.mbglMap.getRenderSegmentDiagnostics());
|
||||
return [NSString stringWithUTF8String:json.c_str()];
|
||||
}
|
||||
|
||||
- (MLNRetainedUnderlayDiagnostics *)retainedUnderlayDiagnostics {
|
||||
if (!_mbglMap) {
|
||||
return [[MLNRetainedUnderlayDiagnostics alloc] initWithEnabled:YES
|
||||
|
||||
@@ -108,6 +108,16 @@ bool Map::setContinuousLayerRange(std::string startLayerId, std::string endLayer
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Map::setRenderSegmentPlan(RenderSegmentPlan plan) {
|
||||
const bool accepted = impl->retainedUnderlayState->setRenderSegmentPlan(std::move(plan));
|
||||
impl->onUpdate();
|
||||
return accepted;
|
||||
}
|
||||
|
||||
RenderSegmentDiagnostics Map::getRenderSegmentDiagnostics() const {
|
||||
return impl->retainedUnderlayState->renderSegmentSnapshot();
|
||||
}
|
||||
|
||||
bool Map::isRetainedUnderlayEnabled() const {
|
||||
return impl->retainedUnderlayState->isEnabled();
|
||||
}
|
||||
|
||||
@@ -279,9 +279,8 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
||||
imageManager->setLoaded(updateParameters->spriteLoaded);
|
||||
|
||||
const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters->layers);
|
||||
if (!layerDiff.added.empty() || !layerDiff.removed.empty() || !layerDiff.changed.empty()) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::LayerMutation);
|
||||
}
|
||||
// Layer mutations are tracked by per-segment immutable-Impl fingerprints below. Source,
|
||||
// tile, image, glyph and camera changes remain deliberately global invalidations.
|
||||
layerImpls = updateParameters->layers;
|
||||
const bool layersAddedOrRemoved = !layerDiff.added.empty() || !layerDiff.removed.empty();
|
||||
|
||||
@@ -426,6 +425,35 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
||||
continuousLayerRange,
|
||||
continuousLayerRange.bottomPresent(),
|
||||
continuousLayerRange.topPresent(static_cast<int32_t>(orderedLayers.size())));
|
||||
|
||||
const auto segmentConfig = updateParameters->retainedUnderlayState->renderSegmentPlanConfig();
|
||||
const bool segmentMetadataChanged = renderSegmentPlan.configRevision != segmentConfig.revision ||
|
||||
renderSegmentPlan.layerCount != static_cast<int32_t>(orderedLayers.size()) ||
|
||||
renderSegmentStyleReady != updateParameters->styleLoaded || layersAddedOrRemoved ||
|
||||
!layerDiff.changed.empty();
|
||||
if (segmentMetadataChanged) {
|
||||
renderSegmentPlan = resolveRenderSegmentPlan(
|
||||
segmentConfig, orderedLayerIds, updateParameters->styleLoaded, retainedDynamicBoundary);
|
||||
renderSegmentStyleReady = updateParameters->styleLoaded;
|
||||
std::vector<StyleLayerDiagnostics> styleLayerDiagnostics;
|
||||
styleLayerDiagnostics.reserve(orderedLayers.size());
|
||||
for (std::size_t index = 0; index < orderedLayers.size(); ++index) {
|
||||
const auto& layer = orderedLayers[index].get();
|
||||
const auto& impl = *layer.baseImpl;
|
||||
StyleLayerDiagnostics value;
|
||||
value.layerIndex = static_cast<int32_t>(index);
|
||||
value.layerId = layer.getID();
|
||||
value.layerType = impl.getTypeInfo()->type;
|
||||
value.visible = impl.visibility != style::VisibilityType::None;
|
||||
if (!impl.source.empty()) value.sourceId = impl.source;
|
||||
if (!impl.sourceLayer.empty()) value.sourceLayer = impl.sourceLayer;
|
||||
styleLayerDiagnostics.push_back(std::move(value));
|
||||
}
|
||||
updateParameters->retainedUnderlayState->recordRenderSegmentResolution(
|
||||
renderSegmentPlan, std::move(styleLayerDiagnostics));
|
||||
}
|
||||
} else {
|
||||
renderSegmentPlan = {};
|
||||
}
|
||||
|
||||
// Update all sources and initialize renderItems.
|
||||
@@ -974,6 +1002,21 @@ bool RenderOrchestrator::continuousRangeSupportsParticleFastPath(int32_t begin,
|
||||
return particle;
|
||||
}
|
||||
|
||||
uint64_t RenderOrchestrator::retainedRangeContentFingerprint(int32_t begin, int32_t endExclusive) const {
|
||||
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
||||
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
||||
uint64_t value = 1469598103934665603ULL;
|
||||
for (int32_t index = first; index < last; ++index) {
|
||||
const auto& layer = orderedLayers[static_cast<std::size_t>(index)].get();
|
||||
const auto identity = reinterpret_cast<std::uintptr_t>(layer.baseImpl.get());
|
||||
value ^= static_cast<uint64_t>(identity);
|
||||
value *= 1099511628211ULL;
|
||||
value ^= static_cast<uint64_t>(index);
|
||||
value *= 1099511628211ULL;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
ParticleFastPathDynamicState RenderOrchestrator::particleFastPathDynamicState(TimePoint timePoint) const {
|
||||
ParticleFastPathDynamicState result;
|
||||
result.blockingTransition = renderLight.hasTransition();
|
||||
|
||||
@@ -120,6 +120,7 @@ public:
|
||||
size_t numLayerGroups() const noexcept;
|
||||
std::optional<int32_t> getRetainedDynamicBoundary() const noexcept { return retainedDynamicBoundary; }
|
||||
const ContinuousLayerRangeResolution& getContinuousLayerRange() const noexcept { return continuousLayerRange; }
|
||||
const ResolvedRenderSegmentPlan& getRenderSegmentPlan() const noexcept { return renderSegmentPlan; }
|
||||
int32_t getOrderedLayerCount() const noexcept { return static_cast<int32_t>(orderedLayers.size()); }
|
||||
bool retainedUnderlayUsesPlacement(int32_t boundary) const;
|
||||
bool retainedUnderlayIsStable(TimePoint, int32_t boundary) const;
|
||||
@@ -128,6 +129,7 @@ public:
|
||||
bool retainedRangeIsStable(TimePoint, int32_t begin, int32_t endExclusive) const;
|
||||
bool retainedRangeHasUnsupportedLayer(int32_t begin, int32_t endExclusive) const;
|
||||
bool continuousRangeSupportsParticleFastPath(int32_t begin, int32_t endExclusive) const;
|
||||
uint64_t retainedRangeContentFingerprint(int32_t begin, int32_t endExclusive) const;
|
||||
ParticleFastPathDynamicState particleFastPathDynamicState(TimePoint) const;
|
||||
uint64_t getRetainedUnderlayContentRevision() const noexcept {
|
||||
return retainedUnderlayContentRevision.load(std::memory_order_acquire);
|
||||
@@ -366,6 +368,8 @@ private:
|
||||
// reallocation on each frame.
|
||||
std::vector<Immutable<style::LayerProperties>> filteredLayersForSource;
|
||||
RenderLayerReferences orderedLayers;
|
||||
ResolvedRenderSegmentPlan renderSegmentPlan;
|
||||
bool renderSegmentStyleReady = false;
|
||||
RenderLayerReferences layersNeedPlacement;
|
||||
|
||||
TaggedScheduler threadPool;
|
||||
|
||||
135
src/mbgl/renderer/render_segment_json.hpp
Normal file
135
src/mbgl/renderer/render_segment_json.hpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/renderer/render_segment.hpp>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <rapidjson/writer.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
inline std::optional<RenderSegmentPlan> parseRenderSegmentPlanJSON(const std::string& json) {
|
||||
rapidjson::Document document;
|
||||
if (document.Parse(json.c_str()).HasParseError() || !document.IsObject() ||
|
||||
!document.HasMember("schemaVersion") || !document["schemaVersion"].IsUint() ||
|
||||
document["schemaVersion"].GetUint() != 1 || !document.HasMember("segments") ||
|
||||
!document["segments"].IsArray() || document.MemberCount() != 2) {
|
||||
return std::nullopt;
|
||||
}
|
||||
RenderSegmentPlan plan;
|
||||
for (const auto& value : document["segments"].GetArray()) {
|
||||
if (!value.IsObject() || !value.HasMember("strategy") || !value["strategy"].IsString() ||
|
||||
value.MemberCount() > 2) {
|
||||
return std::nullopt;
|
||||
}
|
||||
for (auto member = value.MemberBegin(); member != value.MemberEnd(); ++member) {
|
||||
const std::string_view name(member->name.GetString(), member->name.GetStringLength());
|
||||
if (name != "strategy" && name != "startLayerId") return std::nullopt;
|
||||
}
|
||||
RenderSegmentDefinition definition;
|
||||
const std::string strategy = value["strategy"].GetString();
|
||||
if (strategy == "RETAINED") {
|
||||
definition.strategy = RenderSegmentStrategy::Retained;
|
||||
} else if (strategy == "CONTINUOUS") {
|
||||
definition.strategy = RenderSegmentStrategy::Continuous;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (value.HasMember("startLayerId")) {
|
||||
if (!value["startLayerId"].IsString()) return std::nullopt;
|
||||
definition.startLayerId = value["startLayerId"].GetString();
|
||||
}
|
||||
plan.segments.push_back(std::move(definition));
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
inline void writeOptionalString(Writer& writer, const char* key, const std::optional<std::string>& value) {
|
||||
writer.Key(key);
|
||||
if (value) writer.String(value->c_str()); else writer.Null();
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
inline void writeStyleLayerDiagnostics(Writer& writer, const StyleLayerDiagnostics& layer) {
|
||||
writer.StartObject();
|
||||
writer.Key("layerIndex"); writer.Int(layer.layerIndex);
|
||||
writer.Key("layerId"); writer.String(layer.layerId.c_str());
|
||||
writer.Key("layerType"); writer.String(layer.layerType.c_str());
|
||||
writer.Key("visible"); writer.Bool(layer.visible);
|
||||
writeOptionalString(writer, "sourceId", layer.sourceId);
|
||||
writeOptionalString(writer, "sourceLayer", layer.sourceLayer);
|
||||
writer.EndObject();
|
||||
}
|
||||
|
||||
inline std::string renderSegmentDiagnosticsToJSON(const RenderSegmentDiagnostics& snapshot) {
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
writer.StartObject();
|
||||
writer.Key("schemaVersion"); writer.Uint(snapshot.schemaVersion);
|
||||
writer.Key("enabled"); writer.Bool(snapshot.enabled);
|
||||
writer.Key("configured"); writer.Bool(snapshot.configured);
|
||||
writer.Key("valid"); writer.Bool(snapshot.valid);
|
||||
writer.Key("configSource"); writer.String(snapshot.configSource.c_str());
|
||||
writer.Key("configRevision"); writer.Uint64(snapshot.configRevision);
|
||||
writer.Key("topologyRevision"); writer.Uint64(snapshot.topologyRevision);
|
||||
writer.Key("styleLayerCount"); writer.Uint(snapshot.styleLayerCount);
|
||||
writer.Key("segmentCount"); writer.Uint(snapshot.segmentCount);
|
||||
writer.Key("retainedSegmentCount"); writer.Uint(snapshot.retainedSegmentCount);
|
||||
writer.Key("continuousSegmentCount"); writer.Uint(snapshot.continuousSegmentCount);
|
||||
writer.Key("coverageComplete"); writer.Bool(snapshot.coverageComplete);
|
||||
writer.Key("fallback"); writer.Bool(snapshot.fallback);
|
||||
writer.Key("lastReason"); writer.String(snapshot.lastReason.c_str());
|
||||
writer.Key("fastPathEligible"); writer.Bool(snapshot.fastPathEligible);
|
||||
writer.Key("fastPathActive"); writer.Bool(snapshot.fastPathActive);
|
||||
writer.Key("fastPathFallbackReason"); writer.String(snapshot.fastPathFallbackReason.c_str());
|
||||
writer.Key("fastPathHit"); writer.Uint64(snapshot.fastPathHit);
|
||||
writer.Key("fastPathMiss"); writer.Uint64(snapshot.fastPathMiss);
|
||||
writer.Key("fullCpuFrame"); writer.Uint64(snapshot.fullCpuFrame);
|
||||
writer.Key("styleLayers"); writer.StartArray();
|
||||
for (const auto& layer : snapshot.styleLayers) writeStyleLayerDiagnostics(writer, layer);
|
||||
writer.EndArray();
|
||||
writer.Key("segments"); writer.StartArray();
|
||||
for (const auto& segment : snapshot.segments) {
|
||||
writer.StartObject();
|
||||
writer.Key("segmentIndex"); writer.Uint(segment.segmentIndex);
|
||||
writer.Key("strategy"); writer.String(renderSegmentStrategyText(segment.strategy));
|
||||
writeOptionalString(writer, "configuredStartLayerId", segment.configuredStartLayerId);
|
||||
writer.Key("beginIndex"); writer.Int(segment.beginIndex);
|
||||
writer.Key("endExclusive"); writer.Int(segment.endExclusive);
|
||||
writeOptionalString(writer, "resolvedStartLayerId", segment.resolvedStartLayerId);
|
||||
writeOptionalString(writer, "resolvedEndLayerId", segment.resolvedEndLayerId);
|
||||
writer.Key("layerCount"); writer.Uint(static_cast<uint32_t>(segment.layers.size()));
|
||||
writer.Key("layers"); writer.StartArray();
|
||||
for (const auto& layer : segment.layers) writeStyleLayerDiagnostics(writer, layer);
|
||||
writer.EndArray();
|
||||
writer.Key("cache");
|
||||
if (!segment.cache) {
|
||||
writer.Null();
|
||||
} else {
|
||||
const auto& cache = *segment.cache;
|
||||
writer.StartObject();
|
||||
writer.Key("cacheState"); writer.String(cache.cacheState.c_str());
|
||||
writer.Key("contentRevision"); writer.Uint64(cache.contentRevision);
|
||||
writer.Key("build"); writer.Uint64(cache.build);
|
||||
writer.Key("reuse"); writer.Uint64(cache.reuse);
|
||||
writer.Key("invalidation"); writer.Uint64(cache.invalidation);
|
||||
writer.Key("lastInvalidationReason"); writer.String(cache.lastInvalidationReason.c_str());
|
||||
writer.Key("textureCreate"); writer.Uint64(cache.textureCreate);
|
||||
writer.Key("compositeCreate"); writer.Uint64(cache.compositeCreate);
|
||||
writer.Key("resourceRelease"); writer.Uint64(cache.resourceRelease);
|
||||
writer.EndObject();
|
||||
}
|
||||
writer.EndObject();
|
||||
}
|
||||
writer.EndArray();
|
||||
writer.EndObject();
|
||||
return buffer.GetString();
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
172
src/mbgl/renderer/render_segment_plan.hpp
Normal file
172
src/mbgl/renderer/render_segment_plan.hpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/renderer/render_segment.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
enum class RenderSegmentConfigSource : uint8_t { UnconfiguredLegacy, ExplicitPlan, ContinuousRangeCompat };
|
||||
|
||||
inline const char* renderSegmentConfigSourceText(RenderSegmentConfigSource source) noexcept {
|
||||
switch (source) {
|
||||
case RenderSegmentConfigSource::ExplicitPlan: return "explicit-render-segment-plan";
|
||||
case RenderSegmentConfigSource::ContinuousRangeCompat: return "continuous-range-compat";
|
||||
case RenderSegmentConfigSource::UnconfiguredLegacy: return "unconfigured/legacy";
|
||||
}
|
||||
return "unconfigured/legacy";
|
||||
}
|
||||
|
||||
struct RenderSegmentConfig {
|
||||
RenderSegmentConfigSource source = RenderSegmentConfigSource::UnconfiguredLegacy;
|
||||
RenderSegmentPlan plan;
|
||||
std::string continuousStartLayerId;
|
||||
std::string continuousEndLayerId;
|
||||
uint64_t revision = 0;
|
||||
};
|
||||
|
||||
inline ResolvedRenderSegmentPlan resolveRenderSegmentPlan(
|
||||
const RenderSegmentConfig& config,
|
||||
const std::vector<std::string>& orderedLayerIds,
|
||||
bool styleReady,
|
||||
std::optional<int32_t> legacyBoundary = std::nullopt) {
|
||||
ResolvedRenderSegmentPlan result;
|
||||
result.configSource = renderSegmentConfigSourceText(config.source);
|
||||
result.configRevision = config.revision;
|
||||
result.layerCount = static_cast<int32_t>(orderedLayerIds.size());
|
||||
result.configured = config.source != RenderSegmentConfigSource::UnconfiguredLegacy;
|
||||
|
||||
if (!styleReady || orderedLayerIds.empty()) {
|
||||
result.reason = "style-not-ready";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, int32_t> indices;
|
||||
indices.reserve(orderedLayerIds.size());
|
||||
for (std::size_t i = 0; i < orderedLayerIds.size(); ++i) {
|
||||
indices.emplace(orderedLayerIds[i], static_cast<int32_t>(i));
|
||||
}
|
||||
|
||||
if (config.source == RenderSegmentConfigSource::UnconfiguredLegacy) {
|
||||
if (!legacyBoundary || *legacyBoundary <= 0 || *legacyBoundary >= result.layerCount) {
|
||||
result.reason = "not-configured";
|
||||
return result;
|
||||
}
|
||||
result.segments = {
|
||||
{0, RenderSegmentStrategy::Retained, std::nullopt, 0, *legacyBoundary},
|
||||
{1,
|
||||
RenderSegmentStrategy::Continuous,
|
||||
orderedLayerIds[*legacyBoundary],
|
||||
*legacyBoundary,
|
||||
result.layerCount},
|
||||
};
|
||||
result.valid = true;
|
||||
result.reason = "none";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (config.source == RenderSegmentConfigSource::ContinuousRangeCompat) {
|
||||
const auto start = indices.find(config.continuousStartLayerId);
|
||||
const auto end = indices.find(config.continuousEndLayerId);
|
||||
if (start == indices.end()) {
|
||||
result.reason = "start-layer-missing";
|
||||
return result;
|
||||
}
|
||||
if (end == indices.end()) {
|
||||
result.reason = "end-layer-missing";
|
||||
return result;
|
||||
}
|
||||
if (start->second > end->second) {
|
||||
result.reason = "range-order-invalid";
|
||||
return result;
|
||||
}
|
||||
uint32_t segmentIndex = 0;
|
||||
if (start->second > 0) {
|
||||
result.segments.push_back(
|
||||
{segmentIndex++, RenderSegmentStrategy::Retained, std::nullopt, 0, start->second});
|
||||
}
|
||||
result.segments.push_back({segmentIndex++,
|
||||
RenderSegmentStrategy::Continuous,
|
||||
config.continuousStartLayerId,
|
||||
start->second,
|
||||
end->second + 1});
|
||||
if (end->second + 1 < result.layerCount) {
|
||||
result.segments.push_back({segmentIndex,
|
||||
RenderSegmentStrategy::Retained,
|
||||
orderedLayerIds[static_cast<std::size_t>(end->second + 1)],
|
||||
end->second + 1,
|
||||
result.layerCount});
|
||||
}
|
||||
result.valid = true;
|
||||
result.reason = "none";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (config.plan.schemaVersion != 1) {
|
||||
result.reason = "unsupported-schema-version";
|
||||
return result;
|
||||
}
|
||||
if (config.plan.segments.empty()) {
|
||||
result.reason = "empty-segments";
|
||||
return result;
|
||||
}
|
||||
if (config.plan.segments.front().startLayerId && !config.plan.segments.front().startLayerId->empty()) {
|
||||
result.reason = "first-segment-start-layer-forbidden";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int32_t> begins(config.plan.segments.size(), 0);
|
||||
for (std::size_t i = 1; i < config.plan.segments.size(); ++i) {
|
||||
const auto& startLayerId = config.plan.segments[i].startLayerId;
|
||||
if (!startLayerId || startLayerId->empty()) {
|
||||
result.reason = "segment-start-layer-required";
|
||||
return result;
|
||||
}
|
||||
const auto found = indices.find(*startLayerId);
|
||||
if (found == indices.end()) {
|
||||
result.reason = "boundary-layer-missing";
|
||||
return result;
|
||||
}
|
||||
begins[i] = found->second;
|
||||
if (begins[i] <= begins[i - 1]) {
|
||||
result.reason = begins[i] == begins[i - 1] ? "boundary-layer-duplicate" : "boundary-order-invalid";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.segments.reserve(config.plan.segments.size());
|
||||
for (std::size_t i = 0; i < config.plan.segments.size(); ++i) {
|
||||
const int32_t endExclusive = i + 1 < begins.size() ? begins[i + 1] : result.layerCount;
|
||||
if (begins[i] >= endExclusive) {
|
||||
result.segments.clear();
|
||||
result.reason = "empty-segment";
|
||||
return result;
|
||||
}
|
||||
result.segments.push_back({static_cast<uint32_t>(i),
|
||||
config.plan.segments[i].strategy,
|
||||
config.plan.segments[i].startLayerId,
|
||||
begins[i],
|
||||
endExclusive});
|
||||
}
|
||||
result.valid = true;
|
||||
result.reason = "none";
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool renderSegmentCoverageComplete(const ResolvedRenderSegmentPlan& plan) noexcept {
|
||||
if (!plan.valid || plan.segments.empty() || plan.segments.front().beginIndex != 0 ||
|
||||
plan.segments.back().endExclusive != plan.layerCount) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < plan.segments.size(); ++i) {
|
||||
if (plan.segments[i].beginIndex >= plan.segments[i].endExclusive) return false;
|
||||
if (i > 0 && plan.segments[i - 1].endExclusive != plan.segments[i].beginIndex) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <mbgl/renderer/layer_tweaker.hpp>
|
||||
#include <mbgl/renderer/render_target.hpp>
|
||||
#include <mbgl/renderer/retained_underlay_cache.hpp>
|
||||
#include <mbgl/renderer/continuous_layer_range.hpp>
|
||||
#include <mbgl/particle/renderer_debug_counters.hpp>
|
||||
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
@@ -52,10 +51,14 @@ using namespace style;
|
||||
|
||||
namespace {
|
||||
|
||||
ContinuousRenderRanges activeRenderRanges(const RenderOrchestrator& orchestrator) {
|
||||
return selectContinuousRenderRanges(orchestrator.getContinuousLayerRange(),
|
||||
orchestrator.getRetainedDynamicBoundary(),
|
||||
orchestrator.getOrderedLayerCount());
|
||||
const ResolvedRenderSegment* singleContinuousSegment(const ResolvedRenderSegmentPlan& plan) {
|
||||
const ResolvedRenderSegment* result = nullptr;
|
||||
for (const auto& segment : plan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Continuous) continue;
|
||||
if (result) return nullptr;
|
||||
result = &segment;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
RendererObserver& nullObserver() {
|
||||
@@ -104,9 +107,7 @@ Renderer::Impl::Impl(gfx::RendererBackend& backend_,
|
||||
: orchestrator(!backend_.contextIsShared(), backend_.getThreadPool(), localFontFamily_),
|
||||
backend(backend_),
|
||||
observer(&nullObserver()),
|
||||
pixelRatio(pixelRatio_),
|
||||
retainedUnderlayCache(std::make_unique<RetainedUnderlayCache>(RetainedRangeRole::Bottom)),
|
||||
retainedTopCache(std::make_unique<RetainedUnderlayCache>(RetainedRangeRole::Top)) {}
|
||||
pixelRatio(pixelRatio_) {}
|
||||
|
||||
Renderer::Impl::~Impl() {
|
||||
assert(gfx::BackendScope::exists());
|
||||
@@ -140,8 +141,10 @@ void Renderer::Impl::setObserver(RendererObserver* observer_) {
|
||||
|
||||
void Renderer::Impl::synchronizeRetainedUnderlayState(
|
||||
const std::shared_ptr<RetainedUnderlayRuntimeState>& state) {
|
||||
retainedUnderlayCache->synchronizeRuntimeState(state);
|
||||
retainedTopCache->synchronizeRuntimeState(state);
|
||||
retainedRuntimeState = state;
|
||||
for (auto& cache : retainedSegmentCaches) {
|
||||
if (cache) cache->synchronizeRuntimeState(state);
|
||||
}
|
||||
}
|
||||
|
||||
Renderer::Impl::ParticleFastPathUpdateSignature Renderer::Impl::makeParticleFastPathUpdateSignature(
|
||||
@@ -200,49 +203,49 @@ bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr<UpdatePara
|
||||
annotationMutation = updateParameters->annotationManager->hasPendingChanges();
|
||||
}
|
||||
|
||||
const auto retained = retainedUnderlayCache->getDiagnostics();
|
||||
const auto ranges = activeRenderRanges(orchestrator);
|
||||
const auto liveRangeConfig = updateParameters->retainedUnderlayState
|
||||
? updateParameters->retainedUnderlayState->continuousLayerRangeConfig()
|
||||
: ContinuousLayerRangeConfig{};
|
||||
const auto retained = retainedRuntimeState ? retainedRuntimeState->renderSegmentSnapshot()
|
||||
: RenderSegmentDiagnostics{};
|
||||
const auto& plan = orchestrator.getRenderSegmentPlan();
|
||||
const auto* continuous = singleContinuousSegment(plan);
|
||||
const bool oneContinuous = plan.valid && continuous;
|
||||
const std::optional<int32_t> currentBoundary =
|
||||
(ranges.explicitValid || !ranges.explicitConfigured) ? std::optional<int32_t>(ranges.continuousBegin)
|
||||
: std::nullopt;
|
||||
oneContinuous ? std::optional<int32_t>(continuous->beginIndex) : std::nullopt;
|
||||
const bool hasSignature = particlePreparedSignature.has_value();
|
||||
const bool viewportMatches = hasSignature && particlePreparedSignature->viewport == signature.viewport &&
|
||||
particlePreparedBackendSize == backend.getDefaultRenderable().getSize();
|
||||
const bool cameraMatches = hasSignature && particlePreparedSignature->cameraMatrix == signature.cameraMatrix;
|
||||
const bool updateParametersMatch =
|
||||
hasSignature && particleFastPathNonCameraInputsMatch(*particlePreparedSignature, signature);
|
||||
const bool bottomRevisionMatches = !ranges.bottomPresent() ||
|
||||
(retainedUnderlayCache->getStaticRevision() == retainedUnderlayRevisionTracker.current() &&
|
||||
retainedUnderlayCache->getBuildRevision() == retainedUnderlayRevisionTracker.current());
|
||||
const bool topRevisionMatches = !ranges.topPresent() ||
|
||||
(retainedTopCache->getStaticRevision() == retainedUnderlayRevisionTracker.current() &&
|
||||
retainedTopCache->getBuildRevision() == retainedUnderlayRevisionTracker.current());
|
||||
const bool revisionMatches = bottomRevisionMatches && topRevisionMatches &&
|
||||
particlePreparedStaticRevision == retainedUnderlayRevisionTracker.current();
|
||||
bool retainedRevisionsMatch = retainedSegmentCaches.size() == plan.segments.size();
|
||||
bool retainedCachesValid = retainedRevisionsMatch;
|
||||
for (std::size_t i = 0; i < retainedSegmentCaches.size(); ++i) {
|
||||
if (plan.segments[i].strategy != RenderSegmentStrategy::Retained) continue;
|
||||
const auto& cache = retainedSegmentCaches[i];
|
||||
retainedRevisionsMatch = retainedRevisionsMatch && cache &&
|
||||
cache->getBuildRevision() == cache->getStaticRevision();
|
||||
retainedCachesValid = retainedCachesValid && cache && cache->isValid();
|
||||
}
|
||||
const bool revisionMatches = retainedRevisionsMatch &&
|
||||
particlePreparedStaticRevision == retainedUnderlayRevisionTracker.current();
|
||||
const bool boundaryMatches = particlePreparedBoundary == currentBoundary &&
|
||||
particlePreparedContinuousEnd == std::optional<int32_t>(ranges.continuousEndExclusive) &&
|
||||
particlePreparedRangeConfigRevision == ranges.configRevision &&
|
||||
liveRangeConfig.revision == ranges.configRevision &&
|
||||
liveRangeConfig.configured == ranges.explicitConfigured &&
|
||||
particlePreparedBottomPresent == ranges.bottomPresent() &&
|
||||
particlePreparedTopPresent == ranges.topPresent();
|
||||
particlePreparedContinuousEnd ==
|
||||
(oneContinuous ? std::optional<int32_t>(continuous->endExclusive) : std::nullopt) &&
|
||||
particlePreparedRangeConfigRevision == plan.configRevision;
|
||||
const bool topologyMatches = particlePreparedTopologyRevision == retainedUnderlayTopologyTracker.current();
|
||||
const bool contentRevisionMatches =
|
||||
particlePreparedContentRevision == orchestrator.getRetainedUnderlayContentRevision();
|
||||
const bool staticStable = (ranges.explicitValid || (!ranges.explicitConfigured && ranges.bottomPresent())) &&
|
||||
(!ranges.bottomPresent() || orchestrator.retainedRangeIsStable(
|
||||
updateParameters->timePoint, 0, ranges.continuousBegin)) &&
|
||||
(!ranges.topPresent() || orchestrator.retainedRangeIsStable(
|
||||
updateParameters->timePoint, ranges.continuousEndExclusive, ranges.layerCount));
|
||||
bool staticStable = plan.valid;
|
||||
for (const auto& segment : plan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
staticStable = staticStable && orchestrator.retainedRangeIsStable(
|
||||
updateParameters->timePoint, segment.beginIndex, segment.endExclusive);
|
||||
}
|
||||
}
|
||||
|
||||
const ParticleFastPathGateInput input{
|
||||
.hasPreparedTree = !particlePreparedTree.empty(),
|
||||
.retainedEnabled = retained.enabled,
|
||||
.cacheValid = (!ranges.bottomPresent() || retainedUnderlayCache->isValid()) &&
|
||||
(!ranges.topPresent() || retainedTopCache->isValid()),
|
||||
.cacheValid = retainedCachesValid,
|
||||
.revisionMatches = revisionMatches,
|
||||
.contentRevisionMatches = contentRevisionMatches,
|
||||
.cameraMatches = cameraMatches,
|
||||
@@ -265,13 +268,13 @@ bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr<UpdatePara
|
||||
.stillImageRequest = updateParameters->stillImageRequest,
|
||||
.continuousMode = updateParameters->mode == MapMode::Continuous,
|
||||
.debugDisabled = updateParameters->debugOptions == MapDebugOptions::NoDebug,
|
||||
.unsupportedContinuousLayer = particlePreparedContinuousUnsupported,
|
||||
.supportedScene = particlePreparedSceneSupported,
|
||||
.unsupportedContinuousLayer = particlePreparedContinuousUnsupported || !oneContinuous,
|
||||
.supportedScene = particlePreparedSceneSupported && oneContinuous,
|
||||
.annotationMutation = annotationMutation,
|
||||
};
|
||||
const auto decision = evaluateParticleFastPath(input);
|
||||
particleFastPathLastReason = decision.reason;
|
||||
retainedUnderlayCache->recordFastPathDecision(decision.candidate, decision.reason);
|
||||
if (retainedRuntimeState) retainedRuntimeState->recordFastPathDecision(decision.candidate, decision.reason);
|
||||
if (!decision.candidate) {
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::ParticleFastFrameMiss);
|
||||
return false;
|
||||
@@ -285,18 +288,15 @@ bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr<UpdatePara
|
||||
|
||||
void Renderer::Impl::commitPreparedRenderTree(std::unique_ptr<RenderTree> renderTree,
|
||||
const std::shared_ptr<UpdateParameters>& updateParameters) {
|
||||
const auto ranges = activeRenderRanges(orchestrator);
|
||||
const auto& plan = orchestrator.getRenderSegmentPlan();
|
||||
const auto* continuous = singleContinuousSegment(plan);
|
||||
particlePreparedSignature = makeParticleFastPathUpdateSignature(*updateParameters);
|
||||
particlePreparedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
|
||||
particlePreparedTopologyRevision = retainedUnderlayTopologyTracker.current();
|
||||
particlePreparedStaticRevision = retainedUnderlayRevisionTracker.current();
|
||||
particlePreparedBoundary = (ranges.explicitValid || !ranges.explicitConfigured)
|
||||
? std::optional<int32_t>(ranges.continuousBegin)
|
||||
: std::nullopt;
|
||||
particlePreparedContinuousEnd = ranges.continuousEndExclusive;
|
||||
particlePreparedRangeConfigRevision = ranges.configRevision;
|
||||
particlePreparedBottomPresent = ranges.bottomPresent();
|
||||
particlePreparedTopPresent = ranges.topPresent();
|
||||
particlePreparedBoundary = continuous ? std::optional<int32_t>(continuous->beginIndex) : std::nullopt;
|
||||
particlePreparedContinuousEnd = continuous ? std::optional<int32_t>(continuous->endExclusive) : std::nullopt;
|
||||
particlePreparedRangeConfigRevision = plan.configRevision;
|
||||
particlePreparedBackendSize = backend.getDefaultRenderable().getSize();
|
||||
particlePreparedTree.replace(std::move(renderTree));
|
||||
}
|
||||
@@ -310,8 +310,6 @@ void Renderer::Impl::clearPreparedRenderTree() {
|
||||
particlePreparedBoundary.reset();
|
||||
particlePreparedContinuousEnd.reset();
|
||||
particlePreparedRangeConfigRevision = 0;
|
||||
particlePreparedBottomPresent = false;
|
||||
particlePreparedTopPresent = false;
|
||||
particlePreparedContinuousUnsupported = false;
|
||||
particlePreparedBackendSize = {};
|
||||
particlePreparedSceneSupported = false;
|
||||
@@ -324,7 +322,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::FullRendererFrame);
|
||||
if (!particleFastFrame) {
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::ParticleFullFrame);
|
||||
retainedUnderlayCache->recordFullCpuFrame();
|
||||
if (retainedRuntimeState) retainedRuntimeState->recordFullCpuFrame();
|
||||
}
|
||||
auto& context = backend.getContext();
|
||||
context.setObserver(this);
|
||||
@@ -499,78 +497,97 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
|
||||
orchestrator.processChanges();
|
||||
|
||||
const auto ranges = activeRenderRanges(orchestrator);
|
||||
const bool rangeMode = ranges.explicitValid;
|
||||
const bool retainedMode = rangeMode || (!ranges.explicitConfigured && ranges.bottomPresent());
|
||||
const std::optional<int32_t> retainedBoundary = retainedMode
|
||||
? std::optional<int32_t>(ranges.continuousBegin) : std::nullopt;
|
||||
std::optional<RetainedUnderlayKey> bottomKey;
|
||||
std::optional<RetainedUnderlayKey> topKey;
|
||||
const auto& segmentPlan = orchestrator.getRenderSegmentPlan();
|
||||
const bool retainedMode = segmentPlan.valid;
|
||||
const auto* singleContinuous = singleContinuousSegment(segmentPlan);
|
||||
const auto segmentSnapshot = retainedRuntimeState ? retainedRuntimeState->renderSegmentSnapshot()
|
||||
: RenderSegmentDiagnostics{};
|
||||
if (retainedCacheTopologyRevision != segmentSnapshot.topologyRevision ||
|
||||
retainedSegmentCaches.size() != segmentPlan.segments.size()) {
|
||||
retainedSegmentCaches.clear();
|
||||
retainedSegmentCaches.resize(segmentPlan.segments.size());
|
||||
retainedSegmentFingerprints.assign(segmentPlan.segments.size(), 0);
|
||||
bool continuousSeen = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Continuous) {
|
||||
continuousSeen = true;
|
||||
continue;
|
||||
}
|
||||
auto cache = std::make_unique<RetainedUnderlayCache>(
|
||||
segment.segmentIndex,
|
||||
"retained-segment-" + std::to_string(segment.segmentIndex),
|
||||
continuousSeen ? RetainedRangeRole::Top : RetainedRangeRole::Bottom);
|
||||
cache->synchronizeRuntimeState(retainedRuntimeState);
|
||||
retainedSegmentCaches[segment.segmentIndex] = std::move(cache);
|
||||
}
|
||||
retainedCacheTopologyRevision = segmentSnapshot.topologyRevision;
|
||||
}
|
||||
std::vector<std::optional<RetainedUnderlayKey>> segmentKeys(segmentPlan.segments.size());
|
||||
std::vector<RetainedUnderlayDecision> segmentDecisions(
|
||||
segmentPlan.segments.size(), RetainedUnderlayDecision::Disabled);
|
||||
const std::optional<int32_t> retainedBoundary = singleContinuous
|
||||
? std::optional<int32_t>(singleContinuous->beginIndex) : std::nullopt;
|
||||
RetainedUnderlayTopologySnapshot retainedTopology{
|
||||
.boundary = retainedBoundary,
|
||||
.staticPrefix = {},
|
||||
.continuousEndExclusive = retainedMode ? std::optional<int32_t>(ranges.continuousEndExclusive) : std::nullopt,
|
||||
.continuousEndExclusive = singleContinuous
|
||||
? std::optional<int32_t>(singleContinuous->endExclusive)
|
||||
: std::nullopt,
|
||||
.staticSuffix = {}};
|
||||
mat4 retainedCameraMatrix;
|
||||
state.getProjMatrix(retainedCameraMatrix);
|
||||
if (retainedMode && ranges.bottomPresent()) {
|
||||
orchestrator.visitLayerGroupsInRange(0, ranges.continuousBegin, [&](LayerGroupBase& layerGroup, size_t) {
|
||||
retainedTopology.staticPrefix.push_back({.layerIndex = layerGroup.getLayerIndex(),
|
||||
.groupName = layerGroup.getName(),
|
||||
.groupType = static_cast<uint8_t>(layerGroup.getType())});
|
||||
});
|
||||
bottomKey = RetainedUnderlayKey{size, 0, ranges.continuousBegin, retainedCameraMatrix};
|
||||
}
|
||||
if (rangeMode && ranges.topPresent()) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
ranges.continuousEndExclusive, ranges.layerCount, [&](LayerGroupBase& layerGroup, size_t) {
|
||||
retainedTopology.staticSuffix.push_back({.layerIndex = layerGroup.getLayerIndex(),
|
||||
.groupName = layerGroup.getName(),
|
||||
.groupType = static_cast<uint8_t>(layerGroup.getType())});
|
||||
});
|
||||
topKey = RetainedUnderlayKey{
|
||||
size, ranges.continuousEndExclusive, ranges.layerCount, retainedCameraMatrix};
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
retainedTopology.staticPrefix.push_back(
|
||||
{.layerIndex = segment.beginIndex,
|
||||
.groupName = std::string(renderSegmentStrategyText(segment.strategy)) + ":" +
|
||||
std::to_string(segment.endExclusive),
|
||||
.groupType = static_cast<uint8_t>(segment.strategy)});
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
segmentKeys[segment.segmentIndex] = RetainedUnderlayKey{
|
||||
size, segment.beginIndex, segment.endExclusive, retainedCameraMatrix};
|
||||
}
|
||||
}
|
||||
const auto retainedTopologyRevision = particleFastFrame ? retainedUnderlayTopologyTracker.current()
|
||||
: retainedUnderlayTopologyTracker.observe(retainedTopology);
|
||||
const auto bottomGroupCount = retainedTopology.staticPrefix.size();
|
||||
bool legacyInRetainedRange = false;
|
||||
if (retainedMode) {
|
||||
for (const auto& item : layerRenderItems) {
|
||||
const auto index = static_cast<int32_t>(item.index);
|
||||
if ((index < ranges.continuousBegin || (rangeMode && index >= ranges.continuousEndExclusive)) &&
|
||||
item.layer.get().usesLegacyRendering()) {
|
||||
legacyInRetainedRange = true;
|
||||
break;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained && index >= segment.beginIndex &&
|
||||
index < segment.endExclusive && item.layer.get().usesLegacyRendering()) {
|
||||
legacyInRetainedRange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (legacyInRetainedRange) break;
|
||||
}
|
||||
}
|
||||
const bool retainedStaticUsesPlacement = retainedMode &&
|
||||
((ranges.bottomPresent() && orchestrator.retainedRangeUsesPlacement(0, ranges.continuousBegin)) ||
|
||||
(ranges.topPresent() && orchestrator.retainedRangeUsesPlacement(
|
||||
ranges.continuousEndExclusive, ranges.layerCount)));
|
||||
const bool retainedStaticStable = retainedMode &&
|
||||
(!ranges.bottomPresent() || orchestrator.retainedRangeIsStable(
|
||||
renderTreeParameters.timePoint, 0, ranges.continuousBegin)) &&
|
||||
(!ranges.topPresent() || orchestrator.retainedRangeIsStable(
|
||||
renderTreeParameters.timePoint, ranges.continuousEndExclusive, ranges.layerCount));
|
||||
bool retainedStaticUsesPlacement = false;
|
||||
bool retainedStaticStable = retainedMode;
|
||||
bool retainedUnsupportedStaticLayer = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained) continue;
|
||||
retainedStaticUsesPlacement = retainedStaticUsesPlacement ||
|
||||
orchestrator.retainedRangeUsesPlacement(segment.beginIndex, segment.endExclusive);
|
||||
retainedStaticStable = retainedStaticStable && orchestrator.retainedRangeIsStable(
|
||||
renderTreeParameters.timePoint, segment.beginIndex, segment.endExclusive);
|
||||
retainedUnsupportedStaticLayer = retainedUnsupportedStaticLayer ||
|
||||
orchestrator.retainedRangeHasUnsupportedLayer(segment.beginIndex, segment.endExclusive);
|
||||
}
|
||||
const bool retainedPlacementStable =
|
||||
!retainedStaticUsesPlacement ||
|
||||
(!renderTreeParameters.placementChanged && renderTreeParameters.symbolFadeChange == 0.0f);
|
||||
const bool retainedUnsupportedStaticLayer = retainedMode &&
|
||||
((ranges.bottomPresent() && orchestrator.retainedRangeHasUnsupportedLayer(0, ranges.continuousBegin)) ||
|
||||
(ranges.topPresent() && orchestrator.retainedRangeHasUnsupportedLayer(
|
||||
ranges.continuousEndExclusive, ranges.layerCount)));
|
||||
const auto retainedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
|
||||
const auto retainedContentReason = orchestrator.getRetainedUnderlayContentReason();
|
||||
const RetainedUnderlayRevisionState retainedRevisionState{
|
||||
.viewport = size,
|
||||
.cameraMatrix = retainedCameraMatrix,
|
||||
.boundary = retainedBoundary,
|
||||
.continuousEndExclusive = retainedMode
|
||||
? std::optional<int32_t>(ranges.continuousEndExclusive)
|
||||
.continuousEndExclusive = singleContinuous
|
||||
? std::optional<int32_t>(singleContinuous->endExclusive)
|
||||
: std::nullopt,
|
||||
.rangeConfigRevision = ranges.configRevision,
|
||||
.rangeConfigRevision = segmentPlan.configRevision,
|
||||
.pixelRatio = updateParameters->pixelRatio,
|
||||
.contentRevision = retainedContentRevision,
|
||||
.topologyRevision = retainedTopologyRevision,
|
||||
@@ -586,23 +603,20 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
const auto retainedStaticRevision = retainedUnderlayRevisionTracker.observe(
|
||||
retainedRevisionState, retainedUnderlayReasonText(retainedContentReason));
|
||||
const bool retainedSupported = retainedMode &&
|
||||
(rangeMode || !ranges.bottomPresent() || bottomGroupCount > 0) &&
|
||||
renderTreeParameters.loaded && !renderTreeParameters.has3D &&
|
||||
renderTreeParameters.debugOptions == MapDebugOptions::NoDebug &&
|
||||
retainedStaticStable && retainedPlacementStable &&
|
||||
!legacyInRetainedRange && !retainedUnsupportedStaticLayer;
|
||||
const bool fastContinuousSupported = rangeMode
|
||||
? orchestrator.continuousRangeSupportsParticleFastPath(
|
||||
ranges.continuousBegin, ranges.continuousEndExclusive)
|
||||
: retainedSupported;
|
||||
if (!particleFastFrame) particlePreparedContinuousUnsupported = rangeMode && !fastContinuousSupported;
|
||||
retainedUnderlayCache->recordFastPathEligibility(retainedSupported && fastContinuousSupported);
|
||||
const bool fastContinuousSupported = singleContinuous &&
|
||||
orchestrator.continuousRangeSupportsParticleFastPath(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive);
|
||||
if (!particleFastFrame) particlePreparedContinuousUnsupported = !singleContinuous || !fastContinuousSupported;
|
||||
if (retainedRuntimeState) retainedRuntimeState->recordFastPathEligibility(
|
||||
retainedSupported && fastContinuousSupported);
|
||||
if (!particleFastFrame) particlePreparedSceneSupported = retainedSupported && fastContinuousSupported;
|
||||
const char* retainedFallbackReason = nullptr;
|
||||
if (!retainedMode) {
|
||||
retainedFallbackReason = "no-boundary";
|
||||
} else if (!rangeMode && ranges.bottomPresent() && bottomGroupCount == 0) {
|
||||
retainedFallbackReason = "empty-static-prefix";
|
||||
} else if (!renderTreeParameters.loaded) {
|
||||
retainedFallbackReason = "style-not-loaded";
|
||||
} else if (renderTreeParameters.has3D) {
|
||||
@@ -618,36 +632,49 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
} else if (retainedUnsupportedStaticLayer) {
|
||||
retainedFallbackReason = "unsupported";
|
||||
}
|
||||
auto bottomDecision = retainedUnderlayCache->evaluate(bottomKey,
|
||||
retainedStaticRevision,
|
||||
retainedSupported,
|
||||
retainedFallbackReason,
|
||||
retainedUnderlayRevisionTracker.reason().c_str());
|
||||
auto topDecision = retainedTopCache->evaluate(topKey,
|
||||
retainedStaticRevision,
|
||||
retainedSupported,
|
||||
retainedFallbackReason,
|
||||
retainedUnderlayRevisionTracker.reason().c_str());
|
||||
const auto wantsResources = [](RetainedUnderlayDecision decision) {
|
||||
return decision == RetainedUnderlayDecision::Build || decision == RetainedUnderlayDecision::Reuse;
|
||||
};
|
||||
bool resourcesReady = true;
|
||||
if (wantsResources(bottomDecision)) {
|
||||
resourcesReady = staticData->shaders &&
|
||||
retainedUnderlayCache->prepareResources(context, *staticData->shaders, *bottomKey);
|
||||
}
|
||||
if (resourcesReady && wantsResources(topDecision)) {
|
||||
resourcesReady = staticData->shaders &&
|
||||
retainedTopCache->prepareResources(context, *staticData->shaders, *topKey);
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained) continue;
|
||||
auto& cache = retainedSegmentCaches[segment.segmentIndex];
|
||||
auto& decision = segmentDecisions[segment.segmentIndex];
|
||||
const auto fingerprint = orchestrator.retainedRangeContentFingerprint(
|
||||
segment.beginIndex, segment.endExclusive);
|
||||
const bool layerContentChanged = retainedSegmentFingerprints[segment.segmentIndex] != 0 &&
|
||||
retainedSegmentFingerprints[segment.segmentIndex] != fingerprint;
|
||||
retainedSegmentFingerprints[segment.segmentIndex] = fingerprint;
|
||||
const auto segmentContentRevision = retainedStaticRevision ^
|
||||
(fingerprint +
|
||||
0x9e3779b97f4a7c15ULL + (retainedStaticRevision << 6U) + (retainedStaticRevision >> 2U));
|
||||
decision = cache->evaluate(segmentKeys[segment.segmentIndex],
|
||||
segmentContentRevision,
|
||||
retainedSupported,
|
||||
retainedFallbackReason,
|
||||
layerContentChanged ? "layer-mutation"
|
||||
: retainedUnderlayRevisionTracker.reason().c_str());
|
||||
if (resourcesReady && wantsResources(decision)) {
|
||||
resourcesReady = staticData->shaders && cache->prepareResources(
|
||||
context, *staticData->shaders, *segmentKeys[segment.segmentIndex]);
|
||||
}
|
||||
}
|
||||
if (!resourcesReady) {
|
||||
if (wantsResources(bottomDecision)) retainedUnderlayCache->markBuildFailed("resource-or-compositor");
|
||||
if (wantsResources(topDecision)) retainedTopCache->markBuildFailed("resource-or-compositor");
|
||||
bottomDecision = topDecision = RetainedUnderlayDecision::Fallback;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained) continue;
|
||||
auto& decision = segmentDecisions[segment.segmentIndex];
|
||||
if (wantsResources(decision)) {
|
||||
retainedSegmentCaches[segment.segmentIndex]->markBuildFailed("resource-or-compositor");
|
||||
decision = RetainedUnderlayDecision::Fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool useRetainedRanges = retainedSupported;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
useRetainedRanges = useRetainedRanges && wantsResources(segmentDecisions[segment.segmentIndex]);
|
||||
}
|
||||
}
|
||||
const bool useRetainedRanges = retainedSupported &&
|
||||
(!ranges.bottomPresent() || wantsResources(bottomDecision)) &&
|
||||
(!ranges.topPresent() || wantsResources(topDecision));
|
||||
|
||||
// Upload layer groups
|
||||
{
|
||||
@@ -663,9 +690,9 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
|
||||
// Tweakers are run in the upload pass so they can set up uniforms.
|
||||
parameters.currentLayer = 0;
|
||||
if (particleFastFrame && retainedMode) {
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
ranges.continuousBegin, ranges.continuousEndExclusive,
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t ordinal) {
|
||||
parameters.currentLayer = static_cast<uint32_t>(ordinal);
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
@@ -683,9 +710,9 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
|
||||
// Give the layers a chance to upload
|
||||
if (particleFastFrame && retainedMode) {
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
ranges.continuousBegin, ranges.continuousEndExclusive,
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t) { layerGroup.upload(*uploadPass); });
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
@@ -698,8 +725,13 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
if (!particleFastFrame) {
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
if (useRetainedRanges && ranges.bottomPresent()) retainedUnderlayCache->upload(*uploadPass);
|
||||
if (useRetainedRanges && ranges.topPresent()) retainedTopCache->upload(*uploadPass);
|
||||
if (useRetainedRanges) {
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
retainedSegmentCaches[segment.segmentIndex]->upload(*uploadPass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Size atlasSize = parameters.patternAtlas.getPixelSize();
|
||||
@@ -818,7 +850,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
|
||||
// Actually render the layers
|
||||
// Drawables
|
||||
const auto drawableOpaquePass = [&] {
|
||||
const auto drawableOpaquePass = [&](int32_t begin, int32_t endExclusive) {
|
||||
const auto debugGroup(parameters.renderPass->createDebugGroup("drawables-opaque"));
|
||||
parameters.pass = RenderPass::Opaque;
|
||||
parameters.depthRangeSize = 1 - (orchestrator.numLayerGroups() + 2) * PaintParameters::numSublayers *
|
||||
@@ -827,7 +859,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
// draw layer groups, opaque pass
|
||||
if (useRetainedRanges && retainedMode) {
|
||||
orchestrator.visitLayerGroupsInRangeReversed(
|
||||
ranges.continuousBegin, ranges.continuousEndExclusive,
|
||||
begin, endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t ordinal) {
|
||||
parameters.currentLayer = static_cast<uint32_t>(orchestrator.numLayerGroups() - 1 - ordinal);
|
||||
layerGroup.render(orchestrator, parameters);
|
||||
@@ -842,7 +874,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
};
|
||||
|
||||
const auto drawableTranslucentPass = [&] {
|
||||
const auto drawableTranslucentPass = [&](int32_t begin, int32_t endExclusive) {
|
||||
const auto debugGroup(parameters.renderPass->createDebugGroup("drawables-translucent"));
|
||||
parameters.pass = RenderPass::Translucent;
|
||||
parameters.depthRangeSize = 1 - (orchestrator.numLayerGroups() + 2) * PaintParameters::numSublayers *
|
||||
@@ -851,7 +883,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
// draw layer groups, translucent pass
|
||||
if (useRetainedRanges && retainedMode) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
ranges.continuousBegin, ranges.continuousEndExclusive,
|
||||
begin, endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t ordinal) {
|
||||
parameters.currentLayer = static_cast<uint32_t>(orchestrator.numLayerGroups() - 1 - ordinal);
|
||||
layerGroup.render(orchestrator, parameters);
|
||||
@@ -877,8 +909,8 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
const LayerRenderItem& layerItem = *it;
|
||||
const RenderItem& item = layerItem;
|
||||
const bool itemInLiveRange = !useRetainedRanges || !retainedMode ||
|
||||
(static_cast<int32_t>(layerItem.index) >= ranges.continuousBegin &&
|
||||
static_cast<int32_t>(layerItem.index) < ranges.continuousEndExclusive);
|
||||
(static_cast<int32_t>(layerItem.index) >= begin &&
|
||||
static_cast<int32_t>(layerItem.index) < endExclusive);
|
||||
if (itemInLiveRange && item.hasRenderPass(parameters.pass)) {
|
||||
item.render(parameters);
|
||||
}
|
||||
@@ -903,20 +935,35 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
drawable3DPass();
|
||||
}
|
||||
drawableTargetsPass();
|
||||
buildRetainedRange(*retainedUnderlayCache, bottomDecision, 0, ranges.continuousBegin);
|
||||
buildRetainedRange(*retainedTopCache, topDecision, ranges.continuousEndExclusive, ranges.layerCount);
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
buildRetainedRange(*retainedSegmentCaches[segment.segmentIndex],
|
||||
segmentDecisions[segment.segmentIndex],
|
||||
segment.beginIndex,
|
||||
segment.endExclusive);
|
||||
}
|
||||
}
|
||||
commonClearPass();
|
||||
context.bindGlobalUniformBuffers(*parameters.renderPass);
|
||||
if (useRetainedRanges && ranges.bottomPresent()) retainedUnderlayCache->composite(orchestrator, parameters);
|
||||
drawableOpaquePass();
|
||||
drawableTranslucentPass();
|
||||
if (useRetainedRanges && ranges.topPresent()) retainedTopCache->composite(orchestrator, parameters);
|
||||
if (useRetainedRanges) {
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
retainedSegmentCaches[segment.segmentIndex]->composite(orchestrator, parameters);
|
||||
} else {
|
||||
drawableOpaquePass(segment.beginIndex, segment.endExclusive);
|
||||
drawableTranslucentPass(segment.beginIndex, segment.endExclusive);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drawableOpaquePass(0, segmentPlan.layerCount);
|
||||
drawableTranslucentPass(0, segmentPlan.layerCount);
|
||||
}
|
||||
drawableDebugOverlays();
|
||||
|
||||
// Give the layers a chance to do cleanup
|
||||
if (particleFastFrame && retainedMode) {
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
ranges.continuousBegin, ranges.continuousEndExclusive,
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t) {
|
||||
layerGroup.postRender(orchestrator, parameters);
|
||||
});
|
||||
@@ -966,7 +1013,12 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
logRetainedRendererCounters(*retainedUnderlayCache, particleFastPathLastReason);
|
||||
for (const auto& cache : retainedSegmentCaches) {
|
||||
if (cache) {
|
||||
logRetainedRendererCounters(*cache, particleFastPathLastReason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
frameCount += 1;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -61,8 +62,10 @@ private:
|
||||
|
||||
const float pixelRatio;
|
||||
std::unique_ptr<RenderStaticData> staticData;
|
||||
std::unique_ptr<RetainedUnderlayCache> retainedUnderlayCache;
|
||||
std::unique_ptr<RetainedUnderlayCache> retainedTopCache;
|
||||
std::vector<std::unique_ptr<RetainedUnderlayCache>> retainedSegmentCaches;
|
||||
std::vector<uint64_t> retainedSegmentFingerprints;
|
||||
std::shared_ptr<RetainedUnderlayRuntimeState> retainedRuntimeState;
|
||||
uint64_t retainedCacheTopologyRevision = 0;
|
||||
RetainedUnderlayRevisionTracker retainedUnderlayRevisionTracker;
|
||||
RetainedUnderlayTopologyTracker retainedUnderlayTopologyTracker;
|
||||
ParticleFastPathPreparedTree<RenderTree> particlePreparedTree;
|
||||
@@ -110,8 +113,6 @@ private:
|
||||
std::optional<int32_t> particlePreparedBoundary;
|
||||
std::optional<int32_t> particlePreparedContinuousEnd;
|
||||
uint64_t particlePreparedRangeConfigRevision = 0;
|
||||
bool particlePreparedBottomPresent = false;
|
||||
bool particlePreparedTopPresent = false;
|
||||
bool particlePreparedContinuousUnsupported = false;
|
||||
Size particlePreparedBackendSize;
|
||||
bool particlePreparedSceneSupported = false;
|
||||
|
||||
@@ -110,7 +110,16 @@ uint64_t RetainedUnderlayRevisionTracker::observe(const RetainedUnderlayRevision
|
||||
|
||||
RetainedUnderlayCache::RetainedUnderlayCache(RetainedRangeRole role_)
|
||||
: runtimeState(std::make_shared<RetainedUnderlayRuntimeState>()), role(role_) {}
|
||||
RetainedUnderlayCache::~RetainedUnderlayCache() = default;
|
||||
RetainedUnderlayCache::RetainedUnderlayCache(uint32_t segmentIndex_,
|
||||
std::string label_,
|
||||
RetainedRangeRole compatibilityRole)
|
||||
: runtimeState(std::make_shared<RetainedUnderlayRuntimeState>()),
|
||||
role(compatibilityRole),
|
||||
renderSegmentIndex(segmentIndex_),
|
||||
renderSegmentLabel(std::move(label_)) {}
|
||||
RetainedUnderlayCache::~RetainedUnderlayCache() {
|
||||
releaseResources();
|
||||
}
|
||||
|
||||
void RetainedUnderlayCache::synchronizeRuntimeState(
|
||||
const std::shared_ptr<RetainedUnderlayRuntimeState>& nextState) {
|
||||
@@ -136,6 +145,9 @@ void RetainedUnderlayCache::synchronizeRuntimeState(
|
||||
runtimeState->recordRangeInvalidation(role == RetainedRangeRole::Top);
|
||||
runtimeState->recordRangeInvalidationReason(
|
||||
role == RetainedRangeRole::Top, RetainedUnderlayReason::RuntimeDisabled);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentInvalidation(*renderSegmentIndex, RetainedUnderlayReason::RuntimeDisabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
releaseResources();
|
||||
@@ -159,6 +171,9 @@ RetainedUnderlayDecision RetainedUnderlayCache::evaluate(const std::optional<Ret
|
||||
releaseResources();
|
||||
}
|
||||
runtimeState->recordRangeCacheState(role == RetainedRangeRole::Top, RetainedRangeCacheState::Disabled);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentCacheState(*renderSegmentIndex, RetainedRangeCacheState::Disabled);
|
||||
}
|
||||
return RetainedUnderlayDecision::Disabled;
|
||||
}
|
||||
if (runtimeState && !runtimeState->isEnabled()) {
|
||||
@@ -166,6 +181,9 @@ RetainedUnderlayDecision RetainedUnderlayCache::evaluate(const std::optional<Ret
|
||||
runtimeState->recordFallback();
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayFallback);
|
||||
runtimeState->recordRangeCacheState(role == RetainedRangeRole::Top, RetainedRangeCacheState::Fallback);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentCacheState(*renderSegmentIndex, RetainedRangeCacheState::Fallback);
|
||||
}
|
||||
return RetainedUnderlayDecision::Fallback;
|
||||
}
|
||||
if (!supported) {
|
||||
@@ -175,6 +193,9 @@ RetainedUnderlayDecision RetainedUnderlayCache::evaluate(const std::optional<Ret
|
||||
invalidate(fallbackReason ? fallbackReason : "unsupported");
|
||||
if (revisionChanged) releaseResources();
|
||||
runtimeState->recordRangeCacheState(role == RetainedRangeRole::Top, RetainedRangeCacheState::Fallback);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentCacheState(*renderSegmentIndex, RetainedRangeCacheState::Fallback);
|
||||
}
|
||||
return RetainedUnderlayDecision::Fallback;
|
||||
}
|
||||
if (revisionChanged) {
|
||||
@@ -185,7 +206,8 @@ RetainedUnderlayDecision RetainedUnderlayCache::evaluate(const std::optional<Ret
|
||||
releaseResources();
|
||||
key = nextKey;
|
||||
} else if (!key || *key != *nextKey) {
|
||||
const char* reason = !key ? "first-frame" : (key->viewport != nextKey->viewport ? "viewport" : "camera-or-boundary");
|
||||
const char* reason = !key ? "first-frame"
|
||||
: (key->viewport != nextKey->viewport ? "viewport" : "camera-or-boundary");
|
||||
invalidate(reason);
|
||||
key = nextKey;
|
||||
}
|
||||
@@ -200,9 +222,18 @@ RetainedUnderlayDecision RetainedUnderlayCache::evaluate(const std::optional<Ret
|
||||
runtimeState->recordRangeReuse(role == RetainedRangeRole::Top);
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayReuse);
|
||||
runtimeState->recordRangeCacheState(role == RetainedRangeRole::Top, RetainedRangeCacheState::Reuse);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentReuse(*renderSegmentIndex);
|
||||
runtimeState->recordSegmentCacheState(
|
||||
*renderSegmentIndex, RetainedRangeCacheState::Reuse, observedStaticRevision);
|
||||
}
|
||||
return RetainedUnderlayDecision::Reuse;
|
||||
}
|
||||
runtimeState->recordRangeCacheState(role == RetainedRangeRole::Top, RetainedRangeCacheState::BuildPending);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentCacheState(
|
||||
*renderSegmentIndex, RetainedRangeCacheState::BuildPending, observedStaticRevision);
|
||||
}
|
||||
return RetainedUnderlayDecision::Build;
|
||||
}
|
||||
|
||||
@@ -214,6 +245,7 @@ bool RetainedUnderlayCache::prepareResources(gfx::Context& context,
|
||||
offscreenTexture = context.createOffscreenTexture(
|
||||
nextKey.viewport, gfx::TextureChannelDataType::UnsignedByte, true, true);
|
||||
runtimeState->recordRangeTextureCreate(role == RetainedRangeRole::Top);
|
||||
if (renderSegmentIndex) runtimeState->recordSegmentTextureCreate(*renderSegmentIndex);
|
||||
compositeLayerGroup.reset();
|
||||
}
|
||||
if (!offscreenTexture || !offscreenTexture->isRenderable()) return false;
|
||||
@@ -226,7 +258,10 @@ bool RetainedUnderlayCache::prepareResources(gfx::Context& context,
|
||||
bool RetainedUnderlayCache::rebuildComposite(gfx::Context& context, gfx::ShaderRegistry& shaders) {
|
||||
auto shader = context.getGenericShader(shaders, CompositeShaderName);
|
||||
auto group = context.createLayerGroup(
|
||||
0, 1, role == RetainedRangeRole::Top ? "retained-top-composite" : "retained-bottom-composite");
|
||||
0,
|
||||
1,
|
||||
renderSegmentIndex ? renderSegmentLabel + "-composite"
|
||||
: (role == RetainedRangeRole::Top ? "retained-top-composite" : "retained-bottom-composite"));
|
||||
if (!shader || !group || !offscreenTexture) return false;
|
||||
|
||||
auto vertices = std::make_shared<gfx::VertexVector<HeatmapTextureLayoutVertex>>(
|
||||
@@ -258,6 +293,7 @@ bool RetainedUnderlayCache::rebuildComposite(gfx::Context& context, gfx::ShaderR
|
||||
for (auto& drawable : builder->clearDrawables()) group->addDrawable(std::move(drawable));
|
||||
compositeLayerGroup = std::move(group);
|
||||
runtimeState->recordRangeCompositeCreate(role == RetainedRangeRole::Top);
|
||||
if (renderSegmentIndex) runtimeState->recordSegmentCompositeCreate(*renderSegmentIndex);
|
||||
return !compositeLayerGroup->empty();
|
||||
}
|
||||
|
||||
@@ -295,6 +331,11 @@ void RetainedUnderlayCache::markBuildSucceeded() {
|
||||
++buildCount;
|
||||
runtimeState->recordBuild();
|
||||
runtimeState->recordRangeBuild(role == RetainedRangeRole::Top);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentBuild(*renderSegmentIndex);
|
||||
runtimeState->recordSegmentCacheState(
|
||||
*renderSegmentIndex, RetainedRangeCacheState::Ready, observedStaticRevision);
|
||||
}
|
||||
runtimeState->recordRangeCacheState(role == RetainedRangeRole::Top, RetainedRangeCacheState::Ready);
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayBuild);
|
||||
}
|
||||
@@ -305,6 +346,9 @@ void RetainedUnderlayCache::markBuildFailed(const char* reason) {
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayFallback);
|
||||
invalidate(reason);
|
||||
runtimeState->recordRangeCacheState(role == RetainedRangeRole::Top, RetainedRangeCacheState::Fallback);
|
||||
if (renderSegmentIndex) {
|
||||
runtimeState->recordSegmentCacheState(*renderSegmentIndex, RetainedRangeCacheState::Fallback);
|
||||
}
|
||||
releaseResources();
|
||||
}
|
||||
|
||||
@@ -314,12 +358,14 @@ void RetainedUnderlayCache::invalidate(const char* reason) {
|
||||
runtimeState->recordInvalidation(retainedReason(reason));
|
||||
runtimeState->recordRangeInvalidation(role == RetainedRangeRole::Top);
|
||||
runtimeState->recordRangeInvalidationReason(role == RetainedRangeRole::Top, retainedReason(reason));
|
||||
if (renderSegmentIndex) runtimeState->recordSegmentInvalidation(*renderSegmentIndex, retainedReason(reason));
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayInvalidation);
|
||||
}
|
||||
|
||||
void RetainedUnderlayCache::releaseResources() {
|
||||
if (offscreenTexture || compositeLayerGroup) {
|
||||
runtimeState->recordRangeResourceRelease(role == RetainedRangeRole::Top);
|
||||
if (renderSegmentIndex) runtimeState->recordSegmentResourceRelease(*renderSegmentIndex);
|
||||
}
|
||||
valid = false;
|
||||
buildRevision = 0;
|
||||
|
||||
@@ -115,6 +115,7 @@ inline bool retainedLayerGroupInRange(int32_t layerIndex, int32_t boundary, Reta
|
||||
class RetainedUnderlayCache {
|
||||
public:
|
||||
explicit RetainedUnderlayCache(RetainedRangeRole = RetainedRangeRole::Bottom);
|
||||
RetainedUnderlayCache(uint32_t segmentIndex, std::string label, RetainedRangeRole compatibilityRole);
|
||||
~RetainedUnderlayCache();
|
||||
|
||||
RetainedUnderlayDecision evaluate(const std::optional<RetainedUnderlayKey>&,
|
||||
@@ -163,6 +164,8 @@ private:
|
||||
std::shared_ptr<RetainedUnderlayRuntimeState> runtimeState;
|
||||
uint64_t observedControlRevision = 0;
|
||||
RetainedRangeRole role;
|
||||
std::optional<uint32_t> renderSegmentIndex;
|
||||
std::string renderSegmentLabel;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
#include <mbgl/renderer/particle_fast_path.hpp>
|
||||
#include <mbgl/renderer/retained_underlay.hpp>
|
||||
#include <mbgl/renderer/render_segment.hpp>
|
||||
#include <mbgl/renderer/render_segment_plan.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace mbgl {
|
||||
@@ -117,17 +120,167 @@ public:
|
||||
rangeResolution.endLayerId = rangeConfig.endLayerId;
|
||||
rangeResolution.configRevision = rangeConfig.revision;
|
||||
rangeResolution.reason = "style-not-ready";
|
||||
renderSegmentConfig.source = RenderSegmentConfigSource::ContinuousRangeCompat;
|
||||
renderSegmentConfig.continuousStartLayerId = rangeConfig.startLayerId;
|
||||
renderSegmentConfig.continuousEndLayerId = rangeConfig.endLayerId;
|
||||
renderSegmentConfig.plan = {};
|
||||
renderSegmentConfig.revision++;
|
||||
fallback.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
controlRevision.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setRenderSegmentPlan(RenderSegmentPlan plan) {
|
||||
bool accepted = plan.schemaVersion == 1 && !plan.segments.empty();
|
||||
if (accepted && plan.segments.front().startLayerId && !plan.segments.front().startLayerId->empty()) {
|
||||
accepted = false;
|
||||
}
|
||||
for (std::size_t i = 1; accepted && i < plan.segments.size(); ++i) {
|
||||
if (!plan.segments[i].startLayerId || plan.segments[i].startLayerId->empty()) accepted = false;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
if (renderSegmentConfig.source == RenderSegmentConfigSource::ExplicitPlan &&
|
||||
renderSegmentConfig.plan == plan) {
|
||||
return accepted;
|
||||
}
|
||||
renderSegmentConfig.source = RenderSegmentConfigSource::ExplicitPlan;
|
||||
renderSegmentConfig.plan = std::move(plan);
|
||||
renderSegmentConfig.continuousStartLayerId.clear();
|
||||
renderSegmentConfig.continuousEndLayerId.clear();
|
||||
renderSegmentConfig.revision++;
|
||||
rangeConfig = {};
|
||||
rangeResolution = {};
|
||||
bottomRangePresent = false;
|
||||
topRangePresent = false;
|
||||
}
|
||||
controlRevision.fetch_add(1, std::memory_order_release);
|
||||
return accepted;
|
||||
}
|
||||
|
||||
RenderSegmentConfig renderSegmentPlanConfig() const {
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
return renderSegmentConfig;
|
||||
}
|
||||
|
||||
ContinuousLayerRangeConfig continuousLayerRangeConfig() const {
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
if (renderSegmentConfig.source == RenderSegmentConfigSource::ExplicitPlan) return {};
|
||||
return rangeConfig;
|
||||
}
|
||||
|
||||
void recordRenderSegmentResolution(const ResolvedRenderSegmentPlan& resolution,
|
||||
std::vector<StyleLayerDiagnostics> styleLayers) {
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
if (resolution.configRevision != renderSegmentConfig.revision && resolution.configured) return;
|
||||
|
||||
std::vector<std::tuple<RenderSegmentStrategy, int32_t, int32_t>> signature;
|
||||
signature.reserve(resolution.segments.size());
|
||||
for (const auto& segment : resolution.segments) {
|
||||
signature.emplace_back(segment.strategy, segment.beginIndex, segment.endExclusive);
|
||||
}
|
||||
const bool topologyChanged = signature != renderSegmentTopologySignature;
|
||||
if (topologyChanged) {
|
||||
++renderSegmentTopologyRevision;
|
||||
renderSegmentTopologySignature = signature;
|
||||
}
|
||||
|
||||
RenderSegmentDiagnostics next;
|
||||
next.enabled = isEnabled();
|
||||
next.configured = resolution.configured;
|
||||
next.valid = resolution.valid;
|
||||
next.configSource = resolution.configSource;
|
||||
next.configRevision = resolution.configRevision;
|
||||
next.topologyRevision = renderSegmentTopologyRevision;
|
||||
next.styleLayerCount = static_cast<uint32_t>(styleLayers.size());
|
||||
next.coverageComplete = renderSegmentCoverageComplete(resolution);
|
||||
next.fallback = resolution.configured && !resolution.valid;
|
||||
next.lastReason = resolution.reason;
|
||||
next.styleLayers = std::move(styleLayers);
|
||||
next.fastPathEligible = fastPathEligible.load(std::memory_order_acquire);
|
||||
next.fastPathActive = fastPathActive.load(std::memory_order_acquire);
|
||||
next.fastPathFallbackReason = particleFastPathMissReasonText(fastPathReason.load(std::memory_order_acquire));
|
||||
next.fastPathHit = fastPathHit.load(std::memory_order_relaxed);
|
||||
next.fastPathMiss = fastPathMiss.load(std::memory_order_relaxed);
|
||||
next.fullCpuFrame = fullCpuFrame.load(std::memory_order_relaxed);
|
||||
|
||||
next.segments.reserve(resolution.segments.size());
|
||||
for (const auto& segment : resolution.segments) {
|
||||
RenderSegmentDiagnosticsEntry entry;
|
||||
entry.segmentIndex = segment.segmentIndex;
|
||||
entry.strategy = segment.strategy;
|
||||
entry.configuredStartLayerId = segment.configuredStartLayerId;
|
||||
entry.beginIndex = segment.beginIndex;
|
||||
entry.endExclusive = segment.endExclusive;
|
||||
if (segment.beginIndex >= 0 && segment.beginIndex < static_cast<int32_t>(next.styleLayers.size())) {
|
||||
entry.resolvedStartLayerId = next.styleLayers[segment.beginIndex].layerId;
|
||||
}
|
||||
if (segment.endExclusive > segment.beginIndex &&
|
||||
segment.endExclusive <= static_cast<int32_t>(next.styleLayers.size())) {
|
||||
entry.resolvedEndLayerId = next.styleLayers[segment.endExclusive - 1].layerId;
|
||||
}
|
||||
if (segment.beginIndex >= 0 && segment.endExclusive <= static_cast<int32_t>(next.styleLayers.size())) {
|
||||
entry.layers.assign(next.styleLayers.begin() + segment.beginIndex,
|
||||
next.styleLayers.begin() + segment.endExclusive);
|
||||
}
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
++next.retainedSegmentCount;
|
||||
entry.cache.emplace();
|
||||
if (!topologyChanged && segment.segmentIndex < renderSegmentDiagnostics.segments.size() &&
|
||||
renderSegmentDiagnostics.segments[segment.segmentIndex].cache) {
|
||||
entry.cache = renderSegmentDiagnostics.segments[segment.segmentIndex].cache;
|
||||
}
|
||||
} else {
|
||||
++next.continuousSegmentCount;
|
||||
}
|
||||
next.segments.push_back(std::move(entry));
|
||||
}
|
||||
next.segmentCount = static_cast<uint32_t>(next.segments.size());
|
||||
renderSegmentDiagnostics = std::move(next);
|
||||
}
|
||||
|
||||
RenderSegmentDiagnostics renderSegmentSnapshot() const {
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
auto result = renderSegmentDiagnostics;
|
||||
result.enabled = isEnabled();
|
||||
result.fastPathEligible = fastPathEligible.load(std::memory_order_acquire);
|
||||
result.fastPathActive = fastPathActive.load(std::memory_order_acquire);
|
||||
result.fastPathFallbackReason = particleFastPathMissReasonText(fastPathReason.load(std::memory_order_acquire));
|
||||
result.fastPathHit = fastPathHit.load(std::memory_order_relaxed);
|
||||
result.fastPathMiss = fastPathMiss.load(std::memory_order_relaxed);
|
||||
result.fullCpuFrame = fullCpuFrame.load(std::memory_order_relaxed);
|
||||
return result;
|
||||
}
|
||||
|
||||
void recordSegmentCacheState(uint32_t segmentIndex,
|
||||
RetainedRangeCacheState state,
|
||||
uint64_t contentRevision = 0) {
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
if (segmentIndex >= renderSegmentDiagnostics.segments.size()) return;
|
||||
auto& entry = renderSegmentDiagnostics.segments[segmentIndex];
|
||||
if (!entry.cache) return;
|
||||
entry.cache->cacheState = retainedRangeCacheStateText(state);
|
||||
entry.cache->contentRevision = contentRevision;
|
||||
}
|
||||
void recordSegmentBuild(uint32_t index) { updateSegmentCache(index, [](auto& value) { ++value.build; }); }
|
||||
void recordSegmentReuse(uint32_t index) { updateSegmentCache(index, [](auto& value) { ++value.reuse; }); }
|
||||
void recordSegmentInvalidation(uint32_t index, RetainedUnderlayReason reason) {
|
||||
updateSegmentCache(index, [reason](auto& value) {
|
||||
++value.invalidation;
|
||||
value.lastInvalidationReason = retainedUnderlayReasonText(reason);
|
||||
});
|
||||
}
|
||||
void recordSegmentTextureCreate(uint32_t index) {
|
||||
updateSegmentCache(index, [](auto& value) { ++value.textureCreate; });
|
||||
}
|
||||
void recordSegmentCompositeCreate(uint32_t index) {
|
||||
updateSegmentCache(index, [](auto& value) { ++value.compositeCreate; });
|
||||
}
|
||||
void recordSegmentResourceRelease(uint32_t index) {
|
||||
updateSegmentCache(index, [](auto& value) { ++value.resourceRelease; });
|
||||
}
|
||||
|
||||
void recordRangeResolution(const ContinuousLayerRangeResolution& value, bool bottom, bool top) {
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
if (value.configRevision != rangeConfig.revision) return;
|
||||
@@ -241,6 +394,14 @@ public:
|
||||
fastPathActive.store(false, std::memory_order_release);
|
||||
}
|
||||
private:
|
||||
template <typename Fn>
|
||||
void updateSegmentCache(uint32_t index, Fn&& fn) {
|
||||
std::lock_guard<std::mutex> lock(rangeMutex);
|
||||
if (index >= renderSegmentDiagnostics.segments.size()) return;
|
||||
auto& cache = renderSegmentDiagnostics.segments[index].cache;
|
||||
if (cache) fn(*cache);
|
||||
}
|
||||
|
||||
std::atomic<bool> enabled{true};
|
||||
std::atomic<uint64_t> controlRevision{0};
|
||||
std::atomic<uint64_t> build{0};
|
||||
@@ -275,6 +436,10 @@ private:
|
||||
ContinuousLayerRangeResolution rangeResolution;
|
||||
bool bottomRangePresent = false;
|
||||
bool topRangePresent = false;
|
||||
RenderSegmentConfig renderSegmentConfig;
|
||||
RenderSegmentDiagnostics renderSegmentDiagnostics;
|
||||
uint64_t renderSegmentTopologyRevision = 0;
|
||||
std::vector<std::tuple<RenderSegmentStrategy, int32_t, int32_t>> renderSegmentTopologySignature;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#include <mbgl/renderer/retained_underlay_cache.hpp>
|
||||
#include <mbgl/renderer/particle_fast_path.hpp>
|
||||
#include <mbgl/renderer/continuous_layer_range.hpp>
|
||||
#include <mbgl/renderer/render_segment_json.hpp>
|
||||
#include <mbgl/renderer/render_segment_plan.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -635,3 +639,282 @@ TEST(ParticleFastPath, PreparedTreeReplacementOwnsLifetime) {
|
||||
EXPECT_EQ(destroyed, 2);
|
||||
EXPECT_TRUE(slot.empty());
|
||||
}
|
||||
|
||||
namespace {
|
||||
RenderSegmentConfig explicitSegments(std::initializer_list<RenderSegmentDefinition> definitions) {
|
||||
RenderSegmentConfig config;
|
||||
config.source = RenderSegmentConfigSource::ExplicitPlan;
|
||||
config.plan.segments.assign(definitions.begin(), definitions.end());
|
||||
config.revision = 1;
|
||||
return config;
|
||||
}
|
||||
|
||||
std::vector<std::string> segmentLayerIds() {
|
||||
return {"background", "water", "weather", "particle", "land", "ais"};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(RenderSegmentPlan, SingleRetainedAndSingleContinuousCoverWholeStyle) {
|
||||
auto retained = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt}}), segmentLayerIds(), true);
|
||||
ASSERT_TRUE(retained.valid);
|
||||
ASSERT_EQ(retained.segments.size(), 1u);
|
||||
EXPECT_EQ(retained.segments[0].beginIndex, 0);
|
||||
EXPECT_EQ(retained.segments[0].endExclusive, 6);
|
||||
EXPECT_TRUE(renderSegmentCoverageComplete(retained));
|
||||
|
||||
auto continuous = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Continuous, std::nullopt}}), segmentLayerIds(), true);
|
||||
ASSERT_TRUE(continuous.valid);
|
||||
EXPECT_EQ(continuous.segments[0].strategy, RenderSegmentStrategy::Continuous);
|
||||
EXPECT_TRUE(renderSegmentCoverageComplete(continuous));
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, PreservesCRRAndRRCWithoutMerging) {
|
||||
auto crr = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Continuous, std::nullopt},
|
||||
{RenderSegmentStrategy::Retained, "weather"},
|
||||
{RenderSegmentStrategy::Retained, "land"}}),
|
||||
segmentLayerIds(), true);
|
||||
ASSERT_TRUE(crr.valid);
|
||||
ASSERT_EQ(crr.segments.size(), 3u);
|
||||
EXPECT_EQ(crr.segments[1].endExclusive, crr.segments[2].beginIndex);
|
||||
EXPECT_EQ(crr.segments[1].strategy, RenderSegmentStrategy::Retained);
|
||||
EXPECT_EQ(crr.segments[2].strategy, RenderSegmentStrategy::Retained);
|
||||
|
||||
auto rrc = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Retained, "weather"},
|
||||
{RenderSegmentStrategy::Continuous, "land"}}),
|
||||
segmentLayerIds(), true);
|
||||
ASSERT_TRUE(rrc.valid);
|
||||
ASSERT_EQ(rrc.segments.size(), 3u);
|
||||
EXPECT_EQ(rrc.segments[0].strategy, RenderSegmentStrategy::Retained);
|
||||
EXPECT_EQ(rrc.segments[1].strategy, RenderSegmentStrategy::Retained);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, ResolvesFiveAlternatingSegmentsWithGlobalIndices) {
|
||||
auto plan = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "water"},
|
||||
{RenderSegmentStrategy::Retained, "weather"},
|
||||
{RenderSegmentStrategy::Continuous, "particle"},
|
||||
{RenderSegmentStrategy::Retained, "land"}}),
|
||||
segmentLayerIds(), true);
|
||||
ASSERT_TRUE(plan.valid);
|
||||
ASSERT_EQ(plan.segments.size(), 5u);
|
||||
for (std::size_t i = 0; i < plan.segments.size(); ++i) {
|
||||
EXPECT_EQ(plan.segments[i].segmentIndex, i);
|
||||
}
|
||||
EXPECT_EQ(plan.segments[3].beginIndex, 3);
|
||||
EXPECT_EQ(plan.segments[4].endExclusive, 6);
|
||||
EXPECT_TRUE(renderSegmentCoverageComplete(plan));
|
||||
|
||||
const auto continuousCount = std::count_if(plan.segments.begin(), plan.segments.end(), [](const auto& segment) {
|
||||
return segment.strategy == RenderSegmentStrategy::Continuous;
|
||||
});
|
||||
ASSERT_EQ(continuousCount, 2);
|
||||
auto fastInput = fastPathInput();
|
||||
fastInput.unsupportedContinuousLayer = true;
|
||||
const auto fastDecision = evaluateParticleFastPath(fastInput);
|
||||
EXPECT_FALSE(fastDecision.candidate);
|
||||
EXPECT_EQ(fastDecision.reason, ParticleFastPathMissReason::UnsupportedContinuousLayer);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, RejectsMissingDuplicateReversedAndEmptyDefinitions) {
|
||||
auto empty = resolveRenderSegmentPlan(explicitSegments({}), segmentLayerIds(), true);
|
||||
EXPECT_FALSE(empty.valid);
|
||||
EXPECT_EQ(empty.reason, "empty-segments");
|
||||
|
||||
auto missing = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "missing"}}),
|
||||
segmentLayerIds(), true);
|
||||
EXPECT_FALSE(missing.valid);
|
||||
EXPECT_EQ(missing.reason, "boundary-layer-missing");
|
||||
|
||||
auto duplicate = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"},
|
||||
{RenderSegmentStrategy::Retained, "weather"}}),
|
||||
segmentLayerIds(), true);
|
||||
EXPECT_FALSE(duplicate.valid);
|
||||
EXPECT_EQ(duplicate.reason, "boundary-layer-duplicate");
|
||||
|
||||
auto reversed = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "land"},
|
||||
{RenderSegmentStrategy::Retained, "weather"}}),
|
||||
segmentLayerIds(), true);
|
||||
EXPECT_FALSE(reversed.valid);
|
||||
EXPECT_EQ(reversed.reason, "boundary-order-invalid");
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, HiddenBoundaryDoesNotAffectResolution) {
|
||||
const auto before = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"}}),
|
||||
segmentLayerIds(), true);
|
||||
const auto after = resolveRenderSegmentPlan(
|
||||
explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"}}),
|
||||
segmentLayerIds(), true);
|
||||
EXPECT_EQ(before, after);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, ContinuousRangeCompatibilityMapsToOneTwoOrThreeSegments) {
|
||||
RenderSegmentConfig config;
|
||||
config.source = RenderSegmentConfigSource::ContinuousRangeCompat;
|
||||
config.continuousStartLayerId = "weather";
|
||||
config.continuousEndLayerId = "particle";
|
||||
config.revision = 8;
|
||||
auto rcr = resolveRenderSegmentPlan(config, segmentLayerIds(), true);
|
||||
ASSERT_TRUE(rcr.valid);
|
||||
ASSERT_EQ(rcr.segments.size(), 3u);
|
||||
EXPECT_EQ(rcr.segments[0].strategy, RenderSegmentStrategy::Retained);
|
||||
EXPECT_EQ(rcr.segments[1].strategy, RenderSegmentStrategy::Continuous);
|
||||
EXPECT_EQ(rcr.segments[2].strategy, RenderSegmentStrategy::Retained);
|
||||
EXPECT_EQ(rcr.configSource, "continuous-range-compat");
|
||||
|
||||
config.continuousStartLayerId = "background";
|
||||
config.continuousEndLayerId = "ais";
|
||||
auto continuousOnly = resolveRenderSegmentPlan(config, segmentLayerIds(), true);
|
||||
ASSERT_TRUE(continuousOnly.valid);
|
||||
ASSERT_EQ(continuousOnly.segments.size(), 1u);
|
||||
EXPECT_EQ(continuousOnly.segments[0].strategy, RenderSegmentStrategy::Continuous);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, BoundaryDeletionFailsClosedAndRestores) {
|
||||
const auto config = explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"}});
|
||||
auto valid = resolveRenderSegmentPlan(config, segmentLayerIds(), true);
|
||||
ASSERT_TRUE(valid.valid);
|
||||
auto missing = resolveRenderSegmentPlan(
|
||||
config, {"background", "water", "particle", "land", "ais"}, true);
|
||||
EXPECT_FALSE(missing.valid);
|
||||
EXPECT_TRUE(missing.segments.empty());
|
||||
EXPECT_EQ(missing.reason, "boundary-layer-missing");
|
||||
EXPECT_TRUE(resolveRenderSegmentPlan(config, segmentLayerIds(), true).valid);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, InsertedLayerNaturallyJoinsContainingSegment) {
|
||||
const auto config = explicitSegments({{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"},
|
||||
{RenderSegmentStrategy::Retained, "land"}});
|
||||
auto plan = resolveRenderSegmentPlan(
|
||||
config, {"background", "water", "weather", "inserted", "particle", "land", "ais"}, true);
|
||||
ASSERT_TRUE(plan.valid);
|
||||
EXPECT_EQ(plan.segments[1].beginIndex, 2);
|
||||
EXPECT_EQ(plan.segments[1].endExclusive, 5);
|
||||
EXPECT_EQ(plan.segments[2].beginIndex, 5);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentDiagnostics, VisibilityChangesMetadataButNotTopologyRevision) {
|
||||
RetainedUnderlayRuntimeState state;
|
||||
RenderSegmentPlan configured;
|
||||
configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"}};
|
||||
ASSERT_TRUE(state.setRenderSegmentPlan(configured));
|
||||
const auto resolved = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), segmentLayerIds(), true);
|
||||
std::vector<StyleLayerDiagnostics> visible;
|
||||
for (std::size_t i = 0; i < segmentLayerIds().size(); ++i) {
|
||||
visible.push_back({static_cast<int32_t>(i), segmentLayerIds()[i], "fill", true, std::nullopt, std::nullopt});
|
||||
}
|
||||
state.recordRenderSegmentResolution(resolved, visible);
|
||||
const auto revision = state.renderSegmentSnapshot().topologyRevision;
|
||||
visible[2].visible = false;
|
||||
state.recordRenderSegmentResolution(resolved, visible);
|
||||
const auto hidden = state.renderSegmentSnapshot();
|
||||
EXPECT_EQ(hidden.topologyRevision, revision);
|
||||
EXPECT_FALSE(hidden.styleLayers[2].visible);
|
||||
EXPECT_EQ(hidden.segments[1].beginIndex, 2);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentDiagnostics, CacheOffKeepsResolvedTopology) {
|
||||
RetainedUnderlayRuntimeState state;
|
||||
RenderSegmentPlan configured;
|
||||
configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"}};
|
||||
ASSERT_TRUE(state.setRenderSegmentPlan(configured));
|
||||
const auto resolved = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), segmentLayerIds(), true);
|
||||
std::vector<StyleLayerDiagnostics> layers;
|
||||
for (std::size_t i = 0; i < segmentLayerIds().size(); ++i) {
|
||||
layers.push_back({static_cast<int32_t>(i), segmentLayerIds()[i], "fill", true, std::nullopt, std::nullopt});
|
||||
}
|
||||
state.recordRenderSegmentResolution(resolved, std::move(layers));
|
||||
state.setEnabled(false);
|
||||
const auto snapshot = state.renderSegmentSnapshot();
|
||||
EXPECT_FALSE(snapshot.enabled);
|
||||
EXPECT_TRUE(snapshot.valid);
|
||||
EXPECT_TRUE(snapshot.coverageComplete);
|
||||
EXPECT_EQ(snapshot.segmentCount, 2u);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentDiagnostics, InvalidPlanStillReturnsOrderedStyleLayers) {
|
||||
RetainedUnderlayRuntimeState state;
|
||||
RenderSegmentPlan configured;
|
||||
configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "missing"}};
|
||||
ASSERT_TRUE(state.setRenderSegmentPlan(configured));
|
||||
const auto resolved = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), segmentLayerIds(), true);
|
||||
ASSERT_FALSE(resolved.valid);
|
||||
std::vector<StyleLayerDiagnostics> layers;
|
||||
for (std::size_t i = 0; i < segmentLayerIds().size(); ++i) {
|
||||
layers.push_back({static_cast<int32_t>(i), segmentLayerIds()[i], "fill", true, std::nullopt, std::nullopt});
|
||||
}
|
||||
state.recordRenderSegmentResolution(resolved, layers);
|
||||
const auto snapshot = state.renderSegmentSnapshot();
|
||||
EXPECT_FALSE(snapshot.valid);
|
||||
EXPECT_TRUE(snapshot.fallback);
|
||||
EXPECT_TRUE(snapshot.segments.empty());
|
||||
ASSERT_EQ(snapshot.styleLayers.size(), segmentLayerIds().size());
|
||||
EXPECT_EQ(snapshot.styleLayers.front().layerId, "background");
|
||||
EXPECT_EQ(snapshot.styleLayers.back().layerId, "ais");
|
||||
}
|
||||
|
||||
TEST(RenderSegmentDiagnostics, SnapshotCoversStyleExactlyAndSerializesContinuousCacheAsNull) {
|
||||
RetainedUnderlayRuntimeState state;
|
||||
RenderSegmentPlan configured;
|
||||
configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "weather"},
|
||||
{RenderSegmentStrategy::Retained, "land"}};
|
||||
ASSERT_TRUE(state.setRenderSegmentPlan(configured));
|
||||
const auto config = state.renderSegmentPlanConfig();
|
||||
const auto resolved = resolveRenderSegmentPlan(config, segmentLayerIds(), true);
|
||||
std::vector<StyleLayerDiagnostics> layers;
|
||||
for (std::size_t i = 0; i < segmentLayerIds().size(); ++i) {
|
||||
layers.push_back({static_cast<int32_t>(i), segmentLayerIds()[i], "fill", true, std::nullopt, std::nullopt});
|
||||
}
|
||||
layers[1].sourceId = "chart";
|
||||
layers[1].sourceLayer = "water";
|
||||
state.recordRenderSegmentResolution(resolved, layers);
|
||||
const auto snapshot = state.renderSegmentSnapshot();
|
||||
ASSERT_TRUE(snapshot.valid);
|
||||
ASSERT_TRUE(snapshot.coverageComplete);
|
||||
ASSERT_EQ(snapshot.styleLayers.size(), 6u);
|
||||
ASSERT_EQ(snapshot.segments.size(), 3u);
|
||||
EXPECT_TRUE(snapshot.segments[0].cache.has_value());
|
||||
EXPECT_FALSE(snapshot.segments[1].cache.has_value());
|
||||
std::vector<std::string> flattened;
|
||||
for (const auto& segment : snapshot.segments) {
|
||||
for (const auto& layer : segment.layers) flattened.push_back(layer.layerId);
|
||||
}
|
||||
EXPECT_EQ(flattened, segmentLayerIds());
|
||||
EXPECT_EQ(snapshot.styleLayers[1].sourceId, std::optional<std::string>{"chart"});
|
||||
EXPECT_EQ(snapshot.segments[0].layers[1].sourceLayer, std::optional<std::string>{"water"});
|
||||
const auto json = renderSegmentDiagnosticsToJSON(snapshot);
|
||||
EXPECT_NE(json.find("\"coverageComplete\":true"), std::string::npos);
|
||||
EXPECT_NE(json.find("\"cache\":null"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, StrictJSONRejectsUnknownFieldsStrategyAndInvalidFirstBoundary) {
|
||||
EXPECT_FALSE(parseRenderSegmentPlanJSON(
|
||||
R"({"schemaVersion":1,"segments":[{"strategy":"RETAINED","unexpected":true}]})"));
|
||||
EXPECT_FALSE(parseRenderSegmentPlanJSON(
|
||||
R"({"schemaVersion":1,"segments":[{"strategy":"DYNAMIC"}]})"));
|
||||
auto parsed = parseRenderSegmentPlanJSON(
|
||||
R"({"schemaVersion":1,"segments":[{"strategy":"RETAINED","startLayerId":"water"}]})");
|
||||
ASSERT_TRUE(parsed);
|
||||
RetainedUnderlayRuntimeState state;
|
||||
EXPECT_FALSE(state.setRenderSegmentPlan(std::move(*parsed)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user