From ea5b8f9d7fc53204a6d7fa088e9cf89be765b5ac Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Fri, 28 Aug 2026 16:38:16 +0800 Subject: [PATCH 001/363] chore: freeze MapLibreParticleLab step 0 baseline --- AGENTS.md | 11 ++ PARTICLE_BASELINE.md | 17 +++ docs/00_项目规范/00_文档目录与命名规范_v1.md | 3 + docs/00_项目规范/01_Codex协作规范_v1.md | 3 + docs/00_项目规范/02_构建与验证规范_v1.md | 3 + docs/00_项目规范/03_Step阶段记录规范_v1.md | 3 + docs/00_项目规范/04_Git与上游同步规范_v1.md | 3 + docs/00_项目规范/05_粒子架构原则_v1.md | 3 + docs/00_项目规范/06_官方基线来源_v1.md | 18 +++ docs/10_架构/MapLibre粒子总体架构_v1.md | 3 + docs/20_调查/README.md | 3 + docs/30_设计/README.md | 3 + docs/40_任务/README.md | 3 + docs/50_Step记录/README.md | 3 + .../50_Step记录/STEP-000_完整环境初始化_v1.md | 27 ++++ docs/60_测试/固定测试场景_v1.md | 9 ++ docs/70_发布/README.md | 3 + docs/报告/README.md | 3 + docs/报告/STEP-000_环境初始化_完成报告_v1.md | 118 ++++++++++++++++++ particle-fixtures/README.md | 3 + particle-fixtures/coastal-mask/.gitkeep | 0 particle-fixtures/shear/.gitkeep | 0 particle-fixtures/uniform-east/.gitkeep | 0 particle-fixtures/uniform-north/.gitkeep | 0 particle-fixtures/vortex/.gitkeep | 0 工具/仓库自检.sh | 9 ++ 工具/同步上游.sh | 3 + 工具/完整构建验证.sh | 62 +++++++++ 工具/生成Codex任务.sh | 28 +++++ 工具/生成新Step.sh | 33 +++++ 工具/首次推送Gitea.sh | 3 + 粒子开发记录.md | 18 +++ 32 files changed, 398 insertions(+) create mode 100644 AGENTS.md create mode 100644 PARTICLE_BASELINE.md create mode 100644 docs/00_项目规范/00_文档目录与命名规范_v1.md create mode 100644 docs/00_项目规范/01_Codex协作规范_v1.md create mode 100644 docs/00_项目规范/02_构建与验证规范_v1.md create mode 100644 docs/00_项目规范/03_Step阶段记录规范_v1.md create mode 100644 docs/00_项目规范/04_Git与上游同步规范_v1.md create mode 100644 docs/00_项目规范/05_粒子架构原则_v1.md create mode 100644 docs/00_项目规范/06_官方基线来源_v1.md create mode 100644 docs/10_架构/MapLibre粒子总体架构_v1.md create mode 100644 docs/20_调查/README.md create mode 100644 docs/30_设计/README.md create mode 100644 docs/40_任务/README.md create mode 100644 docs/50_Step记录/README.md create mode 100644 docs/50_Step记录/STEP-000_完整环境初始化_v1.md create mode 100644 docs/60_测试/固定测试场景_v1.md create mode 100644 docs/70_发布/README.md create mode 100644 docs/报告/README.md create mode 100644 docs/报告/STEP-000_环境初始化_完成报告_v1.md create mode 100644 particle-fixtures/README.md create mode 100644 particle-fixtures/coastal-mask/.gitkeep create mode 100644 particle-fixtures/shear/.gitkeep create mode 100644 particle-fixtures/uniform-east/.gitkeep create mode 100644 particle-fixtures/uniform-north/.gitkeep create mode 100644 particle-fixtures/vortex/.gitkeep create mode 100755 工具/仓库自检.sh create mode 100755 工具/同步上游.sh create mode 100755 工具/完整构建验证.sh create mode 100755 工具/生成Codex任务.sh create mode 100755 工具/生成新Step.sh create mode 100755 工具/首次推送Gitea.sh create mode 100644 粒子开发记录.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..e25036d04 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,11 @@ +# MapLibreParticleLab — Codex 总规则 + +任何修改前必须阅读 `PARTICLE_BASELINE.md`、`docs/00_项目规范/`(包括官方基线来源)、当前 Step 和当前任务。 + +项目目标:在 MapLibre Native Core 中 clean-room 独立实现 Raster/Vector Field Particle,支持 Android OpenGL/Vulkan 与后续 iOS Metal。 + +固定边界:正式实现不使用 React Native/WebView/外部 Overlay;粒子模块高度隔离;官方原文件只做薄接入;Android/iOS 共用 C++ Core;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 且共用 Land/Ocean Mask;第一阶段只用离线 fixture;不得复制 Mapbox 新源码或 Windy APK 内代码。 + +每一阶段必须有 `docs/50_Step记录/STEP-xxx_*.md`。完成报告固定写入 `docs/报告/STEP-xxx_<主题>_Codex完成报告_vN.md`。 + +修改结束必须按构建规范验证、更新 Step、追加 `粒子开发记录.md`、生成中文完成报告,并明确所有未完成/阻塞。 diff --git a/PARTICLE_BASELINE.md b/PARTICLE_BASELINE.md new file mode 100644 index 000000000..856f4eab0 --- /dev/null +++ b/PARTICLE_BASELINE.md @@ -0,0 +1,17 @@ +# MapLibreParticleLab 基线记录 + +- 初始化日期:2026-08-27 +- upstream:https://github.com/maplibre/maplibre-native.git +- origin:https://git.eyaku.jp/tei/MapLibreParticleLab +- Android:android-v13.4.1 +- iOS:ios-v6.28.0 +- 共同 core:18545ca10d296704b2cba675b182ff2c8da24588 +- 分支:particle/main +- Ubuntu:Ubuntu 24.04.4 LTS +- 架构:x86_64 +- Java:17 +- Android API:34 +- NDK:28.2.13676358 +- 默认并行度:3 +- upstream push:DISABLED +- git rerere:true diff --git a/docs/00_项目规范/00_文档目录与命名规范_v1.md b/docs/00_项目规范/00_文档目录与命名规范_v1.md new file mode 100644 index 000000000..fa92de886 --- /dev/null +++ b/docs/00_项目规范/00_文档目录与命名规范_v1.md @@ -0,0 +1,3 @@ +# 文档目录与命名规范 v1 + +正式目录:`00_项目规范/10_架构/20_调查/30_设计/40_任务/50_Step记录/60_测试/70_发布/报告`。正式说明文档文件名和正文使用中文;代码/API/命令保留英文。同主题只用 `_v1/_v2/_v3` 递增,不得用“最新版/修正版/final”制造近义文件。调查=`<主题>调查_vN.md`,设计=`<主题>设计_vN.md`,任务=`STEP-xxx_<主题>_Codex任务_vN.md`,Step=`STEP-xxx_<主题>_vN.md`,完成报告=`STEP-xxx_<主题>_Codex完成报告_vN.md`。完成报告只放 `docs/报告/`。 diff --git a/docs/00_项目规范/01_Codex协作规范_v1.md b/docs/00_项目规范/01_Codex协作规范_v1.md new file mode 100644 index 000000000..e8c963352 --- /dev/null +++ b/docs/00_项目规范/01_Codex协作规范_v1.md @@ -0,0 +1,3 @@ +# Codex 协作规范 v1 + +标准流程:只读调查 → 设计 → Step → 实现 → 构建/测试 → 完成报告 → Step 更新。没有调查结论不得凭猜测大改 Core。每个任务明确目标、必做项、允许/禁止范围、构建测试、必做输出、停止条件。无法完成必须明确阻塞,不伪造 PASS。默认禁止 force push、push upstream、改写官方 tag、删除历史 Step/报告、无授权扩大重构。 diff --git a/docs/00_项目规范/02_构建与验证规范_v1.md b/docs/00_项目规范/02_构建与验证规范_v1.md new file mode 100644 index 000000000..33074b46d --- /dev/null +++ b/docs/00_项目规范/02_构建与验证规范_v1.md @@ -0,0 +1,3 @@ +# 构建与验证规范 v1 + +主环境 Ubuntu 24.04 x86_64,默认并行度 3。Linux 优先官方 CMake/Make 路线,至少保证 OpenGL build、mbgl-render、mbgl-render-test-runner。Android 官方路线:Java17、.nvmrc Node、npm install --ignore-scripts;`cd platform/android && RENDERER=opengl BUILDTYPE=Debug make apackage` 与 `RENDERER=vulkan BUILDTYPE=Debug make apackage`。涉及 renderer/backend 必须双 backend 验证。Linux 不得宣称 iOS/Metal PASS,必须 Mac 同 commit 验证。基础回归只用离线 fixture。GPU 阶段记录粒子数、FPS/frame time、backend 与 pan/zoom/rotate/pitch。 diff --git a/docs/00_项目规范/03_Step阶段记录规范_v1.md b/docs/00_项目规范/03_Step阶段记录规范_v1.md new file mode 100644 index 000000000..fb8fcaf95 --- /dev/null +++ b/docs/00_项目规范/03_Step阶段记录规范_v1.md @@ -0,0 +1,3 @@ +# Step 阶段记录规范 v1 + +Step 连续编号 STEP-000/001/002,不跳号。状态只用:计划/进行中/阻塞/完成/冻结。每个 Step 至少有:状态、目标、基线、必做项勾选、允许修改、禁止修改、构建测试、必做输出、当前结果、阻塞、下一步。执行结束必须更新 Step、追加粒子开发记录、生成 docs/报告 完成报告;未完成项不得勾选。 diff --git a/docs/00_项目规范/04_Git与上游同步规范_v1.md b/docs/00_项目规范/04_Git与上游同步规范_v1.md new file mode 100644 index 000000000..bcf4a0fe3 --- /dev/null +++ b/docs/00_项目规范/04_Git与上游同步规范_v1.md @@ -0,0 +1,3 @@ +# Git 与上游同步规范 v1 + +upstream=MapLibre GitHub,仅 fetch,push=DISABLED;origin=用户 Gitea。日常分支 particle/main。提交小而单一。上游升级使用 upgrade/ 基于官方新 release 重放 Particle commits,完整回归后更新 PARTICLE_BASELINE.md;不得直接把 upstream/main 混进 particle/main。必须启用 git rerere。 diff --git a/docs/00_项目规范/05_粒子架构原则_v1.md b/docs/00_项目规范/05_粒子架构原则_v1.md new file mode 100644 index 000000000..5ef28b5b2 --- /dev/null +++ b/docs/00_项目规范/05_粒子架构原则_v1.md @@ -0,0 +1,3 @@ +# 粒子架构原则 v1 + +目标链:RasterArraySource → RasterArrayTile/Velocity Field → WeatherColorLayer + RasterParticleLayer → RenderRasterParticleLayer → Particle State A/B + GPU Update/Draw/Trail → MapLibre gfx/backend → OpenGL/Vulkan/Metal。WeatherColor 表达定量颜色,Particle 表达方向/运动,默认不得靠降低 WeatherColor opacity 才看导航信息。Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 共用 Land/Ocean Mask。Particle State 格式/坐标系/生命周期/跨 tile 必须单独设计后冻结。 diff --git a/docs/00_项目规范/06_官方基线来源_v1.md b/docs/00_项目规范/06_官方基线来源_v1.md new file mode 100644 index 000000000..e1b5b9c80 --- /dev/null +++ b/docs/00_项目规范/06_官方基线来源_v1.md @@ -0,0 +1,18 @@ +# 官方基线来源 v1 + +本项目初始化版本依据 MapLibre 官方资料固定: + +- MapLibre Native releases:`https://github.com/maplibre/maplibre-native/releases` +- Linux 开发文档:`https://maplibre.org/maplibre-native/docs/book/platforms/linux/` +- Render Tests:`https://maplibre.org/maplibre-native/docs/book/render-tests.html` +- Android release CI:MapLibre Native `.github/workflows/android-release.yml` +- Android `Versions.kt`:当前 core `18545ca` 的 `ndkVersion = 28.2.13676358`,`cmakeVersion = 3.24.0+`。 + +当前冻结: + +- Android `android-v13.4.1` → core `18545ca` +- iOS `ios-v6.28.0` → core `18545ca` +- Android compileSdk = 34 +- Java = 17 + +初始化脚本仍会运行时验证 Android/iOS tag 是否继续指向同一 core,避免只依赖本文档。 diff --git a/docs/10_架构/MapLibre粒子总体架构_v1.md b/docs/10_架构/MapLibre粒子总体架构_v1.md new file mode 100644 index 000000000..c840844a0 --- /dev/null +++ b/docs/10_架构/MapLibre粒子总体架构_v1.md @@ -0,0 +1,3 @@ +# MapLibre 粒子总体架构 v1 + +Layer:Base Map → WeatherColorLayer → RasterParticleLayer → Navigation Reference → AIS/Route/Ownship/Alarm。GPU 核心候选:Velocity Texture、Particle State A/B、Update、Draw、Trail Render Target、Color Ramp。 diff --git a/docs/20_调查/README.md b/docs/20_调查/README.md new file mode 100644 index 000000000..c72133999 --- /dev/null +++ b/docs/20_调查/README.md @@ -0,0 +1,3 @@ +# 调查目录 + +第一份建议 `MapLibre粒子接入点调查_v1.md`,只读调查 Style/Source/Tile/RenderLayer/gfx/ShaderRegistry/OpenGL/Vulkan/Metal。 diff --git a/docs/30_设计/README.md b/docs/30_设计/README.md new file mode 100644 index 000000000..d1598172d --- /dev/null +++ b/docs/30_设计/README.md @@ -0,0 +1,3 @@ +# 设计目录 + +调查后再写设计:ParticleState、VelocityField、Trail、RasterParticleLayer接口、LandOceanMask。 diff --git a/docs/40_任务/README.md b/docs/40_任务/README.md new file mode 100644 index 000000000..f2615d364 --- /dev/null +++ b/docs/40_任务/README.md @@ -0,0 +1,3 @@ +# Codex 任务目录 + +文件名 `STEP-xxx_<主题>_Codex任务_vN.md`;完成报告固定 `docs/报告/`。 diff --git a/docs/50_Step记录/README.md b/docs/50_Step记录/README.md new file mode 100644 index 000000000..c262d4afa --- /dev/null +++ b/docs/50_Step记录/README.md @@ -0,0 +1,3 @@ +# Step 记录目录 + +保存阶段权威状态,初始化脚本生成 STEP-000。 diff --git a/docs/50_Step记录/STEP-000_完整环境初始化_v1.md b/docs/50_Step记录/STEP-000_完整环境初始化_v1.md new file mode 100644 index 000000000..0bb3d88b7 --- /dev/null +++ b/docs/50_Step记录/STEP-000_完整环境初始化_v1.md @@ -0,0 +1,27 @@ +# STEP-000 完整环境初始化 v1 + +## 状态 +进行中 + +## 目标 +建立 MapLibreParticleLab 完整 Linux/Android 开发环境与 Codex 中文工程规范。 + +## 必做项 +- [x] MapLibre 正式 release 基线 +- [x] 全 submodule +- [x] upstream / origin +- [x] particle/main +- [x] 中文文档目录和命名规范 +- [x] Codex 协作规范 +- [x] 构建规范 +- [x] Step 规范 +- [x] Node/npm 环境 +- [x] Android SDK/NDK +- [x] Linux Native 构建 +- [x] Android OpenGL 构建 +- [x] Android Vulkan 构建 +- [x] 初始化完成报告 + +## 必做输出 +- PARTICLE_BASELINE.md +- 环境初始化报告.txt diff --git a/docs/60_测试/固定测试场景_v1.md b/docs/60_测试/固定测试场景_v1.md new file mode 100644 index 000000000..2fd8d0bc5 --- /dev/null +++ b/docs/60_测试/固定测试场景_v1.md @@ -0,0 +1,9 @@ +# 固定测试场景 v1 + +- uniform-east:恒定向东 +- uniform-north:恒定向北 +- vortex:旋涡 +- shear:剪切 +- coastal-mask:Coverage/Mask + +全部离线固定可重复。 diff --git a/docs/70_发布/README.md b/docs/70_发布/README.md new file mode 100644 index 000000000..6bd6117af --- /dev/null +++ b/docs/70_发布/README.md @@ -0,0 +1,3 @@ +# 发布目录 + +保存 Particle release、MapLibre upstream 升级、冻结 commit 和双平台回归结果。 diff --git a/docs/报告/README.md b/docs/报告/README.md new file mode 100644 index 000000000..a22e8c955 --- /dev/null +++ b/docs/报告/README.md @@ -0,0 +1,3 @@ +# Codex 完成报告目录 + +统一 `STEP-xxx_<主题>_Codex完成报告_vN.md`,不得覆盖旧版本。 diff --git a/docs/报告/STEP-000_环境初始化_完成报告_v1.md b/docs/报告/STEP-000_环境初始化_完成报告_v1.md new file mode 100644 index 000000000..8eb3a27cc --- /dev/null +++ b/docs/报告/STEP-000_环境初始化_完成报告_v1.md @@ -0,0 +1,118 @@ +# STEP-000 环境初始化完成报告 v1 + +## 结论 + +MapLibreParticleLab Linux / Android 主开发环境已完成建立,可以进入粒子系统开发阶段。 + +## 主开发环境 + +- Ubuntu 24.04 LTS +- x86_64 +- 4 CPU 线程 +- 约 8 GB RAM +- 默认编译并行度:3 + +## Git + +- 正式开发分支:`particle/main` +- `upstream`:MapLibre 官方 GitHub +- `origin`:用户 Gitea +- `upstream` 只用于读取官方更新 +- `git rerere` 用于长期 Fork 冲突复用 + +## MapLibre 基线 + +- Android:`android-v13.4.1` +- iOS:`ios-v6.28.0` +- Android/iOS 使用共同 MapLibre Native core 基线 +- 实际 commit 以根目录 `PARTICLE_BASELINE.md` 为准 + +## Linux Native 验证 + +已通过: + +- CMake `linux-opengl` configure +- Linux OpenGL backend +- `mbgl-render` +- `mbgl-render-test-runner` + +## Android OpenGL + +已成功生成: + +`platform/android/MapLibreAndroid/build/outputs/aar/MapLibreAndroid-opengl-debug.aar` + +包含: + +- `arm64-v8a/libmaplibre.so` +- `armeabi-v7a/libmaplibre.so` +- `x86/libmaplibre.so` +- `x86_64/libmaplibre.so` + +结论:PASS。 + +## Android Vulkan + +已成功生成: + +`platform/android/MapLibreAndroid/build/outputs/aar/MapLibreAndroid-vulkan-debug.aar` + +包含: + +- arm64-v8a +- armeabi-v7a +- x86 +- x86_64 + +四种 ABI 的 `libmaplibre.so`。 + +Debug AAR 同时包含对应的 Khronos Vulkan Validation Layer。 + +结论:PASS。 + +## Android SDK + +已确认: + +- `ANDROID_HOME=/root/Android/Sdk` +- Android Platform Tools 可用 +- NDK `28.2.13676358` 可用 +- `platform/android/local.properties` 已配置 +- `render-test/android/local.properties` 已配置 + +## 已修正的问题 + +### Linux preset 构建目录 + +实际 CMake preset 输出: + +`build-linux-opengl` + +构建工具已统一使用该目录,不再错误引用 `build`。 + +### Android SDK 定位 + +Gradle 工程同时使用 `ANDROID_HOME` 和本机 `local.properties`,避免新 shell 或 Codex 环境找不到 SDK。 + +## Codex 开发规范 + +已建立: + +- `AGENTS.md` +- 中文构建规范 +- 中文 Codex 协作规范 +- Step 阶段记录规范 +- 文档目录与命名规范 +- Git/upstream 同步规范 +- 粒子总体架构原则 +- `docs/报告/` 固定完成报告目录 + +## STEP-000 状态 + +**完成并冻结。** + +下一阶段: + +**STEP-001:MapLibre Native 粒子接入点只读调查。** + +STEP-001 暂不实现正式 RasterParticleLayer,先调查 Style / Source / Tile / RenderLayer / gfx / Shader / OpenGL / Vulkan / Metal 的最小接入点。 diff --git a/particle-fixtures/README.md b/particle-fixtures/README.md new file mode 100644 index 000000000..758f55d2c --- /dev/null +++ b/particle-fixtures/README.md @@ -0,0 +1,3 @@ +# Particle 固定离线 Fixtures + +第一阶段禁止依赖真实天气服务器。具体数据格式在设计冻结后确定。 diff --git a/particle-fixtures/coastal-mask/.gitkeep b/particle-fixtures/coastal-mask/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/particle-fixtures/shear/.gitkeep b/particle-fixtures/shear/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/particle-fixtures/uniform-east/.gitkeep b/particle-fixtures/uniform-east/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/particle-fixtures/uniform-north/.gitkeep b/particle-fixtures/uniform-north/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/particle-fixtures/vortex/.gitkeep b/particle-fixtures/vortex/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/工具/仓库自检.sh b/工具/仓库自检.sh new file mode 100755 index 000000000..8914dc3c7 --- /dev/null +++ b/工具/仓库自检.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +cd "$(git rev-parse --show-toplevel)" +echo "分支:$(git branch --show-current)"; echo "HEAD:$(git rev-parse HEAD)"; git remote -v +[[ "$(git remote get-url --push upstream || true)" == DISABLED ]] && echo '[OK] upstream push disabled' || echo '[警告] upstream push' +[[ "$(git config --get rerere.enabled || true)" == true ]] && echo '[OK] rerere=true' || echo '[警告] rerere=false' +git submodule status --recursive +for p in AGENTS.md PARTICLE_BASELINE.md docs/00_项目规范/00_文档目录与命名规范_v1.md docs/报告; do [[ -e "$p" ]] && echo "[OK] $p" || echo "[缺少] $p"; done +git status --short diff --git a/工具/同步上游.sh b/工具/同步上游.sh new file mode 100755 index 000000000..de9d777c5 --- /dev/null +++ b/工具/同步上游.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +cd "$(git rev-parse --show-toplevel)"; git fetch upstream --tags --prune; git tag --sort=-creatordate | head -n20 diff --git a/工具/完整构建验证.sh b/工具/完整构建验证.sh new file mode 100755 index 000000000..dbeeb4750 --- /dev/null +++ b/工具/完整构建验证.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +JOBS=3 +while [[ $# -gt 0 ]]; do + case "$1" in + --jobs) JOBS="$2"; shift 2 ;; + *) echo "未知参数:$1"; exit 2 ;; + esac +done + +ROOT="$(git rev-parse --show-toplevel)" +cd "$ROOT" + +mkdir -p build-particle +REPORT="build-particle/构建验证报告.txt" +: > "$REPORT" +exec > >(tee -a "$REPORT") 2>&1 + +# 环境文件内部会安全处理 nvm + nounset。 +if [[ -f "$HOME/.maplibre-particle-env" ]]; then + # shellcheck disable=SC1090 + source "$HOME/.maplibre-particle-env" +fi + +export CMAKE_BUILD_PARALLEL_LEVEL="$JOBS" +export CTEST_PARALLEL_LEVEL="$JOBS" + +echo "=== MapLibreParticleLab 完整构建验证 ===" +echo "HEAD:$(git rev-parse HEAD)" +echo "并行度:$JOBS" +echo "Node:$(node -v)" +echo "npm:$(npm -v)" +echo "Java:$(java -version 2>&1 | head -n1)" +echo + +echo "[1/3] Linux OpenGL" +cmake --preset linux-opengl +cmake --build build-linux-opengl --target mbgl-render mbgl-render-test-runner -j "$JOBS" + +echo +echo "[2/3] Android OpenGL Debug" +( + cd "$ROOT/platform/android" + RENDERER=opengl BUILDTYPE=Debug JOBS="$JOBS" make apackage +) + +echo +echo "[3/3] Android Vulkan Debug" +( + cd "$ROOT/platform/android" + RENDERER=vulkan BUILDTYPE=Debug JOBS="$JOBS" make apackage +) + +cd "$ROOT" + +sed -i \ + 's/- \[ \] Linux Native 构建/- [x] Linux Native 构建/; s/- \[ \] Android OpenGL 构建/- [x] Android OpenGL 构建/; s/- \[ \] Android Vulkan 构建/- [x] Android Vulkan 构建/' \ + docs/50_Step记录/STEP-000_完整环境初始化_v1.md 2>/dev/null || true + +echo +echo "全部构建验证完成。" diff --git a/工具/生成Codex任务.sh b/工具/生成Codex任务.sh new file mode 100755 index 000000000..446525ca8 --- /dev/null +++ b/工具/生成Codex任务.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +NUM="${1:-}"; TOPIC="${2:-}"; [[ -n "$NUM" && -n "$TOPIC" ]] || { echo "用法:$0 001 粒子接入调查"; exit 2; }; cd "$(git rev-parse --show-toplevel)"; F="docs/40_任务/STEP-${NUM}_${TOPIC}_Codex任务_v1.md"; [[ ! -e "$F" ]] || exit 1 +cat > "$F" < "$F" < Date: Sat, 29 Aug 2026 11:13:49 +0800 Subject: [PATCH 002/363] docs: freeze particle project governance v1 --- docs/00_项目总则/README.md | 32 +++++++++++ .../Step指令书与执行报告规范_v1.md | 55 ++++++++++++++++++ docs/00_项目总则/开发环境与系统构建规范_v1.md | 57 +++++++++++++++++++ docs/00_项目总则/当前基线与开发路线_v1.md | 39 +++++++++++++ .../粒子系统项目规则与设计决策_v1.md | 55 ++++++++++++++++++ 5 files changed, 238 insertions(+) create mode 100644 docs/00_项目总则/README.md create mode 100644 docs/00_项目总则/Step指令书与执行报告规范_v1.md create mode 100644 docs/00_项目总则/开发环境与系统构建规范_v1.md create mode 100644 docs/00_项目总则/当前基线与开发路线_v1.md create mode 100644 docs/00_项目总则/粒子系统项目规则与设计决策_v1.md diff --git a/docs/00_项目总则/README.md b/docs/00_项目总则/README.md new file mode 100644 index 000000000..63076bc5b --- /dev/null +++ b/docs/00_项目总则/README.md @@ -0,0 +1,32 @@ +# MapLibreParticleLab 项目总则目录 + +## 定位 +`docs/00_项目总则/` 是本项目长期规则层(Source of Truth)。保存经用户与 GPT 确认的长期设计决策、开发环境、系统构建规范、Codex 协作规则、Step/执行报告规范,以及当前正式基线与路线。普通 Step 默认禁止修改本目录。 + +## 当前有效文件 +1. `粒子系统项目规则与设计决策_v1.md` +2. `开发环境与系统构建规范_v1.md` +3. `Step指令书与执行报告规范_v1.md` +4. `当前基线与开发路线_v1.md` + +## 阅读顺序 +`AGENTS.md` → 本目录 README → 四份当前有效总则 → 当前 Step 指令书 → 上一 Step 执行报告 → 其他阶段文档。 + +## 维护职责 +- GPT:参考“粒子系统”GPT 项目讨论,提出总则更新。 +- 用户:确认或否决长期规则。 +- Codex:普通 Step 只读;只有明确授权的总则更新任务才可修改。 + +## 冲突处理 +若 Step 与总则冲突,Codex 停止冲突部分,在执行报告记录,不得自行改总则。 + +## 版本规则 +同主题仅递增 `_v1`、`_v2`、`_v3`;不使用“最终版/最新版/final/new/修正版”。 + +## 大阶段目录 +建议:`10_架构调查/`、`20_测试App/`、`30_粒子核心/`、`40_天气数据链/`、`50_渲染与视觉/`、`60_性能与后端/`、`70_NavSea集成/`、`80_iOS与Metal/`、`90_升级与维护/`。进入新大阶段前,GPT 先告诉用户目录名、目的、预计 Step 范围。 + +## 换窗口/换会话恢复 +1. 读本目录全部当前有效总则;2. 读当前基线;3. 读上一 Step 执行报告;4. 检查 Git branch/commit/working tree/submodule;5. 再继续下一 Step。 + +分层:GPT 项目讨论=设计过程;项目总则=冻结规则;Step 指令书=工作合同;执行报告=实际事实;Git=最终代码事实。 diff --git a/docs/00_项目总则/Step指令书与执行报告规范_v1.md b/docs/00_项目总则/Step指令书与执行报告规范_v1.md new file mode 100644 index 000000000..b30707ef8 --- /dev/null +++ b/docs/00_项目总则/Step指令书与执行报告规范_v1.md @@ -0,0 +1,55 @@ +# Step 指令书与执行报告规范 v1 + +## 1. 命名 +指令书:`step-xxxx-目的内容.md`。 +报告:`step-xxxx-目的内容-执行报告.md`。 +统一使用“执行报告”。 + +## 2. 同目录 +指令书与报告放同一个大阶段目的目录。 + +## 3. 指令书固定章节 +1. Step 编号和目的 +2. 当前已冻结基线 +3. 本 Step 目标 +4. 非目标/禁止事项 +5. 允许修改范围 +6. 禁止修改范围 +7. 实施要求 +8. 构建要求 +9. 测试要求 +10. 验收标准 +11. Git 要求 +12. 必做输出 +13. 执行报告完整文件名 +14. 执行报告固定内容 +15. 阻塞与停止条件 + +## 4. Codex 前置必读 +AGENTS.md → 本目录 README → 三份长期总则 → 当前基线与路线 → 本阶段已有文档 → 上一 Step 执行报告。 + +## 5. Jest 特殊规则 +GPT 生成任何正式 Step 指令书之前必须先询问用户是否加入 Jest。未经明确确认不得写入。若加入,先确认测试力度、允许修复范围、是否可改生产代码、最大修复轮数、停止条件。 + +## 6. 执行报告固定结构 +1. 执行结论:PASS/PARTIAL/BLOCKED/FAIL +2. Git 基线:branch/start commit/end commit/working tree/submodule +3. 实际修改文件 +4. 新增文件 +5. 实际实现内容 +6. 与指令书差异(无也写“无”) +7. 构建记录:command/renderer/build type/JOBS/result/关键日志/耗时 +8. 测试记录 +9. 生成产物:文件名/路径/大小/SHA256/Renderer/Build Type/ABI +10. 已知问题 +11. 未完成事项 +12. 下一 Step 建议(只建议,不自行创建) + +## 7. 报告必须反映事实 +禁止未执行测试写 PASS、计划写成已完成、隐藏失败、用“应该正常”替代结果、把 Win11 人工验证写成 Linux Codex 已完成。若只完成 Linux APK build,必须明确“Win11 Android Simulator 人工验证待用户执行”。 + +## 8. 总则修改 +普通 Step 禁止修改 `docs/00_项目总则/`。如认为需调整,只在报告提出建议。 + +## 9. GPT 写新 Step 的依据 +必须参考:“粒子系统”GPT 项目既有设计、项目总则、当前基线、上一 Step 报告、当前 Git 事实、用户最新确认。 diff --git a/docs/00_项目总则/开发环境与系统构建规范_v1.md b/docs/00_项目总则/开发环境与系统构建规范_v1.md new file mode 100644 index 000000000..24a65c873 --- /dev/null +++ b/docs/00_项目总则/开发环境与系统构建规范_v1.md @@ -0,0 +1,57 @@ +# 开发环境与系统构建规范 v1 + +## 1. 正式基线 +源码:`/mnt/sda1/MapLibreParticleLab`;稳定分支:`particle/main`。Linux:Ubuntu 24.04 LTS、x86_64、4 CPU threads、约 8GB RAM、默认 `JOBS=3`。Win11:NavSea、Android 模拟器、Particle Test APK 运行、NavSea Android 手工编译。 + +## 2. Step 开始前 +记录:`git branch --show-current`、`git rev-parse HEAD`、`git status --short`、`git submodule status`。 + +## 3. Git 远端 +origin=`ssh://git@192.168.200.230:2222/tei/MapLibreParticleLab.git`;upstream=`https://github.com/maplibre/maplibre-native.git`,只读,push 禁用;保留 `git rerere`。 + +## 4. Step branch +建议 `step/0001-...`。流程:particle/main → Step branch → Codex → build/test/report → GPT/用户审查 → PASS → merge particle/main → push → freeze。 + +## 5. 构建磁盘 +大型 build 必须使用 Linux 本地 SSD;禁止 NAS/SMB/NFS 作为 build root。NAS 只做备份、APK/AAR 中转、归档。 + +## 6. 环境入口 +构建前 `source /root/.maplibre-particle-env`。Codex 不得自行升级 Node/Java/SDK/NDK/Gradle/AGP/CMake/MapLibre baseline,除非 Step 明确要求。 + +## 7. 已验证环境 +Linux Native OpenGL PASS;mbgl-render PASS;mbgl-render-test-runner PASS;Android OpenGL Debug AAR PASS;Android Vulkan Debug AAR PASS;Android OpenGL Release AAR PASS。SDK=`/root/Android/Sdk`;NDK=`28.2.13676358`。 + +## 8. Development Build +默认 Debug + OpenGL + 增量 + Particle Test App。小改不强制 clean/Release/Vulkan/NavSea/全量回归。 + +## 9. Gate Build +关键 Step 才做 Release(按需)、明确 Git commit、完整 build、APK/AAR、Renderer、ABI、大小、SHA256、执行报告、必要时 Vulkan/NavSea。 + +## 10. OpenGL/Vulkan +OpenGL 是主开发 backend;Vulkan 在专门 Step 做正式回归。核心必须遵循 MapLibre gfx 抽象。 + +## 11. Test App 日常链 +MapLibre Native → Particle Test APK → 复制到 Win11 → Android Simulator。NavSea 不参与日常粒子调试。 + +## 12. 产物 +建议统一 `dist/android/`。示例:`particle-test-step-0005-opengl-debug.apk`、`particle-test-step-0009-opengl-release.apk`、`particle-test-step-0014-vulkan-debug.apk`、`maplibre-particle-step-0016-opengl-release.aar`。Gate 产物记录 SHA256。 + +## 13. Linux/Win11 边界 +Linux Codex 默认责任到 APK/AAR build PASS、产物存在、大小、SHA256、执行报告。用户负责复制到 Win11、安装模拟器、人工视觉/交互验证。 + +## 14. NavSea Android +继续由用户在 Win11 手工编译;Linux Codex 不代替用户编译 NavSea。 + +## 15. Gate A/B/C +Gate A:maplibre-react-native 11.3.6 + 官方 Native 13.2.0,PASS。 +Gate B:11.3.6 + 官方 Native 13.4.1,PASS。 +Gate C:11.3.6 + Linux 自编译 Native 13.4.1 OpenGL Release,PASS。 + +## 16. 当前 Release AAR +`MapLibreAndroid-opengl-release.aar`,约 156MB,SHA256=`5c67d2751fafe211a0df4b82deded09ab1c07cf6ca0f4a12a880283287f85e5c`,ABI:arm64-v8a/armeabi-v7a/x86/x86_64。 + +## 17. 构建失败 +只修当前 Step 允许范围;若必须升级工具链/MapLibre、大改 gfx、修改 NavSea/冻结接口、删除测试或降低门槛,STOP 并报告 BLOCKED。 + +## 18. 禁止项 +普通 Step 禁止自行升级 MapLibre/AGP/Gradle/NDK/Java/Node,禁止修改 NavSea/冻结接口、删除测试、降低门槛、用 CPU 粒子冒充正式 GPU 实现、修改 `docs/00_项目总则/`。 diff --git a/docs/00_项目总则/当前基线与开发路线_v1.md b/docs/00_项目总则/当前基线与开发路线_v1.md new file mode 100644 index 000000000..fcec091e5 --- /dev/null +++ b/docs/00_项目总则/当前基线与开发路线_v1.md @@ -0,0 +1,39 @@ +# 当前基线与开发路线 v1 + +## 1. 冻结日期 +2026-08-29 + +## 2. Git +正式分支:`particle/main`。STEP-000 冻结提交:`ea5b8f9d7fc53204a6d7fa088e9cf89be765b5ac`,说明 `chore: freeze MapLibreParticleLab step 0 baseline`。origin=`ssh://git@192.168.200.230:2222/tei/MapLibreParticleLab.git`。upstream=`https://github.com/maplibre/maplibre-native.git`。 + +## 3. MapLibre Native +正式粒子开发 baseline:Android 13.4.1。OpenGL 主开发;Vulkan/Metal 后续。 + +## 4. Linux 构建状态 +Linux Native OpenGL PASS;mbgl-render PASS;mbgl-render-test-runner PASS;Android OpenGL Debug AAR PASS;Android Vulkan Debug AAR PASS;Android OpenGL Release AAR PASS。SDK=`/root/Android/Sdk`;NDK=`28.2.13676358`;JOBS=3。 + +## 5. Release AAR +`MapLibreAndroid-opengl-release.aar`,约 156MB,SHA256=`5c67d2751fafe211a0df4b82deded09ab1c07cf6ca0f4a12a880283287f85e5c`,ABI:arm64-v8a/armeabi-v7a/x86/x86_64。 + +## 6. NavSea +Win11;React Native 0.85.2;New Architecture=true;`@maplibre/maplibre-react-native@11.3.6`。 + +## 7. Gate +Gate A:11.3.6 + 官方 Native 13.2.0,PASS。 +Gate B:11.3.6 + 官方 Native 13.4.1,PASS。 +Gate C:11.3.6 + Linux 自编译 Native 13.4.1 OpenGL Release,PASS。 + +## 8. 固定分工 +Linux:MapLibre Native/Particle/Test App/APK/AAR/Fixture/Native Debug/Git。Win11:Android Simulator/Particle Test APK/NavSea/NavSea Android 编译/产品 Gate。NavSea 不作为日常 Particle 宿主。 + +## 9. 下一大阶段 +建议 `10_架构调查/`。正式建立前 GPT 先向用户说明该阶段目的与预计 Step 范围。 + +## 10. 下一 Step +`step-0001-MapLibre粒子接入点调查.md`。只读调查 Style Layer、Source/Tile/Bucket、RenderLayer/RenderPass、gfx、Shader、Texture/RenderTarget、OpenGL/Vulkan/Metal、Raster/Heatmap/Hillshade/CustomLayer 等;不实现正式粒子。 + +## 11. 近期路线 +STEP-0001 接入点调查;0002 Android Particle Test App;0003 Fixture;0004 RasterParticleLayer 骨架;0005 最小 GPU Particle;0006 Camera/Projection;0007 Particle State 冻结;0008 Velocity/RasterArray/Tile;0009 Trail;0010 生命周期与视觉;0011 WeatherColorLayer;0012 Land/Ocean Mask;0013 OpenGL 性能;0014 Vulkan;0015 Test App 第一阶段冻结;0016 NavSea Particle Gate;0017 NavSea API/Adapter;0018 Android 产品回归;0019 iOS/Metal;0020 NavSea iOS;0021 upstream/长期维护。 + +## 12. 换窗口恢复 +读项目总则 → 当前基线 → 上一 Step 报告 → 检查 Git → 继续下一 Step。 diff --git a/docs/00_项目总则/粒子系统项目规则与设计决策_v1.md b/docs/00_项目总则/粒子系统项目规则与设计决策_v1.md new file mode 100644 index 000000000..bcb620740 --- /dev/null +++ b/docs/00_项目总则/粒子系统项目规则与设计决策_v1.md @@ -0,0 +1,55 @@ +# 粒子系统项目规则与设计决策 v1 + +## DEC-001 Linux 与 Win11 职责分离 +Linux 是 MapLibre Native / Particle Renderer 的主要开发和构建环境,正式源码 `/mnt/sda1/MapLibreParticleLab`。Linux 负责 Native、Particle、Test App、Fixture、AAR/APK、OpenGL/Vulkan、Native 调试、性能与 Git。Win11 负责 NavSea、NavSea Android 编译、Android 模拟器、Particle Test APK 运行和产品 Gate。 + +## DEC-002 独立 Particle Test App +必须建立长期维护的独立测试 App。它不是 Demo,而是粒子 Renderer 的开发、调试、回归和性能平台。日常粒子开发不直接使用 NavSea。Android 第一阶段优先原生 Test App。 + +## DEC-003 NavSea 只用于关键产品 Gate +NavSea 是最终产品,不作为日常粒子调试宿主。只在自编译等价验证、关键 Particle Gate、真实 Layer Stack、Weather/Mask、产品级回归和最终集成时使用。 + +## DEC-004 OpenGL 为 Android 主开发 Backend +初期主开发 OpenGL;架构必须服从 MapLibre `gfx` 抽象,不能锁死 OpenGL。 + +## DEC-005 Vulkan / Metal 不是后补重写 +Particle State、Velocity、Tile、生命周期、Style/RenderLayer、Coverage、Color Ramp、Mask 尽量共享;后端差异主要放在 Shader、Texture、RenderTarget、RenderPass、gfx backend。iOS/Metal 以后单独设计测试流程。 + +## DEC-006 WeatherColorLayer 与 RasterParticleLayer 分离 +定量天气色场和粒子流动分为 `WeatherColorLayer` 与 `RasterParticleLayer`。 + +## DEC-007 天气色场不靠低透明度看底图 +天气定量颜色保持准确;岸线、等深线、航标、航道、文字和导航对象绘制在天气层上方。 + +## DEC-008 Coverage +Wind=`GLOBAL`;Wave/Tide/Current/Sea Temperature 等=`OCEAN_ONLY`。 + +## DEC-009 统一 Land/Ocean Mask +所有 OCEAN_ONLY 字段共用统一 Mask;海岸线语义尽量与 NavSea/MapLibre 海图一致,不依赖天气模型 nodata。 + +## DEC-010 正式目标是 GPU Particle Engine +核心至少包括 Velocity Texture、Particle State Texture A/B、Ping-Pong、Update Pass、Draw Pass、Trail Render Target、Color Ramp。CPU 每粒子循环不能作为正式主实现。 + +## DEC-011 Camera 正确后再冻结 Particle State Model +先验证 Pan/Zoom/Rotate/Pitch/Resize/Camera Animation,再冻结状态格式。 + +## DEC-012 Fixture 优先 +至少:uniform-east/north/west/south、vortex-clockwise/counterclockwise、shear、divergence、convergence、coastal-mask。真实天气服务器不是早期 Renderer 调试前置条件。 + +## DEC-013 thin-hook 原则 +目标约 95% 粒子代码放新增文件/独立模块;MapLibre 官方现有文件只做必要 Layer/Source/RenderLayer/Shader/Build/Backend 薄接入,降低 upstream merge 冲突。 + +## DEC-014 普通 Step 禁止修改项目总则 +需要改变长期规则时,只在执行报告提出建议。 + +## DEC-015 GPT“粒子系统”项目是设计背景来源 +GPT 写设计和指令书时要参考该项目既有讨论;正式权威规则以 Git 仓库本目录为准。 + +## DEC-016 大阶段目录按目的建立 +使用两位数字前缀;进入新阶段前 GPT 先告知用户目录目的和预计 Step 范围。 + +## DEC-017 Step 指令书与执行报告同目录 +`step-xxxx-目的内容.md` 与 `step-xxxx-目的内容-执行报告.md` 放同一大阶段目的目录。 + +## DEC-018 报告由指令书预先定义 +指令书必须事先写清报告完整路径、文件名、章节、Git 信息、构建信息、APK/AAR、大小、SHA256、PASS/PARTIAL/BLOCKED/FAIL。不得执行完后临时追加未定义报告。 From 74be2a5052428717d7c9e133c333da8bd783b958 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 29 Aug 2026 12:33:09 +0800 Subject: [PATCH 003/363] docs: freeze particle architecture investigation and design --- docs/10_架构/20_粒子系统总体架构设计_v1.md | 773 ++++++++++++++ docs/20_调查/10_架构调查报告_v1.md | 391 +++++++ docs/40_任务/10_架构调查指令书_v1.md | 754 ++++++++++++++ docs/40_任务/20_架构设计指令书_v1.md | 1092 ++++++++++++++++++++ docs/报告/10_架构调查_Codex完成报告_v1.md | 68 ++ docs/报告/20_架构设计_Codex完成报告_v1.md | 106 ++ 粒子开发记录.md | 18 + 7 files changed, 3202 insertions(+) create mode 100644 docs/10_架构/20_粒子系统总体架构设计_v1.md create mode 100644 docs/20_调查/10_架构调查报告_v1.md create mode 100644 docs/40_任务/10_架构调查指令书_v1.md create mode 100644 docs/40_任务/20_架构设计指令书_v1.md create mode 100644 docs/报告/10_架构调查_Codex完成报告_v1.md create mode 100644 docs/报告/20_架构设计_Codex完成报告_v1.md diff --git a/docs/10_架构/20_粒子系统总体架构设计_v1.md b/docs/10_架构/20_粒子系统总体架构设计_v1.md new file mode 100644 index 000000000..0b560ca7b --- /dev/null +++ b/docs/10_架构/20_粒子系统总体架构设计_v1.md @@ -0,0 +1,773 @@ +# MapLibreParticleLab 粒子系统总体架构设计 v1 + +设计日期:2026-08-29 + +基线分支:`particle/main` + +基线 Core:`18545ca10d296704b2cba675b182ff2c8da24588` + +性质:正式总体架构设计,不包含实现 + +## 1. 设计输入与适用范围 + +本设计以项目总则、项目规范、`PARTICLE_BASELINE.md`、`docs/20_调查/10_架构调查报告_v1.md` 和当前 MapLibre Native 源码为事实基础。当前仓库不是 React Native 工程,因此架构从 Native Core、Android/iOS 平台薄接入和 MapLibre gfx 开始,不定义不存在的 JS 每帧运行时。 + +现有 `docs/10_架构/MapLibre粒子总体架构_v1.md` 只给出了 Layer 顺序和 GPU 组件候选,与本设计没有冲突。本文件把这些候选收敛为正式模块、接口和生命周期;后续以本文件作为 `30_最小技术验证` 和实现任务的主要架构输入。 + +固定长期边界继续有效:clean-room 独立实现;正式实现不用 React Native/WebView/外部 Overlay;Android/iOS 共用 C++ Core;OpenGL 为 Android 主开发 backend;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=`GLOBAL`;Wave/Tide/Current=`OCEAN_ONLY` 并共用 Land/Ocean Mask;第一阶段只用离线 fixture。 + +## 2. 核心决策摘要 + +1. **唯一正式主路线**:新增独立 Core `RasterParticleLayer`、`RenderRasterParticleLayer` 和隔离的 `mbgl::particle` 模块,进入 Style/RenderTree,通过 MapLibre gfx 执行 GPU simulation、trail 和最终合成。 +2. `CustomLayerHost` 与 `CustomDrawableLayer` 不作为正式粒子引擎宿主;需要时只可用于一次性技术探针,不能进入公共 API、产品依赖或长期双轨。 +3. 新增 `RasterArraySource` 作为 Field 数据边界;WeatherColorLayer 与 RasterParticleLayer 可引用同一 Field snapshot 和 Mask,但拥有不同 RenderLayer、GPU 状态和生命周期。 +4. 每帧由 MapLibre render loop 驱动;活动粒子通过 Core render state 持续声明 `needsRepaint`,不建立第二套 UI/JS 动画循环。 +5. Camera/Matrix 只从当前 render frame 的 `PaintParameters/TransformParameters/TransformState` 读取。 +6. 第一版 GPU simulation 使用跨 backend 的 render-pass ping-pong,不以 compute 为前置;compute 仅作为后续 capability-gated 优化。 +7. 第一阶段正式视觉范围包含粒子、颜色映射和屏幕空间 trail;最小验证可先无 trail 验证状态更新,但在第一阶段冻结前必须通过 trail 验证。 +8. 第一阶段实现/日常验证范围是 Android OpenGL;Android Vulkan 在第一阶段冻结前做 gfx 兼容 gate;iOS OpenGL/Metal 保留相同 Core contract,在独立后续阶段验证。 +9. 下一阶段选择 **B:进入 `30_最小技术验证`**,不直接进入正式实现。 + +## 3. 候选路线比较与选择 + +| 候选 | 优点 | 不作为正式主路线的原因 | 定位 | +|---|---|---|---| +| `CustomLayerHost/RenderCustomLayer` | 已有同帧 transform 和 backend callback;示例可运行 | Android 使用 raw host 指针且 API 标记 experimental;平台 context/encoder 不对称;公共生命周期容易平台化 | 只允许一次性验证,不进入正式 API | +| `CustomDrawableLayer` | 已使用 gfx DrawableBuilder,跨 backend 程度较高 | 当前接口偏向线、面、符号、几何 drawable;不天然表达 velocity、state ping-pong、trail target 和 source contract | 作为 gfx/Drawable 设计参考 | +| 独立 Core particle layer | 可进入 Style、RenderTree、layer order、repaint、context lifecycle;可隔离数据与 GPU 资源;平台只需薄 wrapper | 需要新增 Layer/Source/RenderLayer、shader 注册和少量 RenderTarget 调度薄接入 | **正式唯一主路线** | +| MapView Overlay/独立 renderer | 开发表面上独立 | 与地图 frame、matrix、depth/layer order、context 和生命周期不同步;违反固定规则 | 拒绝 | + +正式路线的退出条件不是“以后再决定”:从第一个可保留实现开始,产品 API 和生产资源均必须归属独立 Core layer。若技术验证临时使用 CustomLayer,验证结束后仅保留测试结论;正式 `RasterParticleLayer` 可渲染第一张 fixture 且通过 lifecycle gate 时,任何 CustomLayer 粒子探针不得继续作为产品路径。 + +主路线风险是现有通用 RenderTarget/LayerGroup 是否足以表达多 offscreen pass、跨 backend 的资源状态转换,以及新增 Layer 对 `needsRepaint` 的最小接入是否能够保持 thin-hook。风险不通过平台分叉规避,而由第 26 节的独立 RenderLayer、State/Trail ping-pong 和 context recreation 验证决定;若验证失败,只允许调整粒子专用 gfx 抽象或必要薄 hook,不回退到 Overlay、JS loop 或两套平台引擎。 + +## 4. 粒子系统总体架构图 + +```text +Host Native API + ├─ RasterArraySource ───────────────┐ + ├─ WeatherColorLayer ───────┐ │ + └─ RasterParticleLayer ─────┼───────┘ + ▼ + Style immutable snapshot + ▼ + RenderTree / RenderOrchestrator / layer order + ┌────────────┴────────────┐ + ▼ ▼ + RenderWeatherColorLayer RenderRasterParticleLayer + │ + mbgl::particle Core + Field / Simulation / Mask / GPU / Controller + │ + gfx Texture/Buffer/RenderTarget/Drawable/Shader + │ + OpenGL / Vulkan / Metal backend → GPU +``` + +正式 Layer 顺序由宿主 Style 控制,推荐: + +```text +Base Map + → WeatherColorLayer + → RasterParticleLayer + → Navigation Reference / coast / labels + → AIS / Route / Ownship / Alarm +``` + +WeatherColor 保持定量色场;导航信息通过更高 Style Layer 显示,不以降低天气层透明度作为默认策略。 + +## 5. Core 模块边界与建议目录 + +```text +include/mbgl/style/sources/ + └─ raster_array_source.hpp 公共 Field Source API +include/mbgl/style/layers/ + ├─ weather_color_layer.hpp 定量色场 Layer API + └─ raster_particle_layer.hpp 粒子 Layer API + +src/mbgl/particle/ + ├─ field/ Field descriptor/snapshot/validation + ├─ data_adapter/ fixture 与未来数据适配 + ├─ simulation/ 状态语义、时间步、respawn + ├─ render/ pass 描述、frame parameters + ├─ gpu/ velocity/state/trail/mask resources + ├─ mask/ Land/Ocean Mask snapshot/cache/contract + └─ controller/ 状态机、revision、repaint、错误 + +src/mbgl/style/sources/ RasterArraySource Impl +src/mbgl/style/layers/ 两个 Layer Impl/properties +src/mbgl/renderer/sources/ RenderRasterArraySource +src/mbgl/renderer/layers/ + ├─ render_weather_color_layer.* + └─ render_raster_particle_layer.* +src/mbgl/layermanager/ 两个 LayerFactory 的薄注册 +shaders/particle/ update/draw/trail/composite shader contract +platform/android/ 类型安全 Java/Kotlin + JNI 薄 wrapper +platform/darwin/ ObjC/Swift Source/Layer peer 薄 wrapper +``` + +目标仍是约 95% 粒子代码位于新增文件。允许修改的官方既有文件只限 Layer/Source factory 注册、shader/build 清单、RenderTarget/repaint 所需的最小 hook;不得把算法写入 Map、RasterLayer、Camera、MapView 或 backend 主文件。 + +### 5.1 Particle Field + +`ParticleFieldDescriptor` 描述:field id/type、revision、语义单位、bounds、CRS、grid registration、尺寸、U/V、NoData、时间片、display domain 和数据版本。`ParticleFieldSnapshot` 是 descriptor 与只读数据的不可变组合。 + +Field 数据进入 Core 后统一为:U 为正东、V 为正北、canonical simulation unit 为米/秒。原始数据单位转换属于 Data Adapter,不进入每帧 shader 的业务分支。 + +### 5.2 Particle Data Adapter + +第一阶段 `OfflineFixtureParticleAdapter` 负责读取、校验、字节序转换并产生 immutable snapshot。未来真实天气协议必须通过新的 adapter 转成同一 snapshot,Renderer 不认识 URL、HTTP、GRIB、厂商字段名或 Server 协议。 + +### 5.3 Particle Simulation + +Core 定义状态语义、固定/受限 timestep、field sample、NoData、mask、respawn 和 deterministic seed。正式每粒子更新在 GPU;CPU 只负责 fixture 解码、验证、测试参考值和资源描述,不允许每帧 CPU 粒子循环冒充正式实现。 + +### 5.4 Particle Render + +负责 simulation pass、particle draw、trail fade/accumulation、最终 translucent composite、viewport 和当前 frame transform。它不拥有天气色场 RenderLayer。 + +### 5.5 Particle GPU Resources + +负责 velocity texture、state A/B、trail A/B、mask texture、uniform、shader set、context generation、创建/释放/重建。公共逻辑只使用 gfx 抽象;backend-specific 格式映射、barrier/encoder 细节留在 gfx/backend。 + +### 5.6 Particle Mask + +Mask 是可共享的 immutable snapshot 与 GPU resource,不属于某一个 RasterParticleLayer。Wave/Tide/Current 和未来 OCEAN_ONLY field 通过 mask id/revision 引用同一资源。 + +### 5.7 Particle Controller / Layer State + +Controller 只管理 enable、source/mask revision、map/data/GPU readiness、状态机、repaint 和错误;UI/宿主不得持有或销毁 GPU texture。 + +## 6. 与 Style、RenderTree 和 gfx 的关系 + +- `RasterArraySource` 进入 Style Source 系统,负责提供 immutable Field snapshot,不直接绘制。 +- `WeatherColorLayer` 和 `RasterParticleLayer` 分别引用 source id,进入 Style Layer、LayerFactory、RenderLayer 和 RenderTree。 +- `RenderRasterParticleLayer` 最终合成属于 `Translucent` pass,按 Style layer index 与其他地图内容排序。 +- simulation 与 trail 使用独立 offscreen render targets,在主 buffer 合成前完成;最终 composite drawable 仍在该 Layer 的 translucent 位置绘制。 +- Layer 活动且可见时参与 `needsRepaint`;首次 enable、数据/选项变化和恢复时调用一次现有 invalidate/`triggerRepaint` 路径唤醒渲染。 +- 每帧 transform 直接来自当前 `PaintParameters`,不缓存 UI 线程推测值。 + +### 6.1 复用的 gfx 能力 + +| 现有能力 | 用途 | +|---|---| +| `Texture2D` | velocity、state、mask、trail texture | +| `DynamicTexture` | 仅在技术验证证明适合局部 field 更新时使用;不是默认 state 资源 | +| Buffer/UniformBuffer | quad/point geometry、frame/field/options uniform | +| `RenderTarget` | simulation output、trail accumulation | +| `DrawableBuilder` | 最终 composite 与可由通用 drawable 表达的 draw geometry | +| `DrawableTweaker` | 同帧 transform/options uniform 更新 | +| Shader registry | 后端 shader 注册与查询 | +| UploadPass | field/mask/静态资源低频上传 | +| CommandEncoder | pass encoding/present,由 render thread 和 gfx 管理 | + +新增粒子专用抽象仅描述语义:`ParticleVelocityResource`、`ParticleStateResources`、`ParticleTrailResources`、`ParticleMaskResource`、`ParticleShaderSet`、`ParticleFrameParameters`。这些类型不能暴露 GL/Vulkan/Metal handle。 + +## 7. WeatherColorLayer 与 RasterParticleLayer + +| 边界 | WeatherColorLayer | RasterParticleLayer | +|---|---|---| +| 目的 | 定量连续色场 | 方向、运动、动态流线 | +| RenderLayer | 独立 `RenderWeatherColorLayer` | 独立 `RenderRasterParticleLayer` | +| GPU 状态 | color ramp/field sampling 资源 | state A/B、simulation、trail、particle draw | +| 动画 | 不承担粒子动画 | 由 MapLibre frame loop 驱动 | +| 可共享 | RasterArraySource snapshot、grid metadata、time slice、Mask snapshot/cache | 同左 | +| 不共享 | RenderLayer、粒子 state、trail target、动画状态 | 同左 | + +二者不是同一 Layer 的模式开关。删除或禁用粒子 Layer 不影响 WeatherColor;更换天气颜色样式不重置 Particle State,除非 Field revision、Mask 或影响 simulation 的参数同时改变。 + +## 8. Field 与显示域模型 + +`FieldType` 第一版至少包含 `WIND`、`WAVE`、`TIDE`、`CURRENT`。`DisplayDomain` 只有 `GLOBAL` 和 `OCEAN_ONLY`。 + +规则由 Core validation 强制执行: + +- WIND → `GLOBAL`,陆地与海洋均可显示,不依赖 Land/Ocean Mask。 +- WAVE/TIDE/CURRENT → `OCEAN_ONLY`,必须有有效 Mask;mask 缺失时 fail closed,不在陆地或未知区继续显示。 +- 未来新增 OCEAN_ONLY field 复用同一 Mask contract,不创建 field-specific mask engine。 + +Field snapshot 的 revision 单调变化。只改变 UI 可视属性不生成 Field revision;U/V、bounds、grid、time slice、NoData 或数据版本变化必须生成新 revision。 + +## 9. 第一阶段离线 fixture contract v1 + +### 9.1 目录与文件 + +每个 fixture 目录固定包含: + +```text +particle-fixtures// +├─ metadata.json +├─ field.f32 +└─ mask.u8 仅 OCEAN_ONLY/Mask 场景必需 +``` + +`metadata.json` 最小结构: + +```json +{ + "schema": "maplibre-particle-fixture/v1", + "fixtureId": "uniform-east", + "fieldType": "WIND", + "displayDomain": "GLOBAL", + "dataVersion": 1, + "timeIndex": 0, + "timestamp": null, + "crs": "EPSG:4326", + "bounds": { "west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0 }, + "grid": { + "width": 64, + "height": 32, + "registration": "node", + "origin": "north-west", + "rowOrder": "north-to-south", + "columnOrder": "west-to-east" + }, + "vector": { + "uPositive": "east", + "vPositive": "north", + "unit": "m/s", + "layout": "interleaved-uv", + "encoding": "float32-le", + "noData": -9999.0, + "file": "field.f32" + }, + "mask": { "mode": "none" }, + "integrity": { + "fieldByteLength": 16384, + "fieldSha256": "" + } +} +``` + +实际 width/height 和 checksum 由 fixture 生成任务填写,上例数值只展示 contract,不冻结具体场景分辨率。 + +### 9.2 Binary 规则 + +- `field.f32`:little-endian IEEE-754 float32,row-major,按 `u0,v0,u1,v1...` 交错;文件长度必须等于 `width × height × 2 × 4`。 +- 任一 cell 的 U 或 V 等于 `noData`,整个 cell 无效;不依赖 NaN payload 表达 NoData。 +- adapter 校验有限值、长度、checksum、bounds、grid 和 domain 后,转换为 canonical immutable Field。 +- `mask.u8`:row-major、与 metadata 指定 grid 对齐;`0=LAND`、`255=OCEAN`,`1..254` 为未来 coverage/shore antialias 保留,v1 validator 不接受未声明的中间值。 +- Mask 文件长度必须等于 mask width × height,并记录 byte length/SHA256。 +- v1 fixture 的 `bounds.west < bounds.east`,单文件不跨反经线;全球使用 `[-180,180]`。运行时 Field/Mask 模型仍支持以 unwrapped longitude interval 表达未来反经线数据。 +- `timeIndex` 必须存在;有真实时间语义时同时填写 UTC ISO-8601 `timestamp`,确定性 synthetic fixture 可令其为 `null`。 + +### 9.3 Mask metadata + +OCEAN_ONLY 场景把 `mask` 写为: + +```json +{ + "mode": "embedded", + "maskId": "coast-v1", + "dataVersion": 1, + "file": "mask.u8", + "encoding": "uint8-land-ocean", + "width": 64, + "height": 32, + "registration": "node", + "origin": "north-west", + "noDataPolicy": "land" +} +``` + +### 9.4 预留 fixture 覆盖 + +| fixture | 合同目标 | +|---|---| +| `uniform-east` | WIND/GLOBAL,常量 +U,验证东西向和平移/wrap | +| `uniform-north` | WIND/GLOBAL,常量 +V,验证纬向和 projection | +| `vortex` | WIND/GLOBAL 或明确区域 bounds,验证旋转方向和采样连续性 | +| `shear` | WIND/GLOBAL 或区域 bounds,验证速度梯度和 timestep | +| `coastal-mask` | CURRENT/OCEAN_ONLY,必须含 mask,验证海岸裁剪和 fail-closed | + +后续 fixture 可增加 west/south、双向 vortex、divergence/convergence,但不改变 v1 contract。 + +## 10. Offline Fixture 到 GPU 数据流 + +```text +Offline Fixture files + → Worker: read / checksum / decode / validate + → ParticleFieldSnapshot(shared immutable, revision N) + → Map/Style thread: RasterArraySource Impl snapshot + → RendererFrontend immutable UpdateParameters + → Render thread: compare revision + → UploadPass: velocity texture + optional mask texture + → ParticleState A/B initialization + → Simulation pass + → Particle draw + Trail A/B + → RasterParticleLayer translucent composite + → MapLibre main render pass → GPU +``` + +### 10.1 低频步骤 + +fixture I/O、checksum、decode、unit normalization、Field validation、CPU snapshot 创建、velocity/mask upload、shader/resource 创建。仅在 source revision、mask revision、context generation、viewport target size或影响资源布局的选项变化时执行。 + +### 10.2 每帧步骤 + +读取当前 frame transform;计算受限 timestep;state A→B 或 B→A simulation;particle draw;trail fade/accumulate;final composite;更新 rendering stats;在仍活动时声明下一帧 repaint。 + +### 10.3 Camera 变化时 + +更新 transform、pixel ratio、viewport、visible field intersection、world-wrap selection 等 uniform。v1 不重建 velocity texture,不重新解析 Field,不重建 Particle State;viewport resize 只重建/清空 trail target。任何 pan/zoom/bearing/pitch/resize 的有效变化清空旧 screen-space trail,避免错误残影;后续可验证 trail reprojection,但不属于 v1。 + +## 11. 线程模型 + +```text +Map/UI thread + ├─ 创建/删除 Source、Layer + ├─ 修改业务 options、enable、source/mask reference + └─ 发布 immutable Impl/snapshot revision + │ + ▼ +Worker thread + ├─ fixture I/O、decode、validate、unit normalize + └─ 产出 shared_ptr + │ ownership/immutable snapshot + ▼ +Render thread + ├─ snapshot revision diff + ├─ GPU resource create/upload/release/recreate + ├─ 当前 frame Camera/Matrix + └─ encode simulation/trail/draw/composite + ▼ +GPU + └─ velocity sample、particle update、trail、rasterization +``` + +- Field 解码:Style 所属 worker/thread pool 或独立受控 worker;不得在 render frame 内 decode。 +- CPU 插值:只用于 validator、测试参考或查询 API;正式每帧 field sampling 在 GPU。 +- GPU resource 创建/释放:持有有效 `gfx::BackendScope` 的 render thread。 +- 每帧 simulation:render thread 编码,GPU 执行。 +- Camera Matrix:当前 `PaintParameters.transformParams/state`。 +- Data 交接:`shared_ptr`/MapLibre Immutable snapshot + monotonic revision;render thread 原子替换当前引用后再上传。 +- 避免复制:decode 只产生一次 owning buffer;source/render snapshot 共享 const ownership;只在 GPU revision 变化时上传,不通过 Java/ObjC/JS 每帧传大数组。 +- context loss:平台/backend 通知 Core;render thread 丢弃 GPU generation,CPU snapshot 保留;新 context 下 render thread 重建。 + +## 12. Camera / Matrix 同帧同步 + +```text +Map camera mutation + → TransformState snapshot in UpdateParameters + → RenderTree TransformParameters + → PaintParameters for current frame + ├─ state: center/zoom/bearing/pitch/size/frustum + ├─ proj/aligned/near-clipped matrix + └─ pixel ratio/frame count/time + → ParticleFrameParameters + → simulation visible domain + particle draw/composite +``` + +规则: + +- pan/zoom/bearing/pitch 全部使用当前 render frame snapshot;不接受 UI/JS 另行推算的 Camera。 +- position state 存储与 Camera 无关的 Field-normalized/geographic 语义位置;绘制时转为 canonical Web Mercator/world coordinate,再使用当前 frame matrix。 +- resize/pixel ratio 改变更新 viewport uniform,并重建 trail target;state 和 velocity texture保持。 +- GLOBAL field 的 longitude 在 canonical interval 内 wrap;绘制时选择距离当前 map center 最近的 unwrapped world copy。 +- 区域 Field 以 descriptor 的 unwrapped longitude interval 判断范围;v1 fixture 不跨反经线,运行时 contract 保留跨反经线能力。 +- projection/unprojection 只复用 MapLibre Core utility/transform,不在平台层复制公式。 +- v1 Camera 任意有效变化清空 screen-space trail;粒子位置继续保留并以新 transform 绘制。 + +## 13. 帧驱动与动画时钟 + +唯一 frame driver 是 MapLibre render loop。 + +1. enable 或资源 ready 时,Controller 通过现有 Core invalidate/`triggerRepaint` 路径唤醒第一帧。 +2. `RENDERING` 且可见、Field/GPU ready 时,Particle Render 在当前 RenderTree 中声明 `needsRepaint=true`,使 MapLibre 请求下一帧。 +3. 地图静止但粒子活动时,以上机制持续;不依赖 camera transition。 +4. disable、Layer 不可见、App background、View 不可见、Field 不可用、ERROR 或 layer removal 时停止额外 repaint。 +5. App foreground/context restore 后,只有资源重新 READY 且 layer enabled/visible 才恢复。 +6. simulation time 使用 monotonic render time;采用固定逻辑步长加有界 accumulator,过长 pause 后不追赶所有丢失步,避免恢复瞬间爆炸。 + +平台 `requestRender`/CADisplayLink 只是现有 MapLibre frontend 的落地机制,不形成第二套粒子时钟。 + +## 14. GPU 粒子状态模型 + +### 14.1 冻结的逻辑状态 + +每个 particle 逻辑上至少包含: + +- `position`:Field-normalized 2D position,可无损映射到 descriptor bounds;与屏幕 Camera 无关。 +- `age`:秒或归一化寿命语义。 +- `seed`:稳定随机/respawn 输入。 +- `alive/valid`:可由 age/特殊值派生,不要求独立 channel。 + +Exact texture format、channel packing 和最终最大 particle count **不在本阶段冻结**;它们必须在 Camera correctness 和 backend capability 验证后选择。 + +### 14.2 Ping-pong 与 simulation + +- `Particle State A/B` 同尺寸,当前帧只读 current、只写 next,帧末交换。 +- texture texel count 至少覆盖 particle count;多余 texel 标记 inactive。 +- velocity texture 保存 canonical U/V 与 validity;shader 按 descriptor 将物理速度转换为 Field-normalized displacement。 +- timestep 使用受限固定步长;speed factor 是业务参数,不能改变原始 Field snapshot。 +- Field bounds 外、NoData、age 到期或 OCEAN_ONLY mask 非 ocean 时执行 deterministic respawn。 +- respawn domain 优先取 Field bounds 与当前可见 geographic bounds 的有效交集;交集为空时不绘制且不忙循环重建资源。 +- viewport clipping 只影响 draw,不以离开屏幕立即销毁 state;region/domain 无效时才 respawn。 + +### 14.3 为什么第一阶段不用 compute 作为前置 + +当前目标同时覆盖 Android OpenGL/Vulkan 与后续 iOS Metal,而现有跨 backend 最稳定的共同语义是 render pass、texture、render target 和 shader registry。第一阶段以 full-screen/texture render-pass simulation 验证主架构,避免先冻结 Vulkan/Metal compute pipeline、workgroup 和 barrier contract。 + +后续 compute 只能作为 `ParticleSimulationBackend` 的 capability-gated 实现;逻辑 state、Field、Mask、Controller 和公共 API 不变。无 compute 时继续使用 render-pass simulation,不回退到正式 CPU 每粒子循环。 + +## 15. Trail 架构 + +第一阶段正式视觉范围包含 trail,但最小验证顺序允许先验证无 trail 粒子点/短线,再增加 trail gate。 + +```text +Trail Current texture + → Fade pass → Trail Next + → Particle draw blend into Trail Next + → Composite Trail Next at RasterParticleLayer position + → swap Current/Next +``` + +- 使用 screen-space Trail A/B,避免依赖所有 backend 对同一 attachment 同时读写的行为。 +- accumulation/fade 与 particle draw 是独立 pass;最终 composite 使用 premultiplied alpha contract。 +- resize/pixel ratio/render target scale 变化:重建并 clear A/B。 +- pan/zoom/bearing/pitch/frustum 有效变化:v1 clear A/B,不错误复用旧屏幕轨迹。 +- Field/mask revision、simulation reset、disable、style reload、context loss、App background、ERROR、layer removal:clear 或释放。 +- opacity/trail fade 的纯样式变化可从下一帧生效;是否清空由 property 语义明确,改变 fade 不强制清空,改变坐标/particle size 可清空。 +- context loss 后不恢复旧 trail 内容,只重建空 target。 + +## 16. Land/Ocean Mask 设计 + +### 16.1 数据与资源 + +Mask 数据属于独立 `ParticleMask` 模块。CPU 层是 `LandOceanMaskSnapshot`:mask id/revision、CRS、bounds、grid、registration、coverage policy 和 immutable byte data。GPU 层是按 `(maskId, revision, backend context generation)` 缓存的 texture,可被多个 OCEAN_ONLY Layer 共享。 + +第一阶段使用 fixture `mask.u8` GPU texture;CPU geometry 只允许作为未来 mask 生产/测试输入,不在每帧做 point-in-polygon。未来可扩展 tiled/pyramid mask,但保持同一 snapshot/sample contract。 + +### 16.2 坐标、分辨率和采样 + +- CRS 为 EPSG:4326,bounds/grid registration 与 Field contract 相同。 +- fixture v1 mask 默认与对应 Field 同 bounds/width/height;架构允许独立分辨率,shader 使用各自 transform。 +- binary 只接受 LAND/OCEAN;未知/NoData 对 OCEAN_ONLY 按 LAND 处理。 +- v1 采用 nearest/明确阈值采样,避免线性过滤把海值渗入陆地。未来 coverage mask 可显式声明 coverage encoding 后再允许平滑。 +- zoom 不触发 mask 重建;未来 pyramid 只在 LOD 变化时切换 resource revision。 +- bearing/pitch 不改变 mask sample:particle 在 geographic/Field space 采样,再与地图使用同帧 transform 绘制。 +- GLOBAL Wind 完全绕过 mask branch/resource requirement。 + +### 16.3 wrap、反经线与海岸 artifact + +- 全球 mask longitude 周期 repeat;区域 mask 使用 unwrapped interval。 +- 反经线数据拆分或使用 unwrapped bounds,不能用 `west > east` 的模糊单文件语义。 +- 海岸 artifact 风险包括 mask 分辨率不足、Field 与 Mask registration 不同、nearest 锯齿、trail 残留和海图海岸语义差异。 +- v1 通过严格 metadata validation、nearest sample、未知即陆地、Camera 变化清 trail 和 coastal fixture 控制;最终生产 Mask 来源/分辨率留待后续数据设计与 NavSea 语义 Gate。 + +### 16.4 更新、缓存和恢复 + +- 仅 mask id/revision/context generation 改变时重新 upload;Camera 和每帧 animation 不重建。 +- CPU snapshot 可跨 context 保留;GPU texture 由 render thread 在新 context 重建。 +- mask 创建失败时 OCEAN_ONLY layer 进入 ERROR/不绘制,不能降级为 GLOBAL。 + +## 17. Land/Ocean Mask 共用图 + +```text +LandOceanMaskSnapshot(maskId, revision) + │ + ▼ +Shared ParticleMaskCache + ├─ CPU immutable bitmap + └─ GPU mask texture per context generation + │ + ┌──────┼─────────┬──────────────┐ + ▼ ▼ ▼ ▼ + Wave Tide Current Future OCEAN_ONLY + +Wind/GLOBAL ─────────────────────→ bypass Mask +``` + +## 18. 生命周期状态机 + +```text +DISABLED + └─ enable → WAITING_MAP +WAITING_MAP + ├─ map/render context ready, no field → WAITING_FIELD + └─ disable/remove/destroy → DISABLED +WAITING_FIELD + ├─ valid field snapshot → PREPARING + └─ invalid field → ERROR +PREPARING + ├─ GPU resources ready → READY + ├─ context lost → CONTEXT_LOST + └─ resource/shader failure → ERROR +READY + ├─ enabled + visible + foreground → RENDERING + ├─ background/invisible → PAUSED + └─ new field/mask revision → PREPARING +RENDERING + ├─ background/invisible → PAUSED + ├─ context lost → CONTEXT_LOST + ├─ new resource revision → PREPARING + ├─ disable/remove → DISABLED + └─ failure → ERROR +PAUSED + ├─ foreground/visible, resources valid → READY → RENDERING + ├─ context lost → CONTEXT_LOST + └─ disable/remove/destroy → DISABLED +CONTEXT_LOST + ├─ new context + CPU snapshot valid → PREPARING + └─ disable/remove/destroy → DISABLED +ERROR + ├─ valid new field/options/manual retry/context generation → PREPARING + └─ disable/remove/destroy → DISABLED +``` + +责任: + +- Map/Style thread 管理 Layer/Source 存在性、enable 和 immutable revision。 +- Worker 管理 decode job 的取消和 CPU snapshot。 +- Render thread 管理所有 GPU resource 和 context generation。 +- GPU 不拥有跨 Map 生命周期的资源责任。 +- Style reload 删除旧 RenderLayer 时先停止 repaint,再释放 GPU;同 id 新 layer 按新实例从 WAITING_FIELD/PREPARING 开始。 +- Map destroy/layer removal 是终止边界:取消 worker callback、断开 observer、释放 render resources,不自动复活。 + +## 19. 每帧渲染链图 + +```text +MapLibre requests frame + → RenderTree prepared with current TransformState + → RenderRasterParticleLayer checks state/revisions + → Upload only if Field/Mask/resource revision changed + → Simulation pass: State Current → State Next + → Trail fade: Trail Current → Trail Next + → Draw particles into Trail Next (Mask/domain applied) + → Main translucent pass: composite Trail Next by layer index + → present + → active? needsRepaint=true : stop extra repaint +``` + +## 20. Android 薄接入 + +```text +Kotlin/Java Source + Layer API + → typed JNI peer (no raw host pointer) + → Core RasterArraySource / RasterParticleLayer + → RenderRasterParticleLayer / mbgl::particle / gfx + → Android OpenGL or Vulkan backend + → existing MapRenderer requestRender + Surface lifecycle +``` + +- Java/Kotlin 只表达 source/layer/options/status;不接收 texture id、不执行每帧 simulation、不持有 GPU resources。 +- 正式接口需要类型安全 JNI wrapper;禁止把 `CustomLayer(String,long)` 的 raw `long CustomLayerHost` 作为粒子 API。 +- SurfaceView/TextureView 的 surface/context recreation 统一转为 Core context generation/lifecycle;不在两种 View 内分别写 particle engine。 +- OpenGL/Vulkan 差异留在 gfx shader/resource implementation;Core logical resources 和 pass graph 相同。 +- repaint 复用 `Map::triggerRepaint` → `AndroidRendererFrontend` → `MapRenderer.requestRender`,平台不增加粒子 timer。 +- 第一阶段 Android OpenGL 完成日常开发;Vulkan 在冻结前验证 state/trail/mask/context-loss,不另写业务逻辑。 + +## 21. iOS 薄接入 + +```text +ObjC/Swift Source + Layer peer + → Core RasterArraySource / RasterParticleLayer + → RenderRasterParticleLayer / mbgl::particle / gfx + → OpenGL or Metal backend + → existing MLNMapView RendererFrontend / CADisplayLink / draw callback +``` + +- `MLNCustomStyleLayer`、`MLNCustomDrawableStyleLayer` 和 `MLNBackendResource` 是已确认能力与验证参考,不是正式粒子引擎所有者。 +- 正式 iOS API 应是 Core Source/Layer 的 ObjC peer;ObjC/Swift 不保存 particle state、不编写第二套 simulation。 +- CADisplayLink 继续驱动 MapLibre view;粒子通过 Core `needsRepaint` 使 MapLibre 保持活动,不添加独立 display link。 +- Metal/OpenGL resource 与 encoder 只在 gfx/backend 内可见;平台 wrapper 不暴露给业务 API。 +- iOS 最终 backend 和设备能力在后续专项验证;不能据当前源码宣称 Metal/OpenGL 已通过粒子 gate。 + +## 22. 最小公共 API 设计 + +以下为语义 API,不冻结具体 C++/Java/ObjC 方法签名: + +```text +RasterArraySource(id) + setField(ParticleFieldSnapshot) + clearField() + getRevision()/getDescriptor() + +RasterParticleLayer(id, sourceId) + setEnabled(bool) + setDisplayDomain(DisplayDomain) 受 FieldType 固定规则校验 + setParticleOptions(ParticleOptions) + setMaskId(optional maskId) OCEAN_ONLY 必需 + getParticleOptions() + getStatus()/getLastError() + +Style + add/remove RasterArraySource + add/remove RasterParticleLayer + observe ParticleStatus changes 低频状态,不是每帧 telemetry +``` + +数据更新通过 Source revision,而不是把 field 数组设为 Layer paint property。公共 API 禁止 GL texture id、VkImage、MTLTexture、command encoder、backend workgroup、raw native pointer。 + +Layer display domain 必须符合业务规则;例如尝试把 CURRENT 改为 GLOBAL 返回 validation error,不静默放宽。 + +## 23. 配置分层 + +### 23.1 稳定业务参数 + +- particle count(目标值,受 capability/memory clamp) +- speed factor +- lifetime +- opacity +- particle size/line width +- trail enabled +- trail fade +- color ramp/reference +- enabled/visibility + +业务参数具有 backend-independent 范围、默认值和 validation;具体默认值在最小验证后冻结。 + +### 23.2 内部 backend 参数 + +- state/velocity/trail texture format +- buffer alignment、binding index +- render target channel type/scale +- shader variant/defines +- compute workgroup/barrier(未来) +- upload staging、sampler/filter 细节 + +这些由 capability profile 与 gfx implementation 决定,不能出现在产品业务 API 或 fixture contract。 + +## 24. 错误与降级策略 + +| 情况 | 行为 | 重试 | +|---|---|---| +| metadata/binary/checksum/Field 无效 | Source 拒绝新 revision;Layer 保留最后一个有效 snapshot或进入 WAITING_FIELD/ERROR | 仅新数据 revision | +| Field 与 viewport 无交集 | READY 但不绘制、不持续空忙 repaint | Camera/Field 改变时恢复 | +| OCEAN_ONLY Mask 缺失/无效 | fail closed,Layer ERROR;地图继续 | 有效 mask revision | +| GPU resource 创建失败 | 释放本次部分资源,Layer ERROR;其他 Layer 不受影响 | 一次受控低质量 profile 可选,否则人工/新配置 | +| backend 不支持必要 render target/format | Layer ERROR/UNSUPPORTED;不回退 CPU 正式粒子 | backend capability 变化或实现更新 | +| context loss | 停止 repaint,丢弃 GPU generation,保留 CPU snapshot,进入 CONTEXT_LOST | 新 context 自动 PREPARING | +| shader 创建失败 | 释放 particle shader/resource,Layer ERROR | 新 context/shader revision 时一次 | +| memory pressure | 先释放 trail、闲置 cache;允许按内部 profile 降低 trail scale/particle count并记录状态 | 有界一次;仍失败则 ERROR | +| Layer removal/Map destroy | 取消任务、停止 callback/repaint、释放 GPU/CPU ownership | 不自动重试 | + +任何错误不得使 MapLibre Map、其他 Style Layer 或平台 View 崩溃。自动重试必须按 revision/context generation 去重,禁止每帧重试形成死循环。 + +## 25. 性能预算与后续测量 + +以下是设计目标,不是已验证结论。正式 PASS 必须绑定具体设备、分辨率、backend、build type、fixture 和 particle count。 + +### 25.1 第一阶段 Gate 指标 + +| 指标 | 设计目标/测量档位 | +|---|---| +| particle count | 16K correctness、32K target、64K stress;最终默认/最大值待验证 | +| FPS/frame time | 声明设备上 32K 的目标下限 30 FPS/p95 ≤33.3ms;不得写成已达到 | +| Particle CPU time | 32K target p95 ≤3ms/frame,不含 MapLibre 基础渲染 | +| Particle GPU time | 32K target p95 ≤12ms/frame,分别记录 simulation/trail/composite | +| Particle GPU memory | 1080p、32K target ≤64MiB,含 state/trail/velocity/mask,不含 base map | +| Field upload | 1024×1024 U/V target ≤200ms,且只在 revision 变化时发生 | +| Context recreation | target ≤1s 恢复可绘制;旧 trail 不要求恢复 | +| correctness | pan/zoom/bearing/pitch/resize/wrap/mask/context loss 无崩溃、无明显错位 | + +若没有声明的 baseline 设备,只能报告测量值,不能判定性能 PASS。第一阶段必须提供 CPU/GPU time、资源字节数、upload/recreate time 的 instrumentation;60 FPS、最终最大粒子数、最佳格式和厂商优化属于后续优化。 + +## 26. 后续最小技术验证清单 + +| 优先级 | 验证 | 是否阻塞正式实现 | 可能改变什么 | 最小方式 | +|---|---|---|---|---| +| 1 | 独立 Core RenderLayer 在 Android OpenGL 中进入正确 Style order、读取同帧 transform并持续/停止 repaint | 是 | 主入口与 frame driver | 仅一层、单 fixture、固定少量 state 的 RenderTree probe;不做完整视觉 | +| 2 | gfx render-pass State A/B + Trail A/B 在 Android OpenGL/Vulkan 的创建、采样、交换、barrier和 context recreation | 是 | render-pass simulation 是否可作为共同基线、资源抽象边界 | 最小 ping-pong pattern + trail target,Surface recreate 后重建;记录两 backend | +| 3 | Camera/wrap/Mask contract | 是 | position semantic、trail clear 和 Mask sampling | uniform-east/north + coastal-mask,覆盖 pan/zoom/bearing/pitch/resize/反经线邻近视图 | +| 4 | iOS Core layer 薄 peer 与 Metal/OpenGL 同帧 gfx 资源可达性 | 不阻塞 Android 第一阶段;阻塞 iOS 实现 | iOS wrapper/backend adapter 细节,不得改变 Core engine | Mac/iOS 同 commit 的最小 Source/Layer/render-target probe | + +Android raw `CustomLayerHost` 不再是正式路线的阻塞项,不专门扩展产品 API;若独立 Layer probe 需要对照,可运行现有 CustomLayer 示例,但其结果只作诊断。 + +## 27. 明确不采用的架构 + +| 不采用方案 | 原因 | +|---|---| +| React Native/JS 每帧粒子 | 当前仓库无 RN runtime;高频大数组/Camera/frame 跨桥;无法与 render frame 严格同步 | +| MapView 外部 Overlay | 独立 surface/frame/matrix/layer order;Camera/pitch/context lifecycle 易错;违反固定边界 | +| 每种海洋场各自一套 Mask | 数据重复、海岸语义分叉、缓存和 artifact 不一致;违反统一 Mask 决策 | +| 直接改造 RasterLayer 承担粒子 | 普通 RasterLayer 是静态 raster semantics;会污染官方层并混合数据、simulation、trail | +| Android/iOS 各写一套引擎 | 状态、Field、Mask、生命周期和算法会分叉;违反共享 C++ Core | +| CustomLayer 作为长期产品主宿主 | Android raw host 与 iOS context API 不对称,生命周期/API 难以稳定;不利于 Source/Layer/RenderTree 正式集成 | +| 每帧重建 Field texture | 造成 upload、allocation、driver 和内存抖动;Field 应按 revision 缓存 | +| 每帧大数组跨 Java/ObjC/JS | 复制、序列化和 GC/ARC 压力;只允许低频 immutable snapshot/revision | +| backend-specific GPU API 暴露给业务 | 破坏跨平台、测试和升级边界;handle 只能在 gfx/backend 内部 | +| 正式 CPU 每粒子循环 | 不满足 GPU Particle Engine 目标;CPU 只做 decode/validation/reference test | +| compute-only 第一版 | 当前跨 backend capability 未验证,会过早冻结 workgroup/barrier;先用 render-pass simulation | + +## 28. Camera / Matrix 同步图 + +```text +UI/Native camera API + → mbgl::Map / TransformState + → immutable UpdateParameters + → RenderTree TransformParameters + → current PaintParameters + └→ ParticleFrameParameters + ├→ visible Field intersection / world copy + ├→ GPU simulation uniforms + └→ draw/composite matrix + +Independent UI/JS camera guess ─X→ Particle Renderer +``` + +## 29. Core 模块边界图 + +```text +Style API + ├─ RasterArraySource ─→ ParticleFieldSnapshot + ├─ WeatherColorLayer ─→ RenderWeatherColorLayer + └─ RasterParticleLayer + │ + ▼ +RenderRasterParticleLayer ← current PaintParameters + │ + ▼ +mbgl::particle + ├─ DataAdapter → Field + ├─ Controller → lifecycle/repaint/revision + ├─ Simulation → logical state/timestep/respawn + ├─ Mask → shared domain filter + ├─ GPUResources → velocity/state/trail/uniform + └─ Render → offscreen passes/composite + │ + ▼ +gfx/backend only +``` + +## 30. 架构冻结项 + +以下内容从本设计起冻结,后续普通实现 Step 不得自行改变: + +1. 主路线是独立 Core `RasterParticleLayer/RenderRasterParticleLayer`,进入 Style/RenderTree/gfx;CustomLayer/CustomDrawable 不是正式宿主。 +2. Android/iOS 共用 C++ `mbgl::particle` Field、Simulation、Render、GPU resource contract、Mask、Controller 和状态机;平台只有类型 peer、backend lifecycle 与 capability 薄接入。 +3. `RasterArraySource` 是 Field 数据边界;Field snapshot immutable、revisioned、canonical U-east/V-north/SI unit。 +4. WeatherColorLayer 与 RasterParticleLayer 是不同 Layer/RenderLayer/GPU 状态/动画生命周期,只共享数据 snapshot、grid/time metadata 和 Mask。 +5. Wind=`GLOBAL`;Wave/Tide/Current=`OCEAN_ONLY`;OCEAN_ONLY 统一共享 Land/Ocean Mask,缺失时 fail closed。 +6. fixture contract v1 使用 `metadata.json + field.f32 + optional/required mask.u8`、float32 little-endian interleaved UV、uint8 mask、checksum 和明确 grid/bounds/NoData。 +7. frame driver 是 MapLibre render loop;active layer 贡献 `needsRepaint`,没有独立 UI/JS 主时钟。 +8. Camera/Matrix 来自同一 render frame 的 Paint/Transform parameters;position state 与 Camera 无关。 +9. GPU simulation 的第一共同基线是 render-pass State A/B ping-pong;正式 CPU 每粒子循环和 compute-only 第一版均拒绝。 +10. 第一阶段正式视觉包含粒子、颜色映射和 screen-space Trail A/B;Camera 变化清 trail,不做 v1 trail reprojection。 +11. GPU resource 只由 render thread/gfx 创建;context loss 保留 CPU snapshot、丢弃 GPU generation并重建空 trail。 +12. Android 第一阶段主验证 OpenGL,冻结前必须做 Vulkan gfx gate;iOS OpenGL/Metal 使用同一 Core contract,后续 Mac/iOS 专项验证。 +13. 公共 API 不暴露 backend handle、raw host pointer或每帧大数组;数据更新通过 Source immutable revision。 +14. 下一阶段只执行第 26 节四项最小技术验证,不重新讨论总体架构。 + +## 31. 本阶段不得冻结的内容 + +- 已达到的 FPS、最终最大/default particle count。 +- 最佳 state/velocity/trail texture format 和 channel packing。 +- Vulkan/Metal compute workgroup、barrier 和厂商优化。 +- 最终生产 Mask 来源、分辨率与 NavSea 海岸语义。 +- 真实天气 Server/GRIB/厂商数据格式。 +- NavSea/RN 最终 Host API 和产品桥接。 +- 最终生产设备能力矩阵、iOS 最终 backend。 +- Camera 验证前的最终 Particle State 物理 packing。 + +这些内容只能由后续最小技术验证、数据设计和产品 Gate 冻结。 + +## 32. 下一阶段判定 + +选择:**B,进入 `30_最小技术验证`。** + +原因:独立 Core layer、render-pass ping-pong/trail 在 OpenGL/Vulkan 的实际资源行为、context recreation、同帧 Camera/wrap/Mask correctness 和 iOS backend 薄接入仍缺运行时证据。这些未知项会影响实现细节或架构可行性,不能直接进入正式实现;但它们已被压缩为四项最小验证,不需要重新开展总体架构讨论。 + +## 33. 本次设计边界 + +本阶段未创建 Source、Layer、Renderer、Shader、GPU resource、CustomLayerHost、JNI/ObjC 类或 fixture 数据;未修改生产源码、构建配置、依赖、lock、Step 或项目总则;未运行会写工程状态的构建/测试。 diff --git a/docs/20_调查/10_架构调查报告_v1.md b/docs/20_调查/10_架构调查报告_v1.md new file mode 100644 index 000000000..b0a888f08 --- /dev/null +++ b/docs/20_调查/10_架构调查报告_v1.md @@ -0,0 +1,391 @@ +# MapLibreParticleLab 架构调查报告 v1 + +调查日期:2026-08-29 + +调查分支:`particle/main` +调查性质:只读架构调查,不是粒子方案设计 + +## 1. 调查环境 + +- 仓库:`/mnt/sda1/MapLibreParticleLab`,根目录包含 `PARTICLE_BASELINE.md`、`include/`、`src/`、`platform/android/`、`platform/ios/`、`shaders/`、`particle-fixtures/` 和 `docs/`,确认是 MapLibreParticleLab。 +- 主机基线:Ubuntu 24.04 x86_64;项目基线记录的 Java 17、Android API 34、NDK 28.2.13676358、构建并行度 3 已读取自 `PARTICLE_BASELINE.md` 与 `docs/00_项目规范/06_官方基线来源_v1.md`。 +- 本次只执行文件搜索、源码阅读、配置阅读和 Git 查询;未运行构建、测试、依赖安装或会生成产物的命令。 + +## 2. Git、基线和规则读取 + +- 分支:`particle/main`。 +- HEAD:`127de2271480000fcc8f390d81cf446e98757800`,最近提交主题为 `docs: freeze particle project governance v1`,时间为 2026-08-29 11:13:49 +08:00。 +- 远端:`origin` 为本地 Gitea SSH 地址;`upstream` 为 MapLibre Native GitHub,只启用 fetch,push 显式为 `DISABLED`。 +- 工作区:调查开始时无已跟踪修改;有一个未跟踪的当前任务书 `docs/40_任务/10_架构调查指令书_v1.md`。因此“工作区干净”检查结果为:`未完成`,原因是任务书本身在当前工作区未跟踪,非本次生成。 +- 当前基线:Android `android-v13.4.1`、iOS `ios-v6.28.0`、共用 Core `18545ca10d296704b2cba675b182ff2c8da24588`。实际版本文件为 `platform/android/VERSION` 和 `platform/ios/VERSION`。 +- 已完整读取:根目录 `AGENTS.md`;`PARTICLE_BASELINE.md`;`docs/00_项目总则/` 下当前四份总则;`docs/00_项目规范/` 下当前六份规范及官方基线来源;当前 `STEP-000` 记录和报告;`docs/40_任务/README.md`;本任务书;调查、Step、报告 README;现有 `docs/10_架构/MapLibre粒子总体架构_v1.md`;`粒子开发记录.md`。 +- 本次未修改 `docs/00_项目总则/`、`docs/00_项目规范/`、生产代码、实验源码、构建配置或依赖。 + +## 3. 当前项目整体架构 + +### 3.1 工程形态 + +当前仓库不是 React Native、Expo 或 Bare RN 产品工程。根 `package.json` 的名称是 `maplibre-native`,脚本只有样式规范复制;`tsconfig.json` 只包含 `.github/scripts/*.ts`、`scripts/**/*.mjs` 和声明文件,并设置 `noEmit`。仓库没有业务页面、React 组件、`react-native`/Expo 依赖、Metro 配置或 Babel 配置。 + +因此任务书中的“业务/页面 → RN Map 组件 → RN 封装”在当前仓库不存在,不能据此补画一条虚构调用链。这里的实际入口是各平台 MapLibre SDK 示例、测试应用或直接使用 Native C++ API 的宿主。 + +### 3.2 主要目录 + +```text +MapLibreParticleLab/ +├── include/mbgl/ Core 对外 C++ API +├── src/mbgl/ Core 实现:Map、Style、Source、Render、gfx、线程 +├── platform/android/ Android SDK、JNI、Java/Kotlin View、C++ backend、测试 App +├── platform/ios/ iOS SDK、MLNMapView、OpenGL/Metal backend、示例和测试 +├── platform/darwin/ Darwin 公共绑定、CustomStyleLayer 和 CustomDrawableLayer +├── platform/node/ Node binding 与 Node 测试,不是 RN UI 宿主 +├── shaders/ MapLibre 内置 GLSL/生成清单;无粒子 shader +├── particle-fixtures/ 第一阶段离线粒子场/Mask fixture 占位目录 +├── render-test/ test/ 渲染测试、API 测试、fixture 和服务器测试 +├── scripts/ 样式代码、设备农场和工程脚本 +├── docs/ 总则、架构、任务、调查、Step、报告 +├── vendor/ 第三方源码子模块 +└── CMakeLists.txt、Makefile、bazel/、platform 构建文件 +``` + +### 3.3 入口和边界 + +- Core 入口是 `mbgl::Map`(`include/mbgl/map/map.hpp`),通过构造时注入 `RendererFrontend` 和 `MapObserver`。 +- Android 入口是 `MapView`(`platform/android/MapLibreAndroid/src/main/java/.../MapView.java`),内部创建 `MapRenderer`、`NativeMapView` 和 Java/C++ JNI peer。 +- iOS 入口是 `MLNMapView : UIView`(`platform/ios/src/MLNMapView.h`),内部由 `MLNMapViewImpl` 连接 Core `Map` 和 OpenGL/Metal View。 +- 没有项目自有 `assets/` 业务资源目录;`test/fixtures` 是 MapLibre 测试资源,`particle-fixtures` 目前仅包含 README 和五个 `.gitkeep`。 +- 现有 `build-linux-opengl`、`build-particle` 等是工作区中的构建目录,不视为源码模块。 + +## 4. 依赖与版本 + +| 类别 | 当前可确认内容 | 证据/结论 | +|---|---|---| +| JavaScript 直接依赖 | `@maplibre/maplibre-gl-style-spec`、`simple-git`、`minimatch`、`npm-run-all`、GitHub Actions 等 | 根 `package.json`;不是地图 UI 依赖 | +| JavaScript 开发依赖 | TypeScript、EJS、Express、AWS SDK、MVT/style fixtures 等 | 用于脚本、测试、设备农场和代码生成 | +| RN/React/Expo | 未找到 `react`、`react-native`、Expo、RN MapLibre 包 | 当前仓库没有 RN 产品层 | +| Skia/Reanimated/WebGL JS | 未找到项目直接依赖 | 不能假定存在 | +| Android SDK | `platform/android/VERSION=13.4.1`;`compileSdk=34`、`minSdk=23`;OpenGL/Vulkan/WebGPU flavor;Java SDK 直接依赖 GeoJSON、gestures 等 | `platform/android/MapLibreAndroid/build.gradle.kts` | +| Android Core | `libmaplibre` 由本仓库 C++ Core 构建并通过 JNI/Prefab 接入,不是另一个 RN 包 | Android CMake、JNI 和 Prefab 配置 | +| iOS SDK | `platform/ios/VERSION=6.28.0`;Podspec 通过 `ios-v#{version}` release xcframework;iOS deployment target 12 | `platform/ios/MapLibre.podspec` | +| iOS backend | 当前源码含 OpenGL、Metal、WebGPU 条件路径;当前项目基线将 OpenGL 作为主线、Metal 作为后续验证方向 | `MLNMapView+Impl.mm` 和各 backend 文件 | +| lock 文件 | 根 `package-lock.json` lockfile v3、`pnpm-lock.yaml` lockfile v9;锁定 TypeScript 5.9.3 等脚本依赖 | 未安装/更新 | + +需要区分:`package.json` 的版本 `5.4.0` 是 Native 仓库/脚本包版本;Android/iOS SDK 版本分别由各平台 `VERSION` 决定,不能把三者合并为一个 RN 版本。 + +## 5. MapLibre 实际接入链 + +当前无 RN 绑定,因此实际链路分为 Android SDK、iOS SDK 和直接 Core 三条: + +```text +Android Activity/Test App + → MapView(FrameLayout) + → MapRenderer + NativeMapView(Java JNI peer) + → AndroidRendererFrontend + → mbgl::Map / mbgl::Style + → RendererFrontend::update(UpdateParameters) + → Android MapRenderer render thread + → mbgl::Renderer + → RenderOrchestrator / RenderTree / RenderLayer / gfx::Drawable + → Android OpenGL 或 Vulkan RendererBackend + → Surface/TextureView → GPU +``` + +```text +iOS App/Example + → MLNMapView(UIView) + → MLNMapViewImpl(MapObserver adapter) + → mbgl::Map + RendererFrontend + → RenderOrchestrator / RenderTree / RenderLayer / gfx + → MLNMapViewOpenGLImpl(GLKView) 或 MLNMapViewMetalImpl(MTKView) + → EAGL/CAMetalLayer → GPU +``` + +```text +直接 Core 宿主 + → mbgl::Map(RendererFrontend, MapObserver) + → Map::Impl::onUpdate() + → Immutable Style/Source/Layer snapshot + → Renderer::render() +``` + +当前能够确认的对象:MapView、Camera、Style、RasterSource、ImageSource、Vector/GeoJSON/CustomGeometrySource、Layer、MapObserver 事件和 CustomLayer。当前仓库没有 RN `ShapeSource` 组件层,也没有 RN `onPress` API;Android/iOS Native SDK 的点击、相机和样式 API 需分别通过各自 SDK 查询,不能移植成当前 RN 事实。 + +## 6. MapLibre 生命周期 + +### 6.1 Core 生命周期 + +`Map::Impl::onUpdate()` 在连续模式下读取当前时间、更新 transition,构造包含 `TransformState`、style/source/layer immutable snapshot、图片、字体和 FileSource 的 `UpdateParameters`,再交给 `RendererFrontend::update()`。`Map::triggerRepaint()` 也只是触发同一更新路径。 + +`Renderer::render()` 创建并准备 `RenderTree`,`Renderer::Impl::render()` 依次执行:开始 frame、上传 source/layer 资源、更新 layer group、运行 tweaker、上传 drawable/render target、可选 3D pass、render target、清屏、opaque pass、translucent pass、debug pass、postRender、present、结束 frame,并回调 `onDidFinishRenderingFrame`。 + +### 6.2 Android + +- `MapView.initializeDrawingSurface()` 创建 `MapRenderer` 和 `NativeMapView`;Surface 创建后回到 UI 线程初始化 `MapLibreMap` 并调用 `onStart()`。 +- `MapView.onStart/onResume/onPause/onStop/onDestroy` 分别驱动 Java map/renderer;SurfaceView detach 会触发 native reset,Surface 销毁/重建可能导致 backend 和 `Renderer` 重建。 +- Java `MapRenderer` 的 `onDrawFrame()` 调用 JNI `nativeRender()`;SurfaceView 由 `MapLibreSurfaceView` 的 render thread 请求 dirty/continuous frame,TextureView 由 `TextureViewRenderThread` 的事件队列和 request flag 驱动。 +- `AndroidRendererFrontend::update()` 保存最新 `UpdateParameters`,异步任务把它交给 `MapRenderer` 并调用 Java `requestRender()`。C++ render thread 只持有一次 render 所需的参数快照。 +- Native MapObserver 事件由 JNI 转回 Java `MapChangeReceiver`,包括 camera will/is/did、地图/样式加载、render frame start/end、idle、source 和 shader 事件。 + +### 6.3 iOS + +- `MLNMapView` 创建 Core `Map` 和 `MLNMapViewImpl`;实现通过编译宏选择 OpenGL、Metal 或 WebGPU backend。 +- 显示刷新由 `CADisplayLink` 调用 `updateFromDisplayLink:`;当 `needsDisplayRefresh` 或 pending completion 存在时,调用 backend View 的 `setNeedsDisplay`。 +- `renderSync` 清除刷新标记后调用 `_rendererFrontend->render()`;渲染 View delegate 的 `drawInRect:`/`drawInMTKView:` 再调用 `MLNMapViewImpl::render()`,形成地图自己的渲染帧。 +- View 不可见、进入后台、销毁或恢复时,DisplayLink、drawable、backend 资源和 dormant 状态分别处理;具体资源重建依赖 backend 实现。 +- iOS delegate 可获得相机、地图/样式加载、render frame start/end、idle 等事件;CustomStyleLayer 的 `drawInMapView:withContext:` 在 MapLibre 的自定义 layer 绘制时调用。 + +### 6.4 TS/RN 层与内部事件 + +当前仓库没有 TS/RN 层,所以“TS/RN 直接获得的生命周期事件”:`未确认/不适用`,原因是该层不存在。Native SDK 可观察的事件如上;只存在于 Core/Renderer 的 `UpdateParameters`、RenderTree、LayerGroup、GPU command encoder 的内部状态,当前没有 JS 层封装。 + +## 7. 自定义渲染扩展点分类 + +### A:当前项目已经直接使用 + +- Core `style::CustomLayer` + `CustomLayerHost` + `RenderCustomLayer`:现有 Android Test App 的 `CustomLayerActivity` 和 C++ `ExampleCustomLayer`/`ExampleVulkanCustomLayer` 实际使用。 +- Darwin `MLNCustomStyleLayer`:iOS/Swift 示例实现 `drawInMapView:withContext:`,获得同一 frame 的 projection matrix;Metal 示例使用当前 render encoder。 +- Darwin `MLNCustomDrawableStyleLayer`/Core `CustomDrawableLayer`:示例 `ExampleCustomDrawableStyleLayer` 和 UI 测试实际使用,属于 Drawable/Builder 路径而不是 raw GL callback。 +- 内置 `RenderLayer`、`LayerGroup`、`DrawableBuilder`、`DrawableTweaker`、shader manifest、texture/buffer/render target:是当前地图本身的生产渲染链,粒子尚未接入。 + +### B:当前依赖已暴露,但目标粒子业务尚未使用 + +- Android Java `CustomLayer(String id, long host)` 暴露了 native host 指针入口,但 Java API 明确标注 experimental/do not use;当前只在 SDK test app 示例使用,粒子业务未使用。 +- Android `CustomGeometrySource` 和 iOS `MLNComputedShapeSource` 已暴露,能提供按 tile 的 GeoJSON/几何数据;当前没有粒子/格点数据实现。 +- Core `gfx::Context` 已有 `createTexture2D`、`createDynamicTexture`、`createRenderTarget`、uniform buffer、`createDrawableBuilder`、generic shader 和 upload pass 接口;这些是现有通用基础设施,不等于粒子专用 API。 + +### C:Native Core 支持,但当前高层封装未统一暴露 + +- `CustomLayerHost::render()` 可拿到 width/height、center、zoom、bearing、pitch、FOV、projection matrix 和 near-clipped matrix;Vulkan/Metal 还有 backend-specific render parameters。Android 高层 Java host 没有一个类型安全的 `CustomLayerRenderParameters` API,应用级可直接使用范围未确认。 +- Core `RenderLayer`、`RenderOrchestrator`、`LayerManager`、`RendererObserver` 和 gfx backend 内部 hook 可承载 native 粒子实现,但它们是 Core 内部边界,不是当前 RN/业务 API。 +- iOS `MLNBackendResource` 可暴露 Metal view/device/render pass/command buffer;这是 iOS Native API,当前仓库没有 RN 统一桥接。 + +### D:当前代码无法确认 + +- React Native UI/Main Thread、JS bridge、TurboModule/Fabric、RN `requestAnimationFrame` 或 Reanimated frame callback:当前项目没有 RN host,无法确认。 +- 任何外部 Overlay 与 MapLibre View 的同步质量:当前正式边界禁止外部 Overlay,仓库也没有粒子 Overlay 实现,因此没有实测数据。 +- 未实现的粒子 shader、GPU ping-pong state、trail render target、velocity texture 的具体格式和生命周期:当前只有架构草图,没有实现。 + +## 8. Android 渲染架构 + +- 原生 View:`MapView` 是 `FrameLayout`,将 SurfaceView 或 TextureView renderer 放在 child index 0;不是 RN `View`。 +- renderer:Java `MapRenderer` 仅负责生命周期、JNI 和调度;C++ `MapRenderer` 持有 Android backend、Core `Renderer`、`ActorRef`、update mutex 和 renderer observer。 +- backend:Gradle flavor 通过 `-DMLN_WITH_OPENGL=ON` 或 `-DMLN_WITH_VULKAN=ON` 构建;C++ `AndroidGLRendererBackend` 和 `AndroidVulkanRendererBackend` 分别管理 EGL/GL 或 Vulkan surface/swapchain/device。 +- 帧驱动:SurfaceView 默认可以 WHEN_DIRTY,CONTINUOUS 用于连续渲染/benchmark;TextureView 自有 render thread。Core update 通过 `AndroidRendererFrontend` 调用 `requestRender`。 +- 相机:Java `MapLibreMap`/`Transform` 调 JNI,NativeMapView 调 Core `jumpTo/easeTo/flyTo`、坐标投影和反投影;完整 matrix 仍在 Core Transform/Paint/CustomLayer 参数中。 +- 自定义入口:Core CustomLayer 和 Android wrapper/test example 明确存在;但 Java host 是原始 `long`,生命周期、指针归属、context loss 和参数传递要求高,不能当作稳定业务接口。 +- Native Module:未找到 RN NativeModule/TurboModule/Fabric。Android 的 `NativeMapView` 是 MapLibre Java/C++ peer/JNI,不是 RN module;当前不存在“Native Module 接触 RN MapView 实例”的事实。 + +## 9. iOS 渲染架构 + +- 原生 View:`MLNMapView` 继承 `UIView`;内部由 `MLNMapViewImpl` 将 Core MapObserver 与 ObjC delegate 接起来。 +- OpenGL:`MLNMapViewOpenGLImpl` 持有 `gl::RendererBackend`、Renderable 和 `GLKView`/EAGL context;GLKView delegate 的绘制回调进入 Core render。 +- Metal:`MLNMapViewMetalImpl` 持有 Metal backend、`MTKView`、command queue/buffer 和 render pass descriptor;`drawInMTKView:` 调 `impl->render()`,Core 负责 command encoding/present。 +- Camera/transform:Core `TransformState` 生成 `TransformParameters` 的 projection/aligned/near-clipped matrices;`MLNCustomStyleLayer` 将这些和中心、zoom、方向、pitch、FOV 传给 ObjC drawing context。 +- 自定义接口:`MLNCustomStyleLayer` 是可确认的 Native custom layer;Metal 下可以使用当前 encoder,OpenGL 下存在 deprecated EAGL context API。`MLNCustomDrawableStyleLayer` 是另一条 Drawable/Builder 接口。 +- Native 对象范围:iOS adapter 能访问 Core Map、RendererFrontend 和 backend resource;公开的业务层主要通过 `MLNMapView`、`MLNStyle`、`MLNStyleLayer` 及 delegate 访问。没有 RN 连接层,RN 侧范围为 `未确认`。 +- Android/iOS 差异:Android 有明确 OpenGL/Vulkan flavor 和实验性 raw host;iOS 示例有成熟的 ObjC/Swift CustomStyleLayer 与 Metal encoder 上下文。两者虽共享 Core CustomLayer 接口,但高层资源和生命周期 API 不一致。 + +## 10. JS/UI/Native/Worker/Render/GPU 边界 + +```text +[不存在的 JS/RN 业务层] + │ 当前仓库无桥接事实 + ▼ +Android UI thread / iOS main thread + ├─ Native MapView、Camera、Style API + ├─ Core Map 所在线程(由宿主创建线程决定) + └─ MapObserver 回调 + ▼ immutable UpdateParameters / mailbox / async task +MapLibre Worker/FileSource + ├─ Style/tile 解析、布局、bucket/buffer 计算 + └─ FileSource 网络/缓存/离线 I/O + ▼ +MapLibre Render thread(Android 明确独立;iOS 当前 render delegate 路径需遵循宿主主线程/渲染上下文约束) + → Renderer → RenderTree → LayerGroup/Drawable → gfx Context/CommandEncoder + → OpenGL/Vulkan/Metal backend → GPU +``` + +- 高频数据若走不存在的 JS bridge,当前没有事实可测;按现有边界,不能将每帧大数组交给 Java/ObjC API 作为已支持能力。 +- Android 的 `UpdateParameters` 在 frontend 与 render peer 之间异步保存并加锁,渲染使用最新快照;Java `requestRender` 只触发帧,不传粒子数据。 +- iOS `setNeedsRerender` 只设置刷新标记,`CADisplayLink`/UIView backend 再进入 render;这不是 JS 每帧回调。 +- Native 每帧回调存在:Core `MapObserver`/`RendererObserver` 的 render frame 事件,以及 CustomLayerHost render callback;它们位于地图渲染链内部。 +- GPU 资源直接更新位置存在于 Core `gfx::Context`、UploadPass、Drawable、Texture、RenderTarget 和 backend command encoder;当前没有对业务开放的跨平台粒子资源接口。 + +## 11. Camera、Matrix 与坐标转换能力 + +| 信息 | JS/RN | Native SDK | Core 内部/CustomLayer | 当前结论 | +|---|---|---|---|---| +| center/zoom/bearing/pitch | 当前无 RN host,未确认 | Android `MapLibreMap`、iOS `MLNMapView`/delegate 可访问部分状态 | `TransformState` 全部存在,CustomLayer 参数传出 | 可由 Native/Core 获取 | +| bounds/viewport | 无 RN host | SDK 有尺寸和相机/投影 API | `TransformState::getSize`、frustum offset、tile cover | Core 信息完整 | +| screen ↔ geographic | 无 RN host | Android NativeMapView 和 iOS 公共 API 有相应能力 | `latLngToScreenCoordinate`、`screenCoordinateToLatLng` | 已确认 Core 能力 | +| projection/unprojection | 未确认 | 高层不统一暴露 raw matrix | Core `getProjMatrix`、projection/inverse matrices、camera utility | 内部可用 | +| view/transform matrix | 未确认 | iOS CustomStyleLayer 以 projection context 形式收到;Android Java wrapper 未提供同等类型 API | `TransformState::matrixFor`、`TransformParameters`、`PaintParameters` | 跨平台高层不一致 | + +证据:`TransformParameters` 保存 `projMatrix`、`alignedProjMatrix`、`nearClippedProjMatrix`;`CustomLayerRenderParameters` 从同一 `PaintParameters.state` 读取中心、zoom、bearing、pitch、FOV 和 projection matrix;`PaintParameters::matrixForTile` 负责 tile matrix。 + +严格同步结论:在 Core 的同一渲染帧内,CustomLayer callback 可以获得地图当前 transform,因此“Core 内部同帧使用地图矩阵”已确认;但“Android/iOS 业务高层均能以统一公开 API 获取并接入粒子”未确认,当前不能宣称跨平台正式实现已具备严格同步能力。 + +## 12. 帧驱动能力 + +已确认:Android SurfaceView/TextureView render loop、Android `requestRender`/CONTINUOUS、iOS CADisplayLink、GLKView/MTKView draw callback、MapLibre `triggerRepaint`、Core CustomLayer render callback。 + +未确认/不适用:当前没有 RN `requestAnimationFrame`、RN animation frame、Reanimated frame callback 或 JS 每帧回调。 + +地图 Renderer 的帧驱动不是独立于地图的粒子动画时钟:Core `Map::Impl::onDidFinishRenderingFrame` 在 `needsRepaint` 或 transition 时重新 `onUpdate()`;平台再请求下一帧。若未来另建独立动画循环,现有代码无法保证它与相机快照、drawable context、present 时刻一致,可能产生一帧延迟、不同步或重复渲染;这里只记录风险,不作方案选择。 + +## 13. 当前数据与粒子相关实验代码 + +- `particle-fixtures/README.md` 明确第一阶段禁止真实天气服务器,具体数据格式在设计冻结后确定;`coastal-mask/`、`shear/`、`uniform-east/`、`uniform-north/`、`vortex/` 目前均只有 `.gitkeep`。 +- 全仓库排除 vendor、node_modules、build 后搜索 `particle/wind/current/wave/vector field/rasterarray/velocity`,没有发现粒子运行时、速度场解析、粒子 shader、GPU ping-pong 或 trail 实现。 +- 现有 `raster` 文件是 MapLibre 常规 RasterSource/RasterLayer 测试和内置 raster shader;`interpolate` 多数是 Style Spec/通用工具,不是向量场插值实现。 +- `shaders/` 有 raster、heatmap、hillshade、fill、line、symbol、custom geometry 等现有地图 shader,没有 `particle`、`velocity` 或 trail shader。 +- Android/iOS custom layer 示例是图形 API 示例/测试用途,不是粒子基础代码;应分类为“仅实验/示例用途”,不能直接判定为最终可复用粒子实现。 + +## 14. 海洋/气象格点数据可经过的现有层 + +| 阶段 | 当前仓库事实 | +|---|---| +| Fetch | Core `FileSource` 支持样式、tile、资源和缓存;没有粒子气象数据协议或 fixture loader | +| ArrayBuffer/TypedArray | JS 侧只有 Node/脚本和测试用途;未发现粒子 ArrayBuffer/TypedArray 模型 | +| 二进制解码 | Core 有 tile、PBF、图片等既有解码;没有速度场/海洋格点解码器 | +| Grid 数据模型 | 没有项目粒子 Grid/经纬度格点类型;`grid_index` 等是通用地图工具或测试 | +| 插值 | 有通用数值/Style 插值工具;没有已确认的向量场双线性/球面插值模块 | +| 内存结构 | Core 可用 immutable/vector/CPU buffer;没有粒子数据生命周期模型 | +| GPU upload | gfx Context、UploadPass、Texture2D、UniformBuffer、RenderTarget、Drawable 已存在 | +| Shader uniform | 内置 shader 与 global/layer uniform 已存在;没有粒子 uniform contract | +| 缓存/生命周期 | FileSource、tile cache、style snapshot 和 gfx resource 生命周期已存在;粒子数据生命周期未实现 | + +可复用基础设施只能确认到通用地图层:FileSource/离线 fixture 测试体系、immutable snapshot、thread pool、gfx Context/Texture/Buffer/RenderTarget/Drawable、shader registry 和 frame observer。未来粒子数据格式、解析、插值、上传策略均未确定。 + +## 15. 性能与线程风险 + +| 风险 | 等级 | 当前证据与边界 | +|---|---|---| +| JS 高频循环/每帧 JS→Native | 尚无法判断 | 当前无 JS/RN 宿主;若未来新增,仓库没有现成桥接基准 | +| 大数组/JSON/ArrayBuffer 跨桥复制 | 高 | 当前 MapLibre update 是 immutable snapshot/异步传递,未提供粒子大数组桥接;跨平台高层接口若采用逐帧对象传输会形成风险 | +| Native→GPU 重复复制 | 中 | gfx upload/update 能力存在,但粒子资源尚未定义,复制次数无法实测 | +| Texture 高频重建 | 中 | Context 有 Texture2D/DynamicTexture;RenderCustomLayer 示例未提供粒子资源复用策略 | +| Camera 变化触发大规模重算 | 中 | Map update 会重新计算 RenderTree、tile/layer evaluation;未来粒子若绑定 camera 需避免把每帧状态误当 style/source 变更 | +| React re-render | 尚无法判断 | 没有 React/RN 工程 | +| 外部 Overlay 不同步 | 低(当前)/架构禁止 | 当前没有外部粒子 Overlay;正式规则也禁止该路径 | +| Android/iOS 实现分叉 | 高 | 同 Core 但高层 CustomLayer、context/encoder、线程和 surface 生命周期不同 | +| GPU context/surface loss | 高 | Android `onSurfaceCreated` 会 mark context lost 并重建 backend/Renderer;CustomLayer 有 contextLost;iOS 后台/视图重建也涉及资源生命周期 | +| MapView 销毁后资源泄漏 | 中 | Core/RenderCustomLayer 有 deinitialize/contextLost 路径,raw `long` host 和应用归属仍需后续设计验证 | +| Render pass 顺序/状态污染 | 高 | CustomLayer 在 translucent pass;raw backend callback 需要遵守当前 render pass 和状态恢复约束 | + +## 16. 当前架构图 + +### 16.1 App / MapLibre 总体调用图 + +```text +Android 示例或 iOS 示例 + └→ 平台 MapView + └→ 平台 RendererFrontend / MapObserver adapter + └→ mbgl::Map + ├→ Style / Source / Layer snapshot + ├→ Worker + FileSource + └→ Renderer → RenderTree → gfx → 平台 GPU backend +``` + +### 16.2 JS → Native → MapLibre 调用链 + +```text +JS/RN 业务入口:不存在于当前仓库 + └→ RN MapLibre binding:不存在于当前仓库 + └→ Android Java MapView 或 iOS MLNMapView:由外部宿主提供 + └→ JNI/ObjC++ adapter + └→ mbgl::Map → RendererFrontend → Renderer → GPU +``` + +### 16.3 Android + +```text +UI thread: MapView / MapLibreMap / NativeMapView JNI + └→ AndroidRendererFrontend + AsyncTask/mailbox +Render thread: MapRenderer → Renderer → RenderOrchestrator + └→ OpenGL EGL 或 Vulkan device/swapchain + └→ SurfaceView/TextureView → GPU +Workers/FileSource: tile/style parsing、layout、I/O +``` + +### 16.4 iOS + +```text +main/UI: MLNMapView(UIView) + CADisplayLink + └→ MLNMapViewImpl(MapObserver) + RendererFrontend +draw callback: GLKView 或 MTKView + └→ mbgl::Renderer → RenderTree → gfx command encoder + └→ EAGL/OpenGL 或 Metal command buffer → GPU +``` + +### 16.5 数据与渲染边界 + +```text +FileSource / tile / offline fixture + → immutable Source/Layer/UpdateParameters + → RenderTree / RenderSource / RenderLayer + → gfx Texture/Buffer/Drawable/RenderTarget + → backend command encoding + → GPU + +粒子 field / particle state / trail:当前不存在;未来位置为 [未确认] +``` + +## 17. 关键结论分类 + +### 17.1 已确认事实 + +1. 当前仓库是 MapLibre Native monorepo,不是 RN app。 +2. Android 13.4.1、iOS 6.28.0 和 Core commit 已从版本文件/基线确认。 +3. Core 的真实主链是 Map → RendererFrontend → UpdateParameters → Renderer → RenderTree/Orchestrator → Layer/Drawable → gfx backend。 +4. Android 有 OpenGL/Vulkan flavor、独立 renderer thread、JNI Map peer 和实验性 CustomLayer。 +5. iOS 有 `MLNMapView`、CADisplayLink、OpenGL/Metal backend、Native CustomStyleLayer 和 Drawable 示例。 +6. Core CustomLayer render 参数包含相机和 projection 信息;CustomLayer callback 在地图 render pass 内执行。 +7. 现有 fixture 只有目录占位;没有粒子运行时、格点模型、粒子 shader 或速度场解析器。 +8. Core 已有可复用的 immutable snapshot、worker/FileSource、gfx texture/buffer/render target/drawable/upload/shader registry 基础设施。 + +### 17.2 当前限制 + +- 没有 RN 页面、RN binding、Metro/Babel、NativeModule/TurboModule/Fabric,因此不能从本仓库确认 RN 到 MapView 的调用和生命周期。 +- Android CustomLayer 的 Java API 使用 raw native host 指针且标记 experimental;与 iOS CustomStyleLayer 的上下文能力不对称。 +- 正式粒子数据格式、内存模型、插值、GPU state、trail 和跨平台资源生命周期尚不存在。 +- 不可把现有 RasterSource 直接解释为 Raster/Vector Field source;它当前只处理常规 raster tile/image。 +- 本次未进行设备运行时验证,不能把静态可见接口写成已验证的帧率、延迟或 context-loss 行为。 + +### 17.3 候选扩展点(不作最终选择) + +1. Core `CustomLayerHost`/`RenderCustomLayer` 的同帧 callback。 +2. Core `CustomDrawableLayer` + DrawableBuilder/Tweaker/gfx resource。 +3. 新的隔离 Core particle render layer,与现有 RasterLayer/WeatherColorLayer 分离。 +4. 现有 Source/Tile/FileSource/fixture 管线的薄接入,用于离线数据而非直接改造 RasterSource。 +5. 平台薄接入:Android 复用 renderer surface/context 生命周期,iOS 复用 CustomStyleLayer/Metal 或 Core adapter 的同帧 transform。 + +这些只是调查候选;本报告不决定 CPU/GPU、CustomLayer 最终是否采用、OpenGL/Vulkan/Metal 路线、数据纹理格式、shader 架构或线程模型。 + +### 17.4 未确认问题 + +| 问题 | 当前无法确认原因 | 是否阻塞 `20_架构设计` | 最小确认方式 | +|---|---|---|---| +| 实际目标产品的 RN 宿主和 binding 版本 | 不在当前仓库 | 否;当前阶段可先按 Native Core 事实设计边界 | 后续由产品宿主提供 package/native 工程清单 | +| Android 业务侧能否稳定创建并持有 CustomLayerHost | 当前只有 raw `long` 示例和 JNI peer | 否,但影响平台薄接入细节 | 单独最小 Android 示例运行验证,不改本阶段代码 | +| iOS 当前正式发布构建究竟启用 OpenGL 还是 Metal | 源码同时支持,构建宏由外部工程决定 | 否;设计需保留 backend 分支 | 读取目标 Pod/Xcode 构建设置或做最小设备验证 | +| Core render thread 与 iOS app main thread 的确切调度策略 | 静态源码能确认 DisplayLink/MTKView callback,不能确认目标宿主调度 | 否 | 目标 iOS scheme 的运行时线程标记/最小 sample | +| 粒子目标场的具体文件格式、范围、时间步长 | fixture 只有占位目录且设计尚未冻结 | 否;属于后续数据设计输入 | 由设计阶段冻结离线 fixture contract | +| 目标设备的 GPU 特性、纹理格式和可用 compute 能力 | 当前没有设备矩阵或运行时探测结果 | 否;可在设计中标注 capability gate | Android/iOS 设备专项最小验证 | +| 旋转、pitch、wrap、frustum 下粒子坐标的最终视觉约束 | Core matrix 可用,但粒子规则尚未定义 | 否 | 进入 `20_架构设计` 后以 fixture 和截图测试确认 | + +## 18. 是否追加调查、是否进入 20_架构设计 + +选择:**A,可以直接进入 `20_架构设计`。** + +理由:本次已确认项目实际形态、Core 调用链、Android/iOS render lifecycle、CustomLayer/gfx 位置、相机矩阵来源、frame driver、现有数据基础设施和主要风险。未确认项主要是外部目标宿主、运行时设备和未来数据 contract,不满足“必须先追加一个调查任务”的必要条件;它们可作为 `20_架构设计` 的约束和验证清单。 + +下一阶段必须继续遵守:不复制 Mapbox 新源码或 Windy APK 代码;粒子模块高度隔离;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 并共用 Land/Ocean Mask;第一阶段只使用离线 fixture;平台只做薄接入。 + +## 19. 本次未做和边界声明 + +- 未创建粒子实现、shader、CustomLayer 实现、Native Module 或架构重构。 +- 未升级/安装 npm、Gradle、Pod 或其他依赖。 +- 未修改 Android/iOS 原生工程、Gradle/Podfile/Metro/Babel/TypeScript 配置。 +- 未运行构建和测试,因为任务明确为只读调查,且构建可能写入产物;历史基线构建 PASS 仅引用已有基线记录,不作为本次重新验证结果。 +- 按用户前置要求,未自行创建或改写 Step 文件;现有 `docs/50_Step记录/STEP-000_完整环境初始化_v1.md` 的文件内容仍为“进行中”,本报告不改动其历史状态。 diff --git a/docs/40_任务/10_架构调查指令书_v1.md b/docs/40_任务/10_架构调查指令书_v1.md new file mode 100644 index 000000000..1b32b6cc3 --- /dev/null +++ b/docs/40_任务/10_架构调查指令书_v1.md @@ -0,0 +1,754 @@ +# MapLibreParticleLab 10_架构调查指令书 v1 + +## 1. 任务基本信息 + +- 项目:MapLibreParticleLab +- 大阶段:`10_架构调查` +- 当前基线分支:`particle/main` +- 当前前置状态:总则 v1 已提交并推送 +- 任务性质:只读架构调查 +- 是否允许修改业务代码:否 +- 是否允许升级或安装依赖:否 +- 是否允许顺手修复发现的问题:否 + +--- + +## 2. 任务目标 + +对 MapLibreParticleLab 当前真实代码、依赖、原生工程、MapLibre 接入方式和渲染架构进行一次完整调查。 + +本阶段的目的不是设计最终粒子系统,而是回答: + +> 当前项目实际上是怎样运行的,MapLibre 实际经过哪些层完成地图渲染,未来粒子系统有哪些真实可用的接入位置,以及当前仍有哪些关键问题无法仅通过现有代码确认。 + +最终必须形成一份基于实际仓库源码和配置的中文架构调查报告,为下一阶段 `20_架构设计` 提供事实基础。 + +--- + +## 3. 核心原则 + +### 3.1 以实际代码为唯一事实来源 + +不得根据: + +- 记忆 +- MapLibre 一般架构 +- React Native 常见做法 +- 网上其他项目的实现 +- 对新版 MapLibre 的印象 + +推断当前项目架构。 + +所有结论必须优先来自当前 `particle/main` 实际源码、配置、依赖锁定文件和原生工程。 + +如果某项无法通过仓库确认,必须明确标记为: + +`未确认` + +不得自行补全。 + +### 3.2 本任务只调查,不设计 + +本任务不得直接决定: + +- 最终粒子渲染技术路线 +- CPU 粒子还是 GPU 粒子 +- CustomLayer 是否作为最终方案 +- 是否修改 MapLibre Native +- 是否自行创建原生 Renderer +- 是否使用 Metal / OpenGL / Vulkan +- 最终数据纹理格式 +- 最终 Shader 架构 +- 最终线程模型 + +可以列出候选扩展位置和现有限制,但不得提前作最终技术选型。 + +### 3.3 不预设多个调查任务 + +本次应尽可能一次完成整个 `10_架构调查`。 + +只有在调查后发现某个关键问题同时满足以下条件时,才建议追加新的调查任务: + +1. 无法依靠当前项目源码确认; +2. 该问题会直接影响下一阶段技术路线; +3. 需要额外读取第三方源码、编译验证、最小实验或平台专项验证。 + +不得为了形式完整主动拆出大量 `10-01 / 10-02 / 10-03` 子任务。 + +--- + +## 4. 开始前检查 + +开始调查后先确认以下信息: + +- [ ] 当前仓库确实为 MapLibreParticleLab +- [ ] 当前分支为 `particle/main` +- [ ] 读取当前 Git 状态 +- [ ] 确认工作区是否干净 +- [ ] 找到并完整阅读当前“总则 v1” +- [ ] 确认项目根目录结构 +- [ ] 确认主要源码目录 +- [ ] 确认 Android / iOS 原生目录是否存在 +- [ ] 确认依赖清单及 lock 文件 + +如果当前分支或代码状态与上述基线明显不符: + +不要修改仓库状态。 + +继续进行能够完成的只读调查,并在报告中明确记录实际状态。 + +--- + +## 5. 必做调查内容 + +### 5.1 项目整体架构 + +调查当前项目的真实目录与模块组织。 + +至少确认: + +- 项目入口 +- React Native / Expo / Bare RN 等实际工程形态 +- TypeScript / JavaScript 使用情况 +- Android 工程 +- iOS 工程 +- 公共源码目录 +- 地图相关源码目录 +- 实验代码目录 +- 数据处理目录 +- Shader 或图形相关资源目录 +- assets +- scripts +- docs +- 测试目录 +- 构建配置 +- Metro 配置 +- Babel 配置 +- TypeScript 配置 + +报告中需要给出简化的当前项目结构图。 + +只列架构相关目录,不需要机械复制整个文件树。 + +### 5.2 当前依赖与版本 + +读取实际: + +- `package.json` +- lock 文件 +- Android Gradle 相关配置 +- iOS Pod 相关配置 +- 与地图和图形渲染相关的依赖配置 + +重点确认: + +- React Native 版本 +- React 版本 +- MapLibre React Native 包及实际版本 +- MapLibre Android / iOS Native 版本,如能够从当前工程确认 +- Skia 或其他图形库是否存在 +- Reanimated 等与渲染相关的辅助依赖 +- OpenGL / Metal / WebGL / GPU 相关第三方库 +- Native Module / TurboModule / Fabric 相关配置 + +必须区分: + +1. 直接依赖 +2. 间接依赖 +3. 原生 MapLibre 底层实际版本 + +如果底层版本无法确认,明确写“未确认”。 + +--- + +## 6. MapLibre 当前接入链调查 + +这是本任务重点。 + +必须从项目实际地图页面或地图 Host 开始向下追踪。 + +调查: + +`业务/页面` +↓ +`React Native Map 组件` +↓ +`MapLibre React Native 封装` +↓ +`Android / iOS Native View` +↓ +`MapLibre Native` + +需要确认当前代码实际使用的: + +- MapView +- Camera +- Style +- Source +- Layer +- ShapeSource +- RasterSource +- ImageSource +- CustomLayer,如存在 +- 地图事件 +- 地图生命周期事件 +- onPress +- camera change +- style load +- render/frame 相关事件 + +报告中必须给出一张文字形式的实际调用关系图。 + +例如: + +`Screen → MapHost → MapView → RN MapLibre Binding → Native Map View` + +但最终结构必须根据当前代码得出,不得照抄该例子。 + +--- + +## 7. 地图生命周期调查 + +调查当前代码能够观察或控制哪些生命周期阶段。 + +至少关注: + +- 地图组件创建 +- Native Map 创建 +- Style 加载开始 +- Style Ready +- 地图 Ready +- Camera 变化 +- 地图移动 +- 地图缩放 +- 地图旋转 +- 每帧刷新能力 +- App 前后台 +- MapView unmount +- Native 资源释放 + +明确: + +### 当前 TS/RN 层已经直接获得哪些生命周期事件 + +以及: + +### 哪些生命周期只存在 Native 或 MapLibre 内部,目前 JS 层拿不到 + +--- + +## 8. 自定义渲染扩展点调查 + +调查当前工程和当前依赖版本实际存在的自定义渲染能力。 + +重点寻找: + +- CustomLayer +- CustomLayerHost +- Native Layer +- Render Layer +- MapLibre 自定义 layer 接口 +- 自定义 View Overlay +- Surface +- Texture +- GL Context +- Metal Context +- Renderer hook +- Render pass +- Native callback +- Frame callback + +每个发现必须分类。 + +### A. 当前项目已经直接使用 + +### B. 当前依赖已经暴露,但项目尚未使用 + +### C. MapLibre Native 可能支持,但 RN 封装当前未暴露 + +### D. 当前代码无法确认 + +不要把 B/C/D 写成“当前已经支持”。 + +--- + +## 9. Android 渲染架构调查 + +单独调查 Android。 + +至少确认: + +- MapLibre Android Native 如何被当前 RN 包引入 +- MapView 对应原生 View +- renderer 所处代码边界 +- 当前工程是否显式涉及 OpenGL +- 是否能够找到 render thread 相关实现或接口 +- 地图帧刷新由谁驱动 +- Camera / transform 数据当前在哪一层 +- Native Module 是否能够接触 MapView 实例 +- 是否存在可插入自定义渲染的明确入口 +- JS 与 Native 的当前桥接方式 + +如果当前项目使用 New Architecture,也需要说明其实际启用状态。 + +--- + +## 10. iOS 渲染架构调查 + +Android 与 iOS 不得合并推断。 + +单独确认: + +- iOS MapLibre Native 依赖 +- MapView 原生类型 +- Metal / OpenGL 实际情况 +- renderer 生命周期 +- Camera / transform 数据 +- RN 到 Native 的连接方式 +- 是否能够确认自定义渲染接口 +- 当前 Native 层能够访问的 MapLibre 对象范围 + +如果 Android 与 iOS 能力不一致,必须明确列出。 + +--- + +## 11. JS / UI / Native / Render / GPU 边界 + +基于当前工程实际情况,整理线程和执行层级。 + +至少区分: + +- JavaScript Thread +- React Native UI/Main Thread +- Android/iOS Native 层 +- MapLibre 内部 Worker +- MapLibre Render Thread +- GPU + +如果某个平台实际名称有所不同,使用项目和 MapLibre 当前版本对应的真实名称。 + +重点调查: + +- 当前高频数据从 JS 传 Native 会经过什么链路 +- 地图 Camera 高频变化如何传播 +- 是否存在 JS 每帧回调 +- 是否存在 Native 每帧回调 +- 是否存在直接 GPU 资源更新的位置 +- 哪些数据如果每帧跨 JS Bridge 会形成明显风险 + +这里只描述架构事实和风险,不提前设计最终优化方案。 + +--- + +## 12. Camera 与坐标变换能力 + +粒子系统未来必须与地图同步,因此重点调查当前代码能够获得哪些: + +- center +- zoom +- bearing +- pitch +- bounds +- viewport size +- screen coordinate +- geographic coordinate +- projection / unprojection +- camera matrix +- projection matrix +- view matrix +- transform matrix + +区分: + +1. JS API 可以直接获取 +2. Native API 可以获取 +3. MapLibre 内部存在但当前封装未暴露 +4. 无法确认 + +尤其要明确: + +> 当前是否可以获得足够信息,使自定义粒子渲染与 MapLibre 地图的平移、缩放、旋转、pitch 保持严格同步。 + +如果不能确认,不得猜测。 + +--- + +## 13. 帧驱动能力 + +调查未来粒子动画最关键的问题: + +> 谁负责下一帧? + +确认当前可利用的: + +- requestAnimationFrame +- RN animation frame +- Reanimated frame callback +- Native Choreographer / DisplayLink +- MapLibre render loop +- CustomLayer render callback +- GPU render callback + +需要说明: + +- 哪些帧驱动来自地图 Renderer 本身 +- 哪些与地图 Renderer 独立 +- 如果使用独立动画循环,理论上可能出现什么同步问题 + +这里只进行架构分析。 + +--- + +## 14. 当前数据与实验代码调查 + +搜索当前仓库内已经存在的: + +- particle +- wind +- current +- wave +- grid +- vector field +- raster +- interpolation +- texture +- shader +- velocity +- animation +- GPU +- GL +- Metal +- MapLibre custom rendering + +以及相关实验代码。 + +对发现的代码进行分类: + +- 可复用基础代码 +- 仅实验用途 +- 与最终架构可能无关 +- 已弃用 +- 状态不明确 + +不得因为文件名相似就判定“可复用”。 + +需要查看真实实现。 + +--- + +## 15. 海洋/气象格点数据未来可能经过的现有层 + +本任务不设计数据系统,只调查当前项目有哪些已经存在的基础设施。 + +关注未来可能涉及: + +`原始格点数据` +→ `解析` +→ `内存结构` +→ `插值` +→ `渲染资源` +→ `地图显示` + +调查当前是否已经存在: + +- 数据 Fetch 层 +- ArrayBuffer +- TypedArray +- 二进制解码 +- Grid 数据模型 +- 经纬度格点模型 +- 缓存 +- Worker +- Native Buffer +- Texture Upload +- Shader Uniform +- 数据生命周期管理 + +报告只描述现状。 + +--- + +## 16. 性能风险调查 + +不得运行大型性能优化工作。 + +仅根据真实架构识别潜在风险。 + +至少关注: + +- JS 高频循环 +- 每帧 JS → Native 调用 +- 大数组 Bridge 传输 +- JSON 序列化 +- ArrayBuffer 复制 +- Native → GPU 重复复制 +- Texture 高频重建 +- 地图 Camera 变化导致的大规模重新计算 +- React re-render +- MapView Overlay 与地图不同步 +- Android/iOS 实现分叉 +- GPU context 共享风险 +- 地图销毁后的资源泄漏 + +风险需要标记等级: + +- 高 +- 中 +- 低 +- 尚无法判断 + +--- + +## 17. 当前架构图 + +最终调查报告必须至少包含以下几个简化架构图。 + +全部使用 Markdown 文本图即可。 + +### 17.1 当前 App / MapLibre 总体调用图 + +### 17.2 JS → Native → MapLibre 调用链 + +### 17.3 Android 地图渲染相关层级 + +### 17.4 iOS 地图渲染相关层级 + +### 17.5 当前能够确认的数据与渲染边界 + +不得为了图看起来完整而补画未经确认的组件。 + +未知位置使用: + +`[未确认]` + +--- + +## 18. 关键结论分类 + +调查报告结尾必须将全部关键结论整理成四类。 + +### 18.1 已确认事实 + +只能放已有源码或配置证据的事实。 + +### 18.2 当前限制 + +当前架构真实存在的限制。 + +### 18.3 候选扩展点 + +当前看来可能承载未来粒子系统的位置。 + +这里只列候选,不做最终选择。 + +### 18.4 未确认问题 + +列出不能通过本次仓库静态调查确认,但会影响后续架构设计的问题。 + +每个“未确认问题”注明: + +- 为什么当前无法确认 +- 是否会阻塞 `20_架构设计` +- 如果需要继续调查,建议采用什么最小方式确认 + +--- + +## 19. 是否需要追加调查任务的判断 + +本次调查结束时必须明确给出: + +### A. 可以直接进入 `20_架构设计` + +或: + +### B. 必须追加一个新的 `10_架构调查` 子任务 + +只有关键技术事实无法确认并且会直接影响架构选择时才能选择 B。 + +如果选择 B,只提出真正需要的最少调查任务。 + +不得为了保险而把所有未知问题拆成新任务。 + +--- + +## 20. 禁止事项 + +本任务禁止: + +- 修改生产源码 +- 修改实验源码 +- 新建粒子实现 +- 修改 MapLibre 接入 +- 升级 MapLibre +- 升级 React Native +- 安装任何新 npm 包 +- 安装任何新 Pod +- 修改 Gradle 依赖 +- 修改 Podfile +- 修改 Metro +- 修改 Babel +- 修改 TypeScript 配置 +- 修改 Android 原生代码 +- 修改 iOS 原生代码 +- 创建 Shader 实现 +- 创建 CustomLayer 实现 +- 创建 Native Module +- 进行架构重构 +- 顺手修复发现的问题 + +发现问题只记录。 + +--- + +## 21. 允许执行的验证 + +允许执行不会改变项目状态的只读命令,例如: + +- Git 状态查询 +- Git 分支查询 +- 文件搜索 +- 文本搜索 +- 查看 package 配置 +- 查看 Gradle 配置 +- 查看 Pod 配置 +- 查看源码 +- 查看当前项目已有脚本定义 + +如果某个命令可能: + +- 自动安装依赖 +- 修改 lock 文件 +- 修改构建产物 +- 修改 Native 工程 +- 写入项目配置 + +则不要执行。 + +本任务主要依赖静态调查。 + +--- + +## 22. 必做输出 + +### 22.1 架构调查成果 + +必须生成中文调查报告: + +`docs/20_调查/10_架构调查报告_v1.md` + +如果同名文件已经存在,不得覆盖,应递增版本号。 + +该报告是后续 `20_架构设计` 的主要输入。 + +### 22.2 Codex 完成报告 + +必须生成中文执行完成报告: + +`docs/报告/10_架构调查_Codex完成报告_v1.md` + +如果 `docs/报告/` 不存在,则创建该目录。 + +如果同名报告已经存在,不得覆盖旧报告,应递增版本号。 + +--- + +## 23. 架构调查报告必须包含 + +`docs/20_调查/10_架构调查报告_v1.md` 至少包含: + +1. 调查环境 +2. Git / 分支 / 工作区状态 +3. 总则 v1 读取情况 +4. 当前项目整体架构 +5. 核心依赖与版本 +6. MapLibre 接入链 +7. MapLibre 生命周期 +8. 自定义渲染能力 +9. Android 渲染架构 +10. iOS 渲染架构 +11. JS / Native / Render / GPU 边界 +12. Camera / Matrix / 坐标转换能力 +13. 帧驱动能力 +14. 当前数据与粒子相关实验代码 +15. 当前可复用基础设施 +16. 性能与线程风险 +17. 当前架构图 +18. 已确认事实 +19. 当前限制 +20. 候选扩展点 +21. 未确认问题 +22. 是否需要追加调查任务 +23. 是否已经具备进入 `20_架构设计` 的条件 + +--- + +## 24. Codex 完成报告必须包含 + +`docs/报告/10_架构调查_Codex完成报告_v1.md` 至少包含: + +1. 本次实际调查范围 +2. 必做项逐项状态:`已完成 / 未完成 / 无法验证` +3. 未完成或无法验证的原因 +4. 实际生成或修改的文档清单 +5. 是否修改生产代码 +6. 是否安装或升级依赖 +7. 是否存在需要用户关注的阻塞项 +8. 下一步建议 + +--- + +## 25. 必做项完成检查 + +完成前逐项检查: + +- [ ] 已读取总则 v1 +- [ ] 已确认当前实际 Git 基线 +- [ ] 已调查整体目录和工程形态 +- [ ] 已确认核心依赖版本 +- [ ] 已追踪 MapLibre 实际调用链 +- [ ] 已调查地图生命周期 +- [ ] 已调查自定义渲染扩展点 +- [ ] 已分别调查 Android +- [ ] 已分别调查 iOS +- [ ] 已调查线程与执行边界 +- [ ] 已调查 Camera / 坐标 / Matrix 能力 +- [ ] 已调查帧驱动能力 +- [ ] 已搜索现有粒子/场数据/Shader 实验代码 +- [ ] 已调查当前可复用的数据基础设施 +- [ ] 已整理性能风险 +- [ ] 已绘制必要架构图 +- [ ] 已区分事实、限制、候选和未知项 +- [ ] 已判断是否需要追加调查 +- [ ] 已判断能否进入 `20_架构设计` +- [ ] 未修改生产代码 +- [ ] 未升级或安装依赖 +- [ ] 已生成 `docs/20_调查/` 下的正式调查报告 +- [ ] 已生成 `docs/报告/` 下的 Codex 完成报告 + +不得静默跳过任何必做项。 + +无法完成的项目必须明确写: + +`未完成` + +或: + +`无法验证` + +并说明原因。 + +--- + +## 26. 完成标准 + +本任务完成的标准不是“找到一个粒子方案”。 + +真正完成标准是: + +> 已经能够基于 MapLibreParticleLab 当前真实代码,准确描述项目、React Native、Native、MapLibre 和 GPU 之间的现有边界,并明确下一阶段进行粒子系统架构设计时,哪些能力已经确认、哪些扩展位置值得考虑、哪些关键事实仍需要额外验证。 + +达到这个状态后,`10_架构调查` 才算完成。 + +除非存在真正阻塞架构选择的未知项,否则下一步直接进入: + +`20_架构设计` diff --git a/docs/40_任务/20_架构设计指令书_v1.md b/docs/40_任务/20_架构设计指令书_v1.md new file mode 100644 index 000000000..a3ce374f7 --- /dev/null +++ b/docs/40_任务/20_架构设计指令书_v1.md @@ -0,0 +1,1092 @@ +# MapLibreParticleLab 20_架构设计指令书 v1 + +## 1. 任务基本信息 + +- 项目:MapLibreParticleLab +- 大阶段:`20_架构设计` +- 当前基线分支:`particle/main` +- 前置阶段:`10_架构调查` 已完成 +- 前置调查报告:`docs/20_调查/10_架构调查报告_v1.md` +- 前置完成报告:`docs/报告/10_架构调查_Codex完成报告_v1.md` +- 任务性质:架构设计 +- 是否允许修改生产代码:否 +- 是否允许实现粒子系统:否 +- 是否允许安装或升级依赖:否 +- 是否允许运行会修改工程状态的构建/测试:否 + +--- + +## 2. 任务目标 + +基于 `10_架构调查` 已确认的真实仓库结构、MapLibre Core / Android / iOS 渲染链、CustomLayer / CustomDrawable / gfx 扩展能力、Camera/Matrix、帧驱动和生命周期事实,完成 MapLibreParticleLab 第一版正式粒子系统总体架构设计。 + +本阶段必须完成的是: + +> 从已经确认的候选扩展点中做出明确架构选择,冻结粒子系统的模块边界、数据边界、渲染边界、线程边界、平台边界、生命周期、Mask 机制和第一阶段离线 fixture 接口,并明确哪些内容必须进入后续最小技术验证。 + +本阶段不是实现阶段。 + +不得直接创建粒子 Shader、Renderer、Source、CustomLayerHost 实现或平台业务代码。 + +--- + +## 3. 必须先读取的现有文档 + +开始设计前必须完整读取并遵守: + +1. 根目录 `AGENTS.md` +2. `PARTICLE_BASELINE.md` +3. `docs/00_项目总则/` 当前全部有效总则 +4. `docs/00_项目规范/` 当前全部有效规范 +5. `docs/20_调查/10_架构调查报告_v1.md` +6. `docs/报告/10_架构调查_Codex完成报告_v1.md` +7. `docs/10_架构/` 下当前已有架构文档 +8. `粒子开发记录.md` + +如果现有架构文档与本次真实调查结果有冲突: + +- 不得静默覆盖; +- 必须在设计文档中明确指出冲突; +- 以当前有效总则、规范和 `10_架构调查报告_v1` 中的已确认事实作为设计输入; +- 旧文档仅作为历史参考。 + +--- + +## 4. 已冻结的调查事实 + +本任务不得重新把下列已确认事实当成“未知问题”反复调查。 + +### 4.1 仓库形态 + +当前仓库是 MapLibre Native monorepo,不是 React Native / Expo / Bare RN App。 + +当前不存在: + +- RN 页面 +- RN MapLibre binding +- Metro +- Babel +- NativeModule / TurboModule / Fabric 粒子宿主 +- JS 每帧粒子运行时 + +因此当前架构设计必须先以 Native Core + Android/iOS 平台薄接入为事实基础。 + +### 4.2 Core 主渲染链 + +当前已确认主链: + +`mbgl::Map` +→ `RendererFrontend` +→ `UpdateParameters` +→ `Renderer` +→ `RenderTree / RenderOrchestrator` +→ `RenderLayer / LayerGroup / Drawable` +→ `gfx` +→ 平台 GPU backend + +### 4.3 当前候选扩展点 + +已确认候选包括: + +1. Core `CustomLayerHost / RenderCustomLayer` +2. Core `CustomDrawableLayer` +3. 独立 Core particle render layer +4. 现有 Source / FileSource / tile / fixture 管线的薄接入 +5. Android/iOS 仅负责必要的 backend 与生命周期薄适配 + +本任务必须在这些真实候选基础上做架构选择。 + +### 4.4 平台差异 + +- Android 当前存在 OpenGL / Vulkan backend。 +- iOS 当前存在 OpenGL / Metal backend。 +- 两个平台共享 Core,但高层 CustomLayer、context、encoder、surface 生命周期不完全对称。 +- Android raw `CustomLayerHost` 业务侧稳定用法尚未经过运行时验证。 +- iOS 最终目标构建采用 OpenGL 还是 Metal,需要以后结合目标工程确认。 + +### 4.5 当前粒子实现状态 + +当前仓库没有: + +- 正式粒子运行时 +- 速度场解析器 +- 粒子 shader +- GPU ping-pong particle state +- trail render target +- 正式 Grid 数据模型 +- 正式粒子数据 contract + +`particle-fixtures/` 当前只有占位目录。 + +--- + +## 5. 架构设计原则 + +### 5.1 粒子系统必须高度隔离 + +不得把粒子逻辑散落到: + +- RasterLayer +- 普通 Style Layer +- Map 主循环 +- Camera +- Source 通用逻辑 +- Android MapView +- iOS MLNMapView + +粒子系统必须形成清晰、可独立关闭、可独立测试、可独立演进的模块边界。 + +### 5.2 不复制外部闭源或其他产品实现 + +不得复制: + +- Windy APK 代码 +- Mapbox 新版闭源实现 +- 其他商业产品的反编译实现 + +允许参考当前仓库已有 MapLibre 结构和公开 API 形成自己的架构。 + +### 5.3 跨平台共用优先 + +尽量把: + +- 粒子状态 +- 场数据 +- 坐标转换 +- 粒子更新 +- 渲染资源描述 +- Shader contract +- Mask contract +- 生命周期状态机 + +放在 Core 层。 + +Android / iOS 只保留不可避免的: + +- backend resource 接入 +- surface/context/device 生命周期 +- 平台 render callback 连接 +- 平台能力探测 + +不得分别实现两套粒子系统。 + +### 5.4 设计必须支持 backend 差异 + +第一版架构不得假定所有平台永远使用同一种 GPU API。 + +设计必须至少能表达: + +- Android OpenGL +- Android Vulkan +- iOS OpenGL +- iOS Metal + +但本阶段不要求全部 backend 同时实现。 + +必须区分: + +- 架构必须支持 +- 第一阶段实际验证 +- 后续扩展 + +--- + +## 6. 必须完成的核心架构决策 + +本任务最重要的输出不是列候选,而是明确选择。 + +必须对以下问题逐项给出: + +- 推荐方案 +- 为什么 +- 为什么不选其他候选 +- 风险 +- 后续验证点 + +### 6.1 粒子渲染核心入口 + +必须在以下候选中做主路线选择: + +- `CustomLayerHost / RenderCustomLayer` +- `CustomDrawableLayer` +- 新建独立 Core particle render layer +- 其他基于当前源码能够成立的方案 + +不得只写“都可以”。 + +必须明确: + +> 第一阶段正式主路线是什么。 + +如果采用“两阶段方案”,例如先 CustomLayer 验证、后独立 RenderLayer 正式化,也必须说明每阶段退出条件,不能无限双轨。 + +### 6.2 与现有 MapLibre RenderTree 的关系 + +设计必须说明粒子模块: + +- 是否进入 Style / Layer 系统 +- 是否进入 RenderTree +- 是否属于 translucent pass +- 是否需要自己的 render target +- 是否需要 trail accumulation +- 是否参与 MapLibre repaint +- 是否需要主动 `triggerRepaint` +- Camera 更新时如何取得同帧 Transform + +### 6.3 gfx 层使用边界 + +明确粒子系统可使用哪些现有 gfx 能力: + +- Texture2D +- DynamicTexture +- Buffer +- UniformBuffer +- RenderTarget +- DrawableBuilder +- DrawableTweaker +- Shader registry +- UploadPass +- CommandEncoder + +并明确哪些需要新增粒子专用抽象。 + +不得直接把 backend-specific OpenGL/Vulkan/Metal 调用散到公共粒子业务逻辑。 + +--- + +## 7. 模块划分 + +必须给出明确的模块图和建议源码目录。 + +至少考虑以下职责,名称可根据现有 MapLibre 目录风格调整: + +### 7.1 Particle Field + +职责: + +- 描述风、浪、潮、海流等矢量场 +- 范围 +- 网格 +- 时间片 +- U/V 分量 +- NoData +- 有效区 +- 数据版本 + +### 7.2 Particle Data Adapter + +职责: + +- 从第一阶段 fixture 转为统一 Particle Field +- 后续可替换为真实数据源 +- 不把真实天气 Server 协议耦合到 Renderer + +### 7.3 Particle Simulation + +职责: + +- 粒子状态 +- age +- seed +- advection +- respawn +- field sample +- timestep + +必须明确哪些在 CPU,哪些预期在 GPU。 + +### 7.4 Particle Render + +职责: + +- 粒子绘制 +- trail +- fade +- opacity +- viewport +- transform +- render pass + +### 7.5 Particle GPU Resources + +职责: + +- velocity texture +- particle state +- trail target +- uniforms +- shader +- context loss / recreate + +### 7.6 Particle Mask + +职责: + +- GLOBAL +- OCEAN_ONLY +- Land/Ocean Mask +- mask texture / geometry contract +- mask lifecycle + +### 7.7 Particle Controller / Layer State + +职责: + +- enable/disable +- type +- data ready +- GPU ready +- map ready +- context lost +- renderable state +- repaint request + +不得让 UI 直接管理 GPU resource。 + +--- + +## 8. WeatherColorLayer 与 RasterParticleLayer 边界 + +必须明确区分: + +### WeatherColorLayer + +用于: + +- 颜色场 +- 栅格填色 +- 连续色带 +- 不承担粒子动画 + +### RasterParticleLayer + +用于: + +- 风向/海流等动态粒子表现 +- 粒子状态 +- 动画 +- trail +- GPU simulation/render + +两者可以共享: + +- 数据来源 +- grid metadata +- mask +- time slice + +但不得共享: + +- RenderLayer 实例 +- 粒子 GPU 状态 +- 动画生命周期 + +设计文档必须明确二者不是一个 Layer 的两种开关模式。 + +--- + +## 9. Wind / Wave / Tide / Current 显示域 + +当前规则必须进入正式架构: + +### Wind + +`GLOBAL` + +允许覆盖: + +- 海洋 +- 陆地 + +### Wave / Tide / Current + +`OCEAN_ONLY` + +只显示海洋部分。 + +不能要求每种海洋数据各自实现一套陆地裁剪。 + +必须设计统一可复用: + +`Land/Ocean Mask` + +机制。 + +--- + +## 10. Land/Ocean Mask 架构 + +必须给出独立设计。 + +至少明确: + +- Mask 数据来源属于哪个模块 +- Mask 是 CPU geometry 还是 GPU texture,或允许两级实现 +- Mask 坐标系 +- Mask 分辨率 +- viewport / tile / world 范围 +- zoom 时的行为 +- wrap / antimeridian +- pitch / bearing 下如何与地图保持一致 +- Mask 更新条件 +- Mask 缓存 +- 海岸边界 artifact 风险 +- context loss 后如何恢复 + +Mask 必须可以被: + +- Wave +- Tide +- Current +- 未来其他 OCEAN_ONLY 场 + +共享。 + +不得把 Mask 写死到某一种粒子 Layer 内。 + +--- + +## 11. 第一阶段离线 fixture contract + +第一阶段仍然只使用离线 fixture。 + +架构设计必须冻结一个“最小 fixture contract”。 + +至少定义: + +- fixture id +- field type +- bounds +- grid width / height +- coordinate reference +- U/V 分量 +- NoData +- timestamp 或 timeIndex +- data version +- mask 信息 +- endian / precision,如涉及二进制 +- metadata 文件 +- binary/data 文件 + +必须覆盖现有预留 fixture: + +- `uniform-east` +- `uniform-north` +- `vortex` +- `shear` +- `coastal-mask` + +本阶段可以设计格式,但不得生成真实天气数据下载器。 + +--- + +## 12. 数据流设计 + +必须画出完整数据链: + +```text +Offline Fixture +→ Decode / Validate +→ Particle Field +→ CPU/GPU Resource Preparation +→ Velocity Resource +→ Simulation +→ Render +→ MapLibre Render Pass +→ GPU +``` + +同时说明: + +### 哪些步骤低频 + +例如: + +- fixture load +- data decode +- texture upload + +### 哪些步骤每帧 + +例如: + +- particle simulation +- trail update +- draw + +### 哪些步骤 Camera 变化时更新 + +例如: + +- transform uniform +- viewport +- visible bounds + +不得让每帧流程重新解析 field 或重建 texture。 + +--- + +## 13. 线程模型设计 + +必须明确: + +- Map / UI thread +- worker thread +- render thread +- GPU + +各自负责什么。 + +至少回答: + +### Field 解码在哪执行 + +### CPU 插值在哪执行 + +### GPU resource 创建在哪执行 + +### 每帧 Simulation 在哪里执行 + +### Camera Matrix 从哪里读取 + +### Data 更新如何安全交给 Render Thread + +### 如何避免大数组频繁跨线程复制 + +### immutable snapshot 或 resource handle 如何使用 + +### context loss 时哪个线程负责恢复 + +--- + +## 14. Camera / Matrix 同步设计 + +必须基于当前调查确认的: + +- `TransformState` +- `TransformParameters` +- `PaintParameters` +- `CustomLayerRenderParameters` + +设计粒子同帧同步方式。 + +至少覆盖: + +- pan +- zoom +- bearing +- pitch +- resize +- pixel ratio +- world wrap +- antimeridian +- projection/unprojection + +必须明确: + +> 粒子 Renderer 读取的是 MapLibre 当前 render frame 的 transform,而不是由另一个独立 UI/JS 动画循环猜 Camera。 + +--- + +## 15. 帧驱动与动画时钟 + +必须正式定义谁驱动粒子动画。 + +至少说明: + +- 是否依赖 MapLibre render loop +- 粒子活动时如何持续请求下一帧 +- 粒子关闭时如何停止额外 repaint +- 地图静止但粒子活动时如何继续帧 +- App 后台如何暂停 +- View 不可见如何暂停 +- Map/Style 销毁如何停止 +- context loss 如何停止并恢复 + +不得设计独立于 MapLibre Renderer 的第二套无同步动画循环作为正式主路线。 + +--- + +## 16. GPU 粒子状态模型 + +本阶段必须设计,但不实现。 + +至少定义: + +- particle count +- position state +- age +- seed/random +- current / next state +- velocity sampling +- timestep +- respawn +- viewport clipping +- field bounds +- NoData 处理 + +如果采用 GPU simulation,需要明确: + +- ping-pong resource +- compute 或 render-pass simulation +- backend capability gate +- 无 compute 时的 fallback 思路 + +如果第一阶段不使用 compute,也必须明确原因。 + +--- + +## 17. Trail 架构 + +必须说明是否第一阶段就包含 trail。 + +如果包含: + +至少设计: + +- accumulation target +- fade pass +- particle draw pass +- compositing +- resize +- camera movement +- zoom/bearing/pitch 改变时旧 trail 如何处理 +- context loss +- clear conditions + +如果第一阶段不包含 trail: + +必须明确第一阶段视觉范围,并说明后续如何扩展。 + +--- + +## 18. 生命周期状态机 + +必须设计粒子 Layer/Renderer 状态机。 + +建议至少包含: + +- DISABLED +- WAITING_MAP +- WAITING_FIELD +- PREPARING +- READY +- RENDERING +- PAUSED +- CONTEXT_LOST +- ERROR + +可以调整名称,但必须覆盖: + +- Map 创建 +- Style reload +- 数据更新 +- App background +- App foreground +- Surface recreation +- GPU context loss +- Layer removal +- Map destroy + +必须明确资源释放和重新创建责任。 + +--- + +## 19. Android / iOS 薄接入 + +### 19.1 Android + +架构必须说明: + +- Java/Kotlin 业务 API 应保持多薄 +- JNI 是否需要新增稳定 wrapper +- raw `long CustomLayerHost` 是否允许进入正式接口 +- OpenGL / Vulkan backend 差异如何封装 +- SurfaceView / TextureView 生命周期影响 +- requestRender 如何连接 + +不得把 Android 平台代码变成第二套 particle engine。 + +### 19.2 iOS + +必须说明: + +- `MLNCustomStyleLayer` +- `MLNCustomDrawableStyleLayer` +- Metal / OpenGL resource +- CADisplayLink / MapLibre render callback +- backend resource + +在正式架构中的位置。 + +不得因为 iOS 高层 CustomStyleLayer 比 Android 方便,就把 Core 共用逻辑放到 ObjC/Swift。 + +--- + +## 20. 公共 API 设计 + +本阶段需要设计未来供宿主使用的最小 API,但不实现。 + +例如需要能够表达: + +- 创建/删除粒子 layer +- enable/disable +- 设置 field +- 设置 display domain +- 设置 particle options +- 设置 mask +- 数据更新 +- 状态查询 + +必须遵循 MapLibre 当前 API 风格。 + +公共 API 不得暴露: + +- GL texture id +- VkImage +- MTLTexture +- backend-specific command encoder +- raw native host pointer + +除非是严格内部接口。 + +--- + +## 21. 配置与可调参数 + +架构设计应区分: + +### 稳定业务参数 + +例如: + +- particle count +- speed factor +- lifetime +- opacity +- trail fade + +### 内部 backend 参数 + +例如: + +- texture format +- buffer alignment +- workgroup size +- render target format + +业务层不得直接依赖 backend 参数。 + +--- + +## 22. 错误与降级策略 + +设计必须定义: + +- field 无效 +- field 越界 +- mask 无效 +- GPU resource 创建失败 +- backend 不支持 +- context loss +- shader 创建失败 +- memory pressure + +发生时的行为。 + +原则: + +- 地图本身不得因此崩溃 +- 粒子层可以禁用或进入 ERROR +- 不得破坏其他 Style Layer +- 资源失败后必须可安全释放 +- 是否自动重试必须明确 + +--- + +## 23. 性能预算 + +本阶段不要求真实性能测试,但必须建立设计预算。 + +至少定义需要后续验证的指标: + +- FPS +- frame time +- CPU time +- GPU time +- particle count +- texture memory +- buffer memory +- field upload time +- context recreation time + +并明确: + +- 哪些属于第一阶段 gate +- 哪些属于后续优化 + +不得写未经测试的“能稳定 60 FPS”等结论。 + +--- + +## 24. 后续最小技术验证清单 + +架构设计完成后,必须给出“最少数量”的技术验证。 + +只列会影响实现路线的关键验证。 + +可能包括但不限于: + +- Android CustomLayerHost 稳定生命周期 +- Android OpenGL/Vulkan 中粒子资源能否共用 MapLibre render context +- iOS Metal / OpenGL CustomStyleLayer 同帧资源接入 +- context loss / Surface recreate +- MapLibre transform 下的同帧矩阵同步 +- trail render target +- OCEAN_ONLY mask + +不得为了保险列几十个验证任务。 + +必须按: + +1. 是否阻塞正式实现 +2. 是否会改变架构 +3. 最小验证方式 + +进行排序。 + +--- + +## 25. 必须明确拒绝的架构 + +设计报告必须专门列出“不采用的方案”。 + +至少评估: + +- React Native / JS 每帧粒子 +- MapView 外部 Overlay +- 每种海洋场各自一套 Mask +- 直接改造现有 RasterLayer 承担粒子 +- Android/iOS 各写一套独立粒子引擎 +- 每帧重新创建 field texture +- 每帧大数组跨 Java/ObjC/JS 边界 +- backend-specific GPU API 暴露到公共业务 API + +说明拒绝原因。 + +--- + +## 26. 必须生成的架构图 + +正式设计文档至少包含: + +### 26.1 粒子系统总体架构图 + +### 26.2 Core 模块边界图 + +### 26.3 Offline Fixture → GPU 数据流图 + +### 26.4 每帧渲染链图 + +### 26.5 Camera / Matrix 同步图 + +### 26.6 Android 薄接入图 + +### 26.7 iOS 薄接入图 + +### 26.8 Land/Ocean Mask 共用图 + +### 26.9 生命周期状态机图 + +全部使用 Markdown / Mermaid / ASCII 均可。 + +图中不得补入当前不存在且未经设计定义的外部系统。 + +--- + +## 27. 正式设计结论必须冻结的内容 + +设计报告结尾必须列出“架构冻结项”。 + +至少包括: + +- 主粒子渲染路线 +- Core / Platform 分工 +- Layer 边界 +- WeatherColorLayer / RasterParticleLayer 边界 +- Field 模型 +- Fixture contract +- Wind GLOBAL +- Wave/Tide/Current OCEAN_ONLY +- Land/Ocean Mask +- Frame Driver +- Camera Matrix 来源 +- GPU resource 生命周期 +- Android/iOS 薄接入原则 +- 第一阶段 backend 验证范围 +- 第一阶段粒子视觉范围 +- 后续最小技术验证项 + +冻结项是后续 `30_最小技术验证` 和实现任务的依据。 + +--- + +## 28. 不得在本阶段冻结的内容 + +以下如果没有运行时证据,不得假装已确定: + +- 最终 FPS +- 最终最大 particle count +- 最终最佳 texture format +- 最终 Vulkan/Metal compute workgroup +- 具体 GPU 厂商优化 +- 真实天气 Server 数据格式 +- NavSea / RN 最终 Host API +- 最终生产设备能力矩阵 + +这些只能记录为后续验证项。 + +--- + +## 29. 禁止事项 + +本任务禁止: + +- 修改生产源码 +- 创建粒子 Renderer 实现 +- 创建粒子 Shader +- 创建 GPU Buffer/Texture 实现 +- 创建 CustomLayerHost 实现 +- 创建 Android JNI 粒子模块 +- 创建 iOS 粒子类 +- 修改现有 MapLibre Renderer +- 修改 RasterLayer +- 修改 Style parser +- 安装/升级依赖 +- 改 Gradle +- 改 Pod +- 改 CMake +- 改 package lock +- 运行会写入工程状态的自动构建 +- 顺手修复无关问题 + +本任务只允许: + +- 阅读 +- 设计 +- 生成/更新架构文档 +- 生成 Codex 完成报告 +- 追加 `粒子开发记录.md` + +--- + +## 30. 必做输出 + +### 30.1 正式架构文档 + +生成: + +`docs/10_架构/20_粒子系统总体架构设计_v1.md` + +如果同名文件已经存在,不得覆盖,应递增版本号。 + +该文件是本阶段最主要的正式输出。 + +### 30.2 Codex 完成报告 + +生成: + +`docs/报告/20_架构设计_Codex完成报告_v1.md` + +如果同名文件已经存在,不得覆盖,应递增版本号。 + +### 30.3 开发记录 + +追加: + +`粒子开发记录.md` + +记录: + +- `20_架构设计` 已执行 +- 生成的架构文档 +- 核心架构选择 +- 是否需要进入最小技术验证 +- 仍未确认的关键事项 + +不得重写历史记录。 + +--- + +## 31. 不主动创建 Step + +本任务不主动新建或修改 `docs/50_Step记录/` 下的 Step 文件。 + +除非用户已有明确 Step 指令,否则只生成本任务规定的: + +- 架构文档 +- Codex 完成报告 +- 开发记录追加 + +--- + +## 32. Codex 完成报告格式 + +必须逐项报告: + +- `已完成` +- `未完成` +- `无法验证` + +至少包括: + +1. 读取前置文档 +2. 候选渲染路线比较 +3. 主路线选择 +4. Core 模块设计 +5. Data/Field contract +6. GPU resource 设计 +7. Mask 设计 +8. 线程模型 +9. Camera/Matrix 同步 +10. 帧驱动 +11. Android 薄接入 +12. iOS 薄接入 +13. 生命周期 +14. 错误/降级 +15. 性能预算 +16. 最小技术验证清单 +17. 正式架构冻结项 +18. 生成文件 +19. 是否修改生产代码 +20. 是否修改依赖或构建配置 + +--- + +## 33. 完成标准 + +本任务完成,不是指“列出几个可选方案”。 + +必须达到: + +> 已经基于当前 MapLibreParticleLab 的真实 MapLibre Native 架构,为粒子系统选择唯一主架构路线,并冻结 Core / Platform 边界、Particle Field、Simulation、Render、GPU Resource、Mask、Frame Driver、Camera Matrix、生命周期和第一阶段 fixture contract,使下一阶段能够直接针对少量关键未知项做最小技术验证,而不再重新讨论总体架构。 + +如果仍然存在两个以上彼此冲突的主路线且没有选择,则本任务不能标记为完成。 + +--- + +## 34. 下一阶段判定 + +设计结束时必须明确选择: + +### A. 直接进入实现 + +只有所有关键架构假设都已有运行时证据时才允许。 + +或: + +### B. 进入 `30_最小技术验证` + +如果仍存在会影响实现成败的 backend、CustomLayer、context、matrix、trail 或 Mask 等运行时未知项,应选择 B。 + +按照当前调查结果,预期大概率选择 B,但必须由本次设计结果最终判断。 diff --git a/docs/报告/10_架构调查_Codex完成报告_v1.md b/docs/报告/10_架构调查_Codex完成报告_v1.md new file mode 100644 index 000000000..23859fb5d --- /dev/null +++ b/docs/报告/10_架构调查_Codex完成报告_v1.md @@ -0,0 +1,68 @@ +# 10_架构调查 Codex 完成报告 v1 + +日期:2026-08-29 + +任务:`docs/40_任务/10_架构调查指令书_v1.md` + +分支:`particle/main` +结论:调查完成,建议直接进入 `20_架构设计` + +## 1. 实际调查范围 + +本次对当前仓库的目录和工程形态、Git 基线、项目总则与规范、JavaScript/TypeScript 配置、Android Gradle/JNI/renderer、iOS Pod/MapView/backend、MapLibre Core Map/Style/Source/Renderer/Layer/gfx、CustomLayer、Camera/Matrix、帧驱动、线程边界、粒子搜索结果、fixture 和潜在性能风险进行了只读调查。 + +## 2. 必做项状态 + +| 必做项 | 状态 | 说明 | +|---|---|---| +| 读取总则 v1 | 已完成 | 根 AGENTS、`docs/00_项目总则/`、`docs/00_项目规范/` 已完整读取 | +| 确认 Git 基线 | 已完成 | 分支、HEAD、远端、submodule 状态已查询 | +| 确认整体目录和工程形态 | 已完成 | 确认是 MapLibre Native monorepo,不是 RN app | +| 确认核心依赖版本 | 已完成 | package/lock、Android VERSION/Gradle、iOS VERSION/Podspec 已读取 | +| 追踪 MapLibre 实际调用链 | 已完成 | Android、iOS、Core 三条链已追踪 | +| 调查地图生命周期 | 已完成 | Core、Android、iOS 分别记录 | +| 调查自定义渲染扩展点 | 已完成 | A/B/C/D 分类已记录 | +| 分别调查 Android | 已完成 | MapView、JNI、renderer thread、OpenGL/Vulkan、CustomLayer | +| 分别调查 iOS | 已完成 | MLNMapView、CADisplayLink、OpenGL/Metal、CustomStyleLayer | +| 调查线程和执行边界 | 已完成 | Native、Worker、FileSource、Render、GPU 已区分 | +| 调查 Camera/坐标/Matrix | 已完成 | Core TransformState、CustomLayer 参数和高层差异已记录 | +| 调查帧驱动 | 已完成 | Android requestRender、iOS DisplayLink、Core render callback 已记录 | +| 搜索粒子/场数据/Shader 实验 | 已完成 | 无运行时粒子实现;fixture 仅占位,内置 shader 无粒子 shader | +| 调查可复用基础设施 | 已完成 | FileSource、immutable、thread pool、gfx resource、upload/shader registry | +| 整理性能风险 | 已完成 | 逐项标记高/中/低/尚无法判断 | +| 绘制必要架构图 | 已完成 | 总体、JS/Native、Android、iOS、数据边界共五图 | +| 区分事实/限制/候选/未知 | 已完成 | 报告第 17 节分类 | +| 判断是否追加调查 | 已完成 | 选择 A,不追加子任务 | +| 判断能否进入 20_架构设计 | 已完成 | 可以进入,未知项作为设计输入 | +| 未修改生产代码 | 已完成 | 无源码改动 | +| 未升级或安装依赖 | 已完成 | 无 lock/Gradle/Pod/npm 改动 | +| 生成正式调查报告 | 已完成 | `docs/20_调查/10_架构调查报告_v1.md` | +| 生成 Codex 完成报告 | 已完成 | 本文件 | + +## 3. 未完成或无法验证 + +- “工作区干净”未完成:调查开始时存在任务书自身的未跟踪文件,非本次产生。 +- RN 页面、RN binding、NativeModule/TurboModule/Fabric 和 JS 每帧回调无法验证:当前仓库没有 RN 宿主或对应配置。 +- 目标产品最终 iOS backend、目标宿主线程调度、Android 业务侧 raw CustomLayerHost 的稳定用法、设备 GPU 能力和正式粒子数据格式无法仅凭当前静态源码确认。 +- 本次未重新构建或测试:任务性质为只读调查,构建可能改变产物;已有历史构建结果仅按基线记录引用。 + +这些项目已在调查报告中使用“未确认/无法验证”明确标记,没有用外部项目或记忆补全。 + +## 4. 实际生成/修改文档 + +- 新增 `docs/20_调查/10_架构调查报告_v1.md`。 +- 新增 `docs/报告/10_架构调查_Codex完成报告_v1.md`。 +- 追加 `粒子开发记录.md` 的本次调查记录。 +- 未新增或修改 Step 文件:遵循用户“当前不要自行创建 Step”的明确限制。 +- 未修改 `docs/00_项目总则/` 或 `docs/00_项目规范/`。 + +## 5. 生产代码与依赖 + +- 生产代码:未修改。 +- 实验源码:未修改。 +- Android/iOS/Gradle/Pod/Metro/Babel/TypeScript 配置:未修改。 +- 依赖:未安装、未升级、未修改 lock 文件。 + +## 6. 阻塞项和下一步 + +没有阻塞 `20_架构设计` 的关键未知项。建议下一步进入 `20_架构设计`,将本报告中的 CustomLayer/Core gfx 候选、Android/iOS 差异、离线 fixture contract、Land/Ocean Mask 边界和 context-loss 生命周期作为设计约束;在需要选择最终 GPU/线程/数据格式前,再安排最小平台运行时验证。 diff --git a/docs/报告/20_架构设计_Codex完成报告_v1.md b/docs/报告/20_架构设计_Codex完成报告_v1.md new file mode 100644 index 000000000..d3d41347f --- /dev/null +++ b/docs/报告/20_架构设计_Codex完成报告_v1.md @@ -0,0 +1,106 @@ +# 20_架构设计 Codex 完成报告 v1 + +日期:2026-08-29 + +任务:`docs/40_任务/20_架构设计指令书_v1.md` + +分支:`particle/main` + +起始提交:`127de2271480000fcc8f390d81cf446e98757800` + +结束提交:`127de2271480000fcc8f390d81cf446e98757800`(本任务未提交) + +执行结论:PASS(文档设计任务) + +下一阶段:B,进入 `30_最小技术验证` + +## 1. 前置文档与 Git 基线 + +| 项目 | 状态 | 结果 | +|---|---|---| +| 根 AGENTS/基线 | 已完成 | 已读取 `AGENTS.md`、`PARTICLE_BASELINE.md` | +| 项目总则 | 已完成 | 已完整读取 `docs/00_项目总则/` 全部当前有效文件 | +| 项目规范 | 已完成 | 已完整读取 `docs/00_项目规范/` 全部当前有效文件 | +| 前置调查/报告 | 已完成 | 已读取调查报告和 Codex 完成报告 | +| 当前架构/记录/Step | 已完成 | 已读取现有架构文档、开发记录和 STEP-000 | +| 当前设计任务书 | 已完成 | 1092 行已完整读取 | +| Git baseline | 已完成 | `particle/main`,HEAD 为上述起始提交;submodule 已查询 | +| 工作区干净 | 未完成 | 开始时已有上一阶段文档变更及用户提供的两份未跟踪任务书;均予以保留 | + +## 2. 设计必做项状态 + +| 必做项 | 状态 | 结果 | +|---|---|---| +| 候选渲染路线比较 | 已完成 | 比较 CustomLayer、CustomDrawable、独立 Core layer、Overlay | +| 唯一主路线选择 | 已完成 | 独立 Core RasterParticleLayer/RenderRasterParticleLayer + gfx | +| Core 模块设计 | 已完成 | Field、Adapter、Simulation、Render、GPU、Mask、Controller | +| 与 Style/RenderTree/gfx 关系 | 已完成 | 正式进入 Style/RenderTree,offscreen pass + translucent composite | +| WeatherColor/Particle 边界 | 已完成 | 分离 Layer/RenderLayer/GPU 状态/动画生命周期 | +| Data/Field contract | 已完成 | RasterArraySource、immutable snapshot、revision、canonical U/V | +| Fixture contract | 已完成 | metadata.json、field.f32、mask.u8、字节序、校验和、五个预留场景 | +| GPU resource 设计 | 已完成 | velocity、State A/B、Trail A/B、Mask、uniform、shader set | +| GPU state/simulation | 已完成 | 逻辑状态、render-pass ping-pong、NoData/mask/respawn;compute 延后 | +| Trail 架构 | 已完成 | 第一阶段包含 screen-space ping-pong trail,Camera 变化清空 | +| Land/Ocean Mask | 已完成 | 独立 snapshot/cache/texture,OCEAN_ONLY 共享,缺失 fail closed | +| 数据流与频率分类 | 已完成 | 低频、每帧、Camera 变化三类已定义 | +| 线程模型 | 已完成 | Map/UI、Worker、Render、GPU 的所有权和交接已定义 | +| Camera/Matrix 同步 | 已完成 | 只读取当前 render frame Paint/Transform parameters | +| 帧驱动 | 已完成 | MapLibre render loop + needsRepaint,禁用独立粒子时钟 | +| Android 薄接入 | 已完成 | typed JNI;禁止 raw long;OpenGL 主线、Vulkan gate | +| iOS 薄接入 | 已完成 | Core Source/Layer peer;CustomStyleLayer 只作参考;后续 Metal/OpenGL gate | +| 生命周期状态机 | 已完成 | DISABLED 至 ERROR/CONTEXT_LOST 的状态、恢复和释放责任已定义 | +| 公共 API/配置分层 | 已完成 | backend-independent source/layer/options/status;内部参数不外露 | +| 错误/降级 | 已完成 | Field、Mask、GPU、backend、context、shader、memory pressure 均定义 | +| 性能预算 | 已完成 | 给出待验证目标和测量档位,未声称性能已达到 | +| 最小技术验证清单 | 已完成 | 压缩为四项,并标记阻塞性和最小方法 | +| 不采用方案 | 已完成 | 逐项拒绝任务书要求的八类方案及 CPU/compute-only 等 | +| 九类架构图 | 已完成 | 总体、Core、fixture 数据流、每帧、Camera、Android、iOS、Mask、状态机 | +| 架构冻结项 | 已完成 | 14 项正式冻结;同时列出不得冻结项 | +| 下一阶段判断 | 已完成 | B:进入 `30_最小技术验证` | + +## 3. 核心架构选择 + +正式主路线是新增独立 Core `RasterParticleLayer`、`RenderRasterParticleLayer` 和 `mbgl::particle` 模块,通过 `RasterArraySource` 接受 immutable Field snapshot,进入 MapLibre Style/RenderTree,并使用 gfx Texture/RenderTarget/Drawable/Shader/UploadPass 完成 GPU simulation、trail 与 translucent composite。 + +CustomLayer/CustomDrawable 不构成产品双轨;Android raw `CustomLayerHost` 不进入正式 API。平台只提供类型安全 Source/Layer peer、backend lifecycle 与 capability 薄接入。 + +## 4. 实际生成或修改文件 + +- 新增 `docs/10_架构/20_粒子系统总体架构设计_v1.md`。 +- 新增 `docs/报告/20_架构设计_Codex完成报告_v1.md`。 +- 追加 `粒子开发记录.md`。 +- 未修改现有 `docs/10_架构/MapLibre粒子总体架构_v1.md`;其简略 Layer 顺序与本设计一致。 +- 未新增或修改 `docs/50_Step记录/`;符合任务书第 31 节。 + +## 5. 无法验证与未完成 + +### 无法验证 + +- 独立 particle RenderLayer 在 Android OpenGL/Vulkan 的运行时 pass/barrier/context 行为。 +- State/Trail RenderTarget 的实际格式、性能和 context recreation 时间。 +- Camera/wrap/Mask 在真机/模拟器上的视觉正确性。 +- iOS Metal/OpenGL 的粒子 Core layer 薄接入。 +- 最终 FPS、最大粒子数、最佳 texture format、设备能力和生产 Mask 来源。 + +这些均已列入 `30_最小技术验证` 或明确标记为后续 Gate,不影响本次总体设计文档 PASS。 + +### 未完成 + +- 无。本任务要求的设计输出均已完成;生产实现、构建和运行时验证本来就是本任务禁止项。 + +## 6. 代码、依赖、构建与测试 + +- 生产/实验源码:未修改。 +- Android/iOS/Gradle/Pod/CMake/package/lock:未修改。 +- 依赖:未安装、未升级。 +- 构建:未执行;任务明确禁止会修改工程状态的构建。 +- 测试:未执行;本阶段仅做静态架构设计。 +- APK/AAR/其他产物:无。 +- 项目总则/项目规范:未修改。 + +## 7. 与指令书差异、阻塞和建议 + +- 与指令书差异:无。 +- 与长期总则冲突:无。 +- 阻塞:没有阻塞本次设计完成的事项;存在四项会阻塞后续正式实现或平台扩展的运行时验证。 +- 建议:下一份正式指令书进入 `30_最小技术验证`,只覆盖设计文档第 26 节四项验证,不创建完整粒子产品实现,也不重新讨论总体架构。 diff --git a/粒子开发记录.md b/粒子开发记录.md index a4cc6d122..eba8655de 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -16,3 +16,21 @@ - Vulkan Debug AAR 同时包含 Khronos Validation Layer。 - Android SDK 路径已通过 `ANDROID_HOME` 与 `local.properties` 固化。 - 下一步进入 STEP-001:MapLibre Native 粒子接入点只读调查。 + +## 2026-08-29 — 10_架构调查 + +- 按 `docs/40_任务/10_架构调查指令书_v1.md` 完成只读架构调查。 +- 确认当前仓库是 MapLibre Native monorepo,不是 React Native 应用;确认 Android 13.4.1、iOS 6.28.0 和共用 Core 渲染链。 +- 确认 Android OpenGL/Vulkan、iOS OpenGL/Metal、Core CustomLayer/gfx、Camera/Matrix、帧驱动和线程边界;粒子 fixture 目前只有空目录占位,暂无粒子运行时代码。 +- 生成 `docs/20_调查/10_架构调查报告_v1.md` 和 `docs/报告/10_架构调查_Codex完成报告_v1.md`。 +- 本次未修改生产代码、原生工程、依赖或项目总则;遵循用户要求未自行创建或修改 Step 文件。 + +## 2026-08-29 — 20_架构设计 + +- 按 `docs/40_任务/20_架构设计指令书_v1.md` 完成第一版正式粒子系统总体架构设计。 +- 生成 `docs/10_架构/20_粒子系统总体架构设计_v1.md` 和 `docs/报告/20_架构设计_Codex完成报告_v1.md`。 +- 正式主路线冻结为独立 Core `RasterParticleLayer/RenderRasterParticleLayer` + `RasterArraySource` + 隔离 `mbgl::particle` 模块,进入 Style/RenderTree 并使用 MapLibre gfx;CustomLayer/CustomDrawable 不作为产品主宿主。 +- 冻结 WeatherColor/Particle 分层、Field/fixture contract、Wind GLOBAL、海洋场 OCEAN_ONLY 共用 Mask、MapLibre frame driver、同帧 Camera Matrix、GPU resource/context-loss 和平台薄接入原则。 +- 下一阶段选择 `30_最小技术验证`,集中验证独立 RenderLayer、OpenGL/Vulkan ping-pong/trail/context、Camera/wrap/Mask 和 iOS 薄接入。 +- 最终 FPS、最大粒子数、最佳 texture format、生产 Mask 来源、真实天气协议和生产 Host API 仍未确认。 +- 本次未修改生产代码、依赖、构建配置、项目总则或 Step,未运行构建/测试。 From d046be5e522de9f8a02c5133d0ac1c8ff12ce949 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 29 Aug 2026 12:55:54 +0800 Subject: [PATCH 004/363] test: add core particle render layer validation --- CMakeLists.txt | 7 + bazel/core.bzl | 7 + .../30_01_CoreRenderLayer接入验证指令书_v1.md | 731 ++++++++++++++++++ ...01_CoreRenderLayer接入最小技术验证报告_v1.md | 193 +++++ ..._CoreRenderLayer接入验证_Codex完成报告_v1.md | 141 ++++ .../particle_validation_layer_factory.hpp | 16 + .../layers/particle_validation_layer.hpp | 32 + .../src/cpp/style/layers/layer_manager.cpp | 3 + .../src/main/AndroidManifest.xml | 12 + .../ParticleCoreLayerValidationActivity.kt | 93 +++ ...ctivity_particle_core_layer_validation.xml | 48 ++ .../src/main/res/values/descriptions.xml | 1 + .../src/main/res/values/strings.xml | 4 + .../src/main/res/values/titles.xml | 1 + .../src/mbgl/layermanager/layer_manager.cpp | 3 + .../particle_validation_layer_factory.cpp | 25 + .../render_particle_validation_layer.cpp | 132 ++++ .../render_particle_validation_layer.hpp | 30 + src/mbgl/renderer/render_layer.hpp | 3 + src/mbgl/renderer/render_orchestrator.cpp | 2 +- .../layers/particle_validation_layer.cpp | 110 +++ .../layers/particle_validation_layer_impl.hpp | 36 + 粒子开发记录.md | 11 + 23 files changed, 1640 insertions(+), 1 deletion(-) create mode 100644 docs/40_任务/30_01_CoreRenderLayer接入验证指令书_v1.md create mode 100644 docs/60_测试/30_01_CoreRenderLayer接入最小技术验证报告_v1.md create mode 100644 docs/报告/30_01_CoreRenderLayer接入验证_Codex完成报告_v1.md create mode 100644 include/mbgl/layermanager/particle_validation_layer_factory.hpp create mode 100644 include/mbgl/style/layers/particle_validation_layer.hpp create mode 100644 platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt create mode 100644 platform/android/MapLibreAndroidTestApp/src/main/res/layout/activity_particle_core_layer_validation.xml create mode 100644 src/mbgl/layermanager/particle_validation_layer_factory.cpp create mode 100644 src/mbgl/renderer/layers/render_particle_validation_layer.cpp create mode 100644 src/mbgl/renderer/layers/render_particle_validation_layer.hpp create mode 100644 src/mbgl/style/layers/particle_validation_layer.cpp create mode 100644 src/mbgl/style/layers/particle_validation_layer_impl.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a940a55d8..26ab14508 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,6 +246,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/types.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/vertex_buffer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/background_layer_factory.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/particle_validation_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/circle_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/custom_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/fill_extrusion_layer_factory.hpp @@ -357,6 +358,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/style/layer_properties.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/background_layer.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/particle_validation_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/circle_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer_host.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer_init_parameters.hpp @@ -501,6 +503,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/upload_pass.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/vertex_vector.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/background_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/particle_validation_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/circle_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/fill_extrusion_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/fill_layer_factory.cpp @@ -578,6 +581,8 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/image_manager_observer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_background_layer.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_background_layer.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_particle_validation_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_particle_validation_layer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_circle_layer.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_circle_layer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -747,6 +752,8 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/layer_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layer_observer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/particle_validation_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/particle_validation_layer_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer_properties.cpp diff --git a/bazel/core.bzl b/bazel/core.bzl index bbbc4b7a9..198ba465d 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -64,6 +64,7 @@ MLN_GENERATED_STYLE_SOURCE = [ "src/mbgl/style/layers/color_relief_layer.cpp", "src/mbgl/style/layers/background_layer_properties.cpp", "src/mbgl/style/layers/background_layer.cpp", + "src/mbgl/style/layers/particle_validation_layer.cpp", "src/mbgl/style/layers/location_indicator_layer_properties.cpp", "src/mbgl/style/layers/location_indicator_layer.cpp", "src/mbgl/style/light.cpp", @@ -162,6 +163,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/gfx/upload_pass.hpp", "src/mbgl/gfx/vertex_vector.hpp", "src/mbgl/layermanager/background_layer_factory.cpp", + "src/mbgl/layermanager/particle_validation_layer_factory.cpp", "src/mbgl/layermanager/circle_layer_factory.cpp", "src/mbgl/layermanager/custom_layer_factory.cpp", "src/mbgl/layermanager/fill_extrusion_layer_factory.cpp", @@ -233,6 +235,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/renderer/image_manager_observer.hpp", "src/mbgl/renderer/layers/render_background_layer.cpp", "src/mbgl/renderer/layers/render_background_layer.hpp", + "src/mbgl/renderer/layers/render_particle_validation_layer.cpp", + "src/mbgl/renderer/layers/render_particle_validation_layer.hpp", "src/mbgl/renderer/layers/render_circle_layer.cpp", "src/mbgl/renderer/layers/render_circle_layer.hpp", "src/mbgl/renderer/layers/render_custom_layer.cpp", @@ -406,6 +410,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/layer_observer.hpp", "src/mbgl/style/layers/background_layer_impl.cpp", "src/mbgl/style/layers/background_layer_impl.hpp", + "src/mbgl/style/layers/particle_validation_layer_impl.hpp", "src/mbgl/style/layers/circle_layer_impl.cpp", "src/mbgl/style/layers/circle_layer_impl.hpp", "src/mbgl/style/layers/custom_layer.cpp", @@ -660,6 +665,7 @@ MLN_CORE_HEADERS = [ "include/mbgl/i18n/collator.hpp", "include/mbgl/i18n/number_format.hpp", "include/mbgl/layermanager/background_layer_factory.hpp", + "include/mbgl/layermanager/particle_validation_layer_factory.hpp", "include/mbgl/layermanager/circle_layer_factory.hpp", "include/mbgl/layermanager/custom_layer_factory.hpp", "include/mbgl/layermanager/fill_extrusion_layer_factory.hpp", @@ -673,6 +679,7 @@ MLN_CORE_HEADERS = [ "include/mbgl/layermanager/location_indicator_layer_factory.hpp", "include/mbgl/layermanager/raster_layer_factory.hpp", "include/mbgl/layermanager/symbol_layer_factory.hpp", + "include/mbgl/style/layers/particle_validation_layer.hpp", "include/mbgl/map/bound_options.hpp", "include/mbgl/map/camera.hpp", "include/mbgl/map/change.hpp", diff --git a/docs/40_任务/30_01_CoreRenderLayer接入验证指令书_v1.md b/docs/40_任务/30_01_CoreRenderLayer接入验证指令书_v1.md new file mode 100644 index 000000000..9d590d13a --- /dev/null +++ b/docs/40_任务/30_01_CoreRenderLayer接入验证指令书_v1.md @@ -0,0 +1,731 @@ +# MapLibreParticleLab 30_最小技术验证-01_Core RenderLayer 接入验证指令书 v1 + +## 1. 任务基本信息 + +- 项目:MapLibreParticleLab +- 阶段:`30_最小技术验证` +- 子任务:`30-01 Core RenderLayer 接入验证` +- 当前主线:`particle/main` +- 前置阶段: + - `10_架构调查` 已完成 + - `20_架构设计` 已完成 +- 前置正式架构文档: + - `docs/10_架构/20_粒子系统总体架构设计_v1.md` +- 任务性质:最小运行时技术验证 +- 本任务不是正式粒子功能实现 +- 本任务允许最少量验证代码,但必须保持隔离、可识别、可移除 +- 不允许顺手重构 MapLibre +- 不允许扩大到 GPU 粒子、真实场数据、Trail 或 Land/Ocean Mask 实现 + +--- + +## 2. 为什么本阶段先只做 30-01 + +当前总体架构已经选择独立 Core 粒子 Layer 作为正式主路线。 + +本阶段后续原计划还需要验证: + +1. Core RenderLayer / Style order / 同帧 transform / repaint +2. GPU State A/B + Trail A/B ping-pong 与 context recreation +3. Camera / world wrap / Land-Ocean Mask +4. iOS Core Layer 薄接入 + +但第 1 项属于前置门槛。 + +如果第 1 项失败,说明当前正式主路线可能需要调整,后续 GPU、Mask 和平台验证的代码位置、生命周期和验证方式都会变化。 + +因此本任务只完成 `30-01`。 + +只有 `30-01` 得出 PASS 后,才进入下一项。 + +不得提前为后续验证大量写代码。 + +--- + +## 3. 必须先读取 + +Codex 开始前必须完整读取: + +1. 根目录 `AGENTS.md` +2. `PARTICLE_BASELINE.md` +3. `docs/00_项目总则/` 下当前全部有效文档 +4. `docs/00_项目规范/` 下当前全部有效文档 +5. `docs/20_调查/10_架构调查报告_v1.md` +6. `docs/10_架构/20_粒子系统总体架构设计_v1.md` +7. `docs/报告/10_架构调查_Codex完成报告_v1.md` +8. `docs/报告/20_架构设计_Codex完成报告_v1.md` +9. `粒子开发记录.md` +10. 与下列现有实现直接相关的当前源码: + - Style Layer 注册链 + - RenderLayer / RenderTree / RenderOrchestrator + - RasterLayer 或其他最接近的现有 Layer 示例 + - CustomLayer / CustomDrawableLayer 示例 + - Android MapRenderer / AndroidRendererFrontend + - `triggerRepaint` / `requestRender` + - `TransformState` / `TransformParameters` / `PaintParameters` + +不得凭记忆或 MapLibre 通用知识直接开写。 + +--- + +## 4. 开始前 Git 检查 + +先执行只读检查并记录: + +- 当前分支 +- 当前 HEAD +- `git status` +- `origin` / 当前公开远端 +- `particle/main` 是否与远端同步 + +如果存在与本任务无关的本地修改: + +- 不得删除 +- 不得覆盖 +- 不得顺带提交 +- 在完成报告中记录 +- 本任务提交时只 stage 本任务相关文件 + +如果当前不是 `particle/main`,不要擅自切换覆盖用户工作。 + +先明确报告实际状态。 + +--- + +## 5. 验证目标 + +本任务只回答下面四个问题。 + +### V1. 独立 Core Layer 能否进入真实 MapLibre Style / RenderTree 顺序 + +必须验证一个最小实验 Layer: + +- 由 Core Style/Layer 系统识别 +- 能生成对应 RenderLayer +- 能进入真实 RenderTree +- 能按 Style Layer 顺序参与渲染 +- 不依赖 MapView 外部 Overlay +- 不依赖 JS/RN 每帧逻辑 +- 不把实现塞进现有 RasterLayer + +### V2. 该 Layer 能否在同一 MapLibre render frame 获得当前 Transform + +至少验证能够在真正绘制路径中读取或使用: + +- viewport size +- zoom +- bearing +- pitch +- projection / transform matrix 中架构要求的必要部分 + +验证重点不是把数值打印出来,而是确认这些值来自: + +> 当前 MapLibre render frame 的真实 `PaintParameters / TransformParameters / TransformState` 链 + +而不是另建独立 Camera 状态。 + +### V3. Layer 活动时能否驱动持续 repaint + +验证: + +- Layer 处于“动画活动”状态时,可以通过 MapLibre 自己的 render loop 持续获得下一帧 +- 不建立独立于 MapLibre 的第二套动画循环 +- 不依赖 Android UI Timer +- 不依赖 JS `requestAnimationFrame` + +### V4. Layer 停止活动后能否停止额外 repaint + +验证: + +- Layer 切换为静止/disabled 后,不继续无意义请求下一帧 +- 地图自身正常 pan/zoom/render 不受影响 +- 不产生永久 continuous rendering + +--- + +## 6. 本任务不是粒子实现 + +验证代码只能证明架构入口成立。 + +禁止在本任务实现: + +- 速度场 +- U/V Grid +- Field decode +- particle position +- age +- seed +- advection +- velocity texture +- particle state texture +- ping-pong +- Trail +- fade pass +- Land/Ocean Mask +- Wave/Tide/Current +- Wind fixture +- 真正 Particle Shader +- compute shader + +如需要视觉确认,只允许使用最简单的验证图元或调试绘制。 + +例如: + +- 单色矩形 +- 三角形 +- 简单全屏/局部 quad +- 简单随帧变化的 alpha / position + +不得把验证图元包装成“粒子初版”。 + +--- + +## 7. 验证代码命名 + +验证代码必须明确表明其临时技术验证属性。 + +优先使用: + +`ParticleValidation*` + +或: + +`RasterParticleValidation*` + +不要直接把不成熟的验证代码命名成最终生产类并假装已经冻结。 + +如果必须使用正式架构类名才能验证注册链,例如: + +- `RasterParticleLayer` +- `RenderRasterParticleLayer` + +则必须: + +1. 只实现验证所需最小骨架; +2. 在代码和文档中明确 `VALIDATION ONLY`; +3. 不提前加入未来未验证业务接口; +4. 不把临时调试字段伪装成正式公共 API。 + +--- + +## 8. 最小实现范围 + +Codex 应先调查当前最小实现需要触及哪些文件。 + +原则: + +> 能用 5 个文件解决,不要改 15 个文件。 + +预计可能涉及,但不得机械照搬: + +- Core style layer 声明 +- Core style layer 实现 +- 对应 RenderLayer +- LayerManager / 注册位置 +- Android Test App 或现有测试入口 +- 最小验证日志/状态 + +最终以当前真实代码为准。 + +不得为了“以后可能用到”提前创建整套 particle 目录树。 + +--- + +## 9. Style / RenderTree 验证要求 + +必须证明实验 Layer 是通过正式 MapLibre Layer 链进入渲染。 + +验证报告至少记录实际链: + +```text +Style Layer +→ LayerManager / RenderLayer creation +→ RenderTree / RenderOrchestrator +→ 对应 RenderLayer +→ Render pass +``` + +必须指出: + +- 实际插入哪个 pass +- 与前后普通 Style Layer 的 order 是否正确 +- 是否需要 tile/source +- 当前验证 Layer 是否 source-less +- 如果 source-less 不可行,实际最小 Source 依赖是什么 + +不能只通过 CustomLayer 示例证明。 + +本任务要验证的是总体架构选择的“独立 Core Layer 路线”。 + +--- + +## 10. 同帧 Camera / Matrix 验证 + +验证代码必须从当前 render path 内取得 transform。 + +至少记录: + +- 获取位置 +- 对应源码对象 +- 生命周期 +- 是否每 frame 更新 +- 是否与当前 RenderTree frame 同步 + +至少验证以下地图变化不会让实验 Layer 明显脱离地图: + +- pan +- zoom +- bearing +- pitch +- resize + +本任务不要求完成 world wrap / antimeridian 专项验证。 + +这些留给后续 Mask/Camera 技术验证。 + +--- + +## 11. Repaint 验证 + +实验 Layer 必须提供最小“活动/停止”活动模型。 + +例如内部验证状态: + +```text +ACTIVE +STATIC +``` + +### ACTIVE + +必须证明: + +- 当前 frame 完成后仍会请求 MapLibre 下一帧 +- 连续帧来自 MapLibre render/repaint 链 +- 能观察到 frame counter 持续增长 + +### STATIC + +必须证明: + +- 不再由该 Layer 主动触发下一帧 +- 地图静止时 frame counter 不继续无意义增长 +- 用户操作地图时仍能正常重新绘制 + +不得使用平台 Timer 模拟。 + +--- + +## 12. 日志要求 + +验证期间允许增加最少量调试日志。 + +日志至少能识别: + +- Layer create +- RenderLayer create +- first render +- frame counter +- current active/static +- repaint request +- relevant transform snapshot +- destroy/deinitialize + +禁止每帧输出大量完整 matrix 导致日志爆炸。 + +建议: + +- 首帧输出一次完整关键参数 +- 后续每 N 帧输出 frame counter +- 状态变化时输出 +- 销毁时输出 + +验证完成后是否保留日志,由报告明确说明。 + +--- + +## 13. Android 第一验证平台 + +本任务第一运行平台固定为: + +**Android OpenGL** + +原因: + +- 当前架构设计将它作为第一共同验证入口; +- 本任务只验证 Core Layer + 同帧 transform + repaint; +- 不在本任务同时扩大到 Vulkan / iOS。 + +Vulkan 和 iOS 留到后续对应验证。 + +--- + +## 14. Android 编译规则:必须由用户手工执行 + +本项目 Android 编译必须由用户手工执行。 + +Codex 禁止运行: + +- Gradle Android build +- `./gradlew assemble...` +- `./gradlew install...` +- Android npm build +- 任何等价 Android 编译命令 + +Codex 可以: + +- 检查现有 Gradle 配置 +- 准备代码 +- 给出用户应执行的准确编译命令 +- 分析用户返回的编译错误 +- 在允许范围内修正本任务代码 + +Codex 不得代替用户执行 Android 编译。 + +--- + +## 15. 运行/设备验证规则 + +本任务不得默认替用户执行: + +- ADB 安装 +- ADB 启动 +- ADB 日志抓取 +- 真机操作 + +除非用户在后续明确授权。 + +因此第一轮 Codex 工作完成后,应输出: + +1. 用户手工编译命令 +2. 应打开的 Test App / 页面 / Activity +3. 需要执行的地图操作 +4. 需要观察的日志 +5. PASS/FAIL 判定方法 + +用户提供编译/运行结果后,再完成最终技术验证结论。 + +--- + +## 16. 两阶段执行方式 + +本任务允许同一份指令书分两次 Codex 执行。 + +### 阶段 A:验证代码准备 + +Codex: + +1. 读取基线 +2. 写最小验证代码 +3. 做静态检查 +4. 生成暂定验证报告 +5. 给出用户手工编译/运行步骤 +6. commit + push 到 `particle/main` + +此时报告结论必须写: + +`WAITING_MANUAL_VALIDATION` + +不得写 PASS。 + +### 阶段 B:用户返回实际编译/运行结果后 + +Codex: + +1. 读取用户提供的编译结果、日志和现象 +2. 如果失败,只修复本验证范围内问题 +3. 如果架构假设本身失败,停止,不强行修成另一套架构 +4. 更新正式验证报告 +5. 更新 Codex 完成报告 +6. 更新 `粒子开发记录.md` +7. commit + push 到 `particle/main` + +最终只能是: + +- `PASS` +- `FAIL` +- `BLOCKED` + +--- + +## 17. PASS 标准 + +只有同时满足以下条件,`30-01` 才能 PASS: + +- [ ] 独立 Core Style Layer 被正式识别 +- [ ] 对应 RenderLayer 被创建 +- [ ] 真正进入 RenderTree / Render pass +- [ ] Layer order 正确 +- [ ] 从当前 render frame 获得必要 Transform +- [ ] pan 正常 +- [ ] zoom 正常 +- [ ] bearing 正常 +- [ ] pitch 正常 +- [ ] resize 正常 +- [ ] ACTIVE 时由 MapLibre render loop 持续 repaint +- [ ] STATIC 时停止该 Layer 的额外 repaint +- [ ] 地图普通渲染不受破坏 +- [ ] 未使用外部 Overlay +- [ ] 未建立平台 Timer 动画循环 +- [ ] 未实现超出本任务范围的粒子系统 + +任何一项关键门槛失败,不得标 PASS。 + +--- + +## 18. FAIL 与停止条件 + +出现以下任一情况时,应停止继续向后实现: + +### F1 + +独立 Core Layer 无法在不破坏 MapLibre 现有主架构的情况下进入真实 RenderTree。 + +### F2 + +无法从该 Layer 的真实 render frame 获得足够 transform。 + +### F3 + +必须依赖外部 Overlay 或独立动画 loop 才能工作。 + +### F4 + +持续 repaint 必须通过明显侵入 Map 主循环才能实现。 + +### F5 + +验证要求迫使 Android/iOS 分别拥有不同 particle engine。 + +若触发 F1-F5: + +- 停止 +- 不继续 GPU ping-pong +- 不继续 Mask +- 不继续 iOS 实现 +- 报告失败事实 +- 回到架构设计重新决策 + +不得为了让验证“通过”扩大修改范围。 + +--- + +## 19. 禁止事项 + +本任务禁止: + +- 正式实现 Particle Field +- 正式实现 RasterArraySource 数据链 +- 正式实现 GPU Simulation +- 正式实现 Trail +- 正式实现 Mask +- 接真实天气数据 +- 引入新第三方依赖 +- 升级 MapLibre +- 升级 Android SDK / NDK / Gradle +- 修改无关 Layer +- 重构 Renderer +- 重构 Map +- 修改 iOS 代码 +- 实现 Vulkan 版本 +- 建立 RN/JS bridge +- 新增产品 API +- 清理与本任务无关的旧代码 + +--- + +## 20. 文档输出 + +### 20.1 技术验证报告 + +生成: + +`docs/60_测试/30_01_CoreRenderLayer接入最小技术验证报告_v1.md` + +如果同主题已有文件,按版本递增,不覆盖旧文件。 + +报告必须区分: + +- 静态确认 +- 用户手工编译结果 +- 用户运行结果 +- 实际日志证据 +- 推论 + +没有用户运行结果时,只能写: + +`WAITING_MANUAL_VALIDATION` + +### 20.2 Codex 完成报告 + +生成: + +`docs/报告/30_01_CoreRenderLayer接入验证_Codex完成报告_v1.md` + +同主题已有报告时递增版本号。 + +### 20.3 开发记录 + +追加: + +`粒子开发记录.md` + +不得覆盖历史内容。 + +--- + +## 21. 不主动创建 Step + +本任务不主动创建或修改 `docs/50_Step记录/`。 + +只有用户明确要求时才创建 Step。 + +--- + +## 22. Git 收尾规则 + +本任务凡产生文件修改,都必须提交并 push。 + +目标: + +- 本地主线:`particle/main` +- 公开远端对应分支:`particle/main` + +### 22.1 提交范围 + +只允许提交: + +- 本任务验证代码 +- 本任务验证文档 +- 本任务完成报告 +- 本任务对 `粒子开发记录.md` 的追加 + +不得把与本任务无关的本地修改一起提交。 + +### 22.2 阶段 A + +验证代码准备完成后: + +1. 检查 `git diff` +2. 明确列出本任务文件 +3. commit +4. push 到公开 `particle/main` +5. 确认远端可见 + +推荐提交信息: + +`test: add core particle render layer validation` + +### 22.3 阶段 B + +用户手工验证结果回填后: + +1. 更新验证报告 +2. 更新完成报告 +3. 更新开发记录 +4. 如有必要修复最小验证代码 +5. commit +6. push 到公开 `particle/main` + +推荐提交信息: + +`test: finalize core particle render layer validation` + +### 22.4 Push 失败 + +如果 push 失败: + +- 不得声称任务完整完成 +- 完成报告必须写明: + - push 失败 + - 错误原因 + - 本地 commit hash + - 目标远端 + - 目标分支 + +--- + +## 23. Git 信息记录 + +Codex 完成报告必须记录: + +- 任务开始 HEAD +- 本次主要变更 commit hash +- commit message +- push 是否成功 +- 公开远端 +- 远端分支 +- push 后远端可见状态 +- 是否存在未提交的无关本地改动 + +为避免“提交文件记录自己的 commit hash”产生自引用问题: + +- 完成报告中的 `主要变更 commit hash` 记录包含验证代码和主要文档的实质性提交; +- 如果随后还有仅用于回填报告元数据的收尾提交,Codex 最终回复中必须另外报告最终远端 HEAD; +- 不得为了回填当前提交自己的 hash 无限 amend。 + +--- + +## 24. Codex 完成报告必须逐项说明 + +至少包括: + +1. 前置文档读取 +2. 当前 Git 基线 +3. 实际修改文件 +4. Style Layer 接入位置 +5. RenderLayer 创建链 +6. RenderTree / pass +7. Transform 获取链 +8. Repaint 链 +9. Android OpenGL 验证入口 +10. 用户手工编译命令 +11. 用户手工运行步骤 +12. 当前状态: + - WAITING_MANUAL_VALIDATION + - PASS + - FAIL + - BLOCKED +13. 是否修改生产代码 +14. 是否扩大架构范围 +15. 是否安装/升级依赖 +16. Git commit +17. push 结果 +18. 是否可以进入下一个最小技术验证 + +--- + +## 25. 进入下一验证的条件 + +只有最终结果: + +`PASS` + +才允许进入下一个技术验证。 + +下一项预计验证: + +> GPU State A/B + Trail A/B 的 gfx ping-pong、RenderTarget、同步/barrier 与 context recreation。 + +如果本任务是: + +- FAIL +- BLOCKED + +则不得提前执行下一项。 + +必须先回到架构层决定如何处理。 + +--- + +## 26. 本任务完成定义 + +`30-01` 真正完成必须满足: + +> 用户手工 Android OpenGL 编译和运行后,已经通过实际运行证据确认:独立 Core Layer 可以进入 MapLibre 正式 Style/RenderTree 渲染链,在同一 render frame 使用 MapLibre 当前 Transform,并通过 MapLibre 自己的 repaint 机制在活动时持续渲染、停止时停止额外帧。 + +只有静态代码完成、没有实际运行结果,不叫验证完成。 + +只能标记: + +`WAITING_MANUAL_VALIDATION` diff --git a/docs/60_测试/30_01_CoreRenderLayer接入最小技术验证报告_v1.md b/docs/60_测试/30_01_CoreRenderLayer接入最小技术验证报告_v1.md new file mode 100644 index 000000000..d72806b1e --- /dev/null +++ b/docs/60_测试/30_01_CoreRenderLayer接入最小技术验证报告_v1.md @@ -0,0 +1,193 @@ +# 30-01 Core RenderLayer 接入最小技术验证报告 v1 + +日期:2026-08-29 + +当前状态:`WAITING_MANUAL_VALIDATION` + +本报告只记录阶段 A 的代码准备与静态证据。Android OpenGL 编译、安装、运行、手势和日志结果尚未由用户回填,因此不得标记 PASS。 + +## 1. 验证范围 + +本验证只回答:独立 Core Style Layer/RenderLayer 是否能进入正式 RenderTree 顺序、读取同帧 transform、在 ACTIVE 时通过 MapLibre 自身 loop 请求下一帧,并在 STATIC 时停止额外 repaint。 + +未实现速度场、粒子状态、advection、GPU ping-pong、Trail、Mask、真实天气数据或 compute shader。 + +## 2. 最小验证实现 + +- Style type:`particle-validation`。 +- Style class:`style::ParticleValidationLayer`,明确标记 `VALIDATION ONLY`。 +- Render class:`RenderParticleValidationLayer`。 +- Source:不要求 source;`LayerTypeInfo::Source::NotRequired`。 +- Pass:`RenderPass::Translucent`。 +- 状态:style paint 字段 `particle-validation-active=true/false`,只用于本验证。 +- Drawable:一个不输出像素的验证 Drawable,用于进入真实 LayerGroup/pass 顺序并在 drawable tweaker 中取得当前 `PaintParameters`;不使用 `CustomLayerHost`,不构成外部 CustomLayer 路线。 +- Android API:没有新增 Java/JNI 产品 API;Android 只把该类型注册为 JSON style 可用的 core-only layer。 + +## 3. Style / RenderTree / pass 静态确认 + +实际链路为: + +```text +Android Test App inline Style JSON +→ style::conversion::Converter> +→ LayerManagerAndroid::getFactory("particle-validation") +→ ParticleValidationLayerFactory::createLayer +→ style::ParticleValidationLayer immutable Impl +→ RenderOrchestrator layer diff/order +→ LayerManagerAndroid::createRenderLayer +→ ParticleValidationLayerFactory::createRenderLayer +→ RenderParticleValidationLayer +→ source-less LayerRenderItem +→ RenderParticleValidationLayer::update +→ source-less LayerGroup(layerIndex) +→ validation Drawable(RenderPass::Translucent) +→ main translucent pass 的 LayerGroup 顺序 +``` + +Test App style 顺序固定为: + +```text +validation-background +→ validation-below(蓝色大圆) +→ particle-validation-core +→ validation-above(黄色小圆) +``` + +验证 Drawable 的 `layerIndex` 由 RenderOrchestrator 正式 style order 分配,LayerGroup 以同一 index 注册。运行日志同时记录 `layerIndex` 和 pass 内的 `currentLayer`。实际 Android 顺序仍需用户运行日志确认。 + +## 4. 同帧 Transform 静态确认 + +验证日志在 DrawableTweaker 的 `execute(gfx::Drawable&, PaintParameters&)` 中生成。该回调发生在 MapLibre main translucent pass 对应 LayerGroup/Drawable 的真实执行路径中,直接读取: + +- `PaintParameters::state.getSize()`:viewport; +- `PaintParameters::state.getZoom()`:zoom; +- `PaintParameters::state.getBearing()`:bearing; +- `PaintParameters::state.getPitch()`:pitch; +- `PaintParameters::transformParams.projMatrix`:当前 frame projection matrix 摘要; +- `PaintParameters::frameCount`:MapLibre frame number。 + +未创建独立 Camera 状态,未从 Android UI 层复制或推算 transform。 + +## 5. Repaint 链静态确认 + +本任务增加一个薄 Core hook: + +```text +RenderParticleValidationLayer::needsRepaint() +→ RenderOrchestrator::hasTransitions() 汇总 +→ RenderTreeParameters::needsRepaint +→ RendererObserver::onDidFinishRenderingFrame(needsRepaint) +→ Map::Impl::onDidFinishRenderingFrame +→ needsRepaint=true 时 Map::Impl::onUpdate() +→ AndroidRendererFrontend::update() +→ Android MapRenderer.requestRender() +→ 下一 MapLibre render frame +``` + +所有其他 RenderLayer 默认返回 false,因此现有 Layer 行为不变。 + +- ACTIVE:验证 Layer 可见且 active 时返回 true。 +- STATIC:active=false 时返回 false;地图自己的 transition、手势和 source 更新仍可请求 frame。 +- 未使用 Android Timer、Handler 循环、JS `requestAnimationFrame` 或第二套动画 loop。 + +## 6. 日志格式 + +日志关键字统一为 `ParticleValidation`,包括: + +- Style Layer create/destroy; +- RenderLayer create/destroy; +- ACTIVE/STATIC; +- validation Drawable create/destroy; +- 首帧及每 60 帧:frame counter、MapLibre frame、状态、repaint、pass、layer order、viewport、zoom、bearing、pitch、projection matrix 摘要。 + +预期 ACTIVE 样例: + +```text +ParticleValidation: render frame=60 mapFrame=... state=ACTIVE repaint=requested pass=translucent layerIndex=... currentLayer=... viewport=... zoom=... bearing=... pitch=... proj=[...] +``` + +预期 STATIC 样例: + +```text +ParticleValidation: state=STATIC ... +ParticleValidation: render frame=1 ... state=STATIC repaint=stopped ... +``` + +地图静止后不应继续出现该 STATIC 实例的周期性 `frame=60/120/...`。 + +## 7. Codex 已执行检查 + +### 7.1 通过 + +- `git diff --check`:PASS。 +- 新增/修改 C++ 文件 `clang-format`:PASS。 +- Android Manifest、layout、values XML 使用 Python 标准库只读解析:PASS。 +- Android `MapLibreMap.getCameraPosition/setCameraPosition` API 静态确认:PASS。 +- Linux OpenGL CMake 重新配置:PASS。 +- 精确编译以下 Linux Core 对象:PASS: + - `particle_validation_layer_factory.cpp.o` + - `particle_validation_layer.cpp.o` + - `render_particle_validation_layer.cpp.o` + - `render_orchestrator.cpp.o` + - default `layer_manager.cpp.o` + +### 7.2 未完成 + +- Linux OpenGL 全量增量编译:公共 `RenderLayer` 头变化触发 321 个对象重编译;在 `41/321` 无错误时主动停止,随后改为上述任务相关对象精确编译并通过。 +- Android OpenGL Gradle/NDK/Test App 编译:按指令书禁止 Codex 执行,等待用户手工完成。 +- Android 运行、日志、pan/zoom/bearing/pitch/resize、ACTIVE/STATIC:等待用户。 + +## 8. 用户手工编译 + +在项目 Linux 环境执行: + +```bash +cd /mnt/sda1/MapLibreParticleLab +source /root/.maplibre-particle-env +cd platform/android +RENDERER=opengl BUILDTYPE=Debug JOBS=3 make android-x86-64 +``` + +预期 APK: + +```text +platform/android/MapLibreAndroidTestApp/build/outputs/apk/opengl/debug/MapLibreAndroidTestApp-opengl-debug.apk +``` + +若目标设备不是 x86_64,请将 `android-x86-64` 换成对应现有 Make target;不要切换 Vulkan,本任务平台固定 Android OpenGL。 + +## 9. 用户手工运行步骤 + +1. 安装并打开 `MapLibre Native Android TestApp`。 +2. 在 `Custom Layer` 分类打开 `Particle Core Layer Validation`。 +3. 确认页面显示蓝色大圆和其上的黄色小圆,初始状态文字为 ACTIVE。 +4. 过滤日志关键字:`adb logcat | grep ParticleValidation`;也可使用 Android Studio Logcat 搜索同一关键字。 +5. ACTIVE 静置至少 3 秒,确认 `frame=60/120/...` 持续增长。 +6. 分别执行 pan、双指 zoom、rotate、pitch,确认地图正常,并保存变化前后的 transform 日志。 +7. 旋转设备或改变模拟器窗口尺寸,确认 `viewport` 更新且页面正常。 +8. 点击 STATIC,静置至少 3 秒;确认不再周期性增长。 +9. STATIC 下再次 pan/zoom,确认地图仍会按正常地图事件重绘;操作停止后不继续产生验证层额外帧。 +10. 再点 ACTIVE,确认连续帧恢复。 + +## 10. PASS / FAIL 判定 + +### PASS 候选证据 + +- Style Layer、RenderLayer 和 validation Drawable 均创建; +- 日志显示 `pass=translucent` 且 layer order 与 style 顺序一致; +- viewport/zoom/bearing/pitch/projection 摘要随对应操作更新; +- ACTIVE frame counter 持续增长; +- STATIC 静止后停止周期增长,但地图手势仍正常; +- 无崩溃、黑屏、永久 continuous rendering 或普通地图破坏。 + +### FAIL/停止 + +若 layer 无法创建、无法进入 pass、transform 不是同帧、ACTIVE 无法持续、STATIC 无法停止,或必须改用 Overlay/Timer/大改 Map 主循环,则记录 FAIL 并停止后续 GPU/Mask/iOS 验证。 + +## 11. 当前结论 + +静态代码和 Linux 任务相关对象编译支持该路线具备可验证性,但缺少 Android OpenGL 实际编译与运行证据。 + +当前唯一合法结论:`WAITING_MANUAL_VALIDATION`。 + +不得进入下一项 GPU State A/B + Trail A/B 验证。 diff --git a/docs/报告/30_01_CoreRenderLayer接入验证_Codex完成报告_v1.md b/docs/报告/30_01_CoreRenderLayer接入验证_Codex完成报告_v1.md new file mode 100644 index 000000000..e793baa7d --- /dev/null +++ b/docs/报告/30_01_CoreRenderLayer接入验证_Codex完成报告_v1.md @@ -0,0 +1,141 @@ +# 30-01 Core RenderLayer 接入验证 Codex 完成报告 v1 + +日期:2026-08-29 + +任务:`docs/40_任务/30_01_CoreRenderLayer接入验证指令书_v1.md` + +执行阶段:A(验证代码准备) + +当前结论:`WAITING_MANUAL_VALIDATION` + +## 1. 前置文档读取 + +已读取根 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目总则/` 全部当前有效文件、`docs/00_项目规范/` 全部当前有效文件、架构调查报告、总体架构设计、两份前置 Codex 报告、开发记录、当前 STEP-000 和本任务书。已按任务要求调查 Style 注册、RenderLayer/RenderTree/RenderOrchestrator、Background/Raster/Custom 示例、Android renderer/frontend、repaint 和 Transform/Paint 链。 + +## 2. Git 基线 + +- 分支:`particle/main`。 +- 任务开始 HEAD:`74be2a5052428717d7c9e133c333da8bd783b958`。 +- 开始时远端同步:`particle/main...origin/particle/main = 0 0`。 +- 开始时无已跟踪修改;有用户提供的未跟踪当前任务书。 +- submodule:已查询,无本任务修改。 +- 主要变更 commit:阶段 A 实质提交后回填。 +- commit message:`test: add core particle render layer validation`。 +- push:阶段 A 实质提交后回填。 +- 公开远端:`ssh://git@192.168.200.230:2222/tei/MapLibreParticleLab.git`。 +- 远端分支:`particle/main`。 + +## 3. 实际修改文件 + +### Core 验证代码 + +- 新增 `ParticleValidationLayer`、Impl/properties、factory、`RenderParticleValidationLayer`。 +- 修改 Core/default 与 Android LayerManager 注册。 +- 修改 `RenderLayer` 增加默认 false 的 `needsRepaint()`。 +- 修改 `RenderOrchestrator` 汇总 Layer repaint 请求。 +- 修改 CMake/Bazel source lists。 + +### Android Test App + +- 新增 `ParticleCoreLayerValidationActivity` 和 layout。 +- 注册 Manifest。 +- 新增页面标题、说明和 ACTIVE/STATIC 文案。 + +### 文档 + +- 纳入当前正式任务书。 +- 新增技术验证报告和本完成报告。 +- 追加 `粒子开发记录.md`。 +- 未创建或修改 Step。 + +## 4. Style Layer 接入位置 + +JSON type `particle-validation` 经 Android `LayerManagerAndroid` 的 core-only factory 正式解析为 `style::ParticleValidationLayer`。其 `LayerTypeInfo` 为 source-less、无 layout、无 tile kind。它不是 RasterLayer 分支,也不是 MapView 外部 Overlay。 + +## 5. RenderLayer 创建链 + +```text +Style JSON → LayerManagerAndroid → ParticleValidationLayerFactory +→ ParticleValidationLayer immutable Impl +→ RenderOrchestrator layer diff +→ ParticleValidationLayerFactory::createRenderLayer +→ RenderParticleValidationLayer +``` + +## 6. RenderTree / pass + +`RenderParticleValidationLayer` 进入 source-less `LayerRenderItem`,按 style index 创建 source-less LayerGroup,并注册一个无像素输出的 validation Drawable。Drawable 属于 `RenderPass::Translucent`,随正常 LayerGroup order 进入 main translucent pass。Test App 将它固定放在蓝色参考圆与黄色参考圆之间。 + +## 7. Transform 获取链 + +```text +UpdateParameters::TransformState +→ RenderTreeParameters::TransformParameters +→ 当前 frame PaintParameters +→ validation DrawableTweaker::execute +``` + +读取 viewport、zoom、bearing、pitch、projection matrix 摘要和 MapLibre frame count。没有独立 Camera 状态。 + +## 8. Repaint 链 + +ACTIVE 的 `RenderParticleValidationLayer::needsRepaint()` 返回 true,由 RenderOrchestrator 写入 RenderTree `needsRepaint`,再经 RendererObserver、Map::Impl::onDidFinishRenderingFrame/onUpdate 和 AndroidRendererFrontend.requestRender 请求下一 MapLibre frame。STATIC 返回 false。没有 Timer、Handler loop、JS/RN frame loop。 + +## 9. Android OpenGL 验证入口 + +Test App → `Custom Layer` 分类 → `Particle Core Layer Validation`。虽然位于现有 UI 分类中,但实现不是 CustomLayer/CustomLayerHost;分类只用于减少 Test App 导航改动。 + +## 10. 用户手工编译命令 + +```bash +cd /mnt/sda1/MapLibreParticleLab +source /root/.maplibre-particle-env +cd platform/android +RENDERER=opengl BUILDTYPE=Debug JOBS=3 make android-x86-64 +``` + +APK 预期路径:`platform/android/MapLibreAndroidTestApp/build/outputs/apk/opengl/debug/MapLibreAndroidTestApp-opengl-debug.apk`。 + +## 11. 用户手工运行步骤 + +详见技术验证报告第 9 节。核心操作是:打开验证页面;保存 `ParticleValidation` 日志;验证 pan/zoom/rotate/pitch/resize;ACTIVE 静置观察连续 frame;STATIC 静置观察停止;STATIC 下操作地图确认正常重绘。 + +## 12. 构建与检查记录 + +- Android/Gradle/npm/ADB:未执行,遵循任务书禁止项。 +- Linux OpenGL CMake configure:PASS。 +- Linux OpenGL 全量增量:在 `41/321` 无错误时主动停止,原因是公共头变化导致完整 Core 重编译;未宣称全量 PASS。 +- 本任务新增/直接修改的 5 个 Linux Core 对象精确编译:PASS。 +- C++ clang-format、`git diff --check`:PASS。 +- Android XML 标准库解析:PASS。 +- APK/AAR:本轮未生成新产物。 + +## 13. 当前状态 + +`WAITING_MANUAL_VALIDATION`。 + +用户尚未提供 Android OpenGL 编译、运行、手势、resize、ACTIVE/STATIC 和日志证据,不能写 PASS。 + +## 14. 是否修改生产代码 + +是,但只包含验证所需薄接入:新增隔离的 VALIDATION ONLY Core Layer/RenderLayer、注册/build list,以及通用默认 false repaint hook。没有粒子生产功能;没有改 RasterLayer、Map、MapView 主循环或 backend 算法。 + +## 15. 是否扩大架构范围 + +否。未实现 GPU particle、Field、RasterArraySource、Trail、Mask、Vulkan、iOS、RN/JS bridge 或真实数据。 + +## 16. 依赖状态 + +未安装、未升级、未修改任何依赖、lock、SDK、NDK、Gradle、Java、Node 或 MapLibre baseline。 + +## 17. 已知问题与未完成 + +- Android OpenGL 编译和运行待用户。 +- 五项地图变化和 ACTIVE/STATIC 行为待实际证据。 +- 全量 Linux OpenGL 增量未完成;任务相关对象已编译通过。 +- 验证 Drawable故意不输出像素;以 pass/order/transform/repaint 日志为技术证据,蓝/黄参考圆用于确认地图与 style layer context。 +- commit/push 元数据将在提交后回填一次,不循环 amend。 + +## 18. 是否可以进入下一验证 + +否。只有用户证据回填并将本任务最终结论更新为 PASS 后,才能进入 GPU State A/B + Trail A/B 验证。 diff --git a/include/mbgl/layermanager/particle_validation_layer_factory.hpp b/include/mbgl/layermanager/particle_validation_layer_factory.hpp new file mode 100644 index 000000000..9aacfd2a7 --- /dev/null +++ b/include/mbgl/layermanager/particle_validation_layer_factory.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace mbgl { + +// VALIDATION ONLY: factory for the 30-01 Core RenderLayer experiment. +class ParticleValidationLayerFactory final : public LayerFactory { +public: + const style::LayerTypeInfo* getTypeInfo() const noexcept override; + std::unique_ptr createLayer(const std::string&, + const style::conversion::Convertible&) noexcept override; + std::unique_ptr createRenderLayer(Immutable) noexcept override; +}; + +} // namespace mbgl diff --git a/include/mbgl/style/layers/particle_validation_layer.hpp b/include/mbgl/style/layers/particle_validation_layer.hpp new file mode 100644 index 000000000..255aacb70 --- /dev/null +++ b/include/mbgl/style/layers/particle_validation_layer.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace mbgl { +namespace style { + +// VALIDATION ONLY: minimal source-less Core layer for 30-01 architecture validation. +class ParticleValidationLayer final : public Layer { +public: + explicit ParticleValidationLayer(const std::string& layerID); + ~ParticleValidationLayer() override; + + bool isActive() const; + void setActive(bool); + + class Impl; + const Impl& impl() const; + Mutable mutableImpl() const; + +private: + explicit ParticleValidationLayer(Immutable); + + std::optional setPropertyInternal(const std::string&, const conversion::Convertible&) override; + StyleProperty getProperty(const std::string&) const override; + Value serialize() const override; + std::unique_ptr cloneRef(const std::string& id) const override; + Mutable mutableBaseImpl() const override; +}; + +} // namespace style +} // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp index 85f39dc11..adb974efd 100644 --- a/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp @@ -19,12 +19,15 @@ #include "symbol_layer.hpp" #include "fill_extrusion_layer.hpp" #include "location_indicator_layer.hpp" +#include namespace mbgl { namespace android { LayerManagerAndroid::LayerManagerAndroid() { + // VALIDATION ONLY: JSON style access; intentionally no Android product API/JNI peer. + addLayerTypeCoreOnly(std::make_unique()); #if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME) addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_FILL_DISABLE_ALL) diff --git a/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml b/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml index 7cdedd553..679d63a6a 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml @@ -286,6 +286,18 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity" /> + + + + + maplibreMap = map + map.cameraPosition = CameraPosition.Builder() + .target(LatLng(0.0, 0.0)) + .zoom(2.0) + .build() + loadValidationStyle(active = true) + } + + findViewById