Jak rozwiązujemy duże wymagania dotyczące pamięci wideo w grze 2D?

40

Jak rozwiązujemy duże wymagania dotyczące pamięci wideo w grze 2D?


Opracowujemy grę 2D (Factorio) w allegro C / C ++, a wraz ze wzrostem zawartości gry mamy problem z rosnącym zapotrzebowaniem na pamięć wideo.

Obecnie zbieramy wszystkie informacje o obrazach, które zostaną użyte jako pierwsze, przycinamy wszystkie te zdjęcia tak bardzo, jak to możliwe i organizujemy je w duże atlasy tak ściśle, jak to możliwe. Atlasy te są przechowywane w pamięci wideo, której rozmiar zależy od ograniczeń systemu; obecnie są to zwykle 2 obrazy o wielkości do 8192 x 8192, więc wymagają one pamięci wideo od 256 Mb do 512 Mb.

Ten system działa dla nas całkiem dobrze, ponieważ dzięki niektórym niestandardowym optymalizacjom i podzieleniu wątku renderowania i aktualizacji jesteśmy w stanie narysować dziesiątki tysięcy obrazów na ekranie przy 60 fps; mamy wiele obiektów na ekranie, a umożliwienie dużego oddalenia jest kluczowym wymogiem. Ponieważ chcielibyśmy dodać więcej, pojawią się pewne problemy z wymaganiami dotyczącymi pamięci wideo, więc ten system prawdopodobnie nie wytrzyma.

Jedną z rzeczy, które chcieliśmy wypróbować, jest posiadanie jednego atlasu z najczęstszymi obrazami, a drugiego jako pamięci podręcznej. Obrazy byłyby tam przenoszone z bitmapy pamięci na żądanie. Z tym podejściem wiążą się dwa problemy:

  1. Rysowanie z bitmapy pamięci do bitmapy wideo jest boleśnie powolne, w allegro.
  2. Nie jest możliwa praca z bitmapą wideo w innym wątku niż w allegro, więc jest praktycznie bezużyteczna.

Oto kilka dodatkowych wymagań, które mamy:

  • Gra musi być determinacyjna, więc problemy z wydajnością / czasem ładowania nie mogą nigdy zmienić stanu gry.
  • Gra odbywa się w czasie rzeczywistym, a wkrótce także w trybie wieloosobowym. Za wszelką cenę musimy unikać nawet najmniejszego jąkania.
  • Większość gry to jeden ciągły otwarty świat.

Test polegał na narysowaniu 10 000 duszków w partii dla rozmiarów od 1x1 do 300 x 300, kilka razy dla każdej konfiguracji. Zrobiłem testy na Nvidii Geforce GTX 760.

  • Bitmapa wideo do rysowania bitmapy wideo wymagało 0.1us na duszka, gdy źródłowa bitmapa nie zmieniała się pomiędzy poszczególnymi bitmapami (wariant atlasu); rozmiar nie miał znaczenia
  • Bitmapa wideo na rysunek bitmapy wideo, podczas gdy źródłowa bitmapa była przełączana między rysunkami (wariant inny niż atlas), zajęła 0,56us na duszka; rozmiar też nie miał znaczenia.
  • Bitmapa pamięci do rysowania bitmapy wideo była bardzo podejrzana. Rozmiary od 1x1 do 200x200 wymagały 0,3us na bitmapę, więc nie tak strasznie wolno. W przypadku większych rozmiarów czas zaczął gwałtownie rosnąć - od 9us dla 201x201 do 3116us dla 291x291.

Korzystanie z atlasu zwiększa wydajność o współczynnik większy niż 5. Gdybym miał 10 ms na renderowanie, w atlasie jestem ograniczony do 100 000 duszków na ramkę, a bez niego limit 20 000 duszków. Byłoby to problematyczne.

Próbowałem też znaleźć sposób na przetestowanie kompresji bitmapy i formatu bitmapy 1bpp dla cieni, ale nie byłem w stanie znaleźć sposobu na zrobienie tego w allegro.

