Почему glBufferSubData нужно ждать, пока VBO не будет использоваться glDrawElements?

#opengl

#opengl

Вопрос:

В OpenGL Insights говорится, что «драйвер OpenGL должен ждать, потому что VBO используется glDrawElements из предыдущего кадра».

Это меня сильно смутило. Как я знаю, glBufferSubData скопирует данные во временную память, а затем передаст на GPU позже.

Так почему драйверу все еще нужно ждать? он может просто добавить команду передачи в очередь команд, отложив передачу данных на GPU до завершения glDrawElements, верно?

—— ДОБАВЛЕНО —————————————————————————

В OpenGL Insights говорится:

http://www.seas.upenn.edu /~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf (Страница 397)

Однако при использовании glBufferSubData или glMapBuffer[Range] ничто в самом API не мешает нам изменять данные, которые в настоящее время используются устройством для рендеринга предыдущего кадра, как показано на рисунке 28.3. Драйверы должны избегать этой проблемы, блокируя функцию до тех пор, пока нужные данные больше не будут использоваться: этоназывается неявной синхронизацией.

А также в разделе «Помимо переноса» от Valve и NVIDIA говорится:

http://media.steampowered.com/apps/steamdevdays/slides/beyondporting.pdf

MAP_UNSYNCHRONIZED

  • Избегает точки синхронизации приложения с GPU (точки синхронизации CPU-GPU)

  • Но приводит к сериализации клиентских и серверных потоков

    • Это приводит к завершению всей незавершенной работы в потоке сервера
    • Это довольно дорого (почти всегда нужно избегать)

Оба они указали, что glBufferSubData / glMapBuffer заблокирует поток приложения, а не только поток драйвера.

Почему это так?

Комментарии:

1. Вот в чем суть этого «ожидания». Это задерживает передачу до завершения старой команды.

2. Это означает именно это. Драйвер не может начать перенос и перезапись буфера в памяти GPU с новым содержимым, потому что он используется в данный момент. Просто избегайте такого поведения. В той же книге следует предложить пару альтернатив: использовать набор буферов roundrobin, удаление буфера, ARB_buffer_storage

3. Спасибо всем, я обновил вопрос, пожалуйста, проверьте его еще раз.

4. Я опубликовал ответ ниже, который объясняет это.

Ответ №1:

Нет правила, говорящего, что драйвер должен ждать. Необходимо убедиться, что содержимое буфера не было изменено до завершения выполнения вызовов рисования, использующих старое содержимое. И он должен использовать данные, переданные вызывающим абонентом, прежде чем glBufferSubData() вызов вернется. Пока результирующее поведение является правильным, любая реализация в драйвере является честной игрой.

Давайте проиллюстрируем проблему типичной последовательностью псевдо-вызовов, помечая вызовы для последующего объяснения:

 (1) glBindBuffer(buf)
(2) glBufferSubData(dataA)
(3) glDraw()
(4) glBufferSubData(dataB)
(5) glDraw()
  

Действующие ограничения:

  • Данные, на которые указывает dataA , не могут быть доступны драйверу после возврата вызова (2). Спецификации OpenGL позволяют вызывающей стороне делать с данными все, что она захочет, после возврата вызова, поэтому они должны быть использованы драйвером до возврата вызова.
  • Данные, на которые указывает dataB , не могут быть доступны драйверу после возврата вызова (4).
  • Команда рисования, полученная в результате вызова (3), должна выполняться, пока содержимое buf есть dataA .
  • Команда рисования, полученная в результате вызова (5), должна выполняться, пока содержимое buf есть dataB .

Из-за изначально асинхронной природы OpenGL интересным примером является call (4) . Допустим, это dataA было сохранено buf в данный момент времени, и команда рисования для вызова (3) была поставлена в очередь для выполнения графическим процессором. Но мы не можем полагаться на то, что графический процессор уже выполнил эту команду рисования. Таким образом, мы не можем сохранить dataB , buf потому что ожидающая команда рисования должна быть выполнена графическим процессором, пока dataA она все еще хранится buf . Но мы не можем вернуться из вызова до того, как мы его использовали dataB .

Существуют различные подходы к решению этой ситуации. Решение методом перебора состоит в том, чтобы просто заблокировать выполнение call (4), пока GPU не завершит выполнение команды рисования из call (3). Это, безусловно, сработает, но может иметь очень плохие последствия для производительности. Поскольку мы ждем, пока графический процессор завершит работу, прежде чем отправлять новую работу, графический процессор, скорее всего, временно простаивает. Это часто называют «пузырем» в конвейере, и это очень нежелательно. Кроме того, приложение также блокируется от выполнения полезной работы до возврата вызова.

Самый простой способ обойти это — скопировать драйвер dataB в call (4), а затем поместить эту копию данных buf после того, как GPU завершит команду рисования из call (3), но до выполнения команды рисования из call (5) . Недостатком является то, что это требует дополнительного копирования данных, но часто оно того стоит, чтобы предотвратить пузырьки конвейера.

Комментарии:

1. Большое вам спасибо. Я обновил вопрос, пожалуйста, проверьте его еще раз.

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

3. seas.upenn.edu /~pcozzi/OpenGLInsights/… (Страница 397)

4. Вы добавили glMapBuffer регистр в вопрос. Я не так много знаю об этом, но я полагаю, что это можно было бы обработать аналогичным образом. Я думаю, что я объяснил проблему glBufferSubData и как ее можно обработать в драйвере как с блокировкой, так и без нее. Есть ли какие-то конкретные части в моем ответе, которые неясны?

5. Но там написано «При использовании glBufferSubData… Драйверы должны избегать этой проблемы, блокируя функцию до тех пор, пока нужные данные больше не будут использоваться «, согласно вашему ответу, его не нужно блокировать.