Jak długo zajmuje OpenGL faktyczna aktualizacja ekranu?

9

Mam prostą aplikację testową OpenGL w C, która rysuje różne rzeczy w odpowiedzi na kluczowe dane wejściowe. (Mesa 8.0.4, wypróbowany z Mesa-EGL i GLFW, Ubuntu 12.04LTS na PC z NVIDIA GTX650). Losowania są dość proste / szybkie (obracanie trójkątów). Mój kod testowy nie ogranicza celowo żadnej liczby klatek na sekundę, wygląda to tak:

while (true)
{
    draw();
    swap_buffers();
}

Zmierzyłem to bardzo dokładnie i stwierdziłem, że czas od jednego eglSwapBuffers()(lub tego glfwSwapBufferssamego) połączenia do następnego wynosi ~ 16,6 milisekund. Czas od połączenia do eglSwapBuffers()momentu tuż przed następnym połączeniem jest tylko trochę mniejszy, mimo że losowanie jest bardzo proste. Czas potrzebny na wywołanie buforów wymiany jest znacznie poniżej 1ms.

Jednak czas od zmiany przez aplikację tego, co rysuje w odpowiedzi na naciśnięcie klawisza, na zmianę, która faktycznie pojawia się na ekranie, wynosi> 150 ms (wartość około 8-9 klatek). Jest to mierzone za pomocą kamery rejestrującej ekran i klawiaturę z prędkością 60 klatek na sekundę.

Dlatego pytania:

  1. Gdzie losowane są buforowane między wezwaniem do zamiany buforów i faktycznie wyświetlane na ekranie? Skąd to opóźnienie? Wygląda na to, że aplikacja cały czas rysuje wiele ramek przed ekranem.

  2. Co może zrobić aplikacja OpenGL, aby spowodować natychmiastowe wyświetlenie ekranu? (tj .: bez buforowania, po prostu blokuj do zakończenia losowania; nie potrzebuję dużej przepustowości, potrzebuję małego opóźnienia)

  3. Co może zrobić aplikacja, aby powyższe natychmiastowe losowanie odbyło się tak szybko, jak to możliwe?

  4. Skąd aplikacja może wiedzieć, co aktualnie jest wyświetlane na ekranie? (Lub jak długo / ile ramek jest bieżące opóźnienie buforowania?)

Alex I.
źródło
Czy potrafisz oddzielić czas, jaki klawiatura potrzebuje na powiadomienie aplikacji, do czasu, w którym faktycznie to renderuje?
ThorinII
@ThorinII: Sprawiedliwy punkt. Prawdopodobnie mogę skonfigurować coś włamywającego za pomocą diody LED na porcie równoległym itp., Aby uzyskać informację o tym, kiedy aplikacja faktycznie naciśnie klawisz. Ale to tylko fgetc (standardowe), jak wolno to może być? : /
Alex I
Miałem wrażenie, że glFlush został użyty do natychmiastowego wykonania wszystkich poleceń.
Programmdude
1
@AlexI sprawdź, czy może to pomóc
gamedev.stackexchange.com/questions/66543/…
1
@ concept3d: Tak, to w zasadzie odpowiedź, po prostu pomyślałem, że chciałbyś trochę powtórzyć :) Najwyraźniej muszę jednak czekać jeden dzień, aby ją przyznać.
Alex I

Odpowiedzi:

6

Każda funkcja API rysowania wywoływana z CPU zostanie przesłana do bufora pierścienia poleceń GPU, która zostanie wykonana później przez GPU. Oznacza to, że funkcje OpenGL są w większości funkcjami nieblokującymi. Procesor i GPU będą działały równolegle.

Najważniejszą rzeczą do zapamiętania jest to, że twoja aplikacja może być związana z procesorem lub GPU. po wywołaniu glFinish procesor powinien poczekać, aż procesor GPU dokończy wykonywanie poleceń rysowania, jeśli procesor graficzny zajmuje więcej czasu i może / powoduje zatrzymanie procesora, wówczas aplikacja jest związana z GPU. Jeśli procesor graficzny zakończy wykonywanie poleceń rysowania, a proces glFinish trwa zbyt długo, aplikacja jest związana z procesorem.

I zauważ, że istnieje różnica między glFlushi glFinish.

glFlush: wskazuje, że wszystkie polecenia wysłane wcześniej do GL muszą zostać wykonane w określonym czasie.

glFinish: wymusza wykonanie wszystkich poprzednich poleceń GL. Wykończenie nie jest zwracane, dopóki wszystkie efekty wcześniej wydanych poleceń na stanie klienta i serwera GL oraz buforze ramki nie zostaną w pełni zrealizowane. ”

glXSwapBuffers wykonuje ukrytą funkcję glFlush przed jej zwróceniem. Kolejne polecenia OpenGL mogą być wydawane natychmiast po wywołaniu glXSwapBuffers, ale nie są wykonywane, dopóki wymiana bufora nie zostanie zakończona.

Rzeczywisty czas ramki najprawdopodobniej zostanie określony na podstawie tego, który z dwóch procesorów / GPU zajmuje więcej czasu na zakończenie swojej pracy.

