Ramy synchronizacji

Struktura synchronizacji jawnie opisuje zależności między różnymi operacjami asynchronicznymi w systemie graficznym Android. Platforma udostępnia interfejs API, który umożliwia składnikom wskazywanie, kiedy bufory są zwalniane. Struktura umożliwia również przekazywanie prymitywów synchronizacji między sterownikami z jądra do przestrzeni użytkownika oraz między samymi procesami w przestrzeni użytkownika.

Na przykład aplikacja może kolejkować pracę do wykonania w GPU. GPU zaczyna rysować ten obraz. Chociaż obraz nie został jeszcze narysowany do pamięci, wskaźnik bufora jest przekazywany do kompozytora okien wraz z ramką, która wskazuje, kiedy zakończy się praca GPU. Kompozytor okien rozpoczyna przetwarzanie z wyprzedzeniem i przekazuje pracę do sterownika wyświetlania. W podobny sposób praca procesora jest wykonywana z wyprzedzeniem. Po zakończeniu działania GPU kontroler wyświetlania natychmiast wyświetla obraz.

Ramy synchronizacji umożliwiają również realizatorom wykorzystanie zasobów synchronizacji w ich własnych komponentach sprzętowych. Wreszcie platforma zapewnia wgląd w potok graficzny, aby pomóc w debugowaniu.

Wyraźna synchronizacja

Jawna synchronizacja umożliwia producentom i konsumentom buforów graficznych sygnalizowanie zakończenia używania bufora. Jawna synchronizacja jest zaimplementowana w przestrzeni jądra.

Korzyści z jawnej synchronizacji obejmują:

  • Mniejsze różnice w zachowaniu między urządzeniami
  • Lepsza obsługa debugowania
  • Ulepszone metryki testowania

Framework synchronizacji ma trzy typy obiektów:

  • sync_timeline
  • sync_pt
  • sync_fence

oś czasu_synchronizacji

sync_timeline to monotonicznie rosnąca oś czasu, którą dostawcy powinni zaimplementować dla każdego wystąpienia sterownika, takiego jak kontekst GL, kontroler wyświetlania lub blitter 2D. sync_timeline zlicza zadania przesłane do jądra dla określonego sprzętu. sync_timeline zapewnia gwarancje dotyczące kolejności operacji i umożliwia implementacje specyficzne dla sprzętu.

Podczas wdrażania sync_timeline postępuj zgodnie z tymi wskazówkami:

  • Podaj przydatne nazwy dla wszystkich sterowników, osi czasu i ogrodzeń, aby uprościć debugowanie.
  • Zaimplementuj operatory timeline_value_str i pt_value_str na osi czasu, aby dane wyjściowe debugowania były bardziej czytelne.
  • Zaimplementuj parametr fill driver_data , aby w razie potrzeby zapewnić bibliotekom przestrzeni użytkownika, takim jak biblioteka GL, dostęp do prywatnych danych osi czasu. data_driver umożliwia dostawcom przekazywanie informacji o niezmiennych sync_fence i sync_pts w celu tworzenia na ich podstawie wierszy poleceń.
  • Nie zezwalaj przestrzeni użytkownika na jawne tworzenie lub sygnalizowanie ogrodzenia. Jawne tworzenie sygnałów/ogrodzeń skutkuje atakiem typu „odmowa usługi”, który zatrzymuje działanie potoku.
  • Nie uzyskuj jawnego dostępu do sync_timeline , sync_pt ani sync_fence . API zapewnia wszystkie wymagane funkcje.

sync_pt

sync_pt to pojedyncza wartość lub punkt na sync_timeline . Punkt ma trzy stany: aktywny, zasygnalizowany i błąd. Punkty zaczynają się w stanie aktywnym i przechodzą do stanów sygnalizowanych lub błędów. Na przykład, gdy odbiorca obrazu nie potrzebuje już bufora, sygnalizowany jest sync_pt , aby producent obrazu wiedział, że można ponownie zapisywać do bufora.

sync_fence

sync_fence to zbiór wartości sync_pt , które często mają różnych rodziców sync_timeline (takich jak kontroler wyświetlania i GPU). sync_fence , sync_pt i sync_timeline to główne operacje podstawowe używane przez sterowniki i przestrzeń użytkownika do komunikowania swoich zależności. Gdy zostanie zasygnalizowane ogrodzenie, wszystkie polecenia wydane przed ogrodzeniem są gwarantowane jako kompletne, ponieważ sterownik jądra lub blok sprzętowy wykonuje polecenia w kolejności.

