Pracuję nad małą grą komputerową opartą na kafelkach / ikonkach z zespołem ludzi i mamy problemy z wydajnością. Ostatni raz korzystałem z OpenGL około 2004 roku, więc uczyłem się, jak korzystać z profilu podstawowego i czuję się trochę zdezorientowany.
Muszę narysować w sąsiedztwie 250-750 kafelków 48x48 na ekranie każdej klatki, a także może około 50 duszków. Płytki zmieniają się tylko po załadowaniu nowego poziomu, a duszki cały czas się zmieniają. Niektóre kafelki składają się z czterech kawałków 24x24, a większość (ale nie wszystkie) duszków ma taki sam rozmiar jak kafelki. Wiele kafelków i duszków używa mieszania alfa.
Teraz robię to wszystko w trybie natychmiastowym, co, jak wiem, jest złym pomysłem. Niemniej jednak, gdy jeden z członków naszego zespołu próbuje go uruchomić, dostaje bardzo złe liczby klatek na sekundę (~ 20-30 fps), i jest znacznie gorzej, gdy jest więcej płytek, szczególnie gdy wiele z tych płytek jest tego rodzaju, że są pocięte na kawałki. To wszystko sprawia, że myślę, że problemem jest liczba wykonywanych sprawdzeń.
Zastanawiałem się nad kilkoma możliwymi rozwiązaniami tego problemu, ale chciałem je uruchomić przez niektórych ludzi, którzy wiedzą o czym mówią, więc nie marnuję czasu na coś głupiego:
PŁYTKI:
- Po załadowaniu poziomu narysuj wszystkie płytki raz w buforze ramki dołączonym do dużej tekstury honowania i po prostu narysuj duży prostokąt z tą teksturą na każdej ramce.
- Umieść wszystkie płytki w statycznym buforze wierzchołków, gdy poziom zostanie załadowany, i narysuj je w ten sposób. Nie wiem, czy istnieje sposób rysowania obiektów o różnych teksturach za pomocą jednego wywołania glDrawElements, czy też jest to coś, co chciałbym zrobić. Może po prostu umieścisz wszystkie płytki w dużej gigantycznej fakturze i użyjesz zabawnych współrzędnych tekstury w VBO?
DUCHY:
- Narysuj każdą duszkę osobnym wywołaniem glDrawElements. Wydaje się, że wiąże się to z dużą ilością zmian tekstur, co, jak mi powiedziano, jest złe. Czy tablice tekstur mogą być przydatne tutaj?
- Jakoś użyj dynamicznego VBO. To samo pytanie dotyczące tekstury, jak numer 2 powyżej.
- Point sprites? To chyba głupie.
Czy któryś z tych pomysłów jest sensowny? Czy jest gdzieś dobre wdrożenie, nad którym mógłbym spojrzeć?
Odpowiedzi:
Najszybszym sposobem renderowania kafelków jest pakowanie danych wierzchołków do statycznego VBO z indeksami (jak wskazuje glDrawElements). Zapisanie go na innym obrazie jest całkowicie niepotrzebne i będzie wymagało tylko dużo więcej pamięci. Przełączanie tekstur jest BARDZO kosztowne, więc prawdopodobnie będziesz chciał spakować wszystkie płytki w tak zwany Atlas Tekstury i nadać każdemu trójkątowi w VBO właściwe współrzędne tekstury. Na tej podstawie renderowanie 1000, a nawet 100000 kafelków nie powinno stanowić problemu, w zależności od sprzętu.
Jedyna różnica między renderowaniem kafelków a renderowaniem sprite polega na tym, że sprite są dynamiczne. Aby uzyskać najlepszą, ale łatwą do osiągnięcia wydajność, możesz po prostu umieścić współrzędne wierzchołków duszka w strumieniu VBO rysującym każdą ramkę i rysować za pomocą glDrawElements. Spakuj również wszystkie tekstury w atlasie tekstur. Jeśli twoje duszki rzadko się poruszają, możesz również spróbować stworzyć dynamiczne VBO i zaktualizować je, gdy duszek się poruszy, ale to tutaj jest całkowita nadmiar, ponieważ chcesz tylko renderować niektóre duszki.
Możesz spojrzeć na mały prototyp, który zrobiłem w C ++ za pomocą OpenGL: Particulate
Wydaje mi się, że renderuję około 10000 punktowych duszków ze średnim fps 400 na zwykłej maszynie (Quad Core @ 2,66 GHz). Jest ograniczony procesorem, co oznacza, że karta graficzna może renderować jeszcze więcej. Zauważ, że nie używam tutaj atlasów tekstur, ponieważ mam tylko jedną teksturę dla cząstek. Cząstki są renderowane za pomocą GL_POINTS, a moduły cieniujące obliczają wówczas rzeczywistą wielkość kwadratu, ale myślę, że istnieje również Quad Renderer.
No i tak, chyba że masz kwadrat i używasz shaderów do mapowania tekstur, GL_POINTS jest dość głupiutki. ;)
źródło
Nawet przy takiej liczbie wywołań losowania nie powinieneś obserwować tego rodzaju spadku wydajności - tryb natychmiastowy może być powolny, ale nie jest tak wolny (na przykład, nawet stary Quake może zarządzać kilkoma tysiącami wywołań w trybie natychmiastowym na klatkę bez upadku tak źle).
Podejrzewam, że dzieje się tutaj coś ciekawszego. Pierwszą rzeczą, którą musisz zrobić, to zainwestować trochę czasu w profilowanie programu, w przeciwnym razie istnieje ogromne ryzyko ponownej analizy w oparciu o założenie, które może skutkować zerowym wzrostem wydajności. Przeprowadź go przez coś tak podstawowego jak GLIntercept i sprawdź, gdzie zmierza twój czas. Na podstawie jego wyników będziesz w stanie rozwiązać problem z pewnymi prawdziwymi informacjami na temat tego, jakie są twoje główne wąskie gardło (wąskie gardło).
źródło
Okej, odkąd moja ostatnia odpowiedź wymknęła się spod kontroli, jest nowa, która może być bardziej przydatna.
O wydajności 2D
Najpierw kilka ogólnych rad: 2D nie wymaga obecnego sprzętu, zadziała nawet w dużej mierze niezoptymalizowany kod. Nie oznacza to jednak, że powinieneś Tryb pośredni, przynajmniej upewnij się, że nie zmieniasz stanów, gdy są niepotrzebne (na przykład nie wiąż nowej tekstury za pomocą glBindTexture, gdy ta sama tekstura jest już związana, a jeśli sprawdzenie procesora to mnóstwo szybciej niż wywołanie glBindTexture) i nie używać czegoś tak błędnego i głupiego jak glVertex (nawet glDrawArrays będzie znacznie szybszy i nie będzie trudniejszy w użyciu, ale nie jest zbyt „nowoczesny”). Dzięki tym dwóm bardzo prostym zasadom czas klatek powinien wynosić co najmniej do 10ms (100 fps). Teraz, aby uzyskać jeszcze większą szybkość, kolejnym logicznym krokiem jest grupowanie, np. Łączenie tylu wywołań rysunkowych w jedno, w tym celu należy rozważyć wdrożenie atlasów tekstur, dzięki czemu można zminimalizować ilość wiązań tekstur, a tym samym zwiększyć liczbę prostokątów, które można narysować za pomocą jednego wywołania dużej ilości. Jeśli nie masz teraz około 2ms (500 fps), robisz coś złego :)
Mapy kafelkowe
Implementacja kodu rysunkowego dla map kafelków polega na znalezieniu równowagi między elastycznością a szybkością. Możesz używać statycznych VBO, ale to nie będzie działać z animowanymi kafelkami lub możesz po prostu wygenerować dane wierzchołków w każdej klatce i zastosować zasady, które wyjaśniłem powyżej, jest to bardzo elastyczne, ale zdecydowanie nie tak szybkie.
W mojej poprzedniej odpowiedzi wprowadziłem inny model, w którym moduł cieniujący zajmuje się całym teksturowaniem, ale wskazano, że wymaga on zależnego wyszukiwania tekstur, a zatem może nie być tak szybki jak inne metody. (Chodzi o to, że przesyłasz tylko wskazania kafelków, a w module cieniującym fragmentów obliczasz współrzędne tekstury, co oznacza, że możesz narysować całą mapę za pomocą tylko jednego prostokąta)
Duszki
Duszki wymagają dużej elastyczności, co sprawia, że bardzo trudno jest je zoptymalizować, oprócz tych omówionych w sekcji „Informacje o wydajności 2D”. I jeśli nie chcesz jednocześnie dziesięciu tysięcy duszków na ekranie, prawdopodobnie nie warto tego robić.
źródło
Jeśli wszystko inne zawiedzie...
Skonfiguruj metodę rysowania flip-flop. Aktualizuj tylko co drugi duszek na raz. Chociaż nawet z VisualBasic6 i prostymi metodami bit-blit możesz aktywnie rysować tysiące duszków na ramkę. Być może powinieneś przyjrzeć się tym metodom, ponieważ Twoja bezpośrednia metoda rysowania ikonek wydaje się nie działać. (Brzmi bardziej tak, jakbyś używał „metody renderowania”, ale próbowałeś użyć jej jako „metody gry”. Renderowanie dotyczy przejrzystości, a nie szybkości).
Możliwe, że ciągle przerysowujesz cały ekran w kółko. Zamiast przerysowywać tylko zmienione obszary. To DUŻO kosztów ogólnych. Koncepcja jest prosta, ale niełatwa do zrozumienia.
Użyj bufora dla dziewiczego tła statycznego. To się nigdy nie renderuje, chyba że na ekranie nie ma duszków. Jest to stale używane do „powrotu” do miejsca, w którym narysowano duszka, aby cofnąć duszka w następnym wywołaniu. Potrzebujesz także bufora do „rysowania”, który nie jest ekranem. Rysujesz tam, a następnie raz narysowany, przerzucasz go na ekran, raz. To powinno być jedno wywołanie na ekranie dla wszystkich twoich duszków. (W przeciwieństwie do rysowania każdego duszka na ekranie, pojedynczo lub próby zrobienia wszystkiego naraz, co spowoduje, że mieszanie alfa nie powiedzie się.) Zapisywanie w pamięci jest szybkie i nie wymaga czasu na ekranie do „rysowania „. Każde połączenie losujące będzie czekało na sygnał zwrotny, zanim spróbuje ponownie wyciągnąć. (Nie synchronizacja w pionie, rzeczywisty takt sprzętowy, który jest znacznie wolniejszy niż czas oczekiwania RAM).
Wyobrażam sobie, że jest to jeden z powodów, dla których widzisz ten problem tylko na jednym komputerze. Lub sprowadza się do renderowania oprogramowania ALPHA-BLEND, którego nie obsługują wszystkie karty. Czy przed rozpoczęciem korzystania z tej funkcji sprawdzasz, czy ta funkcja jest obsługiwana sprzętowo? Czy masz awarię (tryb bez mieszania alfa), jeśli jej nie ma? Oczywiście nie masz kodu, który ogranicza (liczba rzeczy mieszanych), jak zakładam, że to pogorszyłoby zawartość twojej gry. (W odróżnieniu od tego, że były to tylko efekty cząsteczkowe, wszystkie z domieszką alfa, a zatem dlaczego programiści je ograniczają, ponieważ są bardzo podatne na większość systemów, nawet przy wsparciu sprzętowym).
Na koniec sugerowałbym ograniczenie mieszania alfa do tylko tych, które tego potrzebują. Jeśli wszystko tego potrzebuje ... Nie masz innego wyboru, jak tylko wymagać od użytkowników lepszych wymagań sprzętowych lub obniżyć jakość gry do pożądanej wydajności.
źródło
Utwórz arkusz ikon dla obiektów i zestaw kafelków dla terenu, tak jak w innej grze 2D, nie ma potrzeby przełączania tekstur.
Renderowanie płytek może być uciążliwe, ponieważ każda para trójkątów potrzebuje własnych współrzędnych tekstury. Istnieje jednak rozwiązanie tego problemu, nazywa się to renderowaniem instancji .
Tak długo, jak możesz posortować swoje dane w taki sposób, że na przykład możesz mieć listę kafelków trawy i ich pozycji, możesz renderować każdy kafelek trawy za pomocą jednego wywołania losowania, wszystko co musisz zrobić, to podać tablicę modelu do światowych macierzy dla każdego kafelka. Sortowanie danych w ten sposób nie powinno stanowić problemu nawet w przypadku najprostszego wykresu sceny.
źródło