为什么 CSS3 滤镜效果比 HTML5 Canvas 等效效果更高性能?



我目前正在对HTML5画布上的图像应用简单的滤镜效果。类似于此处定义的技术:

HTML5 画布图像对比度 在这个HTML5岩石!博客文章

但是,这种特殊方法需要在重绘之前遍历每个像素并应用修改器。对于我的特定用例,这需要 150ms+ 来重绘我的图像(650px x 650px PNG)

使用 CSS3 的滤镜属性(对比度或亮度)应用相同的效果需要不到 10 毫秒的时间。

我的问题是:CSS3 的过滤器属性如何在"引擎盖下"工作?为什么它的性能明显更高?有没有办法在 Canvas 中实现类似的性能?

Canvas 通过 API 公开。JavaScript 是一个运行时,即使这些天进行了大量优化,如果你打算从 JavaScript 逐像素访问操作 2D 网格,你将付出从运行时一直到包含当前存储在画布中的图像的缓冲区的代价,对于每个像素。机器主要遵循和实现你的数据访问模式,所以你可以说你最终成为这里性能的主要障碍,通过"过度支配"解决方案——例如,通过将过滤器表示为命令式逐像素数据分配,并且因为解释器在优化"自由格式"代码的能力方面通常受到限制。

CSS3 过滤器是一个黑盒,可以批量执行相同的转换,这意味着受约束的 GPU 着色器程序直接在 GPU 上运行,访问通常存储在靠近 GPU 的可立即寻址内存中的图像,后者受益于其设计的SIMD 级指令以处理整个像素矩阵。此外,访问本地内存的本机代码 - 几乎与它一样快,无需详细说明。GPU 利用非常长的执行管道- 简单地说,一长串未重新启动或检查的操作,以便可以尽快执行这些操作。后者的类比是一列重型火车经过一个它知道它没有计划停靠的车站,所以它可以以最高速度通过它,就好像它不在那里一样。这是GPU用来快速处理数据的技巧之一 - 假设环境受限,这样您就不必考虑"一切"并且可以进行更多优化。

即使在 CPU 上运行,没有任何 GPU 帮助,我们谈论的是原生过滤器内核直接在 RAM 中操作图像,没有任何字节码,更重要的是根本不考虑 JavaScript。您声明所需的过滤器,用户代理在画布图像上调用过滤器程序,仅此而已。CPU 还具有处理数据矢量的 SIMD 指令,这显然有很大帮助。过滤器代码甚至不是您的,您只能按名称引用它。

现在,如果你可以直接将某种黑盒过滤器(如CSS中可用的黑盒过滤器)应用于画布像素数据,您可能会达到与CSS相同的速度 - 因为最重要的速度障碍 - 用JavaScript代码表示的逐像素访问 - 被消除了。所以这不仅仅是关于JavaScript,这是关于数据访问的粒度。在这种情况下,将内核批量应用于数据总是比自己在更高级别编写内核代码更快。简单地说,这就把我带到了下面的最后一点。

现在,如果 JavaScript 解释器能够理解您的逐像素循环操作,因为它可以将其全部转换为利用 SIMD 甚至 GPU 着色器的本机代码,所有这些都来自自由类型的 JavaScript 过滤器代码,这将弥合性能差距。但是你会把复杂性转移到JavaScript编译器/解释器中,在这种规模上进行优化的问题在计算机科学中还不是一个完全解决的问题。也许人工智能和机器学习会有所帮助,我不会推测。请注意,我不是在谈论将你的JavaScript编译为等效的本机代码的JIT,这已经是多年来的常态,我说的是将自由类型的JavaScript代码识别为类似于已知甚至任意图像内核的东西,就像人类一样。然后用运行时对应项替换所述代码,该代码提供相同的结果,但由编译器编写以产生它认为将提供最佳性能的内容。

在实践中,我认为如果你更深入地研究 Canvas API,你可以优化基于 Canvas 的 JavaScript 朴素逐像素过滤器。可以通过可从方法调用获取CanvasRenderingContext2D.getImageData()Uint8ClampedArray类型访问图像数据。这种数组类型有一些有趣的"批量"函数,如filterforEachmapreduce等。在浏览 Canvas API 文档时,您需要像"黑客"一样思考,以假设演示人员的心态 - 根据它们可以为您提供的内容来查看可用的方法和数据类型。这样做的回报可能是巨大的。

如果这还不够,可以使用WebGL渲染画布,WebGL是OpenGL的一个子集,通常实现为仅在GPU上运行。着色器保证是WebGL的一部分,这意味着你可以免费获得WebGL的各种高级和超快速画布过滤器程序的门票,这些程序是你自己编写的,但通常在GPU上执行。

最新更新