# STEP-081 iOS Metal Renderer 与真机 Smoke 入口设计 v1 状态:CLOSED / PASS。已在 Pleiades(iPad mini 5 / iPadOS 26.5.2)完成签名安装、Metal runtime、deterministic fixtures 与 QBIN V4 T0/MID/T1/Clear 真机验收。 ## 目标和边界 Metal 路径消费既有 `RasterArraySource` V4 renderer projection、`WeatherColorLayer` 与 `RasterParticleLayer`。没有 iOS Field model、QBIN decoder 分支或公开 iOS SDK API。Smoke 代码仅在 `platform/ios/app/MBXViewController.mm`,RN iOS bridge 仍是明确非目标。 ## OpenGL → Metal pass 对照 | `u_pass.x` | OpenGL 既有语义 | Metal `ParticleGpuValidationShader` | | --- | --- | --- | | `< 0.5` | State A/B update / seed / respawn | 同一 T0/T1 decode、timeMix、VELOCITY/DIRECTION advection 与 fail-closed state write | | `< 1.5` | Trail fade | 同一 retention fade;legacy validation marker 保留 | | `1.5–3.5` | Composite / FrozenComposite | 同一 trail/particle 色彩、mask、speed-color 和 legacy diagnostic 分支 | | `> 3.5` | Trail particle line draw | State read/write displacement line,respawn alpha 抑制跨屏线 | | `> 4.5` | WeatherColor | 同一 scalar/vector magnitude、stops/ramp 和 invalid discard | | `> 5.5` | 无 | Metal-only current-endpoint 1 px quad coverage;只补 raster coverage,不改变 State、速度、时间步或 previous→current 物理长度 | MSL 使用 `ParticleGpuValidationUBO` 的同字段 vec4/float4x4 布局;声明与 C++ 的 `alignas(16)` 相同,并在 MSL 中静态断言 `31 * 16` 字节。此 Step 未改动 C++ UBO、QBIN V4 contract 或 OpenGL source。 ## Metal binding | 类别 | Metal index | Stable ID | | --- | ---: | --- | | vertex attribute `short2 a_pos` | attribute 0 | `idParticleGpuValidationPosVertexAttribute` | | UBO | buffer `drawableReservedUBOCount` | `idParticleGpuValidationUBO` | | previous | texture/sampler 0 | `idParticleGpuValidationPreviousTexture` | | mask | texture/sampler 1 | `idParticleGpuValidationMaskTexture` | | field T0 | texture/sampler 2 | `idParticleGpuValidationFieldTexture` | | field T1 | texture/sampler 3 | `idParticleGpuValidationNextFieldTexture` | | state | texture/sampler 4 | `idParticleGpuValidationStateTexture` | 这些 index 来自 Metal `ShaderSource` 的 `attributes`/`textures` 表以及 existing shader IDs,而非 OpenGL location 推测。注册位于 `src/mbgl/mtl/renderer_backend.cpp`,CMake source/include list 位于 `cmake/metal.cmake`。 ## Smoke 数据链 Test App Runtime Styling 菜单新增 `Particle / QBIN V4 Smoke (T0)`。点击后可选 T0、midpoint、T1 或 clear/release。它从 app bundle `ParticleSmokeFixtures/T0.qbin`、`T1.qbin` 读取现有 RN Wind Golden fixtures;`platform/ios/app/CMakeLists.txt` 在构建后从唯一 fixture 来源复制,不创建第二种数据格式。 private `MLNMapView` seam 提供 `mbglMap`。Smoke 直接创建 `RasterArraySource`,提交 `fixture.wind` T0/T1、Wind profile contract、ColorBinding 与 VectorComponents ParticleBinding,再添加 `WeatherColorLayer` 与 `RasterParticleLayer`。T0=1789239600000、midpoint=1789241400000、T1=1789243200000,全部通过 source-owned requested forecast time 选择窗口/mix。clear 先移除 layers 后移除 source,允许重复创建。Wind Golden fixture 实际范围为 112.5E..157.5E、0..55.776573N,Test App smoke 会把相机置于约 27.8883N / 135E、zoom 3,保证 FIELD_READY 后画面可观察;这只是 Test App 可视化入口,不改变 Core 数据范围。 ## Metal 真机收口结论 真机调试确认两个 Metal 特有问题并形成正式修复: 1. `ShaderProgram::getRenderPipelineState()` 的缓存复用 key 原先只包含调用方 reuse hash,没有包含 color/depth/stencil attachment pixel format。粒子 State 使用 RGBA32Float、Trail 使用 RGBA8Unorm、主 renderable 常见 BGRA8Unorm,同一 shader 因此可能复用到不兼容的 pipeline。修复后 cache key 显式合并 color/depth/stencil format;物理 iPad 上 Trail RGBA8 写入从全零恢复为有效像素。 2. deterministic Vortex 实测真实 previous→current 线段平均约 0.065 px、最大约 0.24 px,全部小于 0.5 px。Metal `MTLPrimitiveTypeLine` 对该亚像素长度没有稳定可见覆盖。正式 previous→current line 保持不变,Metal 额外在真实 currentPosition 绘制 1 px quad coverage;不拉长轨迹、不改变 State、advection、时间步或物理速度。 同时确认 Metal vertex-stage texture/sampler binding、State A/B RGBA32Float、Trail A/B RGBA8、矩阵投影与 Composite 均可工作。 ## 后续边界 STEP-081 已关闭。下一轮如需 iOS RN,必须另行设计桥接;不得把本 App 的 private Core seam 提升为 SDK 或 NavSea API。当前 Metal vertex+fragment 双阶段纹理绑定可在后续独立技术债中收窄为 stage-specific binding,但不得在 STEP-081 收尾中继续重构。