feat: add particle static CPU fast path
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-09-23 08:22:24 +09:00
parent 98e1d2d977
commit 4e3d27e1f3
18 changed files with 1247 additions and 38 deletions

View File

@@ -0,0 +1,239 @@
# STEP-090 Particle-only Static CPU Fast Path Codex 任务 v1
## 目标
基于 STEP-088 retained underlay GPU cache 与 STEP-089 static revision/invalidation gate,真正减少 Particle continuous repaint 下静态 PBF/vector 的 CPU processing。
当前已实测:
- PURE PBF idle:RenderThread ~0%
- PURE PARTICLE 2048 / FPS30:RenderThread ~10.2%
- production Style + 60 个 PBF layer hidden + Particle:RenderThread ~25.5%
- production PBF + Particle:RenderThread ~96~100%
- STEP-088 仅省 static GPU draw,RenderThread 仍 ~96%
- STEP-089 已保证:
- steady state staticRevision/buildRevision 稳定;
- camera/style/tile 等真实变化会 revision bump;
- transient drawable/topology churn 不误触发。
因此 STEP-090 的目标是:
> 当当前帧仅由 Particle animation 触发,且静态状态 revision/topology/camera/viewport/placement 等均稳定时,跳过不必要的静态 PBF/vector CPU pipeline,复用上一帧已准备好的 render state / RenderTree,并只执行 Particle 所需的动态 render。
## 基线
- branch: `particle/main`
- baseline HEAD: `98e1d2d977ba9e007ba313c14d63bfa54ab9d23b`
- STEP-089: CLOSED / PASS
## 核心约束
### 1. Fast path 必须在 createRenderTree 之前判定
当前:
`Renderer::render(UpdateParameters)`
→ `RenderOrchestrator::createRenderTree()`
→ `renderTree->prepare()`
→ `Renderer::Impl::render()`
如果 fast path 只放在 `Renderer::Impl::render()`,已经无法节省 createRenderTree/source update/prepare,不满足 STEP-090。
### 2. 仅允许 Particle-only frame
必须内部区分:
- Particle-only animation frame;
- 其他任何会影响 map/render tree 的更新。
Fast path 只在能够证明“除了 Particle animation,没有其他更新需要处理”时使用。
不得新增 Host/RN 手工调用:
- 不要 `particle.renderFastFrame()`
- 不要 `cacheMapNow()`
- 不要 `invalidateBasemap()`
这是 MapLibre Native 内部自动行为。
### 3. 安全 gate
至少要求:
- retained underlay runtime enabled;
- retained cache valid;
- retained buildRevision == current staticRevision;
- camera / transform unchanged;
- viewport / pixelRatio unchanged;
- styleLoaded;
- dynamic boundary unchanged;
- topologyRevision unchanged;
- no static transition;
- no placement/fade transition affecting correctness;
- no source/tile/image/glyph/style/layer mutation;
- no context reset/debug/3D/custom unsupported;
- no pending change request that would alter render tree;
- no non-particle layer transition/repaint;
- no fading tile;
- no still image request;
- no map mode / special render path that cannot safely reuse。
任何不确定情况必须 full path。
### 4. RenderTree reuse
优先实现一个最小、明确的 cached prepared RenderTree / render state 生命周期:
- full frame 成功创建并 prepare 后,可作为下一次 particle-only fast frame 的候选;
- fast frame 不重新 createRenderTree;
- fast frame 不重新调用 full `RenderTree::prepare()`;
- 复用对象生命周期必须明确,不能悬挂引用;
- full path 创建新 tree 后替换旧缓存;
- revision/context/style reset 时失效。
若 RenderTree 当前设计无法安全长期持有,允许提取一个专用 immutable prepared frame state,但不要大规模重构。
### 5. 动态部分
Particle fast frame必须仍然:
- 正常 advance simulation;
- 正常 render Particle;
- composite retained underlay;
- 正常 present;
- 保持 particle cadence/targetFps 语义。
对于 Particle 之外的动态 layer:
- 如果存在其他 `needsRepaint()` / transition / placement / fading tile,则禁止 fast path,走 full path。
- 不要在本步骤尝试支持 AIS/Ownship 等所有未来动态 layer;先确保 Particle-only fast frame 安全。
WeatherColor:
- 若本身不需要 repaint 且输入未变,可与静态 prepared state 一起复用;
- 若其状态变化,full path。
### 6. Source/PBF processing
Fast path 命中时,目标是明确不执行:
- full `createRenderTree()`
- static PBF/vector source update
- tile pyramid normal per-frame update
- static layer prepare
- static placement orchestration
- static layer group rebuild
允许仍执行 fast frame 所必须的最小 upload/tweaker/render,但要避免重新走静态 source/layer CPU pipeline。
### 7. Runtime ON/OFF
继续使用现有 `retainedUnderlayEnabled` 作为整体优化 gate:
- ON:允许 retained cache + CPU fast path;
- OFF:完全回到原 full-render baseline,用于 A/B;
- 不新增第二个用户开关,除非内部 debug-only 诊断确实必要。
这样同一个 Release 构建仍可通过 RN RendererRuntime 做完整 A/B。
### 8. 诊断
Debug 增加最小 counters/log:
- `ParticleFastFrameHit`
- `ParticleFastFrameMiss`
- `ParticleFullFrame`
- miss reason(debug only)
建议周期日志继续并入 `ParticleRetained:` 或新增稳定前缀 `ParticleFastPath:`,字段至少:
```text
hit=<n>
miss=<n>
full=<n>
reason=<...>
staticRevision=<n>
buildRevision=<n>
```
Release 不做 per-frame 日志。
如现有 diagnostics 适合,可在 Release pull snapshot 中增加累计 hit/miss,但不是强制;不要扩大 RN 高频接口。
### 9. 性能验收目标
不预设固定百分比门槛,但 Android NavSea PBF Test App 必须证明:
- AFTER_CLIP / BEFORE_SYMBOL_CLUSTER 固定 Camera;
- Cache/FastPath ON 时 fast hit 持续增长;
- full frame 不随 Particle frame 一比一增长;
- Particle 仍连续运动;
- static draw 继续不随 particle frame 增长;
- RenderThread CPU 相比 STEP-089 / Cache OFF 有明显下降。
关键基线用于比较:
```text
PURE PARTICLE ~10.2% RenderThread
PBF hidden + Particle ~25.5%
PBF visible + Particle ~96~100%
```
目标方向是让 PBF visible + Particle 大幅向前两者靠近,而不是只减少几个百分点。
### 10. 回退验证
必须验证:
- pan / zoom / rotate → fast path 立即 miss/full;
- tile load/change → full;
- style/layer/source change → full;
- cache OFF → full;
- static state重新稳定后 → fast hit恢复;
- TOP symbol-heavy / placement active → 保守 full/fallback;
- resize/context reset → full;
- no crash / stale map / ordering regression。
## 不做
- 不新增 Host API 触发 fast frame。
- 不把 provider/network/vendor 逻辑放入 Native。
- 不改 RN Weather Service 边界。
- 不做 STEP-091 级别的多动态层通用调度。
- 不做复杂 dependency graph。
- 不做 Jest。
- 不使用 worktree。
## Native 测试
新增 directed tests,至少覆盖:
1. particle-only + stable revision → fast candidate true;
2. revision mismatch → false;
3. camera/viewport mismatch → false;
4. non-particle transition/repaint → false;
5. placement/fade/fading tile → false;
6. cache disabled → false;
7. context/style/source/tile mutation后 → false;
8. full frame重新稳定后 → fast candidate true;
9. cached RenderTree 生命周期/替换不悬挂;
10. 原 12 个 retained tests继续通过。
## Codex 执行规则
- 先完整读取 AGENTS.md、STEP-088 v3、STEP-089 task/Step/报告、当前 renderer.cpp / render_orchestrator / renderer_impl / RenderTree。
- 不提交实施计划,直接调查并实现。
- 不运行 Gradle/ADB/Xcode/大构建。
- 不 commit/push。
- 不触碰无关 dirty/untracked。
- 不使用 worktree。
- 不加 Jest。
- 如果发现当前 RenderTree 不能安全缓存,先在当前架构内采用最小安全方案,不进行无边界重构。
- 如果实现范围明显膨胀到新的架构层,停止并报告阻塞点,不强行完成。
## 完成时必须报告
逐项说明:
- fast path gate 实际位于哪里;
- 如何判断 particle-only;
- 哪些 full-frame 工作被跳过;
- cached RenderTree / prepared state 生命周期;
- 所有 miss/fallback 条件;
- 修改文件;
- 新增 tests;
- RDC 精确验证命令;
- 明确没有新增 Host 手工 fast-frame API。

