这次春节前微信 7.0.3 版本发布之后,开发者们遇到的模糊、卡顿等问题,以及开发者们连日来的反馈让我们寝食难安,好在目前终于针对所有问题,都有了相应的解决方案。但是由于事发突然,很多开发者心急的状况下不太能理解为什么需要做这些修改。因此本文将围绕技术层面和大家分享一些经验,也鞭策引擎团队更加重视平台的兼容性问题。
首先是在微信 7.0.3 版本暴露的问题:
- 部分安卓机型上画面变得模糊
- Label 贴图变黑的问题
- 稳定持续的掉帧问题
- 打开开放数据域或大量文字变化的场景的间歇性卡顿问题
让我们挨个来做纯技术层面的分析和解决方案的解答:
1. 部分安卓机型上画面变得模糊
原因
由于微信调整了 “在屏 Canvas“ 的放缩策略,从微信 wx.getSystemInfo 接口获取的 screenWidth 和 screenHeight 比以往要小很多,而引擎对屏幕的适配依赖于这两个接口作为屏幕尺寸来设置 “在屏Canvas” 的尺寸,最终导致 Canvas 分辨率降低,虽然 Canvas 仍然会被缩放到全屏,但视觉效果上就变模糊了。
解决方案
在发布后的 main.js 文件中的 onStart 函数里添加下列代码(主域与开放数据域都需要添加该代码,否者开放数据域触摸区域会有问题)
cc.ContainerStrategy.prototype._setupContainer = function (view, w, h) {
var locCanvas = cc.game.canvas, locContainer = cc.game.container;
if (!CC_WECHATGAME && cc.sys.os === cc.sys.OS_ANDROID) {
document.body.style.width = (view._isRotated ? h : w) + 'px';
document.body.style.height = (view._isRotated ? w : h) + 'px';
}
// Setup style
locContainer.style.width = locCanvas.style.width = w + 'px';
locContainer.style.height = locCanvas.style.height = h + 'px';
// Setup pixel ratio for retina display
var devicePixelRatio = view._devicePixelRatio = 1;
if (view.isRetinaEnabled()) {
devicePixelRatio = view._devicePixelRatio = window.devicePixelRatio || 1;
}
// Setup canvas
locCanvas.width = w * devicePixelRatio;
locCanvas.height = h * devicePixelRatio;
};
cc.view.enableRetina(true);
解决方案解析
引擎其实早已为浏览器环境提供了 cc.view.enableRetina(true)
接口,这个接口背后其实影响的是 Canvas 尺寸的计算,开启的时候用下面的公式来计算:
canvasSize = screenSize * Math.min(2, devicePixelRatio)
使用 Math.min 是为了避免过高的缩放比例导致像素填充率飙升带来的性能问题,比如 3 倍 devicePixelRatio 相当于 9 倍的像素填充率。解决方案中重写 cc.ContainerStrategy.prototype._setupContainer
就是为了突破这个最高 2 倍的限制,开发者可以自己测试并斟酌是否约束 devicePixelRatio 或是约束在多少。
引擎内部会在后续版本中,研究更稳定可靠的缩放策略。
2. Label 贴图变黑的问题
相关版本
Cocos Creator v2.0.0 - v2.0.6,以及 v2.1.0 有可能遇到这个问题,引擎的最新版本 v2.0.7 和 v2.1.1 开发分支都修复了这个问题。
背景及原因
众所周知,微信小游戏开放早期曾提供一个 weapp-adapter 适配层,用来将微信 API 适配到部分 DOM API 上,以便于大量基于 Web 技术开发的游戏可以尽快接入。很可惜的是这个工具从诞生之初就被告知不会持续维护,所以我们采用了 @finscn(大城小胖)的一个维护版本继续迭代。对 Cocos 引擎来说,大城小胖这个版本最有帮助的一个改动就是 “修改 HTMLImageElement / HTMLCanvasElement / HTMLVideoElement 的实现。可通过 instanceof 检测”。具体来说,就是让下面的代码结果与 Web 端一致:
(canvas instanceof HTMLCanvasElement) === true
我们引擎一直基于这个判断来做贴图提交的分支判断:
if (
img instanceof HTMLCanvasElement ||
img instanceof HTMLImageElement ||
img instanceof HTMLVideoElement
) {
// 使用 DOM Element 作为贴图上传 GPU
}
else {
// 使用 RAW Data(ArrayBuffer) 作为贴图上传 GPU
}
而在微信 7.0.3 中,这个判断失效了,canvas instanceof HTMLCanvasElement
的结果是 false,同时 Label 又是通过离屏 Canvas 来渲染并作为贴图上传到 GPU 的,这就导致 Label 的贴图 Canvas 对象都错误地被当成 ArrayBuffer 来上传了。
解决方案和解析
知道了原因,解决方案就非常简单直接了,我们将贴图提交的分支判断改为了下面的版本(这个问题已经预知,并在 2.0.7 中修复了):
if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) {
// 使用 RAW Data(ArrayBuffer) 作为贴图上传 GPU
}
else {
// 使用 DOM Element 作为贴图上传 GPU
}
注意,判断条件中有三个部分:
- img:确保对象存在
- ArrayBuffer.isView(img):检查是否是 TypedArray
- img instanceof ArrayBuffer:检查是否是 ArrayBuffer
遇到这个问题的开发者如果不愿意升级,可以尝试定制引擎并自己应用相关 PR 中的修改。
3. 稳定持续的掉帧问题
相关版本
只有 v2.0.7 受到影响
原因
引擎的微信适配器中,对 DeviceMotionEvent 的这个修改触发了 v2.0.7 中引擎会默认调用 wx.onAccelerometerChange
并直接监听了微信平台的 Accelerometer 事件,并在整个游戏运行过程中持续响应事件,这带来了不小的损耗,直接导致了部分机型上游戏的丢帧。
解决方案
可以点击编辑器右上角的 “编辑器目录” 按钮进入编辑器目录,找到其中的 “builtin/weapp-adapter/wechatgame/libs/engine/” 文件夹,替换 DeviceMotionEvent.js 文件为最新版本。记得替换后需要重新构建微信主域和开放数据域,打包后必须在微信开发者工具中确保文件替换生效。
解决方案解析
引擎组在调整代码的过程中,并未意识到 wx.onAccelerometerChange
会自动开启事件监听,误以为只是注册监听器,导致了不必要的性能开销,这完全是引擎工作中的疏忽。
4. 打开开放数据域或大量文字变化的场景的间歇性卡顿问题
影响范围
低端机上开启开放数据域或大量文字变化的场景,文字变化包含:文字内容变化、排版变化、颜色变化等。
原因
这个问题背后的原因可能非常复杂,由于我们不了解微信的原生层实现,只能根据已知信息做一些猜测。我们在针对 Demo 进行远程调试并 profile 过程中发现,卡顿时消耗最大的一直都是引擎的 Texture2D.prototype.handleLoadedTexture
接口,这是用来更新贴图的接口。除了资源加载以外,只有文字的变化和开放数据域的更新会调用到这个接口。进一步测试和开发者反馈的信息都印证了的确是打开开放数据域或大量文字变化的场景与卡顿现象有直接关联。与此同时,微信 7.0.3 的一个重大改进就是增加了多线程渲染的支持。这里的多线程渲染,我们猜测应该就是将运行游戏和引擎逻辑的 JS 线程与执行 GL 指令的渲染线程拆分开。JS 线程提交的渲染指令会以某种格式打包并发送给 GL 渲染线程,发送后就可以继续执行,享受到并行计算带来的性能增益。然而在我们出问题的使用场景中,每一帧的渲染,必须等待 handleLoadedTexture 提交贴图给 GL 线程完成,才能执行帧渲染,因为渲染过程依赖 Label 或开放数据域的贴图。这就在多线程渲染的过程中引入了同步等待,对于低端设备来说,很可能产生线程间的资源竞争并导致线程阻塞,最终导致卡顿。
解决方案
从目前的猜测来看,这个问题是微信底层实现导致的,开发者并不需要处理,也无从处理,相信微信团队会尽快修复问题。
总结
以上就是这次事故的技术层面分析汇总,希望大家都已经按照我们之前提供的方案重新提交修复版本,并通过审核,也衷心祝愿所有开发者能在解决问题后过个安心快乐的新年!