Files
MapLibreParticleLab/docs/报告/V26_Metal混合Segment视觉错误_Retained保留修复调查报告_v2.md
tei a9cea8b581
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
fix: retain correct Metal composite vertex buffer slot
2026-10-14 10:16:14 +09:00

8.6 KiB
Raw Blame History

V26 Metal 混合 Segment 视觉错误——保留 Retained 的修复调查报告 v2

日期:2026-10-13 状态:iPad F 组真机 FAIL(2026-10-13 18:51 JST);CommandBuffer 修正候选不能作为修复完成证据。Native 编译/安装/启动通过,但 Retained 视觉验收不通过。禁止发布 V27。 前一版:V26_Metal混合Segment视觉错误_安全降级调查报告_v1.md REJECTED,禁止发布其中关闭 Metal Retained 的方案。

1. 用户强制约束

  • 不允许关闭整个 Retained、不允许把有效 Retained Plan 隐式改成全量 Live。
  • Retained 必须保持 GPU 离屏绘制、缓存 Build/Reuse、Cache Texture 合成;Continuous 执行频率保持独立。
  • Android/iOS 使用同一套 JS Plan 和用户步骤,不以 Platform.OS 分配不同实验语义。
  • 以 Style Z-order 为唯一图层顺序,不改本船、AIS 或天气业务数据。
  • 不新增私有 RN/Native API,不改 Particle Contract。
  • 实际执行:2026-10-13 经用户授权,ChatGPT 通过 RDC 在 Mac 远程执行 iOS Metal 构建、签名、覆盖安装与启动;Android 原生编译和安装未执行。iPad 视觉验收由用户人工确认。

2. 已取得的实际复现

来源:Pleiades iPad mini 5,已安装 V26;Windows NavSea Metro 192.168.200.100:8081。

  • E:全 115 层 CONTINUOUS,Retained 总开关开启,0 Retained Segment,iPad 无闪烁,图标/本船航行线/AIS 航线/色场/粒子正确。快照 /tmp/navsea-v26-ipad-render-20261013/E-0746/start.json。
  • F:R0 仅 background,C1 其余 114 层,Native valid=true, fallback=false,actualExecution=[composite,live],R0 build 96/reuse 2778。冷启动即图标过大、本船/AIS 航线消失;天气仍可见且不闪。快照 /tmp/navsea-v26-ipad-render-20261013/F-0740/start.json。
  • 原始 RC:R0 层 0..30,C1 层 31..114;天气 ON 后闪烁,OFF 后持续。旧 RCR 预检的末端 Layer ID 硬编码与实际 Style 不符,现场最终跑 RC。
  • A:关闭 Retained 不闪;B:Native 天气隐藏不闪;C 和 D 分别只显示色场或粒子都闪。C/D 使用相同 Native Runtime 数据提交,不代表单独 shader 根因。

F 在仅最底层缓存时也失败,说明“缩小缓存至简单图层”不能作为修复。

2026-10-13 新构建真机复测(候选 FAIL)

  • RDC Mac Bazel bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic:943/943,exit 0。新候选 ZIP SHA-256:b89bc06bf03eba20a5790da21215fe7e167da20b3fdfa6a34782420bd503a354。
  • 新 XCFramework 从本地 ZIP 替换至暂存 NavSea.app,codesign 重新签名和验证 PASS,Pleiades 覆盖安装及启动 PASS;没有发布/替换正式 SwiftPM V26。
  • 用户报告新候选 E:地图、天气 ON/OFF 都正常(E 不执行 Retained Cache)。
  • 用户报告新候选 F:FAIL,冷启动光弧等图形巨大、不闪、航向线消失、天气开关无可见效果。不能将无闪烁当作正确。
  • 新 F Native 快照:/tmp/navsea-v27-retained-ipad-stage/F-1851/start.json 和 end.json,时间 2026-10-13T09:51:04Z、generation 3、mapReactTag 802、118 Style Layers;R0 [0,1) background、actualExecution=composite、build=13、reuse=1069、invalidation=13;C1 [1,118) actualExecution=live;valid=true/fallback=false。证明缓存确实工作,但画面仍错。
  • 结论:CommandBuffer 原实现存在执行序不明确的风险,然而此候选未修复视觉缺陷,无法将它认定为根因或 V27 修复。后续重点分析 Metal Composite 的绘制状态、RenderPass、Stencil/Depth 和 Live 绘制的相互作用。
  • 新 F 完成后 Windows Metro 同一 Android/iOS Debug 开关已改回 E,iOS Bundle HTTP 200;用户于 2026-10-13 20:43 JST 确认 iPad 重新运行 E 后显示 OK。由此 E 稳定恢复;F 明确失败。候选仍留在 iPad,仅切回 JS E,不代表回装了旧 V26。
  • 后续本轮只读源代码调查:renderer_impl.cpp 的 F/E 共同走标准 Live drawableOpaquePass/drawableTranslucentPass,区别是 F 额外执行了 RetainedUnderlayCache::beginBuild/finishBuild/composite;mtl::RenderPass 和 PaintParameters::beginRenderTarget() 会重建 Pass 状态及清理 tile clipping mask ID。需重点验证 Cache Composite 的真实 Metal state、Stencil/Depth、顶点/纹理坐标以及 Live drawable 的状态恢复;仅从快照计数不能推断根因。

3. 已从实际代码确认的候选根因

V26 src/mln/mtl/offscreen_texture.cpp 的 OffscreenTextureResource::bind 以 context.getBackend().getCommandQueue()->commandBuffer() 单独取得离屏 CommandBuffer;swap() 对它独立 commit + waitUntilCompleted。

iOS platform/ios/src/MLNMapView+Metal.mm 的默认画面 MLNMapViewMetalRenderableResource 自建 commandQueue / commandBuffer,直到帧末 swap 才提交。

renderer_impl.cpp 先用默认画面 CommandBuffer 组织上传,再绘制 Retained 离屏缓存、调用其 present,之后在默认画面绘制缓存纹理及 Continuous。这两条独立命令队列并不建立“主帧上传 → 离屏构建 → 主帧合成”的 GPU 顺序保证。 这是确定存在的同步风险;是否是图标放大/航线丢失的唯一原因仍需真机验证。

4. 第二版实现(保留 Retained)

  • include/mln/mtl/context.hpp 和 src/mln/mtl/context.cpp:提供帧内借用的主 CommandBuffer 指针,在 beginFrame/endFrame 清理,不持有跨帧引用。
  • src/mln/mtl/command_encoder.cpp:默认画面 UploadPass 创建时,将它的 CommandBuffer 登记为当前帧 CommandBuffer。
  • include/mln/mtl/offscreen_texture.hpp、src/mln/mtl/offscreen_texture.cpp:增加仅供 Retained Cache 显式启用的帧内排序开关。启用后其离屏 RenderPass 复用当前帧主 CommandBuffer;离屏 swap 在共享模式只清理引用,不提前提交主帧,由默认画面帧末统一 commit。未启用此开关的离屏纹理(包括 Snapshot/其它 RenderTarget),即使同帧存在主 CommandBuffer,也维持原先的独立 commit/wait。
  • src/mln/renderer/retained_underlay_cache.cpp:仅在 Metal Retained Cache 分配或使用 offscreenTexture 时显式启用上述开关;OpenGL 路径不变。
  • test/renderer/mtl_retained_attachments.test.cpp:新增共享 CommandBuffer、离屏 swap 后仍允许添加同帧 RenderPass 的针对性 Metal 测试。

预期顺序:一个 Metal CommandBuffer 内 Upload → Retained offscreen render → main composite → live Continuous → final present。各 Pass 的独立 Color/Depth/Stencil attachment 继续保留。 没有修改 RetainedUnderlayCache 的 Build/Reuse/Composite 语义,也没有跳过任意合法 Retained Segment。

5. 验收和停止条件

  1. 检查改动的 C++ 格式与差异;本轮不自动运行 Native 编译或设备操作。
  2. 用户手工构建 Metal Candidate XCFramework(使用现有已验证命令 bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic),在独立测试版中接入。不能覆盖正式 V26、不能用旧 SwiftPM binary 冒充新补丁。
  3. 先运行 MetalRetainedAttachments 的针对性单测,确认共享 CommandBuffer 不提前 commit,独立离屏路径不退化。
  4. iPad 首先复测 F:保留 [R(background),C(rest)] 的 Native Plan,不允许 Native 临时回退全部 Continuous;要求 build>0、reuse>0、actualExecution=[composite,live],并且图标大小和所有航线正常。
  5. F 通过后再测 RC:天气 OFF/ON/OFF,色场、粒子、本船/AIS/海图视觉完整,无异常闪烁;对照 Android OpenGL 同一 JS Plan。
  6. 检查帧率、CPU/GPU 时间、缓存重建/复用、内存;保证 Retained 的性能价值。
  7. F 如果仍错误,立即停止,采集 Metal GPU Capture 对比两个 RenderPass 的资源绑定与混合/Depth/Stencil,不能宣布 bug 已修好,也不得重新关闭 Retained。

6. 当前工程和发行状态

Mac 仓库 /Users/tei/dev/MapLibreParticleLab branch particle/main、当前起点 HEAD b0f34cc9a299a75af1f7e708c63d75eaa42e9ca2。源码改动尚未 commit/push/tag/publish;正式 V26 SOURCE_LOCK 指向 fce326a9009d3fc21eb91c5df362f0769163d07e,它是旧发行身份,不代表此次候选已发布。

Windows NavSea 当前 JS 已取消 iOS 独享 E:Debug Android/iOS 均选 E,Release BASELINE。正式修复后需在新 Native 实际安装成功、用户批准测试窗口后,把共用 JS Test Mode 切到 F/RC 进行对照;不能在旧 V26 上提前切换造成已知异常。

当前明确结论:这是保留 Retained 的候选修复,尚未通过 Native 编译与设备画面确认,不能写成 CLOSED/PASS。