Files
MapLibreParticleLab/docs/报告/STEP-099_RenderSource就绪与RenderSegment失效诊断_Codex完成报告_v1.md
tei c4b481fd2c
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
docs: close STEP-099 render source diagnostics
2026-10-06 15:33:12 +09:00

16 KiB
Raw Permalink Blame History

STEP-099 RenderSource 就绪与 RenderSegment 失效诊断 — Codex 完成报告 v1

1. 执行结论

CLOSED / PASS(诊断阶段完成)。

STEP-099 按任务书保持“纯诊断、fail-closed、不优化正式语义”的范围完成。Renderer/Source/RenderSegment diagnostics、Android Test App T1~T4 场景、定向 native 测试和 Android 实机运行证据均已取得,Q1~Q7 可以据实回答。

本 Step 没有修改 retained cache 算法、fast path gate、singleContinuousSegment、multi-continuous 行为、segment-local invalidation、RenderSegment 执行语义、NavSea、Source isLoaded() 定义或 RN 业务 API 语义。

2. 基线与 Git

  • 仓库:/Users/tei/dev/MapLibreParticleLab-step099
  • branch:step099-render-source-diagnostics
  • frozen release tag:maplibre-particle/rn/13.4.1-particle.17
  • frozen release commit / start base:e8b451fe70054107773e82484c80e90df3889147
  • implementation commit:8a5214d92dd2f8899db76d2194da42999fa0c87d
  • implementation message:diagnostics: add STEP-099 render source readiness audit
  • Integration/NavSea:未修改。
  • 文档 closeout commit、push result、origin/particle/main Final HEAD:Git 提交无法在自身内容中自指其最终 SHA,按项目既有规则由最终交付回读记录。

3. 实际修改范围

生产源码 / diagnostics:

  • include/mbgl/renderer/render_segment.hpp
  • src/mbgl/renderer/render_orchestrator.cpp
  • src/mbgl/renderer/render_orchestrator.hpp
  • src/mbgl/renderer/render_segment_json.hpp
  • src/mbgl/renderer/render_source.hpp
  • src/mbgl/renderer/retained_underlay_runtime_state.hpp
  • src/mbgl/renderer/sources/render_tile_source.cpp
  • src/mbgl/renderer/sources/render_tile_source.hpp
  • packages/maplibre-particle-rn/src/index.ts
  • packages/maplibre-particle-rn/src/types.ts

测试 / Test App:

  • test/renderer/retained_underlay_cache.test.cpp
  • platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt
  • platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step099ReadinessFixture.kt

项目记录:

  • docs/50_Step记录/STEP-099_RenderSource就绪与RenderSegment失效诊断_v1.md
  • docs/报告/STEP-099_RenderSource就绪与RenderSegment失效诊断_Codex完成报告_v1.md
  • 粒子开发记录.md

4. 诊断能力

4.1 全局 readiness

新增只读 snapshot:

  • styleLoaded
  • imageManagerLoaded
  • renderOrchestratorLoaded
  • spriteLoaded
  • hasFadingTiles
  • global retained contentRevision
  • RenderSegment plan/config/topology 信息
  • 最近 mutation sequence / reason / source / affected segment diagnostics

正式 loaded 路径保持原式:

renderTreeParameters->loaded = updateParameters->styleLoaded && isLoaded();

diagnostics snapshot 在旁路采样,失败被 best-effort 隔离,不决定正式 loaded。

4.2 per-source readiness

每个 RenderSource additive 暴露:

  • sourceId / sourceType
  • enabled / isLoaded
  • tileBacked
  • tileCount / incompleteTileCount / loadedTileCount
  • hasFadingTiles
  • last source/tile mutation sequence
  • last tile action / reason

默认非 tile source 返回完整显式初始化的空统计,Android NDK -Werror 下已验证通过;未改变任何 Source isLoaded() 实现。

4.3 RenderSegment 只读依赖

每个 segment additive 暴露:

  • segmentIndex / strategy / beginIndex / endExclusive
  • dependentSourceIds
  • unknownDependencies

规则:

  • 从实际 ordered layers 的 sourceId 推导;
  • shared source 可出现在多个 segment;
  • source-less background 不制造假依赖;
  • symbol/custom 等无法仅靠 sourceId 完整表达的依赖标记为 unknown/global;
  • 依赖图只用于诊断,不参与 cache/loaded/fast path/execution。

4.4 mutation 定位

Source/tile mutation 复用已有回调记录 sequence/source/reason/action。原 retainedUnderlayContentRevision 的增长次数与正式调用路径不变;diagnostics 不增加额外 revision。

