简介
HTML5 canvas 最初起源于苹果(Apple)的一项实验,现在已经成为了web中受到广泛支持的2D快速模式绘图(2D immediate mode graphic)的标准。许多开发者现在利用它来实现众多的多媒体项目、可视化醒目以及游戏等等。然而,随着我们构建的应用程序的复杂度的增加,我们难免会遇到所谓的性能问题。
已经存在众多优化canvas性能的方法了,但是还没有一篇文章将这些方法系统的整理并加以分析。本文的目的就在于将这些方法整理、巩固以使其曾为开发者们更容易理解、消化、吸收的资源。本文囊括了适用于所有计算机绘图环境(computer graphics environments)的最基本的优化方法,以及特定于canvas的优化方法。其中特定于canvas的优化方法可能会随着canvas实现方式的更新而发生变化。特别的,当浏览器开发商实现了canvas GPU 加速时,我们探讨的某些优化方法可能会显得并不是特别有效,这些情况我们会在特定的地方标注出来。
请注意,本文侧重点不在于讨论HTML5 canvas的用法。如果想了解canvas的具体用法可以参见HTML5 Rocks网站中 canvas相关的文章 。比如 Dive into HTML5 chapter 以及 MDN tutorial 。性能测试
为了处理飞速变化着的HTML5 canvas, JSPerf ( jsperf.com )测试证明了我们在文中提到的每一个方法目前都还生效。JSPerf是一个十分有用的web应用程序,web开发者们可以利用此程序编写JavaScript 性能测试用例。每一个测试用例只关注你企图达到的某一方面的结果(比如说清楚画布),每一个这样的测试用例包含有若干个能够达到同一结果的不同方法。JSPerf在一小段时间内尽可能多的运行每一个方法,并没给出一个统计学上有显著意义的每秒中迭代次数。高分意味着更高的性能。浏览者可以在自己的浏览器中打开JSPerf 性能测试页面,而且可以让JSPerf把标准化的测试结果存储在Browserscope(broserscope.org)中。因为本文中提到的优化技术已经备份到了JSPerf的结果中,因此你可以重新运行查看最新的信息以确定相应的方法是否还有效。我已经写了一个小的帮助应用程序(helper applicatin)来将测试的结果绘制成图表嵌入到整篇文章中。
本文中的性能测试结果与特定的浏览器版本有很重要的关系。由于我们不知到您用的浏览器运行在什么操作系统上,更重要的是也不知道在您进行这些测试时HTML5 canvas是否被硬件加速。你可以在Chrome浏览器地址栏中使用 about:gpu 命令来查看Chrome的HTML5 canvas 是否被硬件加速。
1.PRE-RENDER TO AN OFF-SCREEN CANVAS
我们在写一个游戏的时候常常会遇到在多个连续的帧中重绘相似的物体的情况。在这中情况下,你可以通过预渲染场景中的大部分物体来获取巨大的性能提升。预渲染即在一个或者多个临时的不会在屏幕上显示的canvas中渲染临时的图像,然后再把这些不可见的canvas作为图像渲染到可见的canvas中。对于计算机图形学比较熟悉的朋友应该都知道,这个技术也被称做display list。
例如,假定你在重绘以每秒60帧运行的Mario。你既可以在每一帧重绘他的帽子、胡子和“M”也可以在运行动画前预渲染Mario。
没有预渲染的情况: // canvas, context are defined function render() { drawMario(context); requestAnimationFrame(render); } 预渲染的情况: var m_canvas = document.createElement('canvas'); m_canvas.width = 64; m_canvas.height = 64; var m_context = m_canvas.getContext(‘2d’); drawMario(m_context); function render() { context.drawImage(m_canvas, 0, 0); requestAnimationFrame(render); } 关于requestAnimationFrame的使用方法将在后续部分做详细的讲述。下面的图标说明了显示了利用预渲染技术所带来的性能改善情况。(来自于 jsperf ):
当渲染操作(例如上例中的drawmario)开销很大时该方法将非常有效。其中很耗资源的文本渲染操作就是一个很好的例子。从下表你可以看到利用预渲染操作所带来的强烈的性能提升。(来自于jsperf):
然而,观察上边的例子我们可以看出松散的预渲染(pre-renderde loose)性能很差。当使用预渲染的方法时,我们要确保临时的canvas恰好适应你准备渲染的图片的大小,否则过大的canvas会导致我们获取的性能提升被将一个较大的画布复制到另外一个画布的操作带来的性能损失所抵消掉。
上述的测试用例中紧凑的canvas相当的小:
can2.width = 100; can2.height = 40; 如下宽松的canvas将导致糟糕的性能: can3.width = 300; can3.height = 100; 2.BATCH CANVAS CALLS TOGETHER因为绘图是一个代价昂贵的操作,因此,用一个长的指令集载入将绘图状态机载入,然后再一股脑的全部写入到video缓冲区。这样会会更佳有效率。
例如,当需要画对条线条时先创建一条包含所有线条的路经然后用一个draw调用将比分别单独的画每一条线条要高效的多:
for (var i = 0; i < points.length - 1; i++) { var p1 = points[i]; var p2 = points[i+1]; context.beginPath(); context.moveTo(p1.x, p1.y); context.lineTo(p2.x, p2.y); context.stroke(); } 通过绘制一个包含多条线条的路径我们可以获得更好的性能: context.beginPath(); for (var i = 0; i < points.length - 1; i++) { var p1 = points[i]; var p2 = points[i+1]; context.moveTo(p1.x, p1.y); context.lineTo(p2.x, p2.y); } context.stroke(); 这个方法也适用于HTML5 canvas。比如,当我们画一条复杂的路径时,将所有的点放到路径中会比分别单独的绘制各个部分要高效的多( jsperf ):