在小游戏开发这件事上,请了解一下U4内核的游戏模式

小游戏

2018-10-25

现在小游戏的开发非常火热。U4内核在基于Web技术开发小游戏这块有自己的独特技术,即游戏模式。

# WebGL 游戏模式概述

经典的WebGLQqua鱼在不同模式下的表现。当放置两千条鱼的时候,U4内核的游戏模式的频率最高。 WebGL 在浏览器中渲染流水线过长,过于复杂,导致了一些副作用:

  1. 可能的性能下降;

  2. CPU 占用高,更耗电;

  3. 帧间距(Frame Pacing,每一帧花费的时间)不稳定,有时会感觉画面更新有些跳动;

  4. 增加操作延迟(Input Latency);

浏览器是多段多线程高并发的设计,通过并发来提升性能,对比单线程的设计,实际上大部分场景不会出现性能下降,部分场景性能还有所提升,不过也存在部分场景性能下降的可能性

另外因为 WebView 的渲染架构决定了输出必须走 Android View Rendering,无法自己控制屏幕的更新,导致上述副作用比独立的浏览器 Chrome 会更大一些。 WebGL 的游戏模式就是为了解决上述问题而设计,它为 WebGL 的绘制定制了一条新的渲染流水线,脱离了浏览器原来的 DOM 更新流水线,更简单和高效。 WebGL 游戏模式的渲染流水线采用两段式双线程的设计,主线程也就是内核线程运行 JavaScript,WebGL 的调用指令会先 Encode 到 CommandBuffer,在 GPU 线程中再对 CommandBuffer 进行 Decode,调用真正的 GL 指令。这种设计不会破坏 Chrome 的系统架构,特别是不会破坏 Chrome 多进程的架构设计,并且完全兼容于 Chrome 默认的渲染流水线,规避了大量兼容性问题。 WebGL 游戏模式的渲染流水线对比原来默认的流水线提供了更好的性能,更低的 CPU 占用,更稳定的帧间距和更低的操作延迟。对比其它使用 WebGL API,但是非 Web 引擎来说(比如微信小游戏),对开发者而言,U4 2.0 WebGL 游戏模式的最大优势来自于几乎完全的 Web 标准兼容,基于标准 Web 技术开发的 Web Game 只需要做极少量的修改(并且也很容易做到同时兼容其它浏览器),运行场景只要遵循少量的限制条件,即可使自己的 Web Game 运行在游戏模式下,游戏不但可以获得游戏模式带来的性能提升,功耗降低,低操作延迟等优势,同时也完全支持各种标准 Web 技术的使用,比如 DOM,CSS,Service Worker,WebAssembly 等等。

# WebGL 游戏模式使用条件

WebGL 游戏模式需要对使用场景做一定的限制,另外也需要对游戏引擎代码做一点修改,不过代码很容易就能做到兼容其它不支持游戏模式的浏览器。

  • 主要的限制条件

目前已知的一些使用限制条件:

  1. 单个进程同时只能有一个 WebView 运行在游戏模式下,第二个试图使用游戏模式的 WebView 会 fallback 到默认的渲染流水线,这个对开发者是透明的;

  2. 使用游戏模式 WebGL 元素必须是全屏(占据整个页面大小),并且是不透明的;

  3. 在 WebGL 绘制的过程中最好不要同时频繁更新其它 DOM 元素;

对 DOM 元素绘制的限制,游戏模式是支持显示其它 DOM 元素的。但是如果在 WebGL 的绘制过程中同时频繁更新其它 DOM 元素,意味着浏览器需要同时运行两条渲染流水线,这样不但无法获得游戏模式带来的优势,性能反而会变得更糟糕。 基本上这些条件对 WebGL 游戏来说应该都是可以满足的,特别是使用 Cocos2d,白鹭这样的引擎开发的游戏。

需要的代码改动

  • WebGLRenderingContext 创建增加 gameMode 属性 可以调用 WebGLRenderingContext.getContextAttributes,查询返回值的 gameMode 属性来验证是否成功开启游戏模式。 示例代码:
gl = canvas.getContext("webgl", {alpha:false, antialias:false, gameMode:true});
...
var gameMode = gl.getContextAttributes().gameMode ? true : false;
  • WebGLRenderingContext.submit 提交帧

游戏需要调用 WebGLRenderingContext.submit 告诉浏览器当前帧绘制结束,帧输出显示。 示例代码:

function render() {
    // rendering...
    ...
    // submit
    if (UseGameMode() && gl.submit)
      gl.submit();
}
  • 使用 setTimeout 而不是 requestAnimationFrame 这个改动不是必须的,但是浏览器 requestAnimationFrame 的实现比较复杂,需要在 UI 线程注册 vSync 的回调,并且涉及多次进程/线程间的双向通讯,因为 WebGL 游戏模式使用自己的独立窗口进行绘制,UI 线程的 vSync 信号实际并无作用,反而导致了额外的开销,增加了 CPU 的占用。在游戏模式下使用 setTimeout 让主线程自己控制游戏循环,延迟更低,CPU 占用更低。 游戏可以参考下面适配的代码,代码参考了 Cocos2d 的实现:
