14 KiB
RenderSegment 多 Retained 段全局 Placement 门槛——粒子管理员审查说明 v1
日期:2026-10-07 性质:调查结论 / 修改目标 / 管理员审查输入,不是直接实施任务书。 目标读者:Particle Shared Core / Renderer 维护者。
1. 本文要解决的问题
NavSea 当前已经能通过公开 RendererRuntime.setRenderSegmentPlan() 配置任意连续的 RETAINED / CONTINUOUS Segment,且 .19 / .20 的 RenderSegment 实现已经具备 source-local revision / readiness / cache diagnostics 等基础。
但 NavSea 真实运行验证发现:当存在多个 Retained Segment 时,一个与某 Retained Segment 无直接关系的全局 placement/fade 状态,仍可能把所有 Retained Segment 同时打回 fallback,导致多 Segment 不能发挥设计目标。
本文希望 Particle 管理员审查并决定:怎样把剩余的 placement/global fallback 门槛收敛到正确粒度,同时保持 fail-closed、Style Z-order、跨平台语义和 C-001 边界不变。
2. 正式边界
跨项目 Contract 仍为 C-001。
本问题属于 Particle Renderer 内部实现优化,目标上不要求新增 NavSea 私有 Native 通道,也不要求 Particle 理解 AIS、Route、Weather、Alarm 等 NavSea 业务语义。
NavSea 仍只负责:
- 按 Style Z-order 选择 Segment 边界;
- 通过稳定公开 API 提交 RenderSegmentPlan;
- 读取 diagnostics;
- 决定天气等业务能力何时显示。
Particle 仍负责:
- Segment resolve / cache / invalidation / renderer execution;
- placement / transition / fading / source dependency 的正确性;
- GPU resource 和 stale-cache 安全;
- Android / iOS 共用的 Shared Core 语义。
本修改若不改变公共 API、生命周期、错误语义或双方职责,不需要升级 Contract。
3. 当前 Mac 基线需要管理员先确认
本次只读检查发现:
/Users/tei/dev/MapLibreParticleLab- branch:
particle/main - HEAD:
750c14f52708d7dc93d8eb6ec2b21112185afd5c
- branch:
/Users/tei/dev/MapLibreParticleLab-release-v19- branch:
release/particle.19 - HEAD:
6b48f905d2d17a8271b6ff883e682db7bc45dcd3
- branch:
/Users/tei/dev/MapLibreParticleLab-release-v20- branch:
release/particle.20 - HEAD:
53a844e81849f254ba2d169c2d875dbd862cc7d5
- branch:
/Users/tei/dev/MapLibreParticleLab-step101- branch:
step101-render-orchestrator-teardown
- branch:
NavSea 当前实际消费的是 .19 行为;本次检查确认 release/particle.19 与 release/particle.20 在本文相关 placement gate 代码上相同。
注意:当前 particle/main 与 release/particle.19/.20 的 RenderSegment 执行细节存在差异,因此管理员开始修改前必须先明确正式修复基线,不要把 main 与 release 分支默认视为完全一致。
4. NavSea 当前为什么需要多 Segment
NavSea 当前为了避免旧的 retained fallback,采用保守的两段方案:
R0 [0,31) RETAINED
C0 [31,114) CONTINUOUS
C0 从 rn-weather-color 开始一直覆盖到 Style 尾部。
真实 diagnostics 已确认该 Continuous 尾段包含天气之后的大量运行时对象,包括 AIS / ownship / navigation / alarm 等 source/layer。
这能保护 R0 底图复用,但代价是:天气粒子需要持续 repaint 时,天气以上整个 Style 尾部都留在 Continuous 区域。
RenderSegment 的原始设计目标本来就是允许:
R0 → C0 → R1 → C1 → R2 ...
并严格保持 Style Z-order,不把业务语义写进 Renderer。
5. NavSea 真实多段实验 A
第一次临时配置:
R0 [0,31)
C0 [31,79)
R1 [79,98)
C1 [98,114)
Native 接受该配置:
configured=true
valid=true
segmentCount=4
retainedSegmentCount=2
continuousSegmentCount=2
coverageComplete=true
fallback=false
解析结果:
- R0:
background→place-labels-land-ja - C0:
rn-weather-color→ownshipRangeLabelLayer - R1:
routeLineActiveShadowLayer→planSimBoatLabelLayer - C1:
crossCursorGotoLineLayer→chartDangerScanCenterLineLayer
R1 当时依赖:
navigationSourceownshipTrackSourceplanSimRouteSource
结果 R1 为 source-fading fallback,reuse=0。
这个结果本身还不能证明 placement 问题,因为 R1 确实包含活动 source。
6. NavSea 真实多段实验 B:把 R1 收窄到 idle PlanSim
第二次把 R1 收窄为只有 PlanSim Route 的 10 个 layer:
R0 [0,31)
C0 [31,88)
R1 [88,98)
C1 [98,114)
R1:
- start:
planSimRouteShadowLayer - end:
planSimBoatLabelLayer - dependent source: 仅
planSimRouteSource - 当时 PlanSim 为 idle。
5 秒早期诊断仍有 source-fading,所以继续等待到约 20 秒稳定态。
20 秒稳定态关键结果:
fadingSourceIds = []
R0:
sourceReady=true
sourceStable=true
cacheState=fallback
build=0
reuse=0
invalidation=306
lastInvalidationReason=static-placement
finalDecision=fallback
R1:
sourceReady=true
sourceStable=true
cacheState=fallback
build=1
reuse=0
invalidation=306
lastInvalidationReason=static-placement
finalDecision=fallback
这说明稳定 source 已经不是阻塞因素;两个 Retained Segment 被同一个 static-placement 门槛同步打回 fallback。
7. release/particle.19 / .20 当前代码事实
7.1 per-segment source locality 已经存在
.19 / .20 在 renderer_impl.cpp 中会对每个 Retained Segment 单独计算:
retainedSegmentSourceState(begin,end)- source-local revision
- source
ready / stable retainedSegmentIsStable(begin,end)segmentSupportsCache[]- 每个 cache 独立
evaluate() - 每段独立 diagnostics / finalDecision
render_orchestrator.cpp 中 retainedSegmentSourceState() 已通过 RenderSegment dependency 只检查该 Segment 相关 source 的 loaded / fading 状态。
这部分方向是正确的,不应回退到全局 source invalidation。
7.2 剩余 placement gate 仍是全局聚合
.19 / .20 的 renderer_impl.cpp 仍先对全部 Retained Segment 聚合:
bool retainedStaticUsesPlacement = false;
...
retainedStaticUsesPlacement =
retainedStaticUsesPlacement ||
orchestrator.retainedRangeUsesPlacement(...);
const bool retainedPlacementStable =
!retainedStaticUsesPlacement ||
(!renderTreeParameters.placementChanged &&
renderTreeParameters.symbolFadeChange == 0.0f);
然后每个 Segment 的 segmentSupported 又共同依赖同一个 retainedPlacementStable。
因此只要任一 Retained Segment 使用 placement,并且当前全局 frame 有 placementChanged / symbolFadeChange,所有 Retained Segment 都可能失去 cache eligibility。
7.3 full CPU mixed execution 已有基础,但仍有 globalLiveFallback
.19 / .20 已有 visitRenderSegmentExecution(...),理论上可以:
R0 reuse
C0 live
R1 fallback/live
C1 live
R2 reuse
但是进入该混合执行前仍有:
const bool globalLiveFallback =
...
!retainedPlacementStable ||
orchestrator.retainedGlobalLightTransition();
!retainedPlacementStable 会直接导致:
drawableOpaquePass(0, layerCount)
drawableTranslucentPass(0, layerCount)
即整帧退回全 live,绕过 per-segment mixed execution。
这与 NavSea 实验中 R0/R1 同时出现 static-placement fallback 的现象一致。
8. 希望管理员审查的核心修改目标
目标不是简单“关闭 placement 检查”。
目标是:
当 placement / symbol fade 只影响某些 Segment 时,不应让与其无关、且自身内容/依赖/相机均稳定的 Retained Segment 一起失效。
期望行为示例:
R0:不含 placement layer,source stable
C0:Weather continuous
R1:含 placement 或当前 placement unstable
C1:AIS / Alarm continuous
R2:不含 placement layer,source stable
期望:
R0 = reuse
C0 = live
R1 = fallback/live
C1 = live
R2 = reuse
而不是:
R1 placement unstable
→ global retainedPlacementStable=false
→ R0/R1/R2 全部失效
→ 整帧 full live
9. 建议管理员重点评估的实现方向
以下是审查建议,不是强制实现方案。
9.1 先把 placement eligibility 变成 per-retained-segment
对于每个 Retained Segment 分别判断:
- 该 Segment 是否实际
usesPlacement; - 该 Segment 的 layer transition / needsRepaint;
- 该 Segment 的 source readiness / fading;
- placement transition 是否确实会改变该 Segment 的最终像素;
- 是否存在必须全局 fail-closed 的 placement 共享状态。
至少对于 完全不含 placement layer 的 Retained Segment,应评估是否有理由继续受全局 placementChanged / symbolFadeChange 阻塞。
9.2 审查 globalLiveFallback 中的 !retainedPlacementStable
建议确认是否可以:
- 从 globalLiveFallback 移除纯 placement 条件;
- 让 placement 结果进入每段
segmentSupported / decision; - 真正使用现有
visitRenderSegmentExecution()做 mixed retained/live。
真正全局的条件仍可保持全局,例如:
- 3D;
- Debug 模式;
- MapMode 不支持;
- sprite/image/global resource 未就绪;
- global light transition(若确实会改变所有段);
- context/resource 失效;
- 无法证明安全的全局状态。
9.3 不要破坏已有 source-local 机制
.19/.20 已有 retainedSegmentSourceState() 与 dependency-scoped revision/readiness。
目标应是在此基础上继续收窄 placement/global gate,而不是重新把 source/tile mutation 退回“所有 Retained 一起 invalidate”。
9.4 placement 本身若无法简单 local 化,应明确分层
管理员需要判断 MapLibre placement 是否存在以下区别:
- Segment 完全不使用 placement:理论上是否可忽略全局 placementChanged?
- Segment 使用 symbol placement,但其相关 source/layer 没变化:是否仍必须 invalidate?
- cross-source collision / shared collision index 是否会让某 Segment 的符号像素受其他 Segment placement 影响?
symbolFadeChange是全局时间量还是可以按 dependency/range 推导?- 是否需要一个 per-segment placement revision/fingerprint,而不是 bool?
如果 2/3 无法安全局部化,也至少应优先解决第 1 类,让“无 placement Retained Segment”不被无关 placement 拖死。
10. 不能牺牲的正确性约束
任何优化都必须保持:
- Style Z-order 是唯一顺序事实;
- Segment 不重新编号 Layer 的全局 ordinal;
- opaque/translucent/depth 语义不变;
- retained cache 不得显示 stale 像素;
- Camera / viewport / pixelRatio 变化仍正确 invalidation;
- Style reload / topology change 正确释放旧 cache;
- Android / iOS 使用同一 Shared Core 语义;
- 不把 AIS / Route / Weather 等业务名称写进 Renderer;
- 不要求 NavSea 手工 invalidate Native cache;
- 不新增“只为 NavSea 绕过 Renderer”的私有 API。
11. 建议新增/补强的 Core 测试
至少建议覆盖:
Case A:无 placement 的 R0 + 有 placement 的 R1
- R0 stable;
- R1 发生 placement transition;
- 期望 R0 继续 reuse;
- R1 单独 fallback/live;
- 最终 Z-order 正确。
Case B:两个 Retained 都稳定
- R0/R1 均 build 一次;
- 后续 reuse 持续增长;
- Continuous 段持续 live。
Case C:单一 source fading
- fading source 只属于 R1;
- R1 fallback;
- R0 不 invalidation。
Case D:Continuous source 高频 mutation
- mutation 只属于 C1;
- R0/R1 retained revision 不变化;
- cache 可继续 reuse。
Case E:真正全局状态
- global light transition、3D、Debug、context/resource failure 等;
- 继续 fail-closed;
- 不为追求 reuse 放宽安全边界。
Case F:topology 变化
- R-C-R-C → R-C;
- 旧 retained texture/compositor 正确释放;
- 不 composite stale resource。
12. Diagnostics 验收要求
修复后 diagnostics 必须能证明“局部失败不扩散”。
NavSea 重新做 R-C-R-C 真机测试时,至少需要看到类似:
R0:
sourceReady=true
sourceStable=true
cacheState=reuse
reuse 持续增长
R1:
finalDecision=fallback
lastFallbackReason=static-placement
C0/C1:
正常 live
关键验收不是 Fast Path hit。
多 Continuous Segment 本来就可以继续走 full CPU mixed renderer;即使 fastPathHit=0,只要稳定 Retained 段能够独立 reuse,优化就是有效的。
13. 与 NavSea 最新 Trace 的关系
Trace-20261007T224751.json 是 UDP + 天气 UI 的 DevTools Trace。
该 Trace 中约 11–13 秒和 21–23 秒出现两组很高的 JS 峰值;调用栈显示峰值主要落在 React root work 与 WeatherForecastStrip / WeatherTable,其中 WeatherTable 两次重 render 分别约 0.91s / 1.07s,AIS decoder 并不是这两个峰值的主要执行栈。
代码同时显示 WeatherForecastStrip 会订阅 smoothGPS,并用经纬度 toFixed(2) 形成天气采样位置 key;但当前尚未完成对这两次具体 WeatherTable render 触发源的一一证明,因此本文不把“约 10 秒”直接归因于 GPS 跨格或任何单一 NavSea 机制。
这些 JS/UI 峰值属于 NavSea 侧独立调查项,不应作为 Particle RenderSegment 修改的直接证据。
Particle 本次应以 RenderSegment diagnostics 的 R0/R1 static-placement 同步 fallback 为主要证据。
14. 管理员需要给出的结论
请管理员审查后明确回答:
.19/.20当前全局retainedPlacementStable是否确实过度保守?- 对“不含 placement layer”的 Retained Segment,是否可以安全解除全局 placementChanged/symbolFadeChange 阻塞?
globalLiveFallback是否应去掉 placement 条件,转为 per-segment decision?- 若 placement 必须部分全局,最小安全局部化边界是什么?
- 正式修改应基于
release/particle.20、合并回particle/main,还是先在独立工作树完成? - 是否需要新 ADR/RFC?初步判断:若公共 API/C-001 语义不变,只是 Renderer 内部优化,可不新增跨项目 Contract;若管理员认为会改变 RenderSegment 公共语义,再交回 Integration 决定。
15. 当前 NavSea 状态
NavSea 临时 R-C-R-C 实验已经撤回。
当前恢复为原有保守方案:
R0 [0,31) RETAINED
C0 [31,114) CONTINUOUS
因此管理员可以先只做 Native/Core 审查,不需要 NavSea 同步修改。
等 Particle 给出明确方案和可验证版本后,再由 NavSea 重新选择真实 Style 边界并做 R-C-R-C / R-C-R-C-R 真机验证。