Скориставшись багатопоточністю між ігровим циклом та openGL


10

Говорячи в контексті гри, заснованої на рендері openGL:

Припустимо, є дві нитки:

  1. Оновлення логіки гри і фізики тощо для ігрових об'єктів

  2. Робить openGL виклики для малювання кожного ігрового об’єкта на основі даних в ігрових об'єктах (цей потік 1 постійно оновлюється)

Якщо у вас є дві копії кожного ігрового об'єкта в поточному стані гри, вам доведеться призупинити Thread 1, в той час як Thread 2 робить виклики нічиї, інакше ігрові об’єкти оновлюються в середині розіграшу для цього об’єкта, який небажано!

Але зупинка потоку 1 для безпечного здійснення дзвінків з нитки 2 вбиває всю мету багатопотокової / одночасності

Чи є кращий підхід для цього, крім використання сотень чи тисяч чи синхронізації об'єктів / парканів, щоб багатоядерну архітектуру можна було використовувати для продуктивності?

Я знаю, що я все ще можу використовувати багатопотоковість для завантаження текстури та компіляції шейдерів для об'єктів, які ще є частиною поточного стану гри, але як це зробити для активних / видимих ​​об'єктів, не викликаючи конфлікту з малюванням та оновленням?

Що робити, якщо я використовую окремий блокування синхронізації в кожному з ігрових об'єктів? Таким чином, будь-яка нитка блокується лише на одному об'єкті (ідеальний випадок), а не на весь цикл оновлення / малювання! Але наскільки затратним є зняття замків на кожному об’єкті (у грі може бути тисяча предметів)?


1. Якщо припустити, що всього дві нитки не масштабуються добре, 4 ядра дуже поширені і будуть тільки збільшуватися. 2. Відповідь стратегії найкращої практики: застосувати розділення відповідальності за запити команд та систему суб'єкта / модель компонента. 3. Реалістична відповідь: просто зламайте його традиційним способом C ++ за допомогою замків і речей.
День

Один загальний сховище даних, один замок.
Джастін

@justin так само, як я вже сказав із блокуванням синхронізації. Єдиною перевагою багатопотокового читання буде жорстоке вбивство при денному світлі; оновленням потоку доведеться почекати, поки нитка малювання подзвонить для всіх об'єктів, тоді нитка малювання буде чекати, поки цикл оновлення завершить оновлення матеріалів ! що гірше за однопотоковий підхід
Аллахане

1
Схоже, що багатопотоковий підхід в іграх з асинхронною графікою api (наприклад, openGL) як і раніше залишається активною темою, і немає цього стандартного або майже ідеального рішення
Аллахане

1
Сховище даних для вашого двигуна та вашого рендерінга не повинно бути вашими ігровими об’єктами. Має бути певне уявлення про те, що рендер може обробити якомога швидше.
Джастін

Відповіді:


13

Описаний вами підхід із використанням блокування був би дуже неефективним і, швидше за все, повільнішим, ніж використання однієї нитки. Інший підхід збереження копій даних у кожному потоці, ймовірно, буде добре "швидким", але з надмірною вартістю пам'яті та складністю коду для збереження копій синхронізовано.

До цього існує декілька альтернативних підходів, одне популярне рішення для багатопотокової візуалізації - це використання подвійного буфера команд. Це складається з запуску зворотного кінця візуалізації в окремий потік, де виконуються всі дзвінки з викликом і зв'язок з API надання візуалізації. Лицьовий потік, який виконує логіку гри, спілкується з рендером за допомогою командного буфера (подвійний буфер). За допомогою цієї установки у вас є лише одна точка синхронізації після завершення кадру. У той час як передня частина заповнює один буфер командами візуалізації, зворотній кінець споживає інший. Якщо обидві нитки добре збалансовані, жодна не повинна голодувати. Такий підхід є недостатньо оптимальним, оскільки він вводить затримку у наданих кадрах, плюс, драйвер OpenGL, ймовірно, вже робить це у своєму процесі, тому підвищення продуктивності доведеться ретельно вимірювати. Також він використовує лише два ядра, в кращому випадку. Цей підхід застосовується в кількох успішних іграх, таких як Doom 3 і Quake 3

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

Нарешті, рекомендую прочитати:


просто цікаво! як ви знаєте, що Doom 3 використовував такий підхід? Я думав, що розробники ніколи не випускають там техніку!
Аллахане

4
@Allahjane, Doom 3 є відкритим кодом . У наданому посиланням ви знайдете огляд загальної архітектури гри. І ви помиляєтесь, так, рідко можна зустріти повністю ігри з відкритим кодом, але розробники зазвичай розкривають свої прийоми та хитрощі в блогах, газетах та подіях, таких як GDC .
гламперт

1
Хм. Це було дивовижне читання! дякую за те, що мені це відомо! Ви отримаєте галочку :)
Аллахане
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.