不,问题不会发生。
可能应该将其作为重复项关闭,但要知道为什么您应该阅读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 时,浏览器会将您的回调添加到任务列表中,无论您设置的计时器是多少毫秒。当您为 mousemove 或 keydown 或 img.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 在评论中指出,他们认为我没有回答这个问题。我以为我是含蓄的。事件循环的含义是,
- rAF 事件在浏览器决定绘制页面之前不会执行
- 如果当前正在为当前帧绘制页面,它不会决定再次绘制页面
- 在您通过 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 与帧没有任何关系。