Struktura synchronizacji umożliwia wielu konsumentom lub producentom sygnalizowanie, kiedy skończą, przy użyciu bufora, przekazując informacje o zależnościach za pomocą jednego parametru funkcji. Ogrodzenia są wspierane przez deskryptor pliku i są przekazywane z przestrzeni jądra do przestrzeni użytkownika. Na przykład ogrodzenie może zawierać dwie wartości sync_pt , które oznaczają, że dwa oddzielne odbiory obrazu kończą odczytywanie bufora. Gdy ogrodzenie jest sygnalizowane, producenci obrazu wiedzą, że obaj konsumenci są skończeni.

Ogrodzenia, takie jak wartości sync_pt , zaczynają być aktywne i zmieniają stan w zależności od stanu ich punktów. Jeśli wszystkie wartości sync_pt zostaną zasygnalizowane, zostanie zasygnalizowana funkcja sync_fence . Jeśli jeden sync_pt wpadnie w stan błędu, całe sync_fence ma stan błędu.

Członkostwo w sync_fence jest niezmienne po utworzeniu ogrodzenia. Aby uzyskać więcej niż jeden punkt w ogrodzeniu, przeprowadza się scalanie, w którym punkty z dwóch różnych ogrodzeń są dodawane do trzeciego ogrodzenia. Jeśli jeden z tych punktów został zasygnalizowany w ogrodzeniu inicjującym, a drugi nie, trzecie ogrodzenie również nie będzie w stanie zasygnalizowanym.

Aby zaimplementować jawną synchronizację, podaj następujące informacje:

  • Podsystem przestrzeni jądra, który implementuje strukturę synchronizacji dla określonego sterownika sprzętowego. Sterowniki, które muszą być ostrożne, to zazwyczaj wszystko, co uzyskuje dostęp do Hardware Composer lub komunikuje się z nim. Kluczowe pliki obejmują:
    • Podstawowe wdrożenie:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Dokumentacja w kernel/common/Documentation/sync.txt
    • Biblioteka do komunikacji z przestrzenią jądra w platform/system/core/libsync
  • Dostawca musi zapewnić odpowiednie bariery synchronizacji jako parametry funkcji validateDisplay() i presentDisplay() w warstwie HAL.
  • Dwa rozszerzenia GL dotyczące ogrodzenia ( EGL_ANDROID_native_fence_sync i EGL_ANDROID_wait_sync ) oraz obsługa ogrodzenia w sterowniku graficznym.

Studium przypadku: Implementacja sterownika ekranu

Aby użyć interfejsu API obsługującego funkcję synchronizacji, opracuj sterownik ekranu, który ma funkcję bufora wyświetlania. Zanim istniał szkielet synchronizacji, ta funkcja otrzymywała obiekty dma-buf , umieszczała te bufory na ekranie 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 ramach synchronizacji funkcja display_buffer jest bardziej złożona. Umieszczając bufor na wyświetlaczu, bufor jest powiązany z ogrodzeniem, które wskazuje, kiedy bufor będzie gotowy. Możesz ustawić się w kolejce i rozpocząć pracę po oczyszczeniu ogrodzenia.

Kolejkowanie i inicjowanie pracy po oczyszczeniu ogrodzenia niczego nie blokuje. Natychmiast zwracasz własne ogrodzenie, co gwarantuje, że bufor zniknie z wyświetlacza. Podczas kolejkowania buforów jądro wyświetla zależności z ramą 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);

Synchronizacja integracji

W tej sekcji wyjaśniono, jak zintegrować strukturę synchronizacji przestrzeni jądra z częściami przestrzeni użytkownika struktury systemu Android i sterownikami, które muszą się ze sobą komunikować. Obiekty przestrzeni jądra są reprezentowane jako deskryptory plików w przestrzeni użytkownika.

Konwencje integracyjne

Postępuj zgodnie z konwencjami interfejsu Android HAL:

  • Jeśli interfejs API udostępnia deskryptor pliku, który odwołuje się do sync_pt , sterownik dostawcy lub warstwa HAL korzystająca z interfejsu API musi zamknąć deskryptor pliku.
  • Jeśli sterownik dostawcy lub warstwa HAL przekażą deskryptor pliku zawierający sync_pt do funkcji interfejsu API, sterownik dostawcy lub warstwa HAL nie mogą zamykać deskryptora pliku.
  • Aby nadal używać deskryptora pliku ogrodzenia, sterownik dostawcy lub warstwa HAL musi zduplikować deskryptor.

Nazwa obiektu ogrodzenia jest zmieniana za każdym razem, gdy przechodzi przez BufferQueue. Obsługa ogrodzeń jądra umożliwia ogrodzeniu posiadanie ciągów nazw, więc platforma synchronizacji używa nazwy okna i indeksu buforu, który jest umieszczany w kolejce, aby nazwać ogrodzenie, takie jak SurfaceView:0 . Jest to pomocne podczas debugowania w celu zidentyfikowania źródła zakleszczenia, ponieważ nazwy pojawiają się w danych wyjściowych /d/sync i raportach o błędach.

Integracja z ANativeWindow

ANativeWindow jest świadomy ogrodzenia. dequeueBuffer , queueBuffer i cancelBuffer mają parametry ogrodzenia.

Integracja OpenGL ES

Integracja OpenGL ES sync opiera się na dwóch rozszerzeniach EGL:

  • EGL_ANDROID_native_fence_sync umożliwia zawijanie lub tworzenie natywnych deskryptorów plików ogrodzenia systemu Android w obiektach EGLSyncKHR .
  • EGL_ANDROID_wait_sync umożliwia przestoje po stronie GPU, a nie po stronie CPU, przez co GPU czeka na EGLSyncKHR . Rozszerzenie EGL_ANDROID_wait_sync jest takie samo jak rozszerzenie EGL_KHR_wait_sync .

Aby używać tych rozszerzeń niezależnie, zaimplementuj rozszerzenie EGL_ANDROID_native_fence_sync wraz z powiązaną obsługą jądra. Następnie włącz rozszerzenie EGL_ANDROID_wait_sync w sterowniku. Rozszerzenie EGL_ANDROID_native_fence_sync składa się z odrębnego typu obiektu ogrodzenia natywnego EGLSyncKHR . W rezultacie rozszerzenia, które mają zastosowanie do istniejących typów obiektów EGLSyncKHR , niekoniecznie dotyczą obiektów EGL_ANDROID_native_fence , co pozwala uniknąć niepożądanych interakcji.

Rozszerzenie EGL_ANDROID_native_fence_sync wykorzystuje odpowiedni natywny atrybut deskryptora pliku ogrodzenia, który można ustawić tylko w czasie tworzenia i nie można go odpytywać bezpośrednio z istniejącego obiektu synchronizacji. Ten atrybut można ustawić w jednym z dwóch trybów:

  • Prawidłowy deskryptor pliku ogrodzenia otacza istniejący natywny deskryptor pliku ogrodzenia systemu Android w obiekcie EGLSyncKHR .
  • -1 tworzy natywny deskryptor pliku ogrodzenia systemu Android na podstawie obiektu EGLSyncKHR .

Użyj wywołania funkcji DupNativeFenceFD() , aby wyodrębnić obiekt EGLSyncKHR z natywnego deskryptora pliku ogrodzenia systemu Android. Daje to ten sam wynik, co zapytanie o atrybut set, ale jest zgodne z konwencją, że odbiorca zamyka ogrodzenie (stąd zduplikowana operacja). Na koniec zniszczenie obiektu EGLSyncKHR zamyka atrybut ogrodzenia wewnętrznego.

Integracja Hardware Composer

Hardware Composer obsługuje trzy typy barier synchronizacji:

  • Ogrodzenia akwizycji są przekazywane wraz z buforami wejściowymi do setLayerBuffer i setClientTarget . Reprezentują one oczekujący zapis do bufora i muszą sygnalizować, zanim SurfaceFlinger lub HWC spróbuje odczytać z powiązanego bufora w celu wykonania kompozycji.
  • Granice zwolnienia są pobierane po wywołaniu presentDisplay przy użyciu wywołania getReleaseFences . Reprezentują one oczekujący odczyt z poprzedniego bufora na tej samej warstwie. Granica zwolnienia sygnalizuje, że HWC nie używa już poprzedniego bufora, ponieważ bieżący bufor zastąpił poprzedni bufor na wyświetlaczu. Ogrodzenia wydania są przekazywane z powrotem do aplikacji wraz z poprzednimi buforami, które zostaną zastąpione podczas bieżącej kompozycji. Aplikacja musi poczekać na sygnał ograniczenia zwolnienia przed zapisaniem nowej zawartości w buforze, który został do niej zwrócony.
  • Obecne ogrodzenia są zwracane, po jednym na klatkę, w ramach wywołania presentDisplay . Obecne ogrodzenia oznaczają, kiedy kompozycja tej klatki została zakończona, lub alternatywnie, kiedy wynik kompozycji poprzedniej klatki nie jest już potrzebny. W przypadku wyświetlaczy fizycznych presentDisplay zwraca obecne ogrodzenia, gdy bieżąca ramka pojawia się na ekranie. Po zwróceniu obecnych ogrodzeń można bezpiecznie ponownie zapisać w buforze docelowym SurfaceFlinger, jeśli ma to zastosowanie. W przypadku wyświetlaczy wirtualnych obecne ogrodzenia są zwracane, gdy można bezpiecznie odczytać z bufora wyjściowego.