Wiem, że tytuł jest trochę niejasny, ale trudno opisać, czego naprawdę szukam, ale proszę bardzo.
Jeśli chodzi o renderowanie procesora, wydajność jest w większości łatwa do oszacowania i prosta, ale jeśli chodzi o procesor graficzny ze względu na brak informacji technicznych w tle, nie mam pojęcia. Używam XNA, więc byłoby miło, gdyby teoria mogła być z tym powiązana.
Więc tak naprawdę chcę wiedzieć, co się dzieje, kiedy i gdzie (CPU / GPU) wykonujesz określone akcje losowania? Co to jest partia? Jaki wpływ mają efekty, projekcje itp.? Czy dane są utrwalane na karcie graficznej, czy też są przesyłane na każdym kroku? Kiedy mówi się o przepustowości, czy mówisz o wewnętrznej przepustowości karty graficznej, czy o potoku między procesorem a GPU?
Uwaga: tak naprawdę nie szukam informacji o tym, jak przebiega proces rysowania, to jest sprawa GPU, interesuje mnie cały narzut, który to poprzedza.
Chciałbym zrozumieć, co się dzieje, gdy wykonuję akcję X, aby dostosować do tego moje architektury i praktyki.
Wszelkie artykuły (ewentualnie z przykładami kodu), informacje, linki, samouczki, które dają więcej wglądu w pisanie lepszych gier, są bardzo mile widziane. Dzięki :)
Odpowiedzi:
Lubię myśleć o wydajności w kategoriach „ limitów ”. Jest to przydatny sposób na konceptualizację dość skomplikowanego, połączonego systemu. Gdy masz problem z wydajnością, zadajesz pytanie: „Jakie limity uderzam?” (Lub: „Czy jestem związany procesorem / kartą graficzną?”)
Możesz podzielić go na wiele poziomów. Na najwyższym poziomie masz CPU i GPU. Możesz być związany z procesorem (GPU siedzi bezczynnie i czeka na procesor) lub może być związany z GPU (procesor czeka na GPU). Oto dobry post na blogu na ten temat.
Możesz to dalej rozbić. Po stronie procesora możesz wykorzystywać wszystkie swoje cykle na danych znajdujących się już w pamięci podręcznej procesora. Lub możesz mieć ograniczoną pamięć , pozostawiając procesor bezczynny, czekając na dane z pamięci głównej ( zoptymalizuj układ danych ). Nadal możesz to zepsuć.
(Podczas gdy robię szeroki przegląd wydajności w odniesieniu do XNA, zwrócę uwagę, że przydział typu referencyjnego (
class
niestruct
), chociaż normalnie tani, może spowodować wyrzucanie elementów bezużytecznych, co spowoduje spalenie wielu cykli - szczególnie na Xbox 360 . Zobacz tutaj szczegóły).Po stronie GPU zacznę od wskazania tego doskonałego posta na blogu, który zawiera wiele szczegółów. Jeśli chcesz uzyskać szalony poziom szczegółowości w przygotowaniu, przeczytaj tę serię postów na blogu . ( Oto prostszy ).
Mówiąc prościej, niektóre z dużych to: „ limit wypełnienia ” (liczba pikseli, które można zapisać w buforze backbuffera - często jak dużo overdraw można mieć), „ limit shadera ” (jak skomplikowane mogą być twoje shadery i ile danych można przez nie przepchnąć), „ pobieranie tekstur / limit przepustowości tekstur ” (ile danych tekstur można uzyskać dostęp).
I teraz dochodzimy do dużego - o to tak naprawdę pytasz - gdzie procesor i GPU muszą oddziaływać (za pośrednictwem różnych interfejsów API i sterowników). Luźno istnieje „ limit partii ” i „ przepustowość ”. (Należy zauważyć, że pierwsza część serii, o której wspomniałem wcześniej, zawiera szczegółowe informacje).
Ale w zasadzie partia ( jak już wiesz ) dzieje się za każdym razem, gdy wywołasz jedną z
GraphicsDevice.Draw*
funkcji (lub część XNA, na przykładSpriteBatch
, robi to za Ciebie). Jak już bez wątpienia czytałeś, dostajesz kilka tysięcy * na ramkę. Jest to limit procesora - więc konkuruje z innym wykorzystaniem procesora. Zasadniczo jest to sterownik pakujący wszystko o tym, co kazałeś mu narysować, i wysyłający go do GPU.A potem jest przepustowość do GPU. To ile surowych danych możesz tam przenieść. Obejmuje to wszystkie informacje o stanie, które towarzyszą wsadom - wszystko, od ustawiania stanu renderowania i stałych / parametrów modułu cieniującego (który obejmuje takie rzeczy jak świat / widok / matryce projektu), aż do wierzchołków podczas korzystania z
DrawUser*
funkcji. Obejmuje ona także żadnych połączeń doSetData
iGetData
na fakturach, bufory wierzchołków itpW tym miejscu powinienem powiedzieć, że wszystko, co można wywołać
SetData
(tekstury, bufory wierzchołków i indeksów itp.), A takżeEffect
s - pozostaje w pamięci GPU. Nie jest stale wysyłane ponownie do GPU. Polecenie rysowania, które odwołuje się do tych danych, jest po prostu wysyłane ze wskaźnikiem do tych danych.(Również: możesz wysyłać polecenia rysowania tylko z głównego wątku, ale możesz
SetData
z dowolnego wątku.)XNA komplikuje nieco z jego czynią klas państwowych (
BlendState
,DepthStencilState
itp). Dane o stanie są wysyłane dla każdego losowania (w każdej partii). Nie jestem w 100% pewien, ale mam wrażenie, że jest wysyłany leniwie (wysyła tylko stan, który się zmienia). Tak czy inaczej, zmiany stanu są tanie do tego stopnia, że są wolne od kosztu partii.Na koniec należy wspomnieć o wewnętrznym potoku GPU . Nie chcesz wymuszać jego opróżnienia przez zapis do danych, które wciąż muszą czytać, lub odczytywanie danych, które wciąż muszą zapisywać. Opróżnianie potoku oznacza, że czeka on na zakończenie operacji, dzięki czemu wszystko jest w spójnym stanie po uzyskaniu dostępu do danych.
Dwa szczególne przypadki, na które należy uważać, to: Wzywanie
GetData
czegokolwiek dynamicznego - szczególnie takiegoRenderTarget2D
, na którym GPU może pisać. Jest to bardzo niekorzystne dla wydajności - nie rób tego.Innym przypadkiem jest wywoływanie
SetData
buforów wierzchołków / indeksów. Jeśli musisz to robić często, użyjDynamicVertexBuffer
(takżeDynamicIndexBuffer
). Pozwalają one GPU wiedzieć, że często się zmieniają, i wykonać wewnętrzną magię buforowania, aby uniknąć przepłukiwania rurociągu.(Należy również pamiętać, że bufory dynamiczne są szybsze niż
DrawUser*
metody - ale muszą być wstępnie przydzielone przy maksymalnym wymaganym rozmiarze).... I to prawie wszystko, co wiem o wydajności XNA :)
źródło