针对 iOS 14 Web 平台性能变差的优化方案

我们项目也遇到了,升级了ios14的手机掉帧严重。。。

1赞

I’m experiencing my FPS drops in web-mobile. Not sure about other platforms.

ios14修改了JavaScriptcore模块,问题很多,耐心等待吧

相同的问题。

我使用Safari 的dev tools跟踪了一下timeline,发现cocos在iOS 14下 draw()里面的 drawElements() 耗时会达到 60ms。造成js每一帧都非常长。

而iOS14 之前这个数值都是在10ms以内。

此外我测试了使用白鹭引擎的游戏,发现在iOS 14下,白鹭引擎一切正常。drawElements()耗时都在10ms以下。两者都使用的是webgl,而不是webgl2。

1赞

经过我们一天的调试,目前发现这个问题的症结在 vb 和 ib 的共享上, Creator 为了优化性能,多个drawcall 之间是会共享同一份 vb 和 ib 的,每个 drawcall 使用一个偏移值在共享的 vb 和 ib 中找到本次渲染的数据,但是经过我们测试后发现,共享 vb 和 ib 会导致在 iOS 14 上性能下降非常严重。

所以修复方式就是,在提交 drawcall 之后,切换 vb 和 ib

解决方案:

2.2 版本以上:

自定义引擎并手动合并此 pr :

https://github.com/cocos-creator/engine/pull/7415

如果不想自定义引擎的话你也可以在项目脚本最外层手动覆盖这个类型中的方法。

const isIOS14Device = cc.sys.os === cc.sys.OS_IOS && cc.sys.isBrowser && cc.sys.isMobile && /iPhone OS 14/.test(window.navigator.userAgent);
if (isIOS14Device) {
    cc.MeshBuffer.prototype.checkAndSwitchBuffer = function (vertexCount) {
        if (this.vertexOffset + vertexCount > 65535) {
            this.uploadData();
            this._batcher._flush();
        }
    };     
    cc.MeshBuffer.prototype.forwardIndiceStartToOffset = function () {
        this.uploadData();
        this.switchBuffer();
    }  
}

2.1.x 版本

2.1.x 版本原理类似,你依旧需要覆盖 checkAndSwitchBuffer

const isIOS14Device = cc.sys.os === cc.sys.OS_IOS && cc.sys.isBrowser && cc.sys.isMobile && /iPhone OS 14/.test(window.navigator.userAgent);
if (isIOS14Device) {
    cc.MeshBuffer.prototype.checkAndSwitchBuffer = function (vertexCount) {
        if (this.vertexOffset + vertexCount > 65535) {
            this.uploadData();
            this._batcher._flush();
        }
    };    
}

但 2.1.x 中没有实现 forwardIndiceStartToOffset,所以你需要以下额外操作:
自定义引擎找到 model-batcher.js,将 _flush 方法中的最后三行改为:

const isIOS14Device = cc.sys.os === cc.sys.OS_IOS && cc.sys.isBrowser && cc.sys.isMobile && /iPhone OS 14/.test(window.navigator.userAgent);

_flush () {
        let material = this.material,
            buffer = this._buffer,
            indiceStart = buffer.indiceStart,
            indiceOffset = buffer.indiceOffset,
            indiceCount = indiceOffset - indiceStart;
        if (!this.walking || !material || indiceCount <= 0) {
            return;
        }

        let effect = material.effect;
        if (!effect) return;

        // Generate ia
        let ia = this._iaPool.add();
        ia._vertexBuffer = buffer._vb;
        ia._indexBuffer = buffer._ib;
        ia._start = indiceStart;
        ia._count = indiceCount;
        
        // Generate model
        let model = this._modelPool.add();
        this._batchedModels.push(model);
        model.sortKey = this._sortKey++;
        model._cullingMask = this.cullingMask;
        model.setNode(this.node);
        model.setEffect(effect, this.customProperties);
        model.setInputAssembler(ia);
        
        this._renderScene.addModel(model);

        if (isIOS14Device) {
              buffer.uploadData();
              buffer.switchBuffer();
        }
        else {
              buffer.byteStart = buffer.byteOffset;
              buffer.indiceStart = buffer.indiceOffset;
              buffer.vertexStart = buffer.vertexOffset;
        }
  },

2.0.x 版本

自定义引擎,并用此文件mesh-buffer.zip (1.5 KB)覆盖引擎中的 mesh-buffer.js. 然后,使用和 2.1.x 版本相同的改动方式修改。


注意: 自定义引擎后需要重新编译引擎才能生效。建议合并后,使用不同手机进行全面测试。

9赞

@EndEvil
Awesome work!!

I will immediately start testing your solution on top of our games and report back!

2赞

We also replied in English forum

1赞

Works like a charm! I only tested the above 2.2 version on 2.3.3 verison of CC. I did comparions with before and after and compared iOS14 to iOS13 with this update and it is completely compareable.

I am extremely thankful for your swift action here and finding out this in such a short notice!
Full 5 stars :star: :star: :star: :star: :star: from me for the whole Cocos Creator team!

1.x版本呢

1赞

同问下大佬,1.0的解决方案

1.x 没这个问题

可以,经过修改,我们项目比之前流畅了:joy:

引擎版本2.3.0 采用

const isIOS14Device = cc.sys.os === cc.sys.OS_IOS && cc.sys.isBrowser && cc.sys.isMobile && /iPhone OS 14/.test(window.navigator.userAgent);
if (isIOS14Device) {
    cc.MeshBuffer.prototype.checkAndSwitchBuffer = function (vertexCount) {
        if (this.vertexOffset + vertexCount > 65535) {
            this.uploadData();
            this._batcher._flush();
        }
    };     
    cc.MeshBuffer.prototype.forwardIndiceStartToOffset = function () {
        this.uploadData();
        this.switchBuffer();
    }  
}

提示 undefined is not an object (evaluating cc.MeshBuffer.prototype)?

1赞

我们用的是2.0.9用文中的方式修改引擎。修改后帧率是提上高了。不过出现了新的问题,有些spine动画会渲染不出来。不知道还有没有其它方式可以修复掉帧的问题。

这段代码在哪儿写的?

查看了一下打印,spine动画不显示是因为报了这个错,console.error(‘Failed to update data, bytes exceed.’);

修改的是哪个文件夹下的文件?

有demo么?

mesh-buffer.js 的改动是在 destroy 方法中把 array 的迭代方式改成 for in, 这样做有什么特别的原因吗?