function Now() { return performance.now(); }
const FRAME_RATE = 60;
const FRAME_TIME = 1000.0 / FRAME_RATE;
var GameLoopUtil = {
  lastTime: Now(),

  requestNextFrame: function(callback) {
    if (UseGameMode()) {
      let currTime = Now();
      let timeToCall = Math.max(0.5, FRAME_TIME - (currTime - GameLoopUtil.lastTime));
      let id = setTimeout((function() {
                             GameLoopUtil.lastTime = Now();
                             callback()
                           }, timeToCall);
      GameLoopUtil.lastTime = currTime + timeToCall;
      return id;
    } else {
      return requestAnimationFrame(callback);
    }
  },

  cancalNextFrame: function(id) {
    if (UseGameMode()) {
      clearTimeout(id);
    } else {
      cancelAnimationFrame(id);
    }
  }
}
...
GameLoopUtil.requestNextFrame(render);

# 游戏模式 vs Chrome,微信小游戏

相关的测试环境说明,和如何自行测试验证另有文章介绍。

  • WebGL Aqua 测试 WebGL Aqua 在 微信小游戏,Chrome for Android,U4 2.0 上的帧率测试结果:

表1.png | center | 571x312

从帧率测试结果来看: 在没有使用 WebGL2 的条件下,微信小游戏,Chrome 和 U4 游戏模式帧率都差不多,比 U4 默认模式高 20% 左右;从帧率测试结果来看: 在使用 WebGL2 的条件下,U4 游戏模式略高于 Chrome,两者都明显高于微信小游戏(高 25% 左右); 测试说明:

  1. 测试手机是小米 Note3(骁龙660),微信版本是 6.6.7,比之前测试的 6.6.3 版本性能略略提升,Chrome 版本是 67,对比之前测试的版本 64 差别不大;
  2. 全部测试关闭反锯齿,分辨率是 1080p;
  3. 在 WebGL2 下,Demo 使用了 Instance Rendering 和 Vertex Array Object 来降低 Draw Call 和其它 GL API 的调用次数,微信小游戏不支持 WebGL2; WebGL Aqua 在 微信小游戏,Chrome for Android,U4 2.0 上的 CPU 占用测试结果:

表2.png | center | 564x399

从 CPU 占用测试结果来看: 在没有使用 WebGL2 的条件下,微信小游戏比 U4 2.0 游戏模式 CPU 占用大概低 30% 左右,比 U4 2.0 默认模式大概低 50% 左右,U4 2.0 游戏模式比默认模式大概低 25% 左右,比 Chrome 大概要低 10% 左右;从 CPU 占用测试结果来看: 在使用 WebGL2 的条件下,U4 2.0 游戏模式的 CPU 占用已经比较接近微信小游戏(微信小游戏低 10% 左右,考虑 2000 条鱼两者帧率的差异,可以认为这时 CPU 占用已经基本持平),并低于 U4 2.0 的默认模式(低 30% 左右)和 Chrome(低 20% 左右);

测试说明:

  1. 因为帧率会影响 CPU 占用,帧率低也会降低 CPU 占用,所以CPU 占用对比需要根据帧率进行调整,对比相同帧率下的结果;
  2. Chrome 的 CPU 占用需要统计三个进程之和 (adb shell top -m 5 | grep chrome),微信通常会有两个进程占用 CPU (adb shell top -m 5 | grep mm);
  • WebGL Compute 测试 WebGL Compute 在 微信小游戏,Chrome for Android,U4 2.0 上的 CPU 占用测试结果:

表3.png | center | 488x208

从上面测试的结果来看,在高计算低 Draw Call 的场景,U4 游戏模式的 CPU 占用已经十分接近微信小游戏,明显低于 U4 默认模式和 Chrome(25% ~ 30%)。

测试说明: 500 鸟群的条件下,因为计算量太大,帧率急剧下降,所以 Chrome 和 U4 默认模式的 CPU 占用反而大幅下降,这个条件下的测试对实际场景没有太大意义,仅供参考

  • 测试结论 总的结论是 U4 2.0 游戏模式无论是性能还是 CPU 占用都要明显优于 U4 2.0 的默认模式,也是系统 WebView 的渲染方式,同时性能跟 Chrome 和微信小游戏基本持平,并且在使用 WebGL2 优化 Draw Call 数目后更是明显领先于微信小游戏。 在 Draw Call 次数比较高的场景,游戏模式的 CPU 占用虽然高于微信小游戏,但是仍然低于 Chrome,并且可以通过进一步优化 Draw Call 次数达到微信小游戏同帧率下接近甚至相同的水平。而在高计算低 Draw Call 的场景,U4 游戏模式的 CPU 占用已经十分接近微信小游戏,明显低于 U4 默认模式和 Chrome。

# 附录1:游戏模式和微信小游戏更多机型的性能对比

在小米 Note3 上做完 U4 2.0 游戏模式和微信小游戏的性能对比后,测试组做了更多机型的性能对比,结果相当让人吃惊,对比数据如下图所示。

附录1.png | center | 827x315

Oppo R11 和小米 Note3 都使用骁龙 660 的芯片,所以测试数据基本一致,跟上面的结论也基本吻合,U4 2.0 游戏模式性能跟微信小游戏持平或者略高于微信小游戏。但是其它机型的数据显示 U4 2.0 游戏模式的性能都要大幅优于微信小游戏,特定机型上还能达到高出一倍甚至两倍的差距(华为畅享5s WebGL Auqa 2000条鱼)。怎么解释这个数据呢,骁龙 660 的性能对比是预料之中的结果,而其它机型那么大的差距反而不在预期之内。如果非要给出一个解释,可能的结论是:

  1. U4 2.0 游戏模式的渲染架构对不同性能的硬件有更好的伸缩性,能够最大化利用硬件性能;
  2. 微信小游戏的 WebGL 实现在硬件兼容性上存在严重的缺陷;

# 附录2:阿里小游戏的对比测试

调用 “adb shell top -m 5 | grep uc” 查看 CPU 占用,对比业务方提供的 Cocos 和白鹭引擎开发的游戏的测试结果:

表4.png | center | 511x339

测试结果可以看出对于简单的小游戏,游戏模式带来的 CPU 占用下降的效果十分明显,降低了 30% ~ 50% 左右。

这里测试的敲砖块和 Egret Demo 还没有做 setTimeout 优化,游戏还有进一步优化的空间