Разговор в контексте игры на основе рендерера openGL:
Давайте предположим, что есть два потока:
Обновляет игровую логику, физику и т. Д. Для игровых объектов.
Делает вызовы openGL для рисования для каждого игрового объекта на основе данных в игровых объектах (этот поток 1 продолжает обновляться)
Если у вас нет двух копий каждого игрового объекта в текущем состоянии игры, вам придется приостановить поток 1, в то время как поток 2 выполняет вызовы отрисовки, в противном случае игровые объекты будут обновляться в середине вызова отрисовки для этого объекта, что нежелательно!
Но остановка потока 1 для безопасного выполнения вызовов отрисовки из потока 2 убивает всю цель многопоточности / параллелизма
Есть ли лучший подход для этого, кроме использования сотен или тысяч или синхронизации объектов / ограждений, чтобы многоядерную архитектуру можно было использовать для производительности?
Я знаю, что все еще могу использовать многопоточность для загрузки текстур и компиляции шейдеров для объектов, которые еще не являются частью текущего игрового состояния, но как мне сделать это для активных / видимых объектов, не вызывая конфликт с рисованием и обновлением?
Что если я использую отдельную синхронизацию в каждом игровом объекте? Таким образом, любой поток будет блокировать только один объект (в идеальном случае), а не весь цикл обновления / рисования! Но насколько дорого обходятся блокировки на каждом объекте (в игре может быть тысяча объектов)?
источник
Ответы:
Описанный вами подход с использованием блокировок будет очень неэффективным и, скорее всего, медленнее, чем использование одного потока. Другой подход к хранению копий данных в каждом потоке, вероятно, будет работать хорошо «по скорости», но с непомерно высокими затратами памяти и сложностью кода для синхронизации копий.
Существует несколько альтернативных подходов, одно из популярных решений для многопоточного рендеринга - использование двойного буфера команд. Это состоит из запуска сервера рендеринга в отдельном потоке, где выполняются все вызовы отрисовки и связь с API рендеринга. Внешний поток, который запускает игровую логику, связывается с внутренним средством визуализации через буфер команд (с двойной буферизацией), При такой настройке у вас есть только одна точка синхронизации при завершении кадра. Пока внешний интерфейс заполняет один буфер командами рендеринга, внутренний использует другой. Если оба потока хорошо сбалансированы, никто не должен голодать. Однако этот подход является неоптимальным, так как он вводит задержку в отображаемых кадрах, плюс драйвер OpenGL, вероятно, уже делает это в своем собственном процессе, поэтому прирост производительности должен быть тщательно измерен. В лучшем случае он также использует только два ядра. Этот подход использовался в нескольких успешных играх, таких как Doom 3 и Quake 3
Более масштабируемые подходы, которые более эффективно используют многоядерные процессоры, основаны на независимых задачах , когда вы запускаете асинхронный запрос, который обслуживается во вторичном потоке, в то время как поток, выполняющий запрос, продолжает какую-то другую работу. В идеале задача не должна зависеть от других потоков, чтобы избежать блокировок (также избегайте общих / глобальных данных, таких как чума!). Архитектуры на основе задач более удобны в локализованных частях игры, таких как компьютерная анимация, поиск пути AI, процедурная генерация, динамическая загрузка реквизитов сцены и т. Д. Игры, естественно, полны событий, большинство видов событий асинхронны, поэтому легко заставить их работать в отдельных потоках.
Наконец, я рекомендую прочитать:
Основы потоков для игр от Intel.
Эффективный параллелизм Херба Саттера (несколько ссылок на другие полезные ресурсы на этой странице).
источник