HTML Canvas 2D 太慢了

问题描述 投票:0回答:1

请帮助我理解为什么在 2D 上下文中在 HTML 画布上渲染的过程需要这么长时间,并且有多个帧从动画中掉出,并且有大量的点。此外,根据开发者工具的“性能”选项卡,JavaScript代码的执行时间大幅落在分配的16毫秒内,并且主要动画时间被GPU的工作占用。我会提前表明我正在 Google Chrome v122.0.6261.111 浏览器上进行测试。

这是我的示例代码

function main() {
    const canvas = document.getElementById('canvas')
    if (!canvas) return

    canvas.width = window.innerWidth
    canvas.height = window.innerHeight

    const ctx = canvas.getContext('2d')
    if (!ctx) return

        const length = 20000
    const xStep = canvas.width / length
        const diapason = 50
        const datasetsQty = 7
    const datasets = new Array(datasetsQty).fill(undefined).map((item, index) => {
            const bottom = index * 100
            return generateRandomDataset(length, [bottom, bottom + diapason], xStep)
        })

    ctx.strokeStyle = 'red'
    ctx.lineWidth = 2

    requestAnimationFrame(() => {
        renderer(ctx, datasets)
    })
}

requestAnimationFrame(() => {
    main()
})

/**
* global Y offset
*/
let count = 0

/**
*   build one line per dataset with global Y offset
*/
function renderer(ctx, datasets) {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    ctx.beginPath()

    for (let dataset of datasets) {
        for (let i = 0; i < dataset.length; i++) {
            if (!i) ctx.moveTo(dataset[i][0], dataset[i][1] + count)
            else ctx.lineTo(dataset[i][0], dataset[i][1] + count)
        }
    }

    ctx.stroke()
    count++
    count %= window.innerHeight

    requestAnimationFrame(() => {
        renderer(ctx, datasets)
    })
}

/**
* generate random dataset in given Y coord range and X is incrementing value
* [[0, random], [1, random], [n, random]...]
*/
function generateRandomDataset(length, range = [0, 50], xStep) {
    const arr = []
    let x = 0

    for (let i = 0; i < length; i++) {
        arr.push([
            x,
            Math.random() * (range[1] - range[0]) + range[0],
        ])
        x += xStep
    }


    return arr
}
<canvas id="canvas"></canvas>

这里我已经想象出了这个问题。简而言之,有几个数据集(具体为 7 个)。在每个后续帧中,我绘制所有数据集,其 Y 轴偏移量比前一帧低一个像素。 该代码非常原始 - 没有矩阵或路径对象。然而,使用矩阵等并不会影响问题——JavaScript代码发送数据进行渲染所需的时间减少了,但GPU的运行时间与这个事实几乎没有相关性。

    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    ctx.beginPath()

    for (let dataset of datasets) {
        for (let i = 0; i < dataset.length; i++) {
            if (!i) ctx.moveTo(dataset[i][0], dataset[i][1] + count)
            else ctx.lineTo(dataset[i][0], dataset[i][1] + count)
        }
    }

    ctx.stroke()

我会给你一些截图来帮助你理解我们在说什么。 Here 是第一帧的性能测量。请注意,javascript代码执行时间仅仅超过1ms,而GPU加载时间已经超过50ms!当然,一些动画帧会被丢弃。 当数据集超出画布时,情况就会发生变化 - JavaScript 代码的执行时间不会改变,GPU 加载时间也会相应减少。例如,here 是画布上具有一个数据集的最后一帧,以及显示所有数据集的后续帧。正如您所看到的,一旦我们必须再次绘制可见区域中的所有数据集,图形处理器就会立即陷入负载,再次导致帧丢失。

几个事实增加了这个问题的兴趣:

  1. 在 Mozilla 中,动画更加流畅,GPU 上没有如此巨大的负载。
  2. 如果减小Y值的范围而不减少一条线的点数,那么动画会更平滑。 (即当描绘的数据集形状趋于直线时)
  3. 也许如此高的GPU负载是由于画布分配到了单独的级别,这很可能导致GPU的使用

问题本质上已经包含在开头了。为什么 GPU 上有如此巨大的负载以及如何避免它?

提前谢谢您,我真的希望有人可以帮助我解决提出的问题。

javascript html canvas gpu
1个回答
0
投票

你的例子对我来说是这样的:

但是当我将线宽设置为 1 像素后:

ctx.lineWidth = 1

在测量性能时,GPU 上的负载显着下降,视觉渲染没有任何滞后:

© www.soinside.com 2019 - 2024. All rights reserved.