concept3d
źródło
To jest bardzo pomocne. Niektóre możliwe poprawki: opengl.org/sdk/docs/man2/xhtml/glXSwapBuffers.xml „glXSwapBuffers wykonuje domyślną glFlush zanim zwróci” wygląda na to, że tak naprawdę nie robi glFinish, więc bufor poleceń może mieć całkiem sporo w nim, gdy bufory wymiany zostaną zwrócone.
Alex I
... Myślę też, że najciekawszym punktem jest to, że moja bardzo prosta aplikacja testowa nie jest ani ograniczona procesorem, ani GPU - nie ma tu wiele pracy - ale coś innego, w jakiś sposób kończy się zarówno z niską liczbą klatek na sekundę (dokładnie tak samo jak monitor częstotliwość odświeżania ??) i duże opóźnienie (z powodu bufora poleceń, właściwie całkiem dobrze wyjaśniłeś tę część).
Alex I
@AlexI both low framerate (exactly same as monitor refresh rate??nie, chyba że wyraźnie używasz VSync.
concept3d
@AlexI Chciałbym również zauważyć, że czas ramki różni się od FPS, użyj czasu ramki do profilowania aplikacji, ponieważ jest liniowa. Z drugiej strony FPS jest złym pomiarem, który nie jest liniowy.
concept3d
Nie używam jawnie vsync. Nie robię nic, aby zmienić ustawienia defaul vsync, nawet nie wiem, gdzie je zmienić w OpenGL. Właśnie dostaję dokładnie 60 klatek na sekundę (do jednego procenta).
Alex I
4

Technicznie OpenGL nigdy nie aktualizuje ekranu.

Istnieje interfejs API systemu okien, który jest niezależny od GL (np. GLX, WGL, CGL, EGL), który to robi. Zamiana buforów przy użyciu tych interfejsów API zwykle wywołuje niejawnie, glFlush (...)ale w niektórych implementacjach (np. Rasterizer GDI w systemie Windows) robi to w pełni glFinish (...):

 

    * Po lewej stronie znajduje się ścieżka ICD (sprzętowa) SwapBuffers (...). Po prawej stronie ścieżka GDI (oprogramowania).

Jeśli masz włączoną funkcję VSYNC i podwójne buforowanie, każde polecenie, które zmodyfikuje bufor tylny przed wystąpieniem oczekującej wymiany, musi zostać zatrzymane do momentu wymiany. Kolejka poleceń ma ograniczoną głębokość, więc to zablokowane polecenie ostatecznie spowoduje zakorkowanie w rurociągu. Może to oznaczać, że zamiast blokować w SwapBuffers (...)twojej aplikacji, faktycznie blokuje jakieś niepowiązane polecenie GL, dopóki VBLANK się nie zmieni. To, co tak naprawdę sprowadza się do tego, to ile buforów tylnych masz w swoim łańcuchu wymiany.

Tak długo, jak wszystkie tylne bufory są pełne gotowych ramek, które mają zostać jeszcze przeniesione na przód, bufory wymiany pośrednio powodują blokowanie. Niestety, nie ma sposobu, aby jawnie kontrolować liczbę buforów tylnych używanych przez większość interfejsów API systemu okien GL (oprócz 0 jedno-buforowych lub 1 podwójnie buforowanych). Sterownik może swobodnie korzystać z 2 tylnych buforów, jeśli chce (potrójne buforowanie), ale nie można tego żądać na poziomie aplikacji przy użyciu czegoś takiego jak GLX lub WGL.

Andon M. Coleman
źródło
Andon: Dobra informacja, dziękuję. Myślę, że to, co widzę, to częściowo podwójne buforowanie, ale: „próba modyfikacji bufora tylnego przed wystąpieniem zamiany musi zostać zablokowana” - dlaczego? wygląda na to, że wszystko może zostać wysłane do bufora poleceń, łącznie z wymianą :)
Alex I
„jawnie kontroluje liczbę tylnych buforów używanych przez większość interfejsów API systemu okien GL (oprócz 0 jedno-buforowanych lub 1 podwójnie buforowanych)” - w jaki sposób jeden kontroluje bufor pojedynczy / podwójny?
Alex I
@AlexI: Wyjaśniłem język w odniesieniu do tego, dlaczego polecenie może prowadzić do blokowania. W innych interfejsach API istnieje koncepcja zwana renderowaniem z wyprzedzeniem, która jest efektywną głębokością kolejki poleceń. Jeśli chodzi o sterowanie pojedynczym / podwójnym buforowaniem, to jest kontrolowane przez format pikseli wybrany podczas tworzenia kontekstu renderowania. W WGL możesz wybrać pojedynczy lub podwójny bufor, ale jeśli wybierzesz podwójny bufor, sterownik może faktycznie utworzyć 2 bufory tylne (w ten sposób podwójne buforowanie staje się potrójnym buforowaniem), jeśli użytkownik skonfiguruje to w swoim sterowniku.
Andon M. Coleman,
W rzeczywistości nie chcesz renderować zbyt wielu klatek z przodu, ponieważ chociaż zmniejszy to blokowanie, zwiększy również opóźnienie. Najlepszym sposobem na zminimalizowanie opóźnień jest wywołanie glFinish (...)natychmiast po zamianie buforów. Spowoduje to wyczyszczenie kolejki poleceń, ale oznacza również, że procesor graficzny i procesor zostaną zsynchronizowane (co nie jest dobre, jeśli chcesz, aby procesor graficzny cały czas działał).
Andon M. Coleman
1

Zakładam, że znasz ten eksperyment ?

Zasadniczo John Carmack robił coś podobnego, rejestrując ekran i wysyłając na ekran piksele synchronizacji. Odkrył, że znaczna część opóźnień pochodzi z ekranu. Innymi czynnikami były opóźnienie wejściowe z klawiatury, sterowników wideo i / lub kurs wykonania samego programu.

MichaelHouse
źródło