【问题标题】:How does 2D drawing frameworks such as Pixi.js make canvas drawing faster?Pixi.js 等 2D 绘图框架如何让画布绘图更快?
【发布时间】:2023-03-31 16:02:01
【问题描述】:

我为 Javascript 画布 here 找到了一个兔子标记。

当然,我知道他们的默认渲染器使用的是 webGL,但我现在只对原生 2D 上下文性能感兴趣。我在 Firefox 上禁用了 webGL,在生成 16500 个兔子后,计数器显示 FPS 为 25。我决定编写自己的非常简单的小渲染循环,看看 Pixi 增加了多少开销。令我惊讶的是,我的 FPS 仅为 20。

我大致相当于JSFiddle

所以我决定看看他们的源代码here,看起来他们的渲染代码并不神奇:

do  
{
    transform = displayObject.worldTransform;
            ...
    if(displayObject instanceof PIXI.Sprite)
    {

        var frame = displayObject.texture.frame;

        if(frame)
        {
            context.globalAlpha = displayObject.worldAlpha;

            context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]);

            context.drawImage(displayObject.texture.baseTexture.source, 
                               frame.x,
                               frame.y,
                               frame.width,
                               frame.height,
                               (displayObject.anchor.x) * -frame.width, 
                               (displayObject.anchor.y) * -frame.height,
                               frame.width,
                               frame.height);
        }                      
    }

奇怪的是,似乎他们在渲染循环中使用了一个链表,并且两个应用程序上的配置文件显示,虽然我的版本每帧分配相同数量的 cpu 时间,但它们的实现显示 cpu 使用率处于峰值。

不幸的是,我的知识到此结束,我很好奇是否有人可以了解正在发生的事情。

【问题讨论】:

    标签: javascript html canvas


    【解决方案1】:

    在我看来,我认为归结为代码的“可编译性”(可缓存)程度。 Chrome 和 Firefox 使用两种不同的 JavaScript“编译器”/引擎,因为我们知道它们以不同的方式优化和缓存代码。

    画布操作

    使用变换与直接坐标不应该产生影响,因为设置变换只会更新矩阵,在任何情况下都与其中的任何内容一起使用。

    位置值的类型会影响性能,floatinteger 值,但由于您的小提琴和 PIXI 似乎都使用浮点数,这不是这里的关键。

    所以在这里我不认为画布是造成差异的原因。

    变量和属性缓存

    (在这个答案的第一个版本中,我无意中过于关注 prototypal 方面。我试图理解的本质主要是对象遍历,所以这里重新措辞以下文本有点-)

    PIXI 使用对象属性作为小提琴,但 PIXI 中的这些自定义对象的大小较小,因此与遍历画布或图像等较大对象(属性如width 也将在此对象的末尾)。

    由于这个原因(遍历时间),缓存变量是众所周知的经典优化技巧。随着引擎变得更加智能,今天的效果已经不那么好了,尤其是 Chrome 中的 V8,它似乎能够在内部更好地预测/缓存这一点,而在 Firefox 中,不在代码中缓存这些变量似乎仍然会产生一些影响。

    在性能方面重要吗?对于简短的操作来说很少,但是在画布上绘制 16,500 个兔子是很费力的,并且确实会从这样做(在 FF 中)中获益,因此在这种情况下,任何微优化实际上都非常重要。

    演示

    我对“渲染器”进行了原型设计,以更接近 PIXI 并缓存对象属性。这给 Firefox 带来了性能提升:
    http://jsfiddle.net/AbdiasSoftware/2Dbys/8/

    我使用了一台速度较慢的计算机(以扩大影响),它以大约 5 FPS 的速度运行您的小提琴。缓存值后,它以 6-7 fps 的速度运行,这在这台计算机上增加了 20% 以上,表明它确实有效果。在具有较大 CPU 指令缓存等的计算机上,效果可能会更小,但它就在那里,因为这与 FF 引擎本身有关(免责声明:我并不是声称这是一个科学测试,只是一个指针:- ) )。

    /// cache object properties
    var lastTime = 0,
        w = canvas.width,
        h = canvas.height,
        iw = image.width,
        ih = image.height;
    

    下一个版本将这些变量缓存为对象(本身)的属性,以表明与直接使用大型全局对象相比,这也提高了性能 - 结果与上面大致相同:
    http://jsfiddle.net/AbdiasSoftware/2Dbys/9/

    var RENDER = function () {
        this.width = canvas.width;
        this.height = canvas.height;
        this.imageWidth = image.width;
        this.imageHeight = image.height;
    }
    

    总结

    根据结果和之前的经验,我确信 PIXI 可以更快地运行代码,因为它使用自定义的小尺寸对象,而不是直接从画布和图像等大对象(元素)中获取属性。

    在对象遍历树和分支方面,FF 引擎似乎还没有 V8 引擎那么“智能”,因此缓存变量确实会对 FF 产生影响,当需求很高时(例如当每个“帧”绘制 16,500 只兔子)。

    【讨论】:

    • 这很有意义。您是否有任何关于使用链表与数组进行渲染的 cmets? (PIXI好像用的是链表的方式)
    • @NoHarmInTrying 通常数组更快,但不知道 f.ex. V8 在内部(在节点级别)处理链表我不确定在这种情况下会产生什么影响。但在 JavaScript 中,数组也不是真正的数组(类型化数组除外,它是真正的数组)。在过去,您可以将对象属性转换为数组索引(今天:尽可能使用类型数组)以提高性能。
    【解决方案2】:

    我注意到您的版本和 Pixi 的版本之间的一个区别是:

    通过将 x/y 直接传递给 drawImage 函数,您可以在特定坐标处渲染图像:

    drawImage(img, x, y, ...);
    

    ..而 Pixi 翻译整个画布上下文,然后在 0/0(已经转移的上下文)处绘制图像:

    setTransform(1, 0, 0, 1, x, y);
    drawImage(img, 0, 0, ...);
    

    他们还将更多参数传递给drawImagearguments that control "destination rectangle"dxdydwdh

    我怀疑这是隐藏速度差异的地方。但是,将您的测试更改为使用相同的“技术”doesn't really make things better

    但还有别的……

    我将 bunnies 计时到 5000,禁用了 WebGL,Pixi 实际上性能比自定义小提琴版本差

    我在 Pixi 上获得约 27 FPS:

    在 Fiddle 上大约 32-35 FPS:

    这一切都在 Chrome 33.0.1712.4 dev、Mac OS X 上。

    【讨论】:

    • 这很有趣,在 Chrome 31 上,我的 fiddle 版本和 16,500 只兔子的速度为 15 fps,而 Pixi 的速度为 10 fps。但是,对于 Firefox,Pixi 更快。此外,drawImage(img,0,0) 似乎并不比 drawImage(img,x,y) 快。
    • 我不太明白为什么人们不赞成这个答案。即使它没有 100% 回答问题,它也是一个有见地的分析......:-/
    【解决方案3】:

    我怀疑这是一些画布合成问题。画布默认是透明的,所以页面背景需要结合画布内容...

    我在他们的源代码中找到了这个...

    // update the background color
    if (this.view.style.backgroundColor != stage.backgroundColorString &&
        !this.transparent) {
        this.view.style.backgroundColor = stage.backgroundColorString;
    }
    

    也许他们为这个演示设置了不透明的画布(小提琴对我来说真的不起作用,似乎大多数兔子大多数时候都会跳出一个非常大的 dt)?

    我不认为这是一个对象属性访问时间/可编译性的事情:这点是有效的,但我认为它不能解释那么大的差异。

    【讨论】:

    • 是的,大的dts会导致兔子跳出屏幕。一个快速而肮脏的解决方法是将s.dy += 400 * dt; s.x += s.dx * dt; s.y += s.dy * dt 中的dt 更改为固定数字,例如0.016
    • 顺便说一句:这(背景/透明度)是否解释了差异?
    • 不,我不知道如何使画布变得不透明。我尝试将clearRect 更改为fillRect,似乎没有太大区别。
    • 只使用CSS? thecanvas.style.opacity = 1;
    • 试过了,没有明显区别。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    • 1970-01-01
    • 2012-05-09
    • 2011-02-01
    • 2017-03-26
    • 1970-01-01
    相关资源
    最近更新 更多