query 路径不清理或修改 event history,重复 snapshot/JSON 查询保持无副作用。

5. Native 定向测试

5.1 Metal host 定向 GTest

实际执行:

cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
build-macos-metal/mbgl-test-runner --gtest_filter='RenderSegmentDiagnostics.*'

结果:

  • RenderSegmentDiagnostics.*:6/6 PASS
  • 新增 ReadinessSnapshotMapsSourcesToSegmentsWithoutSideEffects:PASS
  • 覆盖 source loaded/unloaded、tile stats、source→segment、shared source、source-less、重复 query、topology/cache/content revision 无副作用。

证据: artifacts/maplibre-particle/android-validation/step099-metal-gtest.log

SHA-256: f0618750ead03eae1b251126dd49488a9269a120373a66e3453fa9aaa1dc0f10

说明:该 6/6 发生在 Android NDK 首轮暴露“默认 readiness struct designated initializer 未显式列完 optional 字段”之前;随后仅补全默认空字段初始化。最终源码由 Android 全量 native build 成功覆盖。为了在最终源码上重跑 Metal,重新 configure 时当前 Mac 缺 libuv pkg-config,configure fail;没有安装系统依赖来制造 PASS。

5.2 Vulkan

macos-vulkan configure:BLOCKED / NOT RUN TESTS。

原因:

The following required packages were not found:
 - libuv

这是当前 Mac host 环境依赖,不是 STEP-099 源码编译错误。

5.3 Jest

NOT RUN。用户未要求 Jest,任务未加入 Jest。

6. Android 构建与设备

6.1 首轮 Android 编译问题

首轮 x86_64 OpenGL build 在 Android NDK -Werror 下发现:

render_source.hpp:84:51:
error: missing field 'tileCount' initializer
[-Werror,-Wmissing-designated-field-initializers]

修复仅补全 RenderSourceReadinessStats 默认空字段初始化,不改变运行语义。

随后 native C++ 已越过原失败点并完整编译。

6.2 磁盘阻塞与恢复

第一次完整 native build 后,在:

MapLibreAndroidTestApp:stripOpenglDebugDebugSymbols

因 Mac 仅剩约 2 GiB 出现:

No space left on device

RDC 只清理可再生 Gradle 9.5.1 cache / 本轮失败中间产物,保留 Android native object;可用空间恢复到约 12 GiB 后增量重跑。

6.3 最终 APK

命令:

./tools/particle_android_validation/build_validation_apk.sh

最终结果:

  • Gradle:BUILD SUCCESSFUL in 4m 54s
  • actionable tasks:86(7 executed / 79 up-to-date)
  • variant:OpenGL Debug
  • ABI:x86_64
  • APK:61,923,024 bytes
  • libmaplibre.so:39,602,856 bytes
  • APK SHA-256: 5de73f0358b835a78d3ae7e2ac22f4efdb2a66775b8c63654a8bb7313957d90a

构建时 metadata:

  • base commit:e8b451fe70054107773e82484c80e90df3889147
  • git_dirty=true
  • 构建内容与随后形成的 implementation commit 8a5214d92 源码一致;当前 source tree 对 implementation commit clean,剩余仅 docs closeout。

6.4 Android 设备

Mac ADB 直接连接:

  • serial:192.168.200.139:5555
  • model:FZG1-2
  • Android:13
  • API:33
  • ABI:x86_64

APK 覆盖安装成功,ParticleCoreLayerValidationActivity 冷启动成功。

运行 log: artifacts/maplibre-particle/android-validation/step099-logcat.txt

log SHA-256: 0f15d75dde77e7dd2f4d98c46a4c1501a628205e8aae62130b0cb96db6b8482f

7. T1~T4 实测

T1 STATIC

场景:

  • R0:静态 GeoJSON retained
  • C:本地静态 RasterArray field 驱动 WeatherColor + Particle
  • R1:静态 retained
  • camera fixed

结果:

  • 启动阶段 1 个 false window:204 ms;
  • 随后 renderOrchestratorLoaded=true 稳定;
  • particle-fixture 与 step099-static 均 loaded;
  • retained cache 建立后持续 reuse;
  • fast path 进入 active=true / eligible=true / reason=none;
  • 没有持续 style-not-loaded。

aggregate:

falseWindowCount=1
falseWindowAvgMs=204
falseWindowMaxMs=204
openFalseWindowMs=0

结论:静态基线可稳定进入 loaded/cache reuse/fast path。

T2 GEOJSON_1HZ

两个 GeoJSON source 每 500 ms 交错更新,因此每个约 1 Hz。

