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:
- Rysowanie z bitmapy pamięci do bitmapy wideo jest boleśnie powolne, w allegro.
- 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.
źródło
Odpowiedzi:
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.
źródło
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ć?
źródło
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.
źródło
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.
źródło
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.
źródło
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.
źródło