View File

@@ -0,0 +1,211 @@
# STEP-090 Particle-only Static CPU Fast Path v1
## 状态
**CLOSED / PASS**
STEP-090 已完成 Particle-only Static CPU Fast Path。该实现建立在 STEP-088 retained underlay GPU cache 与 STEP-089 static revision/invalidation gate 之上,在满足严格安全条件时,于 `Renderer::render()` 的 `createRenderTree()` 之前命中 fast path,复用上一帧已 prepare 的 RenderTree,并跳过静态 PBF/vector 的完整 CPU pipeline。
## 核心实现
- fast-path gate 位于 `Renderer::render()`,早于 `RenderOrchestrator::createRenderTree()`。
- full frame 成功后缓存 prepared RenderTree。
- fast frame 命中时:
- 不重新 createRenderTree;
- 不执行完整 RenderTree::prepare;
- 不执行 full static layer update;
- 只更新 Particle 动态 layer;
- 静态 underlay 继续使用 retained cache;
- 保持 Particle simulation / draw / present 正常运行。
- full path 创建新 RenderTree 前先清除旧 prepared tree,避免引用旧 RenderLayer/RenderSource 生命周期。
- context lost / clearData / reduceMemoryUse / style reload 时清除 prepared tree。
- 没有新增 Host/RN 手工 fast-frame API;仍使用已有 retainedUnderlayEnabled 作为整体优化 ON/OFF A/B 开关。
## Particle-only 安全 gate
只有下列条件同时满足才允许 fast path:
- prepared RenderTree 存在;
- retained runtime enabled;
- retained cache valid;
- static/build/content/topology revision 一致;
- Camera / viewport / pixelRatio 一致;
- UpdateParameters 的非时间语义输入一致;
- style loaded;
- dynamic boundary 一致;
- static prefix stable;
- 当前 placement/fade transition 已结束;
- 无 pending renderer changes;
- 无 non-particle transition;
- 无 non-particle needsRepaint;
- 无 fading tiles;
- Particle 当前确实 needsRepaint;
- 非 still-image request;
- Continuous MapMode;
- debug rendering disabled;
- 当前场景支持 fast path;
- annotation 无 pending mutation。
任一不满足均立即走 full path。
## placement/fade 修正
首轮实机显示 fast path 永久被 `placement-or-fade` 阻止。调查确认 prepared RenderTree 上一 full frame 的 `placementChanged / symbolFadeChange` 是历史帧事件状态,不应永久阻止后续已稳定帧。
最终改为以当前 `placementController.hasTransitions(timePoint)` 为权威判据:
- 当前 placement/fade transition 活跃:full path;
- transition 结束且 STEP-089 revision / camera / tile / style 等语义 gate 均稳定:允许 fast path。
这保留了 active symbol fade/placement 的安全回退,同时避免上一帧 placement pass 永久锁死 fast path。
## Android 性能实测
设备:Android 13 / x86_64 / FZG1-2。
场景:NavSea 正式 63 层 PBF Style,Particle=2048。
### AFTER_CLIP
FastPath ON:
```text
PROC_CPU ≈ 27.7%
MAIN_CPU ≈ 2.9%
RENDER_CPU ≈ 21.7%
```
同 APK、同场景、Cache/FastPath OFF:
```text
PROC_CPU ≈ 111.4%
MAIN_CPU ≈ 2.8%
RENDER_CPU ≈ 97.0%
```
RenderThread 从约 97% 降至约 21.7%。
稳定日志:
```text
fastHit 持续增长
fastMiss 固定
particleFull 固定
fastReason=none
buildRevision == staticRevision
retained build 固定
retained reuse 持续增长
```
代表日志:
```text
frame=2880 ... fastHit=2670 fastMiss=210 particleFull=210 fastReason=none
frame=3000 ... fastHit=2790 fastMiss=210 particleFull=210 fastReason=none
frame=3240 ... fastHit=3030 fastMiss=210 particleFull=210 fastReason=none
```
### BEFORE_SYMBOL_CLUSTER
稳定后 fast path 命中。
```text
PROC_CPU ≈ 26.3%
MAIN_CPU ≈ 2.7%
RENDER_CPU ≈ 20.3%
```
说明优化不依赖单一 AFTER_CLIP 边界。
## 安全回退验证
### Cache OFF / ON
Cache OFF:
- enabled=false;
- fastHit 停止增长;
- fastMiss / particleFull 持续增长;
- fastReason=retained-disabled;
- RenderThread 回到约 97%。
重新 Cache ON:
- cache rebuild 一次;
- buildRevision 恢复匹配;
- fastReason=none;
- fastHit 恢复持续增长。
### Camera pan
pan 前:
- staticRevision/buildRevision 稳定;
- particleFull 固定;
- fastHit 持续增长。
pan 时:
- revision 前进;
- build / invalidation / fastMiss / particleFull 增加;
- full path 正常执行。
稳定后:
- build 固定;
- fastHit 恢复持续增长;
- particleFull 再次固定。
### TOP symbol-heavy
TOP 将全部 63 层置于 Particle 下方。
实测:
- retained buildRevision=0;
- reason=static-transition / static-placement;
- fastHit 不增长;
- fastMiss / particleFull 持续增长;
- 保守 full path 生效。
说明 symbol-heavy static prefix 未被错误复用。
### Viewport / orientation
原始设备设置:
- accelerometer_rotation=1;
- user_rotation=0。
强制旋转后:
- style/cache invalid;
- full path;
- 新 RenderThread 创建;
- cache rebuild;
- 随后 fastReason=none,fastHit 恢复。
恢复原始旋转设置后再次经历同样的 full → rebuild → fast-hit 恢复流程。
设备设置最终已恢复:
- accelerometer_rotation=1;
- user_rotation=0。
## 构建与测试
- git diff --check:PASS。
- Mac OpenGL mbgl-test-runner:PASS。
- retained + ParticleFastPath directed tests:21/21 PASS。
- Android x86_64 stripped Debug Validation APK:BUILD SUCCESSFUL,约 57 MB。
- Android 安装:PASS。
- Android A/B CPU:PASS。
- Camera / Cache / TOP / viewport 回退:PASS。
- 异常日志扫描:
- 无 FATAL EXCEPTION;
- 无 ANR;
- 无 SIGSEGV/SIGABRT;
- 无 GL_INVALID / EGL_BAD;
- 无 OOM。
- Metal/iOS mbgl-core:BUILD SUCCEEDED。
- 临时 ParticleFastPathLife / ParticleFastPathDecision 日志:已移除。
- 未新增 Host/RN fast-frame API。
## 结论
STEP-090 达成目标:Particle continuous repaint 不再强制静态 PBF/vector 每帧重新执行完整 CPU render-tree/source/layer pipeline。
在真实 NavSea PBF 测试场景中,RenderThread CPU 从约 97% 降到约 20~22%,已经接近此前 PURE PARTICLE / PBF-hidden 基线级别。
下一阶段不应立即扩展成通用多动态层调度;应先基于当前 Android/iOS 实际集成结果决定是否需要新的 profiling / production hardening 阶段。

View File

@@ -0,0 +1,114 @@
# STEP-090 Particle-only Static CPU Fast Path Codex 完成报告 v1
## 最终结论
**CLOSED / PASS**
本阶段实现并验证了 MapLibre Native 内部自动 Particle-only CPU fast path。
## 完成项
- 已将 fast-path gate 放在 createRenderTree 之前。
- 已实现 prepared RenderTree 跨帧复用。
- 已实现 Particle-only 动态判定。
- 已跳过 full static createRenderTree / prepare / static layer update。
- 已保持 retained underlay GPU cache。
- 已增加 Debug counters:
- ParticleFastFrameHit
- ParticleFastFrameMiss
- ParticleFullFrame
- 已增加周期性 fastReason 诊断。
- 已处理 annotation pending mutation。
- 已处理 non-particle repaint / transition。
- 已处理 placement/fade / fading tile。
- 已处理 camera / viewport / style / source / tile / topology / revision gate。
- 已处理 context lost / clearData / reduceMemoryUse / style reload prepared tree 清理。
- 未新增 Host/RN 手工 fast-frame API。
- 未实现多动态层通用调度。
- 未加入 Jest。
## 首轮实机问题与修复
首轮 Android 实机:
- prepared tree 实际正常跨帧保存;
- 但 fastHit=0;
- 诊断 initially 显示 no-prepared-tree。
调查发现:
1. clearPreparedRenderTree 覆盖了真实 miss reason,导致诊断误导;
2. 实际阻塞为 placement-or-fade;
3. 上一 full frame 的 placementChanged / symbolFadeChange 被当成永久 blocker。
最终修正:
- clear 不再覆盖上一真实 miss reason;
- placement gate 改为当前 `placementController.hasTransitions(timePoint)`;
- 上一 full frame 的 placementChanged / symbolFadeChange 不再永久阻塞;
- current active placement/fade 仍 fail closed。
## Android A/B 性能
### AFTER_CLIP
FastPath ON:
- Process ≈ 27.7%
- Main ≈ 2.9%
- RenderThread ≈ 21.7%
FastPath OFF:
- Process ≈ 111.4%
- Main ≈ 2.8%
- RenderThread ≈ 97.0%
RenderThread 下降约 75 个百分点。
### BEFORE_SYMBOL_CLUSTER
FastPath ON:
- Process ≈ 26.3%
- Main ≈ 2.7%
- RenderThread ≈ 20.3%
## 运行时行为
稳定 fast path:
- fastHit 持续增长;
- particleFull 固定;
- retained build 固定;
- retained reuse 持续增长;
- staticRevision == buildRevision;
- fastReason=none。
Cache OFF:
- fastReason=retained-disabled;
- fastHit 停止;
- particleFull 持续增长;
- CPU 回到 full-path 基线。
Camera:
- 变化时 full;
- revision/build 更新;
- 稳定后 fastHit 自动恢复。
TOP:
- static-transition/static-placement;
- fast path 不命中;
- 保守 full path。
Viewport/orientation:
- resize/context变化时 full/rebuild;
- 稳定后自动恢复 fast path;
- 设备旋转设置最终恢复原值。
## 测试与构建
- OpenGL:PASS。
- Directed tests:21/21 PASS。
- Android APK:BUILD SUCCESSFUL。
- Android 安装/实机:PASS。
- Metal/iOS mbgl-core:BUILD SUCCEEDED。
- git diff --check:PASS。
- 异常日志扫描:PASS。
## Git
将在 scoped commit/push 后补充最终 commit hash。