【问题标题】:JavaScript buffering for canvas based games: mediocre performance基于画布的游戏的 JavaScript 缓冲:性能一般
【发布时间】:2014-01-22 08:34:15
【问题描述】:

我正在编写一个 JavaScript 游戏。这个游戏显然需要不断地渲染一个屏幕,使用canvas,它必须是Uint8Array(width * height * 4)(就像Canvas的ImageData使用的那样)。为了测试预期的 FPS,我尝试用白色填充该数组。令我惊讶的是,性能是mediocre。在 150 fps 的高端计算机中,我几乎无法用白色填充 1000x1000 位图。考虑到这是最佳性能,没有任何游戏逻辑运行,最终结果可能会低得多。我的问题是:为什么性能这么低,我可以做些什么来改进它?

【问题讨论】:

  • 你真的需要在这里展示一些代码,因为细节很重要。画布已经是双缓冲的,所以你需要缓冲并不明显。特别是因为缓冲区副本可以使游戏崩溃,直接绘制可以运行。在屏幕上绘制(硬件加速)时的性能可能比在屏幕外(这里的一些浏览器切换到软件渲染)要好得多。我担心你无法从这个测试中推断出任何东西。
  • 画布和使用 ImageData 存在很多问题。 #1 您将数据放入 ImageData.data 字段。 #2 你调用 putImageData,系统很可能使用 CPU 将你的数据预乘以 alpha(因为画布仅在内部使用预乘 alpha)到另一个缓冲区。在 Chrome 上,缓冲区必须转移到另一个进程,GPU 进程,这也是另一个副本,之后必须调用 glTexImage2D 将数据上传到纹理,因为画布在大多数硬件上都是硬件加速的。当它全部是软件时 putImageData 更快,但仅此而已。

标签: javascript canvas bitmap webgl


【解决方案1】:

计算使用 putImageData 可以填充 1000x1000 画布的次数不会给您任何实际的结果。原因是图形是流水线的。在普通应用程序中,您每帧只调用一次 putImageData。如果您在某个时刻一帧多次调用它,您将填充管道并使其停止。在一个真正的应用程序中,虽然您会在大部分帧中操作数据并且只上传一次而不会使管道停止。

如果你想看看你能做多少工作,制作一个 1000x1000 的画布,在其上调用 getImageData 以获取 ImageData,对该图像数据进行一定量的操作,调用 putImageData,然后调用 requestAnimationFrame 并再次执行此操作。慢慢增加你所做的操作量,直到它开始以低于 60fps 的速度运行。这将告诉您实际可以做多少工作。

这是一个尝试这个的小提琴 http://jsfiddle.net/greggman/TVA34/

此外,使用 putImageData 也有一些问题。一是 Canvas 需要预乘 alpha,但 putImageData 提供未预乘 alpha,这意味着某些过程必须将您的数据转换为预乘 alpha。然后,现在,大多数 Canvas 实现都是 GPU 加速的。这对 Canvas API 的几乎所有功能都非常有用,但对于 putImageData 来说却很糟糕,因为这些数据必须从 CPU 传输到 GPU。 getImageData 更糟糕,因为将数据从 GPU 复制回 CPU 通常会使 GPU 停滞。 HD-DPI 机器上的故事更糟糕。 getImageData 和 putImageData 必须从 CSS 像素转换为实际使用的分辨率。

如果您使用 WebGL,您至少可以跳过预乘 alpha 转换步骤。

这是一个 WebGL 版本 http://jsfiddle.net/greggman/XLgs6/

有趣的是,在我的 2012 Macbook Pro Retina 上,我发现画布版本在 Chrome 和 Safari 上速度更快。我很好奇为什么,因为我没想到会对它们起作用。

/*
               Canvas   WebGL
Chrome 32    : 710k     650k   numbers are in 'operations per frame`
Firefox 26   :  80k     190k
Safari 7.0.1 : 150k     120k

*/

我的测试也可能无效。仅处理 710k 像素(1000k 像素)似乎很慢。也许像 Math.randomMath.floor 这样的函数之一特别慢,尤其是考虑到它们使用双精度。

【讨论】:

  • +1 只是为 OP 添加一些提示:您可以在字节数组上使用 Uint32Array view 来提高操作缓冲区的性能(适合填充单一颜色或置换像素)。还有一个可能很明显的,如果你只为你的游戏使用像素缓冲区(=没有上下文方法)你不需要每次都getImageData()。完成后重新放置即可。在此处向the jsperf 添加了两个 Uin32 测试。
  • 我打算建议使用 Uint32Array 视图。不幸的是,如果你这样做,它不是跨平台的。也许没有人关心,因为没有流行的大端机器和流行的浏览器。
【解决方案2】:

查看这 2 个测试

只做循环并分配给同一个地址

http://jsperf.com/variable-assign

很好的旧数组扩展(也是另一个流行的 for 循环技巧,假设更快)

http://jsperf.com/fill-an-type-array-expand/3

第一次测试显示地址查找大约需要 3/4 的时间。

第二次测试表明 for 循环占用了 30% 以上的时间。

我认为类型化数组真正缺乏的是一些本机代码,这些代码会执行真正用于游戏开发的块复制(而不是在测试中逐个像素地设置)。 WebGL 可能值得考虑。

【讨论】:

  • 不错的观点,但我猜你犯了一个错误,你不应该在那里有l = a.length / 10。我正在考虑使用 WebGL——我希望我可以在浏览器上为 CPU 编程。
猜你喜欢
  • 1970-01-01
  • 2014-06-05
  • 1970-01-01
  • 2015-09-30
  • 2013-03-26
  • 1970-01-01
  • 2015-12-14
  • 2014-10-10
  • 1970-01-01
相关资源
最近更新 更多