GLantern 绘制效率问题及其解决

在写着架构博文的时候,突然想测试一下动画效果。于是直接就上了 bp.startAnimation(),突然发现帧率才25左右。哦天哪,这效率可比 Pixi 差了不是一点半点。于是返回 GLantern 查看,果然是 GLantern 的问题。大致定位之后就得修补啊……

一开始,我还以为是数组的太大导致的效率低下。此时帧率大概25。

于是我做了一下 CPU 探查,并查看时间线。(抱歉这二者没截图。)

结果是脚本执行时间占大头,在70%左右,同时 CPU 总计占用排名第一的用户函数是 bufferData()(也就是 WebGLRenderingContext.bufferData())。凭这个,我认为是数据量太大导致每次绘制时传输数据缓慢。于是我粗略估算了一下,第一大 PackedArrayBuffer(属于一个颜色数组)的项数量在14万多一点点。才14万,这个数据量还是挺小的。

我做了一个实验,注释或短路掉特定的操作,查看此时的绘制情况。让我惊奇的是,问题源头明显是 RenderHelper.renderPrimitives()RenderHelper.renderBuffered()(因为只有它们调用了 PackedArrayBuffer.syncBuffer(),内部调用 WebGLRenderingContext.bufferData()),但是在将它们的绘制调用(glc.drawElements())注释掉之后,即使保留其他语句,帧率都能达到60。

此时的时间线是这样的:

STATIC_DRAW、无绘制时的时间线

这直接推翻了我“大数据量”的假设,让我更迷惑了:看似完美的绘制逻辑中,哪里出了问题呢?

我想着,先把能优化的优化一下吧。所以我写了一个小小的优化,默认缓存了 Graphics 的内容(原先每一帧都要所有的 IGraphisDataRenderer 重新绘制)。优化前后有什么不同呢?优化前,每一帧都有几次 RenderHelper.renderPrimitives() 调用和几次 RenderHelper.renderBuffered() 调用,优化后只剩下了后者。

这次的绘制帧率不到30,比之前略强了一点:

开始的 FPS

但是时间线的数据和做这个优化之前(注意不是上面第二张图,而是忘记截了的原状态下的时间线)的大相径庭:

做了一点优化后的时间线

查看其时间线可以发现,在后面花了很多时间在非用户脚本优化上,而脚本比例小了很多。也就是说,虽然 drawElements() 调用时间不长,但是 执行处理时间长。这和很久之前的症状是一样的,让我开始怀疑是否是绘制步骤错误或者参数错误。而且,问题一定是出在 RenderHelper.renderBuffered() 中的某个地方。

CPU 探查和堆快照结果如下:

CPU 探查

堆快照——总览

堆快照——最大的数组

可以看到,最大的数组就是 1 MB 多一点。还是一头雾水。我将目标大致放在了 bufferData() 前后。这样的话真凶是使用模式吗?(使用模式影响 OpenGL 对数据的缓存策略。)

一般的例子中用的都是 gl.STATIC_DRAW,意义为写一次频繁用,我蛋疼地试着将它改成了 gl.DYNAMIC_DRAW,意义为频繁写频繁用。令我吃惊的是,帧率马上就上去了:

DYNAMIC_DRAW 的 FPS

CPU 探查结果虽然不是很令人满意,却有了不少的提升:

DYNAMIC_DRAW 的时间线

其他倒是没怎么变化:

CPU 探查

堆快照——总览

堆快照——最大的数组

这么说真凶就是使用模式啦。(其实我并不是很明白为什么——因为实际上传过去的数据几乎不变的。)

堆内存分配那边我看过,并没有内存泄露。

再做一个实验,在改为 gl.DYNAMIC_DRAW 的情况下,如果 Graphics 不默认缓存为位图(贴图)的话,帧率从48下降到42左右。

47 FPS,这个值还是低于 Pixi 的稳定60,而且处理时间还是很长,有没有其他的问题呢?我想到了循环——手工循环(JavaScript)和自动循环(shader),因此转而怀疑起滤镜的应用来。

我目前没有实现自动调整大小的 RenderTarget2D,所以滤镜 shader 每次都应用(对于目前帧大小)1024×512、(5+5)×2+3次,加上其他开销(每个 DisplayObject 有至少两次,一次原始绘制加上至少一次作为孩子被拷贝,一共至少7×2次),量很大。

于是,在 gl.DYNAMIC_DRAW 条件下,将 BlurShaderBlur2Shader 的循环次数改成3+3,在 Graphics 不缓存时平均帧率能达到54,不过边线就更明显了:

3+3、不缓存时的帧率

缓存时能维持在60:

3+3、缓存时的帧率

如果设置为2+2,能轻松达到平均帧率60,不过线条更明显了;设置为1+1就没法看了。

gl.STATIC_DRAW 条件下,缓存时设置为3+3帧率稳定在38左右。可以看到,使用模式不同对效率造成了很大的影响。

关于效果,即使是3+3有边线,窃以为效果还是比 Pixi 的极速shader好的。

综合所有的因素,最后就选择了 gl.DYNAMIC_DRAW、3+3并缓存 Graphics 内容。

X+X 的意义见这里


今天解决了 Visual Studio 2015 的 Web 项目相关的缺失、损坏等问题(就是造成之前 TypeScript 项目属性无法查看的那个)。这个问题还导致我在安装 Web Essentials 时报错说缺少依赖项 Web Developer Tools,在 VS 的安装程序中修改(添加、删除)“Web 开发人员工具”项(也就是 Web Developer Tools)时显示安装成功却实际上没变化(错误依旧)。

StackOverflow 的问题描述

一楼的方案是可用的,修复 Microsoft ASP.NET 5 RC1 即可。

分享到 评论