Marwin
źródło
1
Wielkim fanem twojej gry, poparłem kampanię Indiegogo. Obijam się co kilka miesięcy. Jak dotąd niezła robota! Usunąłem pytania „z jakiej technologii korzystać”, które są nie na temat witryny. Pozostałe pytania są nadal dość ogólne, jeśli masz coś bardziej szczegółowego, powinieneś spróbować zawęzić zakres.
MichaelHouse
Dzięki za wsparcie. Więc gdzie jest miejsce, aby zapytać, jakiej technologii użyć? Nie szukam odpowiedzi z konkretnym zaleceniem silnika, ale nie byłem w stanie znaleźć dogłębnego porównania silników 2d i sprawdzać je ręcznie jeden po drugim, w tym testy wydajności i użyteczności zajęłyby wieki.
Marwin,
Sprawdź na dole tej strony kilka miejsc, w których możesz zadawać pytania, takie jak „z jakiej technologii skorzystać”. Masz całkowicie uzasadnione i uzasadnione pytanie, nie jest to pytanie, z którym mamy do czynienia na tej stronie. Chociaż nie szukasz konkretnego silnika, to naprawdę jedyny sposób, aby odpowiedzieć na pytanie „Czy jest jakaś technologia, która działa na X?”. Ktoś może po prostu odpowiedzieć „tak” i nie zalecić konkretnej, ale to nie byłoby bardzo pomocne. Powodzenia z tym!
MichaelHouse
2
Czy kompresujesz swoje tekstury?
GuyRT
3
@Marwin, tekstury skompresowane mogą działać znacznie lepiej niż tekstury nieskompresowane, ponieważ zmniejszają wymaganą przepustowość pamięci (dotyczy to zwłaszcza platform mobilnych, na których przepustowość jest znacznie mniejsza). Możesz zaoszczędzić ogromną ilość pamięci po prostu kompresując swoje tekstury. Naprawdę jedynym minusem są artefakty, które są nieuchronnie wprowadzone.
GuyRT

Odpowiedzi:

17

Mamy podobny przypadek z naszym RTS (KaM Remake). Wszystkie jednostki i domy są duszkami. Mamy 18 000 duszków dla jednostek i domów oraz terenu, a także kolejne ~ 6 000 dla kolorów drużynowych (stosowanych jako maski). Długotrwale mamy również około 30 000 znaków używanych w czcionkach.

Istnieją więc pewne optymalizacje w stosunku do atlasów RGBA32, których używasz:

  • Najpierw podziel pulę duszków na wiele mniejszych atlasów i używaj ich na żądanie, jak opisano w innych odpowiedziach. Pozwala to również na stosowanie różnych technik optymalizacji dla każdego atlasu z osobna . Podejrzewam, że będziesz miał mniej zmarnowanej pamięci RAM, ponieważ przy pakowaniu do tak ogromnych tekstur na dole zwykle są niewykorzystane obszary;

  • Spróbuj użyć tekstur z paletą . Jeśli używasz shaderów, możesz „zastosować” paletę w kodzie shaderów;

  • Możesz zastanowić się nad dodaniem opcji używania RGB5_A1 zamiast RGBA8 (jeśli na przykład cienie szachownicy są odpowiednie dla twojej gry). Unikaj 8bit Alpha, jeśli to możliwe i używaj RGB5_A1 lub równoważnych formatów z mniejszą precyzją (podobnie jak RGBA4), zajmują one połowę miejsca;

  • Upewnij się, że ciasno upakowujesz duszki do atlasów (patrz algorytmy pakowania bin), w razie potrzeby obracaj duszki i sprawdź, czy możesz nakładać przezroczyste rogi na duszki romb;

  • Możesz wypróbować sprzętowe formaty kompresji (DXT, S3TC itp.) - mogą one radykalnie zmniejszyć zużycie pamięci RAM, ale sprawdzają artefakty kompresji - na niektórych obrazach różnica może być niezauważalna (możesz użyć tego wybiórczo, jak opisano w pierwszym punkcie), ale na niektórych - bardzo wyraźne. Różne formaty kompresji powodują różne artefakty, więc możesz wybrać taki, który najlepiej pasuje do Twojego stylu sztuki.

  • Spójrz na dzielenie dużych duszków (oczywiście nie ręcznie, ale w pakiecie atlasu tekstur) na statyczne duszki i mniejsze duszki dla animowanych części.

Kromster mówi, że popiera Monikę
źródło
2
+1 za używanie DXT, to bardzo dobrze mieć. Świetna kompresja i używana bezpośrednio przez GPU, dzięki czemu narzut jest minimalny.
1
Zgadzam się z dxt. Można również zapytać o obsługę DXT7 (sprzęt DX11 +), który ma taki sam rozmiar jak DXT1, ale (najwyraźniej) wyższą jakość. Jednak musiałbyś albo mieć podwójne tekstury (jeden DXT7 i jeden DXT1), albo kompresować / dekompresować podczas ładowania.
Programmdude
5

Przede wszystkim musisz użyć więcej mniejszych atlasów tekstur. Im mniej tekstur, tym trudniejsze i bardziej sztywne zarządzanie pamięcią. Sugerowałbym rozmiar atlasu 1024, w którym to przypadku miałbyś 128 tekstur zamiast 2 lub 2048, w którym to przypadku miałbyś 32 tekstury, które można załadować i rozładować w razie potrzeby.

Większość gier zarządza zasobami, mając granice poziomów, podczas gdy na ekranie ładowania wyświetlane są wszystkie niepotrzebne zasoby na następnym poziomie, które są rozładowywane, a potrzebne zasoby ładowane.

Inną opcją jest ładowanie na żądanie, które staje się konieczne, jeśli granice poziomów są niepożądane lub nawet jeden poziom jest zbyt duży, aby zmieścił się w pamięci. W takim przypadku gra będzie próbowała przewidzieć, co zobaczy gracz w przyszłości, i załaduje to w tle. (Na przykład: rzeczy, które są obecnie o 2 ekrany od odtwarzacza.) Jednocześnie rzeczy, które nie były już używane przez dłuższy czas, zostaną rozładowane.

Jest jednak jeden problem: co się stanie, gdy wydarzy się coś nieoczekiwanego, czego gra nie była w stanie przewidzieć?

  • Panikuje i wyświetla ekran ładowania, aż zostaną załadowane wszystkie niezbędne rzeczy. To może wydawać się zakłócające doznanie.
  • Przygotuj sprity o niskiej rozdzielczości do wszystkiego, co zostało fabrycznie załadowane, kontynuując grę i wymieniając je, gdy tylko sprity o wysokiej rozdzielczości zakończą się ładować. Dla gracza może to wyglądać na tanie.
  • Wpływaj na rozgrywkę i opóźnij wydarzenie tak długo, jak to konieczne. Np. Nie spawnuj tego wroga, dopóki jego grafika nie zostanie załadowana. Nie otwieraj tej skrzyni skarbów przed załadowaniem wszystkich grafik dla tego łupu itp.
API-Beast
źródło
Dodałem niektóre wymagania, które pominąłem. Ekran ładowania lub jakiekolwiek ładowanie nie jest możliwe. Wszystko musi być zrobione w tle lub pomiędzy poszczególnymi tyknięciami (mniej niż 15 ms dla każdego), podczas gdy większość czasu zwykle jest już wykorzystywana na przygotowania renderowania i aktualizację gry. W każdym razie podział na mniejsze części może zwiększyć elastyczność przełączania, na pewno byłoby to szybsze. Pytanie brzmi, jak bardzo wpływa to na wydajność podczas renderowania, ponieważ zmiana źródłowej bitmapy podczas rysowania spowalnia renderowanie. Musiałbym dokonać dokładnego pomiaru, aby powiedzieć, ile.
Marwin
@Marwin Wpływ na wydajność, tak, ale ponieważ masz do czynienia z 2D, nadal powinno być daleko od tego, aby stało się problemem. Jeśli renderowanie zajmuje obecnie 1 ms na klatkę, a przy użyciu mniejszych tekstur nagle zajmuje 2 ms, to i tak jest więcej niż wystarczająco szybkie, aby osiągnąć spójne 60 FPS. (16ms)
API-Beast
@Marwin Multiplayer to trudna sprawa, zawsze była, zawsze będzie. Prawdopodobnie będziesz musiał tam pójść na kompromis. Będziesz mieć jąkanie, po prostu dlatego, że musisz przesyłać dane przez Internet, pakiety zostaną utracone, pingi mogą nagle wzrosnąć itp. Jąkanie jest nieuniknione, więc co ważniejsze, uczynisz sam model sieci odpornym na jąkanie. Wiedzieć, kiedy czekać i jak czekać na innych graczy.
API-Beast
Witaj, zacinania można prawie uniknąć w trybie dla wielu graczy, pracujemy teraz nad tym obszarem i uważam, że mamy dobry plan. Mógłbym nawet opublikować i odpowiedzieć na własne pytanie opisujące to, co szczegółowo zbadaliśmy później :) Może to być niespodzianka, ale czas renderowania jest w rzeczywistości problemem. Dokonaliśmy wielu optymalizacji, aby przyspieszyć renderowanie. Główny rendering jest teraz wykonany w osobnym wątku i innych drobnych poprawkach. Nie zapominaj, że przy maksymalnym powiększeniu gracz może łatwo zobaczyć dziesiątki tysięcy duszków jednocześnie. Chcielibyśmy nawet pozwolić na jeszcze wyższe poziomy powiększenia później.
Marwin
@Marwin Hm, 10k obiektów zwykle nie powinno stanowić problemu dla komputera PC lub nowoczesnego laptopa, jeśli używasz odpowiedniego dozowania, czy profilowałeś swój kod renderowania?
API-Beast
2

Wow, to jest ogromna ilość animowanych animacji wygenerowanych z modeli 3D, które, jak przypuszczam?

Naprawdę nie powinieneś tworzyć tej gry w surowych 2D. Gdy ustawisz perspektywę, dzieje się coś zabawnego, możesz bezproblemowo mieszać wstępnie renderowane duszki i tła z modelami 3D renderowanymi na żywo, które były często używane w niektórych grach. Jeśli chcesz mieć takie piękne animacje, które wydają się najbardziej naturalnym sposobem na zrobienie tego. Pobierz silnik 3D, skonfiguruj go tak, aby używał perspektywy izometrycznej, i renderuj obiekty, dla których nadal używasz duszków, jako proste płaskie powierzchnie z obrazem na nich. I możesz użyć kompresji tekstur z silnikiem 3D, który sam w sobie jest dużym krokiem naprzód.

Nie sądzę, aby ładowanie i rozładowywanie wiele dla ciebie zrobiło, ponieważ możesz mieć prawie wszystko na ekranie w tym samym czasie.

aaaaaaaaaaaa
źródło
2

Po pierwsze, znajdź najbardziej efektywny format tekstur, jaki możesz, jednocześnie ciesząc się grafiką gry, niezależnie od tego, czy jest to kompresja RGBA4444, DXT itp. Jeśli nie jesteś zadowolony z artefaktów wygenerowanych w skompresowanym obrazie DXT alfa, czy byłoby to wykonalne? aby obrazy były nieprzezroczyste przy użyciu kompresji DXT1 dla koloru w połączeniu z 4 lub 8 bitową teksturą maskowania w skali szarości dla alfa? Wyobrażam sobie, że pozostaniesz na RGBA8888 dla GUI.

Opowiadam się za podzieleniem rzeczy na mniejsze tekstury przy użyciu dowolnego wybranego formatu. Określ elementy, które są zawsze na ekranie, a zatem zawsze ładowane, mogą to być atlasy terenu i GUI. Następnie rozbiję pozostałe elementy, które są zazwyczaj renderowane razem tak bardzo, jak to możliwe. Nie sądzę, abyś stracił zbyt wiele wydajności, nawet do 50-100 losowań na PC, ale popraw mnie, jeśli się mylę.

Następnym krokiem będzie wygenerowanie wersji mipmap tych tekstur, jak ktoś wskazał powyżej. Nie zapisałbym ich w jednym pliku, ale osobno. Skończyłbyś z wersjami 1024x1024, 512x512, 256x256 itd. Każdego pliku i robiłbym to, dopóki nie osiągnę najniższego poziomu szczegółowości, jaki kiedykolwiek chciałbym wyświetlić.

Teraz, gdy masz oddzielne tekstury, możesz zbudować system poziomu szczegółowości (LOD), który ładuje tekstury dla bieżącego poziomu powiększenia i zwalnia tekstury, jeśli nie są używane. Tekstura nie jest używana, jeśli renderowany element nie jest wyświetlany na ekranie lub nie jest wymagany przez bieżący poziom powiększenia. Spróbuj załadować tekstury do pamięci RAM wideo w wątku oddzielnym od wątków aktualizacji / renderowania. Możesz wyświetlać najniższą teksturę LOD, dopóki nie zostanie załadowana wymagana. Może to czasami powodować widoczne przełączanie między teksturami o niskiej szczegółowości / wysokiej szczegółowości, ale wyobrażam sobie, że byłoby to możliwe tylko wtedy, gdy wykonujesz bardzo szybkie pomniejszanie i przesuwasz się po mapie. Możesz uczynić system inteligentnym, próbując wstępnie załadować w miejscu, w którym według ciebie osoba się poruszy lub powiększy i załaduje jak najwięcej w ramach obecnych ograniczeń pamięci.

To jest coś, co sprawdziłbym, czy to pomaga. Wyobrażam sobie, że aby uzyskać ekstremalne poziomy powiększenia, nieuchronnie potrzebujesz systemu LOD.

chrześcijanin
źródło
1

Uważam, że najlepszym rozwiązaniem jest podzielenie tekstury na wiele plików i ładowanie ich na żądanie. Prawdopodobnie Twoim problemem jest to, że próbujesz załadować większe tekstury, które byłyby potrzebne do kompletnej sceny 3D i używasz do tego Allegro.

Do dużego pomniejszenia, które chcesz zastosować, musisz użyć mipmap. Mipmapy to wersje tekstur o niższej rozdzielczości, które są używane, gdy obiekty znajdują się wystarczająco daleko od aparatu. Oznacza to, że możesz zapisać swoją 8192x8192 jako 4096x4096, a następnie kolejną z 2048x2048 i tak dalej, i przełączasz się na niższe rozdzielczości, im mniejszy jest duszek na ekranie. Możesz zarówno zapisać je jako osobne tekstury lub zmienić ich rozmiar podczas ładowania (ale generowanie mipmap podczas działania wydłuży czas ładowania gry).

Właściwy system zarządzania ładowałby wymagane pliki na żądanie i zwalniał zasoby, gdy nikt ich nie używa, a także inne rzeczy. Zarządzanie zasobami jest ważnym tematem w tworzeniu gier, a zarządzanie sprowadza się do prostego mapowania współrzędnych do pojedynczej tekstury, co jest bliskie braku zarządzania.

Pablo Ariel
źródło
1
Przez podział na pliki masz na myśli pliki na dysku twardym? Zakładam, że mógłbym zapisać wszystkie zdjęcia w pamięci RAM na początek, a nawet kopiowanie z bitmapy pamięci na bitmapę wideo jest obecnie zbyt wolne, więc ładowanie z HDD byłoby z pewnością jeszcze wolniejsze. Posiadanie mimpapów mi nie pomoże, ponieważ nadal będę miał największą rozdzielczość w vramie.
Marwin,
Tak, nie musisz ładować wszystkiego, musisz ładować tylko to, czego używasz. Ilekroć chcesz zmienić piksel na teksturze załadowanej do VRAM, system musi przenieść CAŁĄ TEKSTURĘ do RAM, aby zmodyfikować pojedynczy piksel, przenieś go z powrotem do VRAM. Jeśli masz wszystko w jednej teksturze, wiąże się to z przeniesieniem 256 MB do pamięci RAM, a następnie z powrotem do pamięci VRAM, co blokuje cały komputer. Rozdzielenie go na różne pliki i tekstury to właściwy sposób na zrobienie tego.
Pablo Ariel
Modyfikacja tekstury, która wyzwala kopiowanie do pamięci i powrót do pamięci RAM, dotyczy tylko trwałych map bitowych, pamięć podręczna prawdopodobnie nie byłaby ustawiona na trwałą, jedynym minusem byłaby konieczność odświeżenia jej, gdy ekran zostanie utracony / znaleziony. Ale na allegro nawet prosta kopia obrazu 640X480 z vramu do bitmapy pamięci (zapisz podgląd gry) zajmuje dość dużo czasu.
Marwin
1
Muszę mieć wszystko w jednej dużej teksturze, aby zoptymalizować sam rysunek, bez niego efekt przełączania kontekstu między poszczególnymi duszkami spowalnia renderowanie zbyt mocno, przynajmniej w allegro. Nie zrozum mnie źle, ale jesteś tutaj oczywistym kapitanem, ponieważ niejasno sugerujesz, żebym zrobił coś, o co proszę w tym pytaniu.
Marwin,
1
Posiadanie tych tekstur odwzorowanych w mipach w różnych plikach zmusiłoby mnie do przeładowania całego atlasu, gdy odtwarzacz powiększa. Ponieważ silnik ma tylko kilka jednostek ms, nie widzę sposobu, jak to zrobić.
Marwin
0

Polecam tworzenie większej liczby plików atlasu, które można skompresować za pomocą zlib i przesyłać strumieniowo z kompresji dla każdego atlasu, a dzięki większej liczbie plików atlasu i plików o mniejszych rozmiarach możesz ograniczyć ilość aktywnych danych obrazu w pamięci wideo. Zaimplementuj także mechanizm potrójnego bufora, aby przygotować każdą ramkę do rysowania wcześniej i mieć szansę na ukończenie szybciej, aby nie zacinały się na ekranie.

Darxval
źródło