Использование многопоточности между игровым циклом и openGL

10

Разговор в контексте игры на основе рендерера openGL:

Давайте предположим, что есть два потока:

  1. Обновляет игровую логику, физику и т. Д. Для игровых объектов.

  2. Делает вызовы openGL для рисования для каждого игрового объекта на основе данных в игровых объектах (этот поток 1 продолжает обновляться)

Если у вас нет двух копий каждого игрового объекта в текущем состоянии игры, вам придется приостановить поток 1, в то время как поток 2 выполняет вызовы отрисовки, в противном случае игровые объекты будут обновляться в середине вызова отрисовки для этого объекта, что нежелательно!

Но остановка потока 1 для безопасного выполнения вызовов отрисовки из потока 2 убивает всю цель многопоточности / параллелизма

Есть ли лучший подход для этого, кроме использования сотен или тысяч или синхронизации объектов / ограждений, чтобы многоядерную архитектуру можно было использовать для производительности?

Я знаю, что все еще могу использовать многопоточность для загрузки текстур и компиляции шейдеров для объектов, которые еще не являются частью текущего игрового состояния, но как мне сделать это для активных / видимых объектов, не вызывая конфликт с рисованием и обновлением?

Что если я использую отдельную синхронизацию в каждом игровом объекте? Таким образом, любой поток будет блокировать только один объект (в идеальном случае), а не весь цикл обновления / рисования! Но насколько дорого обходятся блокировки на каждом объекте (в игре может быть тысяча объектов)?

Allahjane
источник
1. Предполагается, что только два потока плохо масштабируются, 4 ядра очень распространены и будут только увеличиваться. 2. Ответ стратегии наилучшей практики: примените сегрегацию ответственности командного запроса и модель актора / систему компонентов. 3. Реалистичный ответ: просто взломайте его традиционным для C ++ способом с блокировками и прочим.
День
Одно общее хранилище данных, один замок.
Джастин
@justin, как я уже говорил с синхронизацией блокировки. Единственное преимущество многопоточности будет жестоко убито при дневном свете, поток обновления должен будет ждать, пока поток рисования сделает вызовы для всех объектов, тогда поток рисования будет ждать, пока цикл обновления не завершит обновление материала. ! что хуже, чем однопоточный подход
Аллахжане
1
Кажется, что многопоточный подход в играх с асинхронной графикой API (например, openGL) по-прежнему остается активной темой, и нет стандартного или почти идеального решения этого
Аллахжан
1
Хранилище данных, общее для вашего движка и вашего рендерера, не должно быть вашими игровыми объектами. Должно быть какое-то представление, что средство визуализации может обрабатывать как можно быстрее.
Джастин

Ответы:

13

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

Существует несколько альтернативных подходов, одно из популярных решений для многопоточного рендеринга - использование двойного буфера команд. Это состоит из запуска сервера рендеринга в отдельном потоке, где выполняются все вызовы отрисовки и связь с API рендеринга. Внешний поток, который запускает игровую логику, связывается с внутренним средством визуализации через буфер команд (с двойной буферизацией), При такой настройке у вас есть только одна точка синхронизации при завершении кадра. Пока внешний интерфейс заполняет один буфер командами рендеринга, внутренний использует другой. Если оба потока хорошо сбалансированы, никто не должен голодать. Однако этот подход является неоптимальным, так как он вводит задержку в отображаемых кадрах, плюс драйвер OpenGL, вероятно, уже делает это в своем собственном процессе, поэтому прирост производительности должен быть тщательно измерен. В лучшем случае он также использует только два ядра. Этот подход использовался в нескольких успешных играх, таких как Doom 3 и Quake 3

Более масштабируемые подходы, которые более эффективно используют многоядерные процессоры, основаны на независимых задачах , когда вы запускаете асинхронный запрос, который обслуживается во вторичном потоке, в то время как поток, выполняющий запрос, продолжает какую-то другую работу. В идеале задача не должна зависеть от других потоков, чтобы избежать блокировок (также избегайте общих / глобальных данных, таких как чума!). Архитектуры на основе задач более удобны в локализованных частях игры, таких как компьютерная анимация, поиск пути AI, процедурная генерация, динамическая загрузка реквизитов сцены и т. Д. Игры, естественно, полны событий, большинство видов событий асинхронны, поэтому легко заставить их работать в отдельных потоках.

Наконец, я рекомендую прочитать:

glampert
источник
просто любопытно! откуда вы знаете, Doom 3 использовал этот подход? Я думал, что разработчики никогда не выпускают там методы!
Аллахжане
4
@Allahjane, Doom 3 с открытым исходным кодом . В приведенной мною ссылке вы найдете обзор общей архитектуры игры. И вы ошибаетесь, да, редко можно найти игры с открытым исходным кодом, но разработчики обычно демонстрируют свои приемы и приемы в блогах, газетах и ​​событиях, таких как GDC .
гламперт
1
Хм. Это было потрясающее чтение! спасибо, что предупредил меня об этом! Вы получаете галочку :)
Аллахжане