实际:

  • 总 source update:122 次,约 61 次/源;
  • 60 秒量级观察中,出现 10 个 loaded=false window;
  • 平均 203 ms;
  • 最大 205 ms;
  • 最终 open window = 0;
  • 总 false 时间约 2.03 s,约占 60 s 的 3.4%,不是“几乎始终 false”。

aggregate:

falseWindowCount=10
falseWindowAvgMs=203
falseWindowMaxMs=205
openFalseWindowMs=0

同时 final sample:

  • global contentRevision 已到 1679;
  • R0 仅依赖 step099-static,但 cache build=241、invalidation=362;
  • R1 依赖动态 GeoJSON,也同样 build=241;
  • mutation diagnostics 能正确指出动态 source 只 affected=[2],但正式 cache invalidation 仍非局部。

T3 PERMANENT_UNREADY

额外加入:

step099-unready-raster-array

它:

  • 没有 layer;
  • 因此 enabled=false;
  • host-submission 无 field;
  • isLoaded=false。

其他主要 source 均 loaded。

实测:

  • renderOrchestratorLoaded=false 持续;
  • retained segment cache 持续 fallback;
  • lastInvalidationReason=style-not-loaded;
  • 观察结束仍没有恢复。

aggregate:

falseWindowCount=0
openFalseWindowMs=21459

结论:即使 source 与当前 layer/segment 无关且 disabled,只要仍在 renderSources 且 isLoaded=false,V17 全局 RenderOrchestrator::isLoaded() 就保持 false。

T4 R-C-R

结构:

  • R0:deps=[step099-static]
  • C:deps=[particle-fixture],WeatherColor + Particle
  • R1:deps=[org.maplibre.annotations, step099-dynamic, step099-static]
  • step099-dynamic 约 1 Hz 更新

实际:

  • dynamic update:61 次;
  • loaded false window:4;
  • 平均 203 ms;
  • 最大 207 ms;
  • global contentRevision:1760 → 2540;
  • mutation diagnostics:step099-dynamic 对应 affected=[2];
  • R0 并不依赖 step099-dynamic,但:
    • build:1 → 121
    • invalidation:20 → 200
  • R1 同步得到相同级别 build/invalidation 增长;
  • fast path 在稳定窗口可恢复 active,但 global revision / transient readiness 会反复制造 full CPU work。

aggregate:

falseWindowCount=4
falseWindowAvgMs=203
falseWindowMaxMs=207
openFalseWindowMs=0

结论:依赖诊断已经能准确表达“R1 source-local”,但 V17 正式 content revision/cache invalidation 仍是 global。

8. Q1~Q7

Q1:单个 permanently unloaded source 会造成 V17 style-not-loaded 吗?

YES / CONFIRMED。

T3 中 step099-unready-raster-array 无 layer、enabled=false、isLoaded=false;其他主要 source loaded,但整体 renderOrchestratorLoaded=false 持续 21.459 s 未恢复,retained cache 持续 style-not-loaded fallback。

源码事实与运行事实一致:RenderOrchestrator::isLoaded() 遍历全部 renderSources,不按 enabled/segment dependency 排除 source。

Q2:1 Hz GeoJSON 会周期性让 isLoaded=false 吗?平均/最大窗口?

YES / CONFIRMED。

T2 双 source 交错各约 1 Hz:

  • 10 次 false window;
  • avg 203 ms;
  • max 205 ms。

T4 单动态 source 约 1 Hz:

  • 4 次 false window;
  • avg 203 ms;
  • max 207 ms。

Q3:错开的多个 1 Hz GeoJSON 会导致 global loaded 几乎无法稳定为 true 吗?

NO / 本次观测不支持。

T2 约 60 s 中总 false 时间约 2.03 s,约 3.4%;绝大多数时间 global loaded=true。它确实产生 readiness thrash,但不是“几乎永远 false”。

Q4:GeoJSON tile mutation 会推进 global retainedUnderlayContentRevision 并影响 retained cache 吗?

YES / CONFIRMED。

T4 global contentRevision 1760 → 2540。仅依赖静态 source 的 R0 也从 build 1 → 121、invalidation 20 → 200。

Q5:不引用 GeoJSON 的 retained segment 仍会受它的 loaded/fading/tile mutation 影响吗?

YES。

运行证据:

  • T4 diagnostics 正确报告动态 source affected=[2];
  • 但 R0 deps=[step099-static] 仍随动态 source mutation 重建/失效。

源码证据:

  • retainedRangeIsStable(begin,end) 虽为 range API,但最后遍历 全部 renderSources,任一 source hasFadingTiles() 都让任意 retained range unstable。

本次 T4 稳态 fade=false,因此 R0 的实际反复失效主要直接证明 global content revision/tile mutation;GLOBAL_FADE 属源码确认、未在本轮场景主动触发的全局 gate。

Q6:tryRenderParticleFastFrame 仍有哪些 global gate 会阻挡未来 localized cache CPU fast path?

确认仍存在:

  1. GLOBAL_UPDATE_SIGNATURE

    • particleFastPathNonCameraInputsMatch 全局比较:
    • styleLoaded / spriteLoaded
    • light
    • images
    • sources
    • layers
    • fontFaces / fileSource / glyphURL
    • transition / placement / tile LOD 等全局输入。
  2. GLOBAL_CONTENT_REVISION

    • particlePreparedContentRevision == orchestrator.getRetainedUnderlayContentRevision()。
  3. GLOBAL_FADE

    • particleFastPathDynamicState 遍历全部 renderSources 汇总 fading;
    • retainedRangeIsStable 也遍历全部 renderSources。
  4. GLOBAL_PENDING / TRANSITION / REPAINT

    • pendingChanges
    • renderLight transition
    • 任意 layer transition
    • 任意非粒子 layer repaint
    • placement transition。
  5. GLOBAL_ANNOTATION_MUTATION

    • AnnotationManager hasPendingChanges()。
  6. RETAINED REVISION / CACHE / TOPOLOGY / BOUNDARY

    • retained cache validity/static revision
    • global retained revision tracker
    • topology revision
    • boundary/config revision。
  7. singleContinuousSegment

    • 仍要求 plan 恰好能由一个 Continuous segment 表达;
    • multi-continuous 继续 fail closed,本 Step 未修改。

因此即使未来 cache invalidation 变成 source/segment-local,fast path 仍有多组 global gate 需要后续 Step 单独设计,不能在 STEP-099 顺手放宽。

Q7:blocker 分类

运行态 CONFIRMED:

  • PERMANENT_SOURCE_UNLOADED
  • DYNAMIC_SOURCE_READINESS_THRASH
  • GLOBAL_CONTENT_REVISION

源码 CONFIRMED,本轮未主动触发独立运行场景:

  • GLOBAL_FADE
  • GLOBAL_UPDATE_SIGNATURE

OTHER:

  • global pendingChanges
  • placement/light/non-particle transition/repaint
  • annotation mutation
  • topology/boundary/revision/cache validity
  • singleContinuous fail-closed

9. 任务边界审计

确认未改:

  • retained cache 核心算法;
  • fast path gate 逻辑;
  • singleContinuousSegment;
  • multi-continuous 策略;
  • segment-local invalidation 正式语义;
  • RenderSegment execution;
  • NavSea;
  • private NavSea Native API;
  • RN 业务 API;
  • Source isLoaded() 定义。

新增 dependency / affectedSegmentIndices 只是观察信息;它没有参与正式 invalidation。

10. 证据文件

忽略目录:

artifacts/maplibre-particle/android-validation/

主要文件:

  • build-info.txt
  • build.log
  • MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
  • step099-logcat.txt
  • step099-device-info.txt
  • step099-metal-gtest.log
  • step099-cmake-metal.log

APK SHA-256:

5de73f0358b835a78d3ae7e2ac22f4efdb2a66775b8c63654a8bb7313957d90a

STEP-099 log SHA-256:

0f15d75dde77e7dd2f4d98c46a4c1501a628205e8aae62130b0cb96db6b8482f

11. 下一阶段建议

STEP-099 只负责定位,不在本 Step 修复。

下一阶段应基于本轮证据拆开处理:

  1. 先决定 global source readiness 是否要引入“影响当前 style/render segment 的 readiness 范围”,解决 permanently unready / transient unrelated source 对全局 loaded 的影响;
  2. 再设计 source/segment-local content revision 与 invalidation,避免 R1 GeoJSON mutation 重建 R0;
  3. 最后单独审查 fast-path global signature/fade/pending/placement 等 gate,不能把 cache locality 与 fast-path locality 混成一个修改;
  4. 保持 Style Z-order 唯一事实来源和当前通用 Render Segment 模型。

12. 最终状态

STEP-099 CLOSED / PASS。

Pass 含义仅为:

  • diagnostics 能回答任务问题;
  • Native 定向测试已有 PASS 证据;
  • 最终 Android x86_64 OpenGL APK BUILD SUCCESS;
  • FZG1-2 T1~T4 实跑完成;
  • Q1~Q7 有源码/运行证据;
  • 未改变正式渲染语义。

不代表:

  • Vulkan host 已通过;
  • iOS runtime 已测试;
  • 下一阶段 localized readiness/invalidation 已实现;
  • fast path global gates 已优化。