From 8384c8bd241bcc70c974449475a9a8ca64a1e626 Mon Sep 17 00:00:00 2001 From: tei Date: Tue, 22 Sep 2026 16:18:58 +0900 Subject: [PATCH] feat: add retained underlay runtime controls --- CMakeLists.txt | 2 + include/mbgl/map/map.hpp | 6 + include/mbgl/renderer/retained_underlay.hpp | 17 + .../MapLibreParticleRNTestHost/App.tsx | 38 +- .../weather/WeatherRuntimeBridgeModule.kt | 15 + .../ios/WeatherRuntimeBridge.mm | 14 + .../src/NativeWeatherRuntimeBridge.ts | 3 + .../src/RendererRuntime.ts | 18 + packages/maplibre-particle-rn/src/index.ts | 2 + packages/maplibre-particle-rn/src/types.ts | 8 + .../src/cpp/native_map_view.cpp | 23 + .../src/cpp/native_map_view.hpp | 4 + .../maplibre/android/maps/MapLibreMap.java | 16 + .../org/maplibre/android/maps/NativeMap.java | 4 + .../maplibre/android/maps/NativeMapView.java | 22 + .../maps/RetainedUnderlayDiagnostics.java | 27 + .../ParticleCoreLayerValidationActivity.kt | 696 ++++++++++++++++-- ...ctivity_particle_core_layer_validation.xml | 5 +- platform/ios/src/MLNMapView.h | 21 + platform/ios/src/MLNMapView.mm | 54 ++ src/mbgl/map/map.cpp | 14 + src/mbgl/map/map_impl.cpp | 1 + src/mbgl/map/map_impl.hpp | 3 + src/mbgl/renderer/renderer.cpp | 1 + src/mbgl/renderer/renderer_impl.cpp | 31 +- src/mbgl/renderer/renderer_impl.hpp | 2 + src/mbgl/renderer/retained_underlay_cache.cpp | 63 +- src/mbgl/renderer/retained_underlay_cache.hpp | 6 + .../retained_underlay_runtime_state.hpp | 94 +++ src/mbgl/renderer/update_parameters.hpp | 4 + .../renderer/retained_underlay_cache.test.cpp | 26 + 31 files changed, 1181 insertions(+), 59 deletions(-) create mode 100644 include/mbgl/renderer/retained_underlay.hpp create mode 100644 packages/maplibre-particle-rn/src/RendererRuntime.ts create mode 100644 platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/RetainedUnderlayDiagnostics.java create mode 100644 src/mbgl/renderer/retained_underlay_runtime_state.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c11962a22..390312c3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,6 +182,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/render_target.cpp ${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/change_request.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layer_group.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/tile_layer_group.cpp @@ -307,6 +308,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer_observer.hpp ${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/shaders/program_parameters.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_source.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/storage/database_file_source.hpp diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 464b36f25..439f953ea 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,11 @@ public: /// Triggers a repaint. void triggerRepaint(); + /** Enables or disables the map-scoped retained static underlay optimization. */ + void setRetainedUnderlayEnabled(bool); + bool isRetainedUnderlayEnabled() const; + RetainedUnderlayDiagnostics getRetainedUnderlayDiagnostics() const; + style::Style& getStyle(); const style::Style& getStyle() const; diff --git a/include/mbgl/renderer/retained_underlay.hpp b/include/mbgl/renderer/retained_underlay.hpp new file mode 100644 index 000000000..62ca5ac2a --- /dev/null +++ b/include/mbgl/renderer/retained_underlay.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace mbgl { + +struct RetainedUnderlayDiagnostics { + bool enabled = true; + uint64_t build = 0; + uint64_t reuse = 0; + uint64_t fallback = 0; + uint64_t invalidation = 0; + std::string lastReason; +}; + +} // namespace mbgl diff --git a/integration/react-native/MapLibreParticleRNTestHost/App.tsx b/integration/react-native/MapLibreParticleRNTestHost/App.tsx index c04521451..a253c8e64 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/App.tsx +++ b/integration/react-native/MapLibreParticleRNTestHost/App.tsx @@ -10,7 +10,12 @@ import { findNodeHandle, } from "react-native"; import { Camera, Map } from "@maplibre/maplibre-react-native"; -import { FieldRuntime, type RuntimeSnapshot } from "@navsea/maplibre-particle-rn"; +import { + FieldRuntime, + RendererRuntime, + type RetainedUnderlayDiagnostics, + type RuntimeSnapshot, +} from "@navsea/maplibre-particle-rn"; import { materializeV4Frame as materializeV2Frame, qbinGoldenDatasets, @@ -60,6 +65,8 @@ function valueSourceFor(id: WeatherDatasetId): "SCALAR" | "VECTOR_MAGNITUDE" { export default function App() { const [reactTag, setReactTag] = useState(null); const [snapshot, setSnapshot] = useState(initialSnapshot); + const [retainedSnapshot, setRetainedSnapshot] = + useState(null); const [mapStatus, setMapStatus] = useState("LOADING"); const [weatherStatus, setWeatherStatus] = useState("WAITING"); const [result, setResult] = useState("Waiting for map/style"); @@ -81,6 +88,9 @@ export default function App() { const refresh = useCallback(async (tag: number) => { setSnapshot(await FieldRuntime.getState(tag)); }, []); + const refreshRetained = useCallback(async (tag: number) => { + setRetainedSnapshot(await RendererRuntime.getRetainedUnderlayDiagnostics(tag)); + }, []); const applyVisualToTag = useCallback(async (tag: number) => { const results = await Promise.all([ FieldRuntime.setParticleCount(tag, Number(visual.count)), @@ -254,12 +264,12 @@ export default function App() { try { await FieldRuntime.attach(tag); setReactTag(tag); - await refresh(tag); + await Promise.all([refresh(tag), refreshRetained(tag)]); setResult("FieldRuntime attached / particle.10"); } catch (error) { setResult("attach error: " + String(error)); } - }, [refresh]); + }, [refresh, refreshRetained]); const onStyleReady = useCallback((event: unknown) => { const tag = findNodeHandle((event as { target?: unknown }).target as never); if (tag != null) void attach(tag); @@ -300,6 +310,12 @@ export default function App() { setParticleVisible(next); } }, [particleVisible, reactTag]); + const toggleRetained = useCallback(async () => { + if (reactTag == null) return; + const next = !(retainedSnapshot?.enabled ?? true); + const result = await RendererRuntime.setRetainedUnderlayEnabled(reactTag, next); + if (result === "ACCEPTED") await refreshRetained(reactTag); + }, [reactTag, refreshRetained, retainedSnapshot?.enabled]); const updateVisual = useCallback( (key: keyof typeof initialVisual, value: string) => { @@ -344,6 +360,22 @@ export default function App() { Source: {activeSource} {result} + + Retained: {retainedSnapshot == null ? "not read" : + `${retainedSnapshot.enabled ? "ON" : "OFF"} build=${retainedSnapshot.build} reuse=${retainedSnapshot.reuse} fallback=${retainedSnapshot.fallback} invalidation=${retainedSnapshot.invalidation} reason=${retainedSnapshot.lastReason}`} + + +