Ramy synchronizacji

Platforma synchronizacji wyraźnie opisuje zależności między różnymi operacjami asynchronicznymi w systemie graficznym Androida. Framework udostępnia interfejs API, który umożliwia komponentom wskazywanie, kiedy bufory są zwalniane. Platforma zapewnia również umożliwia przekazywanie podstawowych elementów synchronizacji między sterownikami w jądrze do przestrzeni użytkownika i między procesami w tej przestrzeni.

Aplikacja może na przykład ustawić w kolejce zadania do wykonania na karcie graficznej. GPU zaczyna rysować obraz. Chociaż obraz nie został jeszcze narysowany w pamięci, wskaźnik bufora jest przekazywany do kompozytora okna wraz z ograniczeniem, które wskazuje, kiedy zakończy się praca na karcie graficznej. Okno kompozytorski rozpoczyna przetwarzanie z wyprzedzeniem i przekazuje zadanie do kontrolera wyświetlania. W podobny sposób procesor trzeba wykonać wcześniej. Gdy GPU zakończy działanie, kontroler wyświetlacza natychmiast wyświetli obraz.

Platforma synchronizacji umożliwia też implementatorom korzystanie z zasobów synchronizacji w ich własnych komponentach sprzętowych. Na koniec framework zapewnia widoczność potoku graficznego, co ułatwia debugowanie.

Wyraźna synchronizacja

Wyraźna synchronizacja umożliwia producentom i odbiorcom buforów graficznych sygnalizowanie, kiedy skończyli korzystać z bufora. Wyraźna synchronizacja jest implementowana w przestrzeni jądra.

Zalety bezpośredniej synchronizacji:

  • Mniejsza zmienność zachowania na różnych urządzeniach
  • Lepsza obsługa debugowania
  • Ulepszone dane testowe

Platforma synchronizacji ma 3 typy obiektów:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline to monotonicznie rosnąca oś czasu, dostawcy powinni wdrożyć dla każdej instancji sterownika, np. kontekst GL, lub np. kontroler wyświetlacza 2D. sync_timeline zlicza zadania przesłane do jądra dla konkretnego sprzętu. sync_timeline zapewnia gwarancje dotyczące kolejności operacji i umożliwia implementacje związane z konkretnym sprzętem.

Podczas implementacji sync_timeline przestrzegaj tych wskazówek:

  • Aby uprościć, podaj przydatne nazwy wszystkich kierowców, osi czasu i płodów i debugowaniu.
  • Aby ułatwić odczytywanie danych debugowania, stosuj w czasoprzestrzeniach operatory timeline_value_strpt_value_str.
  • W razie potrzeby zastosuj funkcję fill driver_data, aby nadać bibliotekom w przestrzeni użytkownika, takim jak biblioteka GL, dostęp do prywatnych danych z osi czasu. data_driver pozwala dostawcom przekazywać informacje o niezmiennych sync_fencesync_pts, aby tworzyć na ich podstawie wiersze poleceń.
  • Nie zezwalaj userspace na jawne tworzenie lub sygnalizowanie ogrodzenia. Wyraźnie tworzenie sygnałów/płodów skutkuje atakiem typu DoS, wstrzymuje działanie potoku.
  • Nie uzyskuj dostępu do usług sync_timeline, sync_pt lub sync_fence. Interfejs API udostępnia wszystkie wymagane funkcje.

sync_pt

sync_pt to pojedyncza wartość lub punkt na sync_timeline Punkt ma 3 stany: aktywny, sygnalizowany i błąd. Punkty zaczynają się w stanie aktywnym i przechodzą do stanu sygnalizowania lub błędu. Jeśli na przykład obraz konsument nie potrzebuje już bufora, pojawia się sygnał sync_pt dzięki czemu producent obrazów wie, że może znowu zapisać go w buforze.

granica_synchronizacji

sync_fence to zbiór wartości sync_pt często mają różne elementy nadrzędne sync_timeline (np. dla wyświetlacza kontroler i GPU). sync_fence, sync_pt i sync_timeline to główne podstawowe elementy, których sterowniki i przestrzeń użytkownika których używają do informowania o zależności. Gdy pojawi się sygnał, polecenia wydane przed ogrodzeniem są zapewne ukończone, ponieważ sterownik jądra lub blok sprzętowy wykonuje polecenia po kolei.

Ramka synchronizacji umożliwia wielu konsumentom i producentom sygnalizowanie, że skończyli korzystać z bufora, przekazując informacje o zależnościach za pomocą jednego parametru funkcji. Ogrodzenia są wspierane przez deskryptor pliku i przekazywane z z jednego jądra systemu operacyjnego. Na przykład bariery mogą zawierać 2 wartości sync_pt, które wskazują, kiedy 2 oddzielne elementy korzystające z obrazu skończyły odczyt bufora. Gdy ogrodzenie jest sygnalizowane, producenci obrazu wiedzą, przez konsumentów.

Ogrodzenia, takie jak wartości sync_pt, zaczynają być aktywne i zmieniają stan w zależności od tego, na stan ich punktów. Jeśli wszystkie wartości sync_pt zostaną zasygnalizowane, sync_fence otrzymuje sygnał. Jeśli jeden sync_pt spadnie w stan błędu, cały element sync_fence zawiera stan błędu.

Po utworzeniu ogrodzenia członkostwo w sync_fence jest nieodwracalne. Aby uzyskać więcej niż jeden punkt ogrodzenia, scalanie polega na tym, że do trzeciego ogrodzenia dodaje się punkty z dwóch różnych płotów. Jeśli jeden z tych punktów został zgłoszony w pierwszym ogrodzeniu, a drugi nie, trzeci ogród również nie będzie zgłoszony.

Aby wdrożyć jawną synchronizację, podaj te informacje:

  • Podsystem w przestrzeni jądra, który wdraża mechanizm synchronizacji dla konkretnego sterownika sprzętowego. Kierowcy, którzy muszą mieć świadomość płotu, wszystko, co uzyskuje dostęp do narzędzia Hardware Composer lub się z nim komunikuje. Najważniejsze pliki to:
    • Implementacja podstawowa:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Dokumentacja: kernel/common/Documentation/sync.txt
    • Biblioteka do komunikacji z przestrzenią jądra w platform/system/core/libsync
  • Dostawca musi podać odpowiednie bariery synchronizacji jako parametry funkcji validateDisplay()presentDisplay() w interfejsie HAL.
  • 2 rozszerzenia GL związane z fence (EGL_ANDROID_native_fence_syncEGL_ANDROID_wait_sync) oraz obsługa fence w sterowniku graficznym.

Studium przypadku: wdrażanie sterowników dla reklam displayowych

Aby używać interfejsu API obsługującego funkcję synchronizacji, opracuj sterownik wyświetlacza, który ma funkcję bufora wyświetlacza. Zanim istniała platforma synchronizacji, ta funkcja otrzymywała obiekty dma-buf, umieszczała je na wyświetlaczu i blokowała, gdy bufor był widoczny. Na przykład:

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

W przypadku platformy synchronizacji funkcja display_buffer jest bardziej złożona. Po umieszczeniu bufora na wyświetlaczu zostaje on powiązany ogrodzeniem wskazującym, kiedy bufor będzie gotowy. Możesz ustawić zadanie w kolejce i zainicjować jego wykonywanie, gdy zniknie blokada.

Dodanie zadania do kolejki i jego uruchomienie po usunięciu bariery nie powoduje blokowania niczego. Od razu zwracasz własną siatkę, co gwarantuje, że bufor nie będzie widoczny na ekranie. Gdy umieszczasz bufory w kolejce, jądro wymienia zależności z ramami synchronizacji:

/*
 * displays buffer when fence is signaled.  returns immediately with a fence
 * that signals when buffer is no longer displayed.
 */
struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence
*fence);

Integracja z synchronizacją

W tej sekcji opisano, jak zintegrować framework synchronizacji w przestrzeni jądra z częściami frameworka Androida w przestrzeni użytkownika i sterownikami, które muszą się ze sobą komunikować. Obiekty przestrzeni jądra są reprezentowane jako deskryptory plików w przestrzeń użytkownika.

Konwencje integracji

Stosuj konwencje interfejsu HAL Androida:

  • Jeśli interfejs API udostępnia deskryptor pliku odnoszący się do elementu sync_pt, sterownik dostawcy lub kod HAL korzystający z interfejsu API musi zamykać deskryptor pliku.
  • Jeśli sterownik dostawcy lub HAL przekazuje do funkcji interfejsu API deskryptor pliku zawierający sync_pt, sterownik dostawcy lub HAL nie może zamykać tego deskryptora.
  • Aby nadal używać deskryptora pliku ogrodzenia, sterownik dostawcy lub HAL musi go powielić.

Nazwa obiektu ogrodzenia zmienia się za każdym razem, gdy przechodzi przez BufferQueue. Obsługa ogrodzenia jądra umożliwia nadawanie ogrodzeniom nazw w postaci ciągów znaków, więc framework synchronizacji używa nazwy okna i indeksu bufora, które są umieszczane w kole, aby nazwać ogrodzenie, np. SurfaceView:0. Ten jest pomocne w debugowaniu w celu identyfikacji źródła zakleszczeń w postaci pojawiających się nazw w danych wyjściowych /d/sync i raportach o błędach.

Integracja ANativeWindow

Okno typu ANativeWindow jest świadome ogrodzenia. Parametry bariery: dequeueBuffer, queueBuffercancelBuffer.

Integracja z OpenGL ES

Synchronizacja OpenGL ES opiera się na 2 rozszerzeniach EGL:

  • EGL_ANDROID_native_fence_sync umożliwia tworzenie lub owijanie natywnych opisów pliku ogrodzenia Androida w obiektach EGLSyncKHR.
  • EGL_ANDROID_wait_sync pozwala na blokowanie GPU, a nie procesora, co powoduje oczekiwanie GPU na EGLSyncKHR. Rozszerzenie EGL_ANDROID_wait_sync jest takie samo jak rozszerzenie EGL_KHR_wait_sync.

Aby korzystać z tych rozszerzeń niezależnie, zaimplementuj tag EGL_ANDROID_native_fence_sync rozszerzenie i powiązane i obsługą jądra systemu operacyjnego. Następnie włącz: EGL_ANDROID_wait_sync w sterowniku. Rozszerzenie EGL_ANDROID_native_fence_sync składa się z osobnego natywnego obiektu ogrodzenia EGLSyncKHR. W rezultacie rozszerzenia, które mają zastosowanie do istniejących typów obiektów EGLSyncKHR, nie muszą mieć zastosowania do obiektów EGL_ANDROID_native_fence, co zapobiega niepożądanym interakcjom.

Rozszerzenie EGL_ANDROID_native_fence_sync używa odpowiedniego natywnego atrybutu pliku opisu ogrodzenia, który można ustawić tylko w momencie tworzenia. Nie można go zapytać bezpośrednio z istniejącego obiektu synchronizacji. Ten atrybut można ustawić na jeden z dwóch trybów:

  • Prawidłowy deskryptor pliku ogrodzenia zawiera w obiekcie EGLSyncKHR istniejący natywny deskryptor pliku ogrodzenia Androida.
  • -1 tworzy natywny plik deskryptora ogrodzenia na Androida na podstawie obiektu EGLSyncKHR.

Użyj wywołania funkcji DupNativeFenceFD(), aby wyodrębnić parametr Obiekt EGLSyncKHR z deskryptora natywnego pliku Android fence. Daje to ten sam wynik co zapytanie dotyczące atrybutu zbioru, ale jest zgodne z konwencją, że odbiorcy zamykają nawias (stąd duplikacja operacji). Na koniec zniszczenie obiektu EGLSyncKHR powoduje zamknięcie atrybutu wewnętrznego ogrodzenia.

Integracja z Composerem

Narzędzie do konfiguracji sprzętu obsługuje 3 typy ogrodzeń synchronizacji:

  • Zdobywanie progów jest przekazywane wraz z buforami wejściowymi do połączenia setLayerBuffer i setClientTarget. Reprezentują one oczekujące zapisanie do bufora i muszą być sygnalizowane, zanim SurfaceFlinger lub HWC spróbuje odczytać z powiązanego bufora, aby wykonać kompozycję.
  • Ogrodzenia zwalniające są pobierane po wywołaniu presentDisplay za pomocą wywołania getReleaseFences. Reprezentują one oczekujący odczyt z poprzedniego bufora w tej samej warstwie. A sygnały dotyczące ogrodzenia sygnału zwalniającego, gdy HWC nie używa już poprzedniego bufora, ponieważ bieżący bufor zastąpił poprzedni bufor na wyświetlaczu. Blokady wersji są przekazywane z powrotem do aplikacji razem z poprzednimi buforami, zostaną zastąpione w bieżącej kompozycji. Aplikacja musi poczekać do rozluźnić granicę przed zapisaniem w buforze nowej zawartości, które zostały im zwrócone.
  • Obecne ogrodzenia są zwracane w ramach wywołania funkcji presentDisplay, po jednym na każdy kadr. Prezentuj ogrodzenia, pokazując, kiedy czy kompozycja tej klatki zakończy się lub na przemian, gdy nie jest już potrzebny wynik kompozycji poprzedniej klatki. W przypadku fizycznych wyświetlaczy funkcja presentDisplay zwraca obecne ogrodzenia, gdy bieżąca klatka jest wyświetlana na ekranie. Po zwróceniu obecnych ograniczeń można ponownie zapisać dane w buforze docelowym SurfaceFlinger (jeśli to konieczne). W przypadku wyświetlaczy wirtualnych zwracane są aktualne zasięgi, gdy można bezpiecznie odczytać dane z bufora wyjściowego.