【问题标题】:CanvasRenderingContext2D.drawImage() very slow in Chrome on large canvasCanvasRenderingContext2D.drawImage() 在大画布上的 Chrome 中非常慢
【发布时间】:2016-03-18 09:13:16
【问题描述】:

我目前正在为我的city building game 项目开发一个网络客户端(HTML5 和 JavaScript)。客户端通过 SignalR 与用 C#/.NET 编写的 Web 服务器进行交互,所有游戏逻辑都驻留在该服务器上。

该实现需要相当大的地图,该地图由一组代表不同图层的画布元素实现。地图的实际绘制包括绘制 25x25px 的单元格,其中一些是动画的。这意味着在“2D 上下文”上发生了很多小的“drawImage”调用。

当前的实现在 Mozilla Firefox、Internet Explorer 和 Edge 中运行良好且流畅。然而,它在 Google Chrome 上非常慢,很可能是由于其硬件加速渲染的实现效果不佳。

瓦片单元 .PNG 图像的获取是通过从 Web 服务器下载它们并将它们存储在内存中的“图像”对象中来完成的。从那里,我在必要时将它们直接绘制到画布上。 如果我目前的研究做得正确,这就是瓶颈所在;源“图像”对象驻留在 CPU 内存中,而目标 Canvas 元素针对 GPU 内存访问进行了优化,导致大量交换。

我尝试将“图像”对象移动到一个大的屏幕外“缓冲区”画布(足够大,以至于应该在 Chrome 上启动硬件加速),但这不会产生明显的差异: https://github.com/Miragecoder/Urbanization/commit/86ac62a785b233eea28c53b8a7d474ef92ffc283

我还尝试通过requestAnimationFrame 实现“drawImage”函数的延迟调用,但这也没有产生明显的差异。

我有以下问题:

  • 我是否正确理解了这个问题?
  • 如何提高 Web 客户端的性能?

一些我研究过但到目前为止没有结果的问题的链接:

【问题讨论】:

  • 查看代码你不会强迫任何东西到 GPU 内存。如果没有空间,只有渲染才会将图像交换到 GPU 内存中。如果你对渲染顺序很小心,你会得到一些好处。即'ctx.drawImage(one,0,0); ctx.drawImage(二,0,0); ctx.drawImage(one,0,0); ' 将产生 3 次交换,而 'ctx.drawImage(one,0,0); ctx.drawImage(one,0,0); ctx.drawImage(二,0,0); ` 只会产生 2 个。较小的图像(整体的一部分)也会有所帮助。
  • 嗨,Blindman67。渲染顺序到底是什么意思?例如,首先开始渲染底层然后向上移动?此外,“drawImage”调用小到 25x25 像素,所以我认为那里几乎不需要优化。感谢您的想法!
  • 我不认为将图像放在另一个画布上对 gpu 有帮助。我不认为说“这些图像已经在 gpu 内存上,不需要重新上传它们”这样优化。优化绘图顺序似乎是最好的。你在所有gpus上都得到同样的慢?英伟达英特尔?
  • 以下提交:github.com/Miragecoder/Urbanization/commit/… 执行以下操作: - 从大型缓冲区画布中提取 25x25 平铺图像(void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth , d高度);); - 按平铺图像对“drawImage”调用进行分组,并以有序的方式执行它们。我相信这是您和 Blindman67 都提出的方法。我认为它确实产生了一些显着的改进,但整体性能仍然很差。感谢您的建议!你还有吗?

标签: javascript google-chrome html5-canvas


【解决方案1】:

您的主要问题似乎是您用于在画布上绘制的不同图像对象的数量。您绝对应该使用Textureatlas,将尽可能多的图形放在一张图片中。

然后,您应该通过指定相关的矩形,从尽可能少的主图像中渲染您的精灵:

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

而不是整个图像:

void ctx.drawImage(image, dx, dy);

详情请参阅CanvasRenderingContext2D.drawImage()。这样,您可以避免过多的上下文切换。正如 Blindman67 所提到的,您应该注意尽可能少地在纹理图集之间切换 - 例如,您可能希望将一个纹理图集用于对 canvas1 的所有渲染,另一个用于对 canvas2 的所有图像等。

【讨论】:

  • 嗨,T Grando!我给了你的建议:github.com/Miragecoder/Urbanization/tree/textureatlas 我在 web 客户端上实现了纹理图集的生成和消费。这绝对是一种更好的方法,性能略有提高。然而,它仍然非常缓慢。我需要对此进行更多研究;谢谢你的建议!
  • 我刚刚将前面提到的更改(以及一些新更改)合并到“主”分支中。我仍然对仍然存在的性能问题感到有些困惑。我应该优化绘制到画布层的顺序吗? (对于每一帧,首先绘制到canvas1,然后绘制到canvas2,等等)现在这些层的绘制是基于先到先得的。我想这可能会导致一些上下文切换?
  • @RobWijkstra 没错。秩序很重要。在目标(canvas1、canvas2 等)之间切换是上下文切换。尝试通过将所有内容绘制到 canvas1,然后将所有内容绘制到 canvas2 等来避免这种情况。
猜你喜欢
  • 2016-06-26
  • 2014-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-26
  • 2018-02-17
  • 1970-01-01
相关资源
最近更新 更多