Может ли WebGL не успевать за requestAnimationFrame? - PullRequest
1 голос
/ 14 июля 2020

Допустим, у меня есть WebGL l oop вроде этого:

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

    window.requestAnimationFrame(draw);
}

Я опасаюсь возможности возникновения следующей проблемы:

  • My requestAnimationFrame callback " draw "вызывается браузером примерно 60 раз в секунду.
  • gl.drawArrays возвращается немедленно, но выполнение базового задания требует от графического процессора более 1/60 секунды.
  • I в конечном итоге вызовы gl.drawArrays () выполняются быстрее, чем их может выполнить графический процессор.

Вопросы:

  1. Может ли эта проблема возникнуть теоретически? Если да, то как правильно с этим бороться? Если нет, то почему?
  2. Гарантируется ли, что обратный вызов requestAnimationFrame не будет вызван до тех пор, пока все мои предыдущие вызовы WebGL не будут выполнены для всех элементов холста на странице? Если да, то задокументировано ли это в спецификациях? Нигде не нашел.

Заранее спасибо!

1 Ответ

1 голос
/ 15 июля 2020

Нет, проблема не может возникнуть.

Вероятно, следует закрыть это как дубликат, но чтобы знать, почему вы должны прочитать событие браузера l oop

Обычно браузер работает так:

   const tasks = [];

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

Он никогда не запускает JavaScript параллельно (кроме рабочих), поэтому обратный вызов requestAnimationFrame не будет вызван в середине другого requestAnimationFrame обратного вызова .

Все, что делает requestAnimationFrame, по сути, говорит, что «в следующий раз браузер перекрасит экран, добавьте этот обратный вызов в список задач для запуска».

Все остальное в браузере случится то же самое. Когда вы загружаете страницу, добавляется задача для выполнения ваших скриптов. Когда вы добавляете setTimeout, браузер добавит ваш обратный вызов в список задач за то количество миллисекунд, на которое вы установили таймер. Когда вы добавляете прослушиватели событий для таких вещей, как mousemove, keydown или img.onload, браузер просто добавляет задачи в список задач при перемещении мыши, нажатии клавиши или завершении загрузки изображения.

Важная часть - нет параллельной работы с точки зрения JavaScript. Каждая задача выполняется до завершения, и браузер никогда не обрабатывает другие задачи, пока текущая задача не завершится.

Что касается документации, которая находится в спецификациях

Обратите внимание, что браузер не может составить страницу без какого-либо ожидания, пока ваши вызовы отрисовки WebGL заполнят буфер рисования холста. Он может запускать вещи параллельно, но не сам JavaScript, но, например, выполняет ранее отправленные команды графического процессора, пока JavaScript добавляет новые или делает что-то еще, но в конечном итоге он блокируется.

Учтите, что из POV из JavaScript он запускает requestAnimationFrames серийно. Одно бывает, другое запрашивает. Из точки обзора композитора, который представляет собой код, который выводит aws все элементы DOM в окно браузера, он работает параллельно с JavaScript, принимая последнее состояние DOM и отрисовывая следующий кадр в браузере. Отчасти это связано с ожиданием звонков в WebGL. Ему не нужно долго ждать, как gl.finish. Ему просто нужно организовать команды графического процессора таким образом, чтобы команды компоновки графического процессора выполнялись после последних команд WebGL. Сам композитор не будет начинать рендеринг новых кадров до тех пор, пока не закончит текущий кадр, и именно композитор фактически решает, что пришло время для нового кадра, решая, что пора для нового кадра, является триггером для выполнения события requestAnimationFrame.

В лучшем случае браузер удвоит или утроит буфер, так что пока браузер составляет этот кадр, он может позволить JavaScript начать выстраивать в очередь команды для следующего кадра, чтобы было больше параллелизма, но, опять же, он не будет выдавать infinite requestAnimationFrame выполняет обратные вызовы, не дожидаясь фрейма (ов), который он генерирует, до завершения sh так или иначе.

Так что нет, проблема, на которую вы указали, не может возникнуть.

примечание: раздел выше между двумя разделителями изначально не существовал. Кайидо указал в комментарии, что они не думали, что я ответил на вопрос. Я думал, что сделал это косвенно. Последствия события l oop заключаются в том, что

  1. события rAF не выполняются, пока браузер не решит, что пора нарисовать страницу
  2. Он не собирается решать нарисуйте страницу снова, если она в данный момент находится в середине отрисовки страницы для текущего кадра.
  3. Очевидно, что sh отрисовка страницы не может быть завершена до тех пор, пока не будут выполнены команды, которые вы отправили на холст через WebGL.

Шаг 3 кажется очевидным и не требует объяснения. Как браузер мог нарисовать страницу, если он не ждал, пока на холсте появится нарисованное вами изображение? Из того, что остальное выпадает из последствий события l oop. Зачем браузеру начинать новый фрейм, если он не закончен с текущим? Это не так. Если он не начал новый фрейм, он не будет вызывать ваш обратный вызов rAF.

Я также хочу прояснить, что это разумный вопрос. Как я упоминал выше, браузер может позволить JavaScript начать создание следующего кадра, пока он занят компоновкой текущего кадра. Если браузер позволяет JavaScript запускать следующий кадр параллельно и ничего не делает, может возникнуть проблема. Так или иначе браузер будет синхронизироваться с графическим процессором и следить за тем, чтобы кадры, которые он отправил на графический процессор, завершились, прежде чем он получит слишком много кадров вперед (1 или 2 кадра). В терминах OpenGL это можно легко сделать с помощью syn c объектов . Я не говорю, что браузер использует объекты syn c. Только то, что браузер может делать что-то подобное, чтобы не пропускать слишком много кадров вперед.

Не знаю, запутает ли это что-то более или менее, но Кайидо отмечает в обсуждении в чате, что если вы используете setTimeout вместо requestAnimationFrame, то в некотором смысле вы получите проблему, которая вас беспокоит. о.

Допустим, вы сделали это

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

    setTimeout(draw);
}

В этом случае с setTimeout, равным 0 (или любым числом в пределах одного кадра), неизвестно, сколько команд графического процессора выдается между композитами. Может быть, он вызывает draw 5 раз между композитами или 500 раз. Он не определен и зависит от браузера.

Я хотел бы отметить, что многократный вызов draw через setTimeout между композитами - это не «фреймы», это просто действительно обфустированный способ выдачи больше работы GPU. Нет функциональной разницы между вызовом draw N раз между композитами через N setTimeouts или просто for (let i = 0; i < N; ++i) { draw(); } l oop. В обоих случаях к графическому процессору добавлялось много работы между композитами. Единственная разница с методом setTimeout заключается в том, что браузер выбирает N за вас.

На самом деле это один из основных моментов использования requestAnimationFrame, потому что setTimeout не имеет никакого отношения к фреймам.

...