Wykorzystanie wielowątkowości między pętlą gry a openGL

10

Mówienie w kontekście gry opartej na renderze OpenGL:

Załóżmy, że istnieją dwa wątki:

  1. Aktualizuje logikę gry i fizykę itp. Dla obiektów w grze

  2. Wykonuje wywołania openGL dla każdego obiektu gry na podstawie danych w obiektach gry (ten wątek 1 aktualizuje się)

Jeśli nie masz dwóch kopii każdego obiektu gry w bieżącym stanie gry, musisz wstrzymać Wątek 1, podczas gdy Wątek 2 wykonuje wywołania losowania, w przeciwnym razie obiekty gry zostaną zaktualizowane w środku wywołania losowania dla tego obiektu, co jest niepożądane!

Ale zatrzymanie wątku 1 w celu bezpiecznego wykonywania połączeń losujących z wątku 2 zabija cały cel wielowątkowości / współbieżności

Czy jest na to lepsze podejście niż używanie setek lub tysięcy lub synchronizowanie obiektów / ogrodzeń, aby architektura wielordzeniowa mogła być wykorzystana do wydajności?

Wiem, że wciąż mogę używać wielowątkowości do ładowania tekstur i kompilowania programów cieniujących dla obiektów, które jeszcze nie są częścią bieżącego stanu gry, ale jak to zrobić dla obiektów aktywnych / widocznych bez powodowania konfliktu podczas rysowania i aktualizacji?

Co się stanie, jeśli użyję osobnej blokady synchronizacji w każdym obiekcie gry? W ten sposób każdy wątek będzie blokował tylko jeden obiekt (idealny przypadek), a nie cały cykl aktualizacji / rysowania! Ale ile kosztuje blokowanie każdego obiektu (gra może mieć tysiąc obiektów)?

Allahjane
źródło
1. Zakładając, że będą tylko dwa wątki, nie skaluje się dobrze, 4 rdzenie są bardzo powszechne i będą się powiększać. 2. Odpowiedź na strategię najlepszych praktyk: zastosuj Segregację Odpowiedzialności za Polecenia i Model aktora / System podmiotowy. 3. Realistyczna odpowiedź: po prostu zhakuj to w tradycyjny sposób C ++ z blokadami i tym podobne.
Den
Jeden wspólny magazyn danych, jeden zamek.
Justin
@ justin, tak jak powiedziałem z blokadą synchronizacji Jedyną zaletą wielowątkowości zostanie brutalnie zamordowany w świetle dziennym, wątek aktualizacji musiałby poczekać, aż wątek rysujący wywoła wszystkie obiekty, a następnie wątek rysujący poczeka, aż pętla aktualizacji zakończy aktualizację ! co jest gorsze niż podejście jednowątkowe
Allahjane,
1
Wygląda na to, że podejście wielowątkowe w grach z interfejsem graficznym asynchronicznym (np. OpenGL) jest wciąż aktywnym tematem i nie ma takiego standardowego lub prawie idealnego rozwiązania
Allahjane
1
Magazyn danych, który jest wspólny dla twojego silnika i twojego renderera, nie powinien być twoimi obiektami gry. Powinno być jakieś przedstawienie, które mechanizm renderujący może przetworzyć tak szybko, jak to możliwe.
Justin

Odpowiedzi:

13

Podejście, które opisałeś, używając zamków, byłoby bardzo nieefektywne i najprawdopodobniej wolniejsze niż użycie pojedynczego wątku. Inne podejście polegające na utrzymywaniu kopii danych w każdym wątku prawdopodobnie działałoby dobrze „pod względem szybkości”, ale przy zaporowym koszcie pamięci i złożoności kodu, aby zachować synchronizację kopii.

Istnieje kilka alternatywnych podejść, jednym z popularnych rozwiązań renderowania wielowątkowego jest użycie podwójnego bufora poleceń. Polega to na uruchomieniu zaplecza mechanizmu renderującego w osobnym wątku, w którym wykonywane są wszystkie wywołania i komunikacja z API renderującym. Wątek frontonu uruchamiający logikę gry komunikuje się z rendererem zaplecza za pośrednictwem bufora poleceń (podwójnie buforowanego). Dzięki tej konfiguracji masz tylko jeden punkt synchronizacji na zakończeniu ramki. Podczas gdy front-end wypełnia jeden bufor komendami renderującymi, back-end zużywa drugi. Jeśli oba wątki są dobrze wyważone, żaden nie powinien głodować. Podejście to jest jednak nieoptymalne, ponieważ wprowadza opóźnienie w renderowanych ramkach, a sterownik OpenGL prawdopodobnie już robi to we własnym procesie, więc przyrost wydajności musiałby być dokładnie zmierzony. W najlepszym wypadku wykorzystuje tylko dwa rdzenie. Takie podejście zastosowano w kilku udanych grach, takich jak Doom 3 i Quake 3

Bardziej skalowalne podejścia, które lepiej wykorzystują procesory wielordzeniowe, oparte są na niezależnych zadaniach , w których uruchamiane jest żądanie asynchroniczne, które jest obsługiwane w wątku wtórnym, a wątek, który uruchomił żądanie, kontynuuje pracę. Najlepiej byłoby, gdyby zadanie nie było zależne od innych wątków, aby uniknąć blokad (unikaj także wspólnych / globalnych danych, takich jak plaga!). Architektury zadaniowe są bardziej użyteczne w zlokalizowanych częściach gry, takich jak animacje komputerowe, wyszukiwanie AI, generowanie procedur, dynamiczne ładowanie rekwizytów scen itp. Gry są naturalnie pełne zdarzeń, większość rodzajów zdarzeń jest asynchroniczna, więc łatwo jest je uruchomić w osobnych wątkach.

Na koniec polecam przeczytać:

glampert
źródło
po prostu ciekawy! skąd wiesz, że Doom 3 zastosował to podejście? Myślałem, że programiści nigdy nie wypuszczają tam technik!
Allahjane,
4
@Allahjane, Doom 3 jest Open Source . W linku, który podałem, znajdziesz przegląd ogólnej architektury gry. I mylisz się, tak, rzadko można znaleźć w pełni gry Open Source, ale programiści zwykle ujawniają swoje techniki i sztuczki w blogach, artykułach i wydarzeniach, takich jak GDC .
glampert
1
Hmm To była niesamowita lektura! dzięki za poinformowanie mnie o tym! Dostajesz kleszcza :)
Allahjane,