# M3 · iOS + Android 真机 > 总纲:[`../PLAN.md`](../PLAN.md) §04 / §06 / §07。 > 本文件是 M3 的详细施工文档。与 [M2](./M2-anim-skinning.md) 尾段并行。 --- ## 目标 把 [M2](./M2-anim-skinning.md) 的工程原样交叉编译到 iOS + Android 真机跑基准场景, 证明三端渲染一致、移动端性能达标、生命周期(context loss / 后台)稳。 ## 前置 - [M2](./M2-anim-skinning.md) 门禁 1–2 通过(骨骼矩阵 + 蒙皮顶点对拍)。 - [M1](./M1-static-render.md) T0b 的交接结论(决定移动端窗口壳用什么)。 - **资产上真机的方案**(见下 T1)—— 不是"原样交叉编译"就完事。 ## 交付物 | 产物 | 位置 | |---|---| | iOS `.app`(Xcode / CMake iOS toolchain) | `platform/ios/` | | Android APK(NDK / Gradle 或纯 CMake + `native_app_glue`) | `platform/android/` | | 三端 CI(macOS 原生 + iOS 模拟器 + Android 模拟器) | `.github/` 或等价 | | 真机性能采集报告 | `test/m3-perf.json` | | 三端快照互拍报告 | `test/m3-snapshot.json` | --- ## 任务分解 ### T1 · 资产上真机方案(PLAN §04 / §08) - 9166 个散装 `.gr2` + `.dds` 不能直接堆进 APK / `.bundle`(体积、iOS 限制)。二选一: - **A(推荐)**:M3 前提前接 `EterPack`(现排 M4),资产走 `.eix/.epk`,`CMappedFile` 的 mmap / `AAsset_getBuffer` 路径。 - **B(最小)**:只把 `test/assets.list` 里那 ~15 个 + 依赖贴图打进去。 - **完成判据**:真机上 `libgr2` 能加载 warrior + dance_1,路径与桌面散文件结果一致。 ### T2 · 平台壳 - **窗口 / 生命周期**:按 M1 T0b 结论——sokol_app(若交接成了)或各平台最小原生壳(iOS `MTKView` + `CADisplayLink`;Android `NativeActivity` / `GLSurfaceView`)。 - **iOS**:全静态链接(无 `dlopen`);`.bundle` 打包资产;`Info.plist` / 签名走开发证书(不涉及上架)。 - **Android**:NDK `arm64-v8a` + `armeabi-v7a`;`AAssetManager` 经 JNI 注入(复用 MobileSource `AndroidMain.cpp` 形态)。 - bgfx `init.type`:iOS = `Metal`;Android = `OpenGLES`(或 `Vulkan`,先 GLES3 稳)。 ### T3 · Android context loss(PLAN §08 高危,确定项) - 切后台 → GL context 连同 GPU 资源可能失效。 - 在 `SUSPENDED` / `RESUMED`(或 `onSurfaceDestroyed/Created`)里按 bgfx 的重置流程:`bgfx::reset` + 必要时重建 `sg_*` / bgfx 资源句柄。 - **测试**:切后台 ×100(`adb shell input keyevent KEYCODE_HOME` + 回前台脚本循环)。 - **完成判据**:100 次无崩、无黑屏、无资源泄漏(`adb shell dumpsys meminfo` 稳定)。 ### T4 · iOS 后台 / drawable 生命周期(PLAN §10.1) - 切后台 / 锁屏 / 来电 → 丢 `CAMetalDrawable`。 - `applicationWillResignActive` 暂停渲染循环;`didBecomeActive` 恢复;`nextDrawable` 返回 nil 时跳过该帧不崩。 - **完成判据**:后台 / 锁屏 / 来电各 ×20 恢复正常。 ### T5 · mediump 精度专项(PLAN §08 / §10.1) - 移动 GPU 上 `mediump` 存不下 60 骨链的骨骼矩阵 / 大坐标 → 抖动 / 爆顶点。 - 顶点着色器里骨骼矩阵与位置强制 `highp`;必要时把模型原点归一(减去包围盒中心)。 - **测试**:highp 前后对比截图,确认抖动 / 爆顶点消失。 - **完成判据**:highp 版无可见抖动。 ### T6 · 性能采集(PLAN §07 性能层) - **基准硬件写死具体机型**:iPhone 11(A13)+ Pixel 6(Mali-G78)+ 一台 Adreno 机。 - **两种角色**:① 简化(≈5k 三角、≈60 骨、单 draw call);② **满配**(多部件 + 武器 + 时装 + 挂点特效,1–2 万三角、多 draw call)。每种 1 → 8 → 20 → 50 个。 - 指标:帧时间 p50/p99、FPS、draw call、GPU 显存(Xcode GPU report / Android GPU Inspector / `adb dumpsys meminfo`)、冷启动到首帧、单 `.gr2` 加载耗时、峰值 RSS。 - 写进 `test/m3-perf.json`,逐项标注是否越线。 ### T7 · 三端快照互拍 + CI - 确定性场景在 macOS / iOS / Android 各渲一帧,两两 SSIM。 - 三端之间应比各自与 oracle 更接近(同 `shaderc` 源、同逻辑)。 - CI:iOS 模拟器 + Android 模拟器构建并启动,断言"到达首帧 + 连续 N 帧无 bgfx / Metal / GLES 验证层报错"(开 `BGFX_DEBUG_*`、Metal API validation、GLES `KHR_debug`)。 --- ## 门禁(go / no-go) - 三端渲染**差异分类通过**(不是"逐像素一致",见 PLAN §07 视觉层:SSIM ≥ 0.98 直接过;0.95–0.98 进分类;< 0.95 fail)。 - 性能:简化角色 1 个 ≥ 60fps、20 个 ≥ 30fps;**满配角色 1 个 ≥ 60fps、8 个 ≥ 30fps**;冷启动 < 3s;基准 RSS < 300MB;单角色加载 < 30ms。 - 无 bgfx / 图形验证层报错。 - **Android 切后台 ×100 恢复正常**;**iOS 后台 / 锁屏 / 来电恢复正常**。 - **mediump 精度专项通过**(highp 后无抖动)。 ## 验证 - 三端快照互拍矩阵(T7)。 - 真机性能逐项对通过线(T6)。 - 生命周期压力测试脚本可复现(T3 / T4)。 ## 本步风险(从 PLAN §08 筛) | 风险 | 缓解 | |---|---| | Android GL context 丢失后 GPU 资源需重建(确定项) | T3 在 suspend/resume 走 bgfx reset;M3 就做别拖 | | iOS 全静态 + 后台丢 drawable | T2/T4 全静态链接 + drawable nil 跳帧 | | 9166 散装资产上真机方式没定 | T1 二选一,M3 交付显式包含 | | 移动 GPU mediump 精度不足 | T5 骨骼矩阵 / 位置用 highp | | `shaderc` 跨编译 metal / 300_es 行为差异 | 单一 `.sc` 源;开验证层;T7 三端快照对拍 | | 深度范围 / Y 翻转后端差异 | `bgfx::getCaps()->homogeneousDepth` / `originBottomLeft` 抹平 | | sokol_app 交接在 iOS/Android 也不成 | 用各平台最小原生壳(T2 备选) | ## DoD 清单 - [~] iOS **模拟器**跑起来,播 dance_1,CPU 蒙皮 + bgfx Metal,输出与 macOS 同 t **像素级一致**(同 `.bin` 着色器、同 `demo_core`)。`test/golden/m3-ios-*.png`。**真机**(差异分类、后台/锁屏/来电、mediump、perf)待设备。 - [ ] Android 真机 —— **脚手架就绪(`platform/android/`)未构建**:本机无 NDK。 - [ ] `test/m3-perf.json` —— 待真机 - [ ] Android 切后台 ×100 —— 待设备(`android_main.cpp` 的 `APP_CMD_TERM/INIT_WINDOW` 分支已写 shutdown/reinit) - [ ] iOS 后台 / 锁屏 / 来电 —— 待设备(`ios_main.mm` 的 `applicationWillResignActive`/`DidBecomeActive` 暂停/恢复 CADisplayLink 已写) - [ ] mediump → highp 专项 —— `vs_pnt_skinned.sc` 已写(骨骼矩阵 + 位置全 `highp`);对比测试待真机 GPU - [ ] CI 三端 job —— 待 - [ ] **M3 全绿 = PLAN §01 判定"方案可行"** —— iOS 一端已验证(编译 + 运行 + 渲染一致);Android 端 + 真机压测待设备 ## M3 实现结果(本轮) | 项 | 结果 | |---|---| | **平台无关核心** | `app/demo_core.{h,cpp}` —— 加载 gr2 + 建场景 + 采样姿势 + 画,无窗口/输入依赖。桌面壳(`app/main.cpp` GLFW)+ iOS 壳(`platform/ios/ios_main.mm` UIKit)+ Android 壳(`platform/android/android_main.cpp` native_app_glue)都调它。 | | **iOS 壳(T2)** | `ios_main.mm`:`UIApplicationMain` → `MetalView`(`+layerClass = CAMetalLayer`)→ `CADisplayLink` 驱 `demo::frame`。bgfx `nwh = CAMetalLayer*`,`init.type = Metal`。生命周期回调(T4 骨架)已接。 | | **iOS 构建 + 运行** | `cmake -G Xcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator` → **BUILD SUCCEEDED**。`xcrun simctl` 装 + 跑 iPhone 16 Pro 模拟器:libgr2 加载 warrior + dance_1、DXT3/DXT1 解码、bgfx Metal init、CADisplayLink 跑 120 帧无崩、`requestScreenShot` 出图。渲染结果与桌面同 t 一致。 | | **交叉编译坑** | ① `BGFX_CONFIG_VIDEO` 默认 ON,`video_mtl.cpp` 在 iOS 模拟器编不过(`CVMetalTextureCache` 不可用)→ 关掉。② shaderc 跑不了目标平台 → `xrender_compile_shaders(... PREBUILT