Z tego dokumentu dowiesz się, jak optymalizować wydajność gry za pomocą narzędzi do identyfikowania i usuwania wąskich gardeł CPU i GPU.
Optymalizacja procesora
Jeśli analiza wykaże, że gra jest ograniczona przez procesor, konieczne jest dalsze zbadanie problemu. Wymaga to zidentyfikowania konkretnych wątków lub interfejsów API, które powodują wąskie gardła i obniżają liczbę klatek na sekundę.
W przypadku optymalizacji procesora uniwersalne rozwiązanie zwykle nie jest skuteczne. Zamiast tego musisz określić najbardziej wymagające obciążenie na podstawie gry lub sceny, a następnie zoptymalizować odpowiednią logikę i funkcje.
Narzędzia do śledzenia czasu w silniku gry
W przeprowadzeniu tej analizy mogą Ci pomóc te narzędzia:
Niesamowite statystyki
W projektach Unreal Engine narzędzie Unreal Insight Tool ułatwia analizę informacji o śledzeniu czasu poszczególnych wątków składających się na klatkę.
Przykładowo wątek GameThread zwykle wykorzystuje największą część czasu procesora, co wynika głównie z czasu Tick Time. Ponadto znaczną część czasu Tick Time zajmują zadania związane z FActorComponentTickFunction.
Aby zoptymalizować FActorComponentTick, konieczne jest wykluczenie obliczeń i wdrożenie wycinania w przypadku postaci i obiektów znajdujących się poza polem widzenia kamery. Dodatkowo korzystanie z animacji opartych na poziomie szczegółowości może jeszcze bardziej zwiększyć wydajność.
Profiler Unity (Unity)
Analiza za pomocą narzędzia Unity Profiler pokazuje, że wątek główny zużywa ponad 45 ms, a funkcja PostLateUpdate.FinishFrameRendering zajmuje 16,23 ms, co czyni ją najbardziej czasochłonną operacją. W tym czasie obserwujemy wiele wywołań funkcji Inl_RenderCameraStack. Warto sprawdzić, czy włączone kamery są potrzebne, i odpowiednio je zoptymalizować.
Narzędzia do profilowania na poziomie systemu
Skorzystaj z tych narzędzi do profilowania:
Perfetto
Korzystając ze śladu Perfetto, możesz określić przypisania rdzeni procesora i szczegóły wykonania każdego wątku na urządzeniu z Androidem. Umożliwia to identyfikowanie wąskich gardeł wydajności przez analizowanie danych o wykonywaniu wątków.
Przypadek obciążenia procesora
Ślad wskazuje, że obciążenie wątków GameThread i RenderThread powoduje opóźnienia w funkcji QueuePresent wątku RHI, co prowadzi do scenariusza ograniczonego przez procesor na podstawie synchronizacji pionowej.
Przypadek obciążenia GPU
Ślad wskazuje, że samo zakończenie pracy GPU trwa ponad 25 ms, co oznacza, że jest to scenariusz ograniczony przez GPU.
Simpleperf
Aby zidentyfikować funkcje o najwyższym bieżącym użyciu procesora, można użyć narzędzia simpleperf. Aby uzyskać optymalne wyniki, zalecamy posortowanie tych funkcji w celu ustalenia priorytetów i rozwiązania w pierwszej kolejności problemów z tymi, które są najczęściej używane.
Simpleperf pomaga analizować dane o funkcjach, które wykorzystują najwięcej czasu procesora. Aby zoptymalizować wykorzystanie procesora, zacznij od funkcji, które zużywają najwięcej mocy obliczeniowej. W tym przykładzie najwięcej zasobów procesora wykorzystuje USkeletalMeshComponent, które jest powiązane z animacją w ActorComponentTickFunctions.
Optymalizacja GPU
Jeśli analiza wykaże, że gra jest ograniczona przez GPU, konieczne jest dalsze zbadanie problemu. Wymaga to użycia różnych narzędzi i technik optymalizacji i analizy GPU.
Aby zoptymalizować GPU, użyj debugera klatek do analizowania potoku renderowania i wywołań rysowania w każdej scenie. Musisz też dobrze rozumieć architekturę procesora graficznego i działanie potoku, aby zidentyfikować niepotrzebne operacje lub obszary do optymalizacji.
W sekcjach poniżej znajdziesz opis metod i narzędzi do optymalizacji procesora graficznego.
Usuwanie niepotrzebnych RenderPassów
Aby zwiększyć wydajność renderowania i zmniejszyć obciążenie GPU, wyeliminuj niepotrzebne przebiegi renderowania. Obejmują one wszystkie przebiegi renderowania, które nie mają wywołań rysowania lub których dane wyjściowe nie są używane w klatce końcowej.
Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować możliwości optymalizacji.
No Draw Calls (Brak wywołań rysowania): sprawdź, czy przepustka renderowania zawiera wywołania rysowania. Jeśli nie ma wywołań rysowania, usuń przepustkę.
Nieużywane dane wyjściowe: sprawdź, czy kolejne przebiegi mają dostęp do danych wyjściowych przebiegu renderowania, np. koloru lub głębi, i czy je wyświetlają. Jeśli nie, usuń kartę.
Dokumenty, które można scalić: sprawdź, które dokumenty możesz scalić:
- Ten sam bufor ramki lub załączniki
- Zgodne operacje ładowania lub przechowywania
- Brak barier zależności
Minimalizowanie operacji wczytywania i zapisywania
Operacje ładowania i przechowywania są zasobożerne, ponieważ wykorzystują dużo pamięci.
Minimalizuj niepotrzebne operacje wczytywania i zapisywania. Wykonuj te czynności tylko wtedy, gdy wymagane są załączniki w RenderPass. W przeciwnym razie zastąp je operacjami Clear lub Don't care, aby zmniejszyć narzut.
Optymalizacja
Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:
Wczytywanie: jeśli załącznik do etapu renderowania nie korzysta z danych z poprzedniego etapu lub załącznika, operacja wczytywania jest zbędna. W takich przypadkach użycie metod
Don't carelubClearmoże zmniejszyć obciążenie.Przechowywanie: jeśli załącznik przepustki renderowania nie jest używany po bieżącej przepustce renderowania, operacja przechowywania jest niepotrzebna. W takich przypadkach używaj właściwości
Don't carelubClear.Zastąp: sprawdź, czy bieżące ustawienia wczytywania lub zapisywania można zastąpić ustawieniami
ClearlubDon't Carebez wpływu na końcową klatkę.
Unikaj odrzucania, aby włączyć Early-Z
Early-Z zwiększa wydajność na platformach mobilnych. discard Instrukcja w shaderze automatycznie wyłącza jednak Early-Z. Jeśli discardinstrukcja nie jest niezbędna, usuń ją.
Przyspieszenie Early-Z
Ta optymalizacja znacznie zmniejsza liczbę operacji shadera fragmentów i zwiększa wydajność procesora graficznego.
Early-Z Testowanie głębi i szablonu
Optymalizacja
Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:
Użycie słowa kluczowego
discardw programach cieniowania fragmentów: słowo kluczowediscarduniemożliwia procesorowi graficznemu przeprowadzanie wczesnych testów głębi, ponieważ widoczność fragmentu nie jest znana z wyprzedzeniem.Modyfikacja
gl_FragDepth: dynamiczna modyfikacjagl_FragDepthzmienia głębię fragmentu, co wyłącza optymalizację Early-Z, ponieważ ostateczna głębia jest nieznana przed przetworzeniem fragmentu.Włączone przekształcanie wartości alfa w wartości pokrycia: gdy przekształcanie wartości alfa w wartości pokrycia jest włączone (często używane w renderowaniu MSAA), pokrycie fragmentu zależy od wartości alfa. Może to opóźnić testowanie głębi i wyłączyć Early-Z.
Optymalizacja formatu tekstury
Optymalny wybór formatu tekstury zmniejsza zużycie pamięci, zwiększa wydajność przepustowości i poprawia wydajność renderowania. Stosowanie formatów o zbyt wysokiej precyzji może marnować zasoby GPU bez zapewniania korzyści wizualnych.
Optymalizacja
Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:
- Używaj formatu
D24S8zamiastD32S8w przypadku buforów głębi i szablonu: używanie formatuD24S8w przypadku buforów głębi i szablonu zmniejsza zużycie pamięci o 20% w porównaniu z formatemD32S8, przy niewielkiej lub żadnej zauważalnej różnicy w jakości obrazu w większości aplikacji. - Używaj kompresji
ASTCw przypadku tekstur kolorów: kompresjaASTCznacznie zmniejsza wykorzystanie pamięci przez tekstury – nawet 8-krotnie w porównaniu z formatami nieskompresowanymi – przy zachowaniu wysokiej jakości wizualnej. - Zamiast formatów zmiennoprzecinkowych o pełnej precyzji używaj formatów zmiennoprzecinkowych o połowicznej precyzji: używaj formatów
R16FlubRG16F, aby zmniejszyć przepustowość pamięci i zużycie miejsca na dane. Te formaty dobrze nadają się do buforów przetwarzania końcowego.
Optymalizacja złożoności geometrii
Minimalizowanie złożoności geometrycznej poprawia wydajność renderowania, zwłaszcza na urządzeniach mobilnych o ograniczonych możliwościach GPU. Obejmuje to używanie mniejszej liczby wierzchołków i trójkątów, konsolidowanie obiektów w celu zmniejszenia liczby wywołań rysowania oraz eliminowanie niewyrenderowanej lub niepotrzebnej geometrii. Techniki takie jak upraszczanie siatki, poziom szczegółowości (LOD) oraz wycinanie frustum i okluzji mogą znacznie zmniejszyć obciążenie procesora graficznego i zwiększyć liczbę klatek na sekundę.
Optymalizacja
Używaj narzędzi do profilowania i debuggerów GPU, takich jak RenderDoc, Android GPU Inspector lub inne analizatory wydajności, aby identyfikować wąskie gardła wydajności związane z geometrią.
Zmniejsz liczbę trójkątów: ogranicz użycie wielokątów, zwłaszcza w przypadku małych lub odległych obiektów.
Użyj poziomu szczegółowości: w zależności od odległości kamery automatycznie używane są prostsze siatki.
Scal małe siatki: konsoliduje obiekty statyczne, aby zmniejszyć liczbę wywołań rysowania i obciążenie procesora.
Odcinanie i usuwanie zasłoniętych obiektów: unikaj renderowania obiektów, które znajdują się poza widokiem lub są zasłonięte przez inne elementy.
Usuwanie niepotrzebnych załączników
Załączniki do przebiegu renderowania (np. kolor, głębia, szablon) zużywają przepustowość pamięci i zasoby GPU, nawet jeśli nie są używane. Usunięcie niepotrzebnych lub zbędnych załączników zwiększa wydajność i zmniejsza zużycie energii, zwłaszcza na platformach mobilnych.
Optymalizacja
Używaj narzędzi do profilowania i debuggerów GPU, takich jak RenderDoc,Android GPU Inspector lub innych analizatorów wydajności, aby identyfikować wąskie gardła wydajności związane z geometrią.
- Sprawdź rzeczywiste użycie: czy istnieją wywołania rysowania lub shadery, które zapisują dane w załączniku lub je z niego odczytują?
- Analizowanie danych wyjściowych klatek: użyj narzędzia
RenderDoclub podobnych narzędzi, aby określić, czy załącznik ma wpływ na obraz końcowy. - Rozważ użycie tymczasowych lub fikcyjnych załączników: tymczasowe załączniki lub operacja magazynu „Nie ma znaczenia” powinny być używane w przypadku danych tymczasowych, które nie wymagają pamięci trwałej.
Optymalizowanie precyzji shadera
Używanie w shaderach zbyt wysokiej precyzji (np. highp zamiast mediump lub lowp) zwiększa obciążenie GPU, zużycie energii i ciśnienie rejestru, zwłaszcza w przypadku mobilnych procesorów graficznych. Używanie najniższej odpowiedniej precyzji w przypadku zmiennych (np. pozycji, kolorów, współrzędnych UV) może poprawić wydajność bez zauważalnego wpływu na wygląd.
Optymalizacja
Używaj narzędzi do profilowania i debuggerów GPU, takich jak RenderDoc, Android GPU Inspector lub inne analizatory wydajności, aby identyfikować wąskie gardła związane z geometrią.
Sprawdź kod shadera: oceń zmienne shadera i upewnij się, że wysoka precyzja jest używana tylko wtedy, gdy jest to konieczne, np. w przypadku obliczeń głębi lub przestrzeni ekranu. Używaj średniej lub niskiej precyzji w przypadku kolorów, współrzędnych UV lub wartości, które nie wymagają wysokiej precyzji.
Używaj debuggerów GPU: narzędzia diagnostyczne, takie jak RenderDoc lub profiler GPU na urządzenia mobilne (np. AGI, Mali/GPU Inspector), identyfikują zwiększone użycie rejestrów lub wstrzymania cieniowania związane z problemami z precyzją.
Włączanie odrzucania tylnych ścian
Renderowanie trójkątów skierowanych tyłem do kamery (tylnych ścian) jest często niepotrzebne w przypadku obiektów stałych.
Optymalizacja
Używanie elementu VK_CULL_MODE_NONE może negatywnie wpływać na wydajność, ponieważ zmusza GPU do renderowania zarówno przedniej, jak i tylnej strony, co zwiększa obciążenie renderowaniem.
Minimalizowanie przerysowania w scenach interfejsu
Eliminuj niepotrzebne wywołania rysowania i przekazywanie renderowania, zwłaszcza w scenach interfejsu, aby zwiększyć wydajność renderowania i zmniejszyć obciążenie procesora graficznego. Na przykład w scenie interfejsu, w której cały świat jest renderowany przed nałożeniem interfejsu na ekran, renderowanie świata staje się zbędne.
Optymalizacja
Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:
- Sprawdź, czy nie ma zbędnego przerysowania. W kontekstach interfejsu użytkownika, w których może być renderowany cały ekran, sprawdź, czy poprzednie etapy renderowania nie są niepotrzebnie nadmiernie rysowane.
- Włącz testowanie głębi i odrzucanie, aby zoptymalizować wydajność.
- Rozważ renderowanie w kolejności od przodu do tyłu.