ARTICLE · 1213241
D2R-Mobile移植到安卓过程中的坑
D2R-Mobile移植到安卓过程中的坑d2r-mobile 这个项目最近挺火的,N多人都在转发。主要是因为它打开了一扇新的大门,将switch上原生游戏经过补丁以后跑在手机上面,因为switch cpu 和手机的指令集基本是一样的,只需要把所有手机环境下不支持的指令都patch成手机环境能正常运行的状态就行,性能损失非常有限。这么一来,不光这一个项目,以后大量switch游戏都可以借鉴这个思路并依赖已经实现的兼容层很简单的移植到手机上。 原项目是iOS的,效果相当好,老机型都可以跑到60fps。然而出来好几天了,也没看见几个安卓版本出现。这不禁引起了我的好奇心,看着手里的几个 Codex 重置卡,决定头铁冲他一把。倒不是说真有多想玩这个游戏,就是想借这个提升一下对移动图形优化方面的理解。另外这套搞清楚了,回头也有机会把自己想要的游戏给弄上手机来玩。 过程就不细说了,大概没日没夜的 /Goal 了两天左右,能进游戏并且可以在一加 Ace 2V 上跑到三四十帧左右。但并不是像想象的那样可以完全不干涉,直接让 GPT 自己搞就能搞定,还是折腾了半天的,正好也学习了一阵子。 得益于原项目的思路和框架比较完善,把项目跑起来能进游戏并没有用多久时间,就是帧率很低,只有7帧左右,但睡了一觉起来,还是7fps,最后终于忍不住去干涉他了,一看具体情况把我气的半死,我开始让他用芯片原厂的工具去采集 profile 然后针对性做优化,结果 GPT 这个傻子搞了两下,遇到了点困难就退缩了,说芯片原厂工具不能解决问题,我去找安卓官方 AGI 工具吧,然而国内手机基本都不支持这东西,然后傻子就恍然大悟:工具都是不靠谱的,我自己打日志来记录时间,我好聪明!于是一晚上就在他自己写日志记录中浪费掉了。早上看到以后我气得大骂他,勒令他搞定官方工具,大概两个小时以后就 17fps 了。 折腾到最后,安卓也和 iOS 的性能相差甚远。我看了一下,目前安卓版本大概还有两个人做的相对好点,一个是 dodola 的,一个是 sydeEvans 的。但基本也就是贴边能玩的状态,那俩虽然号称最高情况能上 60fps,但是实际开的渲染分辨率相当低,图的糊感很强。我坚持用 iOS 版本的渲染分辨率,虽然也能跑到 30-40fps 左右,但手机迅速就过热到开始杀进程,将将够从营地冲去地牢在里面杀两波怪。 有人可能会好奇安卓手机真的比苹果手机差这么多吗?根据这几天蹬这个的结果,可以说是也不是。主要的问题就是在 Vulkan 和 Metal 的差异上。其他地方都大同小异,但是 Metal 因为设计时候原生就是在统一内存架构下,所以 CPU 端可以把任意一片内存直接注册成给 GPU 可以访问的内存,之后两边就都可以访问了。然而 Vulkan 设计的时候要为不同架构的设备考虑,所有需要两边同时访问的内存,都得通过 Vulkan 来分配。Switch 因为也是统一内存,所以他的图形接口和 Metal 非常相似,代码也都是先直接分配系统内存,然后注册成 GPU 可访问。这个在 Metal 底下直接照着做就完了。但是在安卓下,CPU 分配的内存是没有办法再注册成 GPU 可用的,于是实现的时候就只能再分配一个相同大小的 GPU buffer,然后用非常变态的机制,跟踪所有 CPU 写入的内容,原样搬运去 GPU那边,同样 GPU 那边改写的内容,再搬回来。(具体说来,CPU 端是通过 mprotect 来将页面都设置为只读,这样 CPU 写的时候就会产生异常,在捕获异常的地方记录下触发的页面,就知道 CPU 写了哪些页。然后对照之前的快照进行比较,来精确确定哪些地方被写了,然后把写了的地方写入 GPU 内存,这里不能省去比较直接往 GPU 内存写,因为 GPU 那边有可能同时在往别处写,如果为了节省带宽,直接把整个页面都写入 GPU ,那可能会覆盖 GPU 写入的部分造成错误。 GPU 那端比较简单,因为 GPU 可写的部分其实都是标记了的) ,加上为了做比较还保存了一份CPU端的快照,里外里一共占用了三份内存。所以 iOS 机器 6G 内存机器跑的很欢,安卓我这一个 6G 内存平板,进营地就炸内存被系统杀掉了。 所以就这么一个看似简单的问题,直接注定了安卓跑 switch 游戏性能必然比 iOS 大打折扣。明明底层架构本质也没大差,就因为图形接口的限制,直接背上了一个巨大无比的包袱。就这一套东西直接占据了翻译线程的1/5-1/4 的用户态时间,还不算上 mprotect 造成的异常开销和产生的额外系统调用。我开始以为能优化点啥,但是后来才意识到,游戏也不是我们自己写的,我们别说调整渲染管线,就是渲染细节都没有办法控制,只能在底层做一些 hack ,因为调用全是游戏自己调的,顺序你都没办法改。 其他的就是一些比较细节的优化了,比如跟踪资源的依赖,提早提交以免 cpu 和 gpu 互相等待。异步收尾用单独的 worker 线程,缓存住创建的 vulkan 对象以避免重复创建销毁之类,都是比较常见的优化手段。 当然也必须感慨一下 GPT 的能力,我这个开始弄的时候比 dodola 晚,但我故意没给 GPT dodola 这个 repo 相关的内容,他硬是自己想出了前面说的那套 CPU/GPU 人肉同步的逻辑,和 dodola 的方法基本一致。就是后面优化细节的时候太笨了。老实说这问题给到我可是真想不出这么变态的搞法,就算想到了,也没有体力写出来。 项目放 https://github.com/kxn/d2r-mobile 了,还在继续往里蹬,有兴趣的可以看一下,不过我没在 main 上,东西都放其他分支了。















































































