【问题标题】:Can WebGL fail to keep up with requestAnimationFrame?WebGL 会跟不上 requestAnimationFrame 吗?
【发布时间】:2020-07-15 16:29:23
【问题描述】:

假设我有一个这样的 WebGL 循环:

function draw(timestamp)
{
    // ...
    gl.drawArrays(...);

    window.requestAnimationFrame(draw);
}

我担心出现以下问题的可能性:

  • 浏览器每秒调用我的 requestAnimationFrame 回调“draw”大约 60 次。
  • gl.drawArrays 立即返回,但底层作业需要 GPU 超过 1/60 秒的时间来执行。
  • 我最终使 gl.drawArrays() 调用比 GPU 执行它们的速度更快。

问题:

  1. 理论上这个问题真的会发生吗?如果是这样,处理它的正确方法是什么?如果不是,那为什么?
  2. 是否保证在我之前对页面上所有画布元素的所有 WebGL 调用都完成之前不会调用“requestAnimationFrame 回调”?如果是这样,那么它是否记录在规范中?我在任何地方都找不到。

提前致谢!

【问题讨论】:

    标签: webgl requestanimationframe


    【解决方案1】:

    不,问题不会发生。

    可能应该将其作为重复项关闭,但要知道为什么您应该阅读the browser's event loop

    基本上浏览器是这样运行的

       const tasks = [];
    
       // loop forever
       for (;;)  {  
         // execute all tasks
         while (tasks.length) {
           const task = tasks.shift();
           task();
         }
         goToSleepUntilThereAreNewTasks();
       }
    

    它从不并行运行 JavaScript(worker 除外),因此您的 requestAnimationFrame 回调不会在另一个 requestAnimationFrame 回调的中间被调用。

    requestAnimationFrame 所做的只是说“下次浏览器要重新绘制屏幕时,将此回调添加到要运行的任务列表中”。

    浏览器中的所有其他事情都会发生相同的情况。当您加载页面时,会添加一个任务来执行您的脚本。当您添加setTimeout 时,浏览器会将您的回调添加到任务列表中,无论您设置的计时器是多少毫秒。当您为 mousemovekeydownimg.onload 之类的事物添加事件侦听器时,浏览器只会在鼠标移动、按键或图像加载完成时将任务添加到任务列表中。

    重要的部分是 JavaScript 的 POV 没有并行工作。每个任务都会运行完成,并且在当前任务完成运行之前,浏览器永远不会处理任何其他任务。

    至于the specs中的文档


    请注意,浏览器无法在不等待 WebGL 绘图调用填充画布的绘图缓冲区的情况下合成页面。它可以并行运行,而不是 JavaScript 本身,但例如在 JavaScript 添加新命令或执行其他操作时执行先前提交的 GPU 命令,但最终它会阻塞。

    从 JavaScript 的 POV 来看,它正在串行运行 requestAnimationFrames。一个发生,它要求下一个。从合成器的 POV(将所有 DOM 元素绘制到浏览器窗口中的代码)来看,它与 JavaScript 并行运行,获取 DOM 的最后状态并在浏览器中绘制下一帧。其中一部分是等待 WebGL 中的调用。它不需要像gl.finish 那样苦苦等待。它只需要以这样一种方式组织 GPU 命令,以便合成 GPU 命令发生在最后一个 WebGL 命令之后。合成器本身在完成当前帧之前不会开始渲染新帧,并且合成器有效地决定了新帧的时间,决定新帧的时间是执行 requestAnimationFrame 事件的触发器。

    浏览器充其量将双倍或三倍缓冲,这样当浏览器合成这一帧时,它可以让 JavaScript 开始为下一帧排队命令,因此有更多的并行性,但同样,它不会发出无限的 requestAnimationFrame 回调等待它生成的帧以一种或另一种方式完成。

    所以不,你指出的问题不可能发生。


    注意:上面两个分隔符之间的部分最初不存在。 Kaiido 在评论中指出,他们认为我没有回答这个问题。我以为我是含蓄的。事件循环的含义是,

    1. rAF 事件在浏览器决定绘制页面之前不会执行
    2. 如果当前正在为当前帧绘制页面,它不会决定再次绘制页面
    3. 在您通过 WebGL 向画布发出的命令执行完毕之前,它显然无法完成页面的绘制。

    第 3 步似乎很明显,无需说明。如果浏览器不等待画布将您绘制的图像放入其中,它如何绘制页面?剩下的就从事件循环的影响中消失了。如果当前帧没有完成,为什么浏览器会开始一个新帧?它不会。如果它还没有开始一个新的帧,那么它就不会调用你的 rAF 回调。

    我还想明确指出,这是一个合理的问题。就像我上面提到的,浏览器可能会让 JavaScript 在忙于合成当前帧时开始制作下一帧。如果浏览器确实让 JavaScript 并行启动下一帧并且没有采取任何措施来限制您的问题,则可能会发生。浏览器会以一种或另一种方式与 GPU 同步,并确保它已提交给 GPU 的帧在它获得太多帧(1 或 2 帧)之前已经完成执行。在 OpenGL 术语中,它可以使用sync objects 轻松完成此操作。我并不是说浏览器使用同步对象。只是浏览器可以做类似的事情来确保它不会提前获得太多帧。


    我不知道这是否会使事情变得或多或少令人困惑,但 Kaiido 在聊天讨论中指出,如果您使用 setTimeout 而不是 requestAnimationFrame,那么从某种意义上说,您会遇到您担心的问题关于。

    假设你这样做了

    function draw(timestamp)
    {
        // ...
        gl.drawArrays(...);
    
        setTimeout(draw);
    }
    

    在这种情况下,setTimeout 为 0(或任何低于一帧的数字)在合成之间发出多少 GPU 命令是未知的。也许它在复合之间调用draw 5 次或 500 次。它是未定义的,取决于浏览器。

    我要指出的是,在复合材料之间通过setTimeout 多次调用draw 不是“帧”,它只是发布更多GPU 工作的一种非常模糊的方式。 draw 在通过 N setTimeouts 或仅通过简单的 for (let i = 0; i < N; ++i) { draw(); } 循环在复合之间被调用 N 次之间没有功能差异。在这两种情况下,复合材料之间的 GPU 都添加了大量工作。与setTimeout 方法的唯一区别是浏览器为您选择了N

    这实际上是使用requestAnimationFrame 的要点之一,因为setTimeout 与帧没有任何关系。

    【讨论】:

    • 这里没有回答问题。他们说gl.drawArrays() 立即返回,因此 js 可以立即继续,一旦任务完成,事件循环可以继续执行其他任务。但他们担心 GPU 调用,根据 OP,这是并行完成的。如果这个调用需要几秒钟的时间,那么事件循环将完成数千个新调用,这些调用只会累加到 GPU 调用中。所以我猜的问题真的是事件循环中的“更新渲染”步骤是否在内部等待 gl.finish()
    • 第二个问题是“在我之前对页面上所有画布元素的所有 WebGL 调用都完成之前,是否保证不会调用“requestAnimationFrame 回调”?”答案实际上是“是”。因为为了合成页面,浏览器必须等待 WebGL 的绘制调用。因此,由于事件循环,它不会发出另一个 rAF。浏览器可能会增加三倍缓冲区,但在绘制调用完成之前,它有时无法绘制页面
    • “因为为了合成页面,浏览器必须等待 WebGL 的绘图调用”那么这就是问题的答案,但它不在您的答案中,也不在您的任何链接中。
    • 这在我的回答 IMO 中有所暗示,但我确信我可以添加这些细节
    • 感谢您的编辑,但不确定是否需要所有这些“注释:”,我仍然觉得第一段没有遇到问题,它仍然将浏览器的事件循环呈现为简单的东西à -la node.js,还是没有提到“渲染机会”,这应该是这里的关键。现在,附录现在暗示浏览器在将“can-render”标志设置为 true 之前确实会等待 GPU 完成。我认为这在某种程度上是正确的,但这是否意味着如果他们使用其他调度方法,OP 实际上可能会面临他们提到的问题?
    猜你喜欢
    • 2014-03-25
    • 2014-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多