Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
phases, EntityStore world model, ~all GC/CG headers. char create/delete,
private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
token), system-option + game-option + ESC system menu, private-shop 39-grid,
party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.
Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.
Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).
ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
129 lines
8.7 KiB
Markdown
129 lines
8.7 KiB
Markdown
# 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 <dir>)` 用宿主机预编译的 `metal .bin`(Metal 字节码 macOS/iOS 通用)。③ iOS 不建 GLFW(`third_party/CMakeLists.txt` 加 `NOT IOS` 守卫)。 |
|
||
| **Android 壳(T2)** | `platform/android/`:`android_main.cpp`(native_app_glue + `ANativeWindow` → bgfx GLES nwh + `AAssetManager` 解资产)、`CMakeLists.txt`(NDK,链 `xr_democore` + bgfx GLES)、`build.gradle` + `AndroidManifest.xml`(`NativeActivity`)。**未构建**(本机无 NDK,`platform/android/README.md` 记了装 NDK 后的步骤)。 |
|
||
| **T5 GPU 蒙皮 shader** | `app/shaders/vs_pnt_skinned.sc`:`u_bones[64]` 混合,骨骼矩阵 + 位置 + 法线全 `highp`。**未接进 demo**(demo 走 CPU 蒙皮 = M2 数值门禁路径),留 M4 CPU/GPU 一致性 + 真机 mediump 对比。 |
|