Ramy synchronizacji

Framework synchronizacji wyraźnie opisuje zależności między różnymi operacjami asynchronicznymi w systemie graficznym Androida. Platforma udostępnia interfejs API, który umożliwia komponentom wskazywanie, kiedy bufory są zwalniane. Platforma umożliwia też przekazywanie elementó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ć zadania do wykonania na procesorze graficznym. Procesor graficzny zaczyna rysować ten obraz. Chociaż obraz nie został jeszcze narysowany w pamięci, wskaźnik bufora jest przekazywany do kompozytora okien wraz z barierką, która wskazuje, kiedy praca GPU zostanie zakończona. Kompozytor okien rozpoczyna przetwarzanie z wyprzedzeniem i przekazuje pracę do kontrolera wyświetlacza. Podobnie praca procesora jest wykonywana z wyprzedzeniem. Gdy procesor graficzny zakończy pracę, kontroler wyświetlacza natychmiast wyświetli obraz.

Platforma synchronizacji umożliwia też osobom wdrażającym korzystanie z zasobów synchronizacji we własnych komponentach sprzętowych. Na koniec platforma zapewnia wgląd w potok graficzny, co ułatwia debugowanie.

Jawna synchronizacja

Jawna synchronizacja umożliwia producentom i konsumentom buforów graficznych sygnalizowanie, kiedy skończą korzystać z bufora. Synchronizacja jawna jest implementowana w przestrzeni jądra.

Zalety jawnej synchronizacji:

  • Mniejsze różnice w działaniu na różnych urządzeniach
  • Lepsza obsługa debugowania
  • Ulepszone dane testów

Platforma synchronizacji ma 3 typy obiektów:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline to rosnąca monotonicznie oś czasu, którą dostawcy powinni wdrożyć w przypadku każdej instancji sterownika, np. kontekstu GL, kontrolera wyświetlania lub blittera 2D. sync_timeline Liczba zadań przesłanych do jądra dla konkretnego elementu sprzętu. sync_timeline zapewnia kolejność operacji i umożliwia implementacje specyficzne dla sprzętu.

Podczas implementacji sync_timeline postępuj zgodnie z tymi wytycznymi:

  • Nadaj wszystkim sterownikom, osiom czasu i obszarom przydatne nazwy, aby uprościć debugowanie.
  • Wprowadź operatory timeline_value_strpt_value_str na osiach czasu, aby zwiększyć czytelność danych wyjściowych debugowania.
  • Wdróż wypełnienie driver_data, aby w razie potrzeby umożliwić 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_fencesync_pts w celu tworzenia na ich podstawie wierszy poleceń.
  • Nie zezwalaj przestrzeni użytkownika na jawne tworzenie ani sygnalizowanie bariery. Jawne tworzenie sygnałów lub ogrodzeń powoduje atak typu DoS, który zatrzymuje działanie potoku.
  • Nie uzyskuj dostępu do elementów sync_timeline, sync_pt ani sync_fence w sposób jawny. Interfejs API udostępnia wszystkie wymagane funkcje.

sync_pt

sync_pt to pojedyncza wartość lub punkt na sync_timeline. Punkt może mieć 3 stany: aktywny, sygnalizowany i błąd. Punkty zaczynają się w stanie aktywnym i przechodzą do stanu sygnalizowanego lub stanu błędu. Na przykład, gdy odbiorca obrazu nie potrzebuje już bufora, wysyłany jest sygnał sync_pt, aby producent obrazu wiedział, że może ponownie zapisywać dane w buforze.

sync_fence

sync_fence to zbiór sync_pt wartości, które często mają różnych sync_timeline rodziców (np. w przypadku kontrolera wyświetlacza i procesora graficznego). sync_fence, sync_pt i sync_timeline to główne elementy, których sterowniki i przestrzeń użytkownika używają do komunikowania swoich zależności. Gdy bariera zostanie zasygnalizowana, wszystkie polecenia wydane przed nią zostaną wykonane, ponieważ sterownik jądra lub blok sprzętowy wykonuje polecenia w kolejności.

Platforma synchronizacji umożliwia wielu konsumentom lub producentom sygnalizowanie, kiedy skończyli korzystać z bufora, przekazując informacje o zależnościach za pomocą jednego parametru funkcji. Ogrodzenia są obsługiwane przez deskryptor pliku i przekazywane z przestrzeni jądra do przestrzeni użytkownika. Na przykład bariera może zawierać 2 wartości sync_pt, które oznaczają, że 2 osobne aplikacje korzystające z obrazów zakończyły odczytywanie bufora. Gdy zostanie wysłany sygnał o zakończeniu, producenci obrazów wiedzą, że obaj konsumenci zakończyli korzystanie z usługi.

Ogrodzenia, podobnie jak wartości sync_pt, są początkowo aktywne i zmieniają stan w zależności od stanu punktów. Jeśli wszystkie wartości sync_pt zostaną oznaczone, sync_fence zostanie oznaczone. Jeśli jeden z elementów sync_pt przejdzie w stan błędu, cały element sync_fence przejdzie w stan błędu.

Członkostwo w sync_fence jest niezmienne po utworzeniu obszaru. Aby uzyskać więcej niż 1 punkt w ogrodzeniu, przeprowadza się scalanie, w ramach którego punkty z 2 różnych ogrodzeń są dodawane do trzeciego ogrodzenia. Jeśli jeden z tych punktów został zasygnalizowany w pierwotnym obszarze, a drugi nie, to trzeci obszar również nie będzie w stanie zasygnalizowanym.

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

  • Podsystem przestrzeni jądra, który implementuje platformę synchronizacji dla konkretnego sterownika sprzętu. Sterowniki, które muszą być świadome bariery, to zwykle wszystkie sterowniki, które mają dostęp do kompozytora sprzętowego (HWC) lub się z nim komunikują. Najważniejsze pliki to:
    • Podstawowa implementacja:
      • 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 warstwie abstrakcji sprzętowej (HAL).
  • 2 rozszerzenia GL związane z barierkami (EGL_ANDROID_native_fence_syncEGL_ANDROID_wait_sync) oraz obsługa barierek w sterowniku graficznym.

Studium przypadku: wdrażanie sterownika wyświetlacza

Aby korzystać z interfejsu API obsługującego funkcję synchronizacji, opracuj sterownik wyświetlacza z funkcją bufora wyświetlania. Zanim powstała platforma synchronizacji, ta funkcja odbierała dma-bufobiekty, umieszczała te bufory na wyświetlaczu i blokowała się, gdy bufor był widoczny. 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. Podczas wyświetlania bufora jest on powiązany z barierką, która wskazuje, kiedy bufor będzie gotowy. Możesz ustawić w kolejce i rozpocząć pracę po usunięciu ograniczenia.

Kolejkowanie i inicjowanie pracy po usunięciu bariery nie blokuje niczego. Natychmiast zwracasz własny płot, który sygnalizuje, kiedy bufor przestanie być wyświetlany. Podczas kolejkowania buforów 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 synchronizacji

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

Konwencje integracji

Postępuj zgodnie z konwencjami interfejsu HAL Androida:

  • Jeśli interfejs API udostępnia deskryptor pliku, który odnosi się do sync_pt, sterownik dostawcy lub HAL korzystający z interfejsu API musi zamknąć deskryptor pliku.
  • Jeśli sterownik dostawcy lub HAL przekazuje do funkcji API deskryptor pliku zawierający sync_pt, sterownik dostawcy lub HAL nie może zamknąć tego deskryptora.
  • Aby nadal używać deskryptora pliku bariery, sterownik dostawcy lub HAL musi go zduplikować.

Obiekt bariery jest zmieniany za każdym razem, gdy przechodzi przez BufferQueue. Obsługa bariery jądra umożliwia stosowanie ciągów znaków jako nazw barier, więc platforma synchronizacji używa nazwy okna i indeksu bufora, które są umieszczane w kolejce, do nazwania bariery, np. SurfaceView:0. Jest to przydatne podczas debugowania, ponieważ nazwy pojawiają się w danych wyjściowych polecenia /d/sync i w raportach o błędach.

Integracja z ANativeWindow

ANativeWindow obsługuje bariery. dequeueBuffer, queueBuffercancelBuffer mają parametry obszaru.

Integracja OpenGL ES

Integracja synchronizacji OpenGL ES opiera się na 2 rozszerzeniach EGL:

  • EGL_ANDROID_native_fence_sync umożliwia tworzenie lub opakowywanie natywnych deskryptorów plików bariery Androida w obiektach EGLSyncKHR.
  • EGL_ANDROID_wait_sync umożliwia wstrzymanie po stronie GPU, a nie po stronie CPU, dzięki czemu 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 EGL_ANDROID_wait_sync rozszerzenie w sterowniku. Rozszerzenie EGL_ANDROID_native_fence_sync składa się z odrębnego typu obiektu natywnego ogrodzenia EGLSyncKHR. Dzięki temu 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 pozwala uniknąć niepożądanych interakcji.

Rozszerzenie EGL_ANDROID_native_fence_sync wykorzystuje odpowiedni atrybut deskryptora pliku natywnego, który można ustawić tylko w momencie tworzenia i którego nie można bezpośrednio odpytywać z istniejącego obiektu synchronizacji. Ten atrybut może mieć jeden z 2 trybów:

  • Prawidłowy deskryptor pliku bariery otacza istniejący natywny deskryptor pliku bariery Androida w obiekcie EGLSyncKHR.
  • -1 tworzy natywny deskryptor pliku bariery Androida z obiektu EGLSyncKHR.

Użyj wywołania funkcji DupNativeFenceFD(), aby wyodrębnić obiekt EGLSyncKHR z natywnego deskryptora pliku bariery Androida. Daje to taki sam efekt jak wysłanie zapytania o atrybut set, ale jest zgodne z konwencją, że odbiorca zamyka ogrodzenie (stąd zduplikowana operacja). Na koniec zniszczenie obiektu EGLSyncKHR zamyka wewnętrzny atrybut ogrodzenia.

Integracja z kompozytorem sprzętowym

HWC obsługuje 3 typy barier synchronizacji:

  • Acquire fences są przekazywane wraz z buforami wejściowymi do wywołań setLayerBuffersetClientTarget. Oznacza to oczekujący zapis do bufora. Musi on wysyłać sygnał, zanim SurfaceFlinger lub HWC spróbują odczytać dane z powiązanego bufora w celu wykonania kompozycji.
  • Ogrodzenia zwalniające są pobierane po wywołaniu funkcji presentDisplay za pomocą wywołania getReleaseFences. Oznacza to oczekujący odczyt z poprzedniego bufora na tej samej warstwie. Sygnał bariery zwalniania informuje, kiedy HWC nie używa już poprzedniego bufora, ponieważ bieżący bufor zastąpił poprzedni na wyświetlaczu. Ogrodzenia zwalniania są przekazywane z powrotem do aplikacji wraz z poprzednimi buforami, które zostaną zastąpione podczas bieżącej kompozycji. Aplikacja musi poczekać na sygnały bariery zwalniania, zanim zapisze nowe treści w buforze, który został jej zwrócony.
  • Present fences są zwracane po jednym na klatkę w ramach wywołania funkcji presentDisplay. Bariery obecne wskazują, kiedy kompozycja tej klatki została ukończona lub kiedy wynik kompozycji poprzedniej klatki nie jest już potrzebny. W przypadku wyświetlaczy fizycznych funkcja presentDisplay zwraca bieżące bariery, gdy bieżąca klatka pojawia się na ekranie. Po zwróceniu bieżących barier można ponownie zapisywać dane w buforze docelowym SurfaceFlingera, jeśli ma to zastosowanie. W przypadku wirtualnych wyświetlaczy bariery są zwracane, gdy można bezpiecznie odczytać dane z bufora wyjściowego.