diff --git a/android/app/src/main/java/org/metin2port/client/MainActivity.java b/android/app/src/main/java/org/metin2port/client/MainActivity.java index ca6ee82a..ac556c25 100644 --- a/android/app/src/main/java/org/metin2port/client/MainActivity.java +++ b/android/app/src/main/java/org/metin2port/client/MainActivity.java @@ -214,6 +214,11 @@ public class MainActivity extends SDLActivity { } private void applyImmersiveMode() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + final WindowManager.LayoutParams attributes = getWindow().getAttributes(); + attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(attributes); + } final View decor = getWindow().getDecorView(); decor.setSystemUiVisibility( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | diff --git a/android/sync_client.py b/android/sync_client.py index f403ea7e..26aa6d63 100644 --- a/android/sync_client.py +++ b/android/sync_client.py @@ -136,6 +136,10 @@ class Progress: def push(files): """Streams {device path: local path} into files/Client as one tar.""" + # Older Android tar restores ownership even when run as an unprivileged app. + # Match the app identity so extraction does not try to chown files to root. + identity = run_as("id -u && id -g", capture_output=True, check=True).stdout.split() + app_uid, app_gid = map(int, identity) total = sum(p.stat().st_size for p in files.values()) progress = Progress(total) proc = subprocess.Popen(run_as_command("mkdir -p files/Client && cd files/Client && tar -xf -"), @@ -143,7 +147,7 @@ def push(files): with tarfile.open(fileobj=proc.stdin, mode="w|", format=tarfile.GNU_FORMAT, dereference=True) as tar: for name, path in sorted(files.items()): info = tar.gettarinfo(str(path), arcname=name) # dereference: the zh client links the original packs - info.uid = info.gid = 0 + info.uid, info.gid = app_uid, app_gid info.uname = info.gname = "" with open(path, "rb") as f: tar.addfile(info, f) diff --git a/docs/XIAOMI8LITE-GLOBAL-TEST-20260930.md b/docs/XIAOMI8LITE-GLOBAL-TEST-20260930.md new file mode 100644 index 00000000..d54f465a --- /dev/null +++ b/docs/XIAOMI8LITE-GLOBAL-TEST-20260930.md @@ -0,0 +1,68 @@ +# 小米 8 Lite 官方 Global 系统游戏测试(2026-09-30) + +## 结论 + +官方 Global Android 10 可以启动当前客户端、进入本地村庄、使用摇杆移动、打开背包和恢复前台。 +相同绘制尺寸和场景下,与之前 LineageOS 的帧率及 GPU 耗时几乎一致,换回原厂系统没有解决低 FPS。 +这组证据不支持把当前场景的主要性能问题归因于第三方 ROM 独有的驱动异常;优先优化客户端地形渲染。 +不能据此证明所有 ROM 行为相同,或保证其他场景和游戏具有相同表现。 + +## 系统与条件 + +同一设备:MI 8 Lite / platina / Snapdragon 660 / Adreno 512 / 约 4 GB RAM。 +本轮实际 fingerprint 为 `Xiaomi/platina/platina:10/QKQ1.190910.002/V12.0.3.0.QDTMIXM:user/release-keys`, +MIUI 12 Global,Android 10 API 29,bootloader 保持解锁。 + +- 原厂:Vulkan 1.1.87;GLES 3.2 V@415.0,2019-09-28 驱动。 +- 之前 LineageOS 22.2 / Android 15:Vulkan 1.1.128;GLES 3.2 V@0490.0,2020-09-02 驱动。 +- 两轮均为 Release 原生库,2280×1080 窗口,60% 场景分辨率、无 MSAA、30 FPS 上限。 +- 同一 `metin2_map_a1`,24 只模拟怪物,共 28 个角色;每组丢弃首个加载后窗口,取 6 个两秒窗口。 +- 全场景每帧 225 次 3D 绘制、约 295,763 顶点;两轮均 USB 充电。 +- 之前电池温度 35–36°C,本轮 36–37°C;这是电池温度,不是 GPU 温度。 + +## 相同尺寸对比 + +| 条件 | LineageOS FPS | LineageOS GPU ms | Global FPS | Global GPU ms | Global 绘制次数 | +| --- | ---: | ---: | ---: | ---: | ---: | +| 完整场景 | 4.88 | 205.3 | 4.90 | 204.8 | 225 | +| 关闭地形 | 15.45 | 64.7 | 15.20 | 65.9 | 100 | +| 关闭角色及角色阴影 | 5.13 | 194.5 | 5.20 | 193.6 | 192 | +| 关闭特效 | 4.88 | 205.6 | 4.88 | 205.7 | 224 | +| 关闭触控覆盖 | 4.88 | 205.5 | 4.90 | 203.7 | 225 | +| 关闭全部 3D | 30.03 | 17.7 | 29.98 | 18.1 | 0 | +| 重复完整场景 | 4.83 | 206.4 | 4.83 | 206.4 | 225 | + +完整场景首组仅约 0.3% 的帧率差异,重复组则相同到显示精度;没有可确认的性能提升。 +关闭地形使原厂系统约从 4.90 升至 15.20 FPS、GPU 从 204.8 降至 65.9 ms;地形仍是主要瓶颈。 +关闭全部 3D 后达到 30 FPS 上限,不能解释为完整游戏已能达到 30 FPS。 +本地静止场景没有密集技能释放,本表不能代表真实战斗特效的成本。 + +## 测试中修复的 Android 10 兼容问题 + +1. `android/sync_client.py` 的 tar 原先写入 root 属主;原厂旧版 tar 对应用用户执行 chown 时失败。 + 已改为实际应用 UID/GID,完整同步 1,273 个文件(含首次设置种子)、约 1,473.8 MiB 成功;再次增量同步 1,272 个资源全部未变化。 +2. SDL 窗口原先未标记全屏,其异步窗口样式覆盖 Activity 的隐藏系统栏设置,Android 10 下画面只有 2198×954。 + Android 原生窗口改为 SDL 全屏;API 28/29 允许沿屏幕短边使用刘海区域。安装后 CSV 和截图确认 2280×1080,状态栏及导航栏消失。 + +第一轮小画面约 5.8 FPS 的结果存在绘制面积差异,不能作为驱动对比结论。 +保留在 `build/qa/xiaomi8lite-global-20260930/isolation/`;上表采用修复全屏后的 `fullscreen/`。 +Android Release 构建成功,修复版本已经安装到设备。未改地形画质或地形算法。 + +## 功能与限制 + +- 官方系统成功加载 Vulkan,中文资源显示,真实服务器选择界面能打开;手机可 ping 通 192.168.21.203。 +- 本地模拟场景:摇杆移动前后截图确认地形、怪物和角色姿态变化;I 键打开五页背包。正常服务器选择界面按 Home 后,点击桌面图标可恢复原界面,截图为 `resume-icon.png`;单独使用 `am start` 未成功把画面切回前台。 +- 本轮日志未发现客户端 Fatal signal / FATAL EXCEPTION。 +- 未取得游戏测试账号,未进行真实账号登录、联网战斗或切地图验证。 +- GPU 时钟和忙碌节点仍拒绝普通 shell 读取,不能给出实际 GPU 调频曲线。 +- 原厂 Thermal HAL 可用,已保存 CPU/GPU/电池温度;Android 10 上 ADPF 不可用,客户端正常回退。 +- 背包仍存在触控按钮覆盖问题,截图留存;本轮没有修改背包布局。 +- 测试后恢复图标启动的正常真实服务器登录入口,全部渲染开启。 + +## 证据与下一步 + +本轮 CSV、截图、系统属性、Vulkan 能力、温度、资源同步和构建日志: +`build/qa/xiaomi8lite-global-20260930/`。 +之前的对照:[渲染分项测试](XIAOMI8LITE-RENDER-ISOLATION-20260930.md)。 + +下一步优先分析地形多层纹理混合、重复覆盖和通用固定功能着色器的采样成本,在保留可见结果的前提下减少 GPU 工作量。 diff --git a/src/host/vulkan_window.cpp b/src/host/vulkan_window.cpp index 2a36cde2..6fa18cbb 100644 --- a/src/host/vulkan_window.cpp +++ b/src/host/vulkan_window.cpp @@ -31,11 +31,16 @@ VulkanWindow::VulkanWindow(bool vsync, int init_width, int init_height) : vsync_ SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1"); if (!SDL_Init(SDL_INIT_VIDEO)) throw std::runtime_error(SDL_GetError()); SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); + SDL_WindowFlags window_flags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; +#ifdef __ANDROID__ + // Keep SDL's window style in sync with the Activity's immersive mode on Android 10. + window_flags |= SDL_WINDOW_FULLSCREEN; +#endif window_ = SDL_CreateWindow( "Metin2 Native Vulkan Client", std::max(init_width, 320), std::max(init_height, 240), - SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY); + window_flags); if (!window_) throw std::runtime_error(SDL_GetError()); SDL_StartTextInput(window_); Uint32 extension_count = 0;