Files
MapLibreParticleLab/docs/10_架构/20_粒子系统总体架构设计_v1.md
OpenAI Codex 74be2a5052
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
docs: freeze particle architecture investigation and design
2026-08-29 12:33:09 +08:00

43 KiB
Raw Blame History

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. 粒子系统总体架构图

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 控制,推荐:

Base Map
  → WeatherColorLayer
  → RasterParticleLayer
  → Navigation Reference / coast / labels
  → AIS / Route / Ownship / Alarm

WeatherColor 保持定量色场;导航信息通过更高 Style Layer 显示,不以降低天气层透明度作为默认策略。

5. Core 模块边界与建议目录

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 目录固定包含:

particle-fixtures/<fixture-id>/
├─ metadata.json
├─ field.f32
└─ mask.u8          仅 OCEAN_ONLY/Mask 场景必需

metadata.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": "<sha256>"
  }
}

实际 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 写为:

{
  "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 数据流

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. 线程模型

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<const ParticleFieldSnapshot>
           │ 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<const ParticleFieldSnapshot>/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 同帧同步

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。

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 共用图

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. 生命周期状态机

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. 每帧渲染链图

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 薄接入

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 薄接入

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 方法签名:

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 同步图

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 模块边界图

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 或项目总则;未运行会写工程状态的构建/测试。