Platforma 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 ta umożliwia też przekazywanie podstawowych elementów synchronizacji między sterownikami z jądra do przestrzeni użytkownika oraz między procesami w przestrzeni użytkownika.

Aplikacja może na przykład umieszczać w kolejce zadania do wykonania w GPU. 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 ogrodzeniem wskazującym, kiedy kończy się działanie GPU. Okno kompozytorski rozpoczyna przetwarzanie z wyprzedzeniem i przekazuje zadanie do kontrolera wyświetlania. W podobny sposób praca procesora jest wykonywana z wyprzedzeniem. 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

Bezpośrednia synchronizacja pozwala producentom i konsumentom buforów graficznych sygnalizować, że zakończyli pracę, korzystając z bufora. W obszarze jądra wdrożono synchronizację jawną.

Zalety jawnej synchronizacji:

  • Mniejsza zmienność zachowania na różnych urządzeniach
  • Lepsza obsługa debugowania
  • Ulepszenie danych testów

Platforma synchronizacji ma 3 typy obiektów:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

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świetlacza lub blitter 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 ułatwić debugowanie, nadaj przydatne nazwy wszystkim czynnikom, ścieżkom i ogrodzeniem.
  • Zaimplementuj operatory timeline_value_str i pt_value_str na osiach czasu, aby dane wyjściowe debugowania były bardziej czytelne.
  • W razie potrzeby zastosuj funkcję fill driver_data, aby udostępnić 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 przestrzeni użytkownika na bezpośrednie tworzenie lub sygnalizowanie ogrodzenia. Jednoznaczne tworzenie sygnałów lub blokad prowadzi do ataku typu DoS, który powoduje zatrzymanie funkcji 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 osi sync_timeline. Punkt ma 3 stany: aktywny, sygnalizowany i błąd. Punkty zaczynają się w stanie aktywnym i przechodzą do stanu zasygnalizowanego lub błędu. Na przykład gdy konsument obrazów nie potrzebuje już bufora, sygnalizuje się sygnał sync_pt, dzięki czemu producent obrazu wie, że może znowu go w nim zapisać.

sync_fence

sync_fence to zbiór wartości sync_pt, które często mają różnych rodziców sync_timeline (np. kontroler wyświetlacza i procesor graficzny). sync_fence, sync_ptsync_timeline to główne prymitywy, których używają sterowniki i przestrzeń użytkownika do komunikowania się z zależnościami. Gdy zostanie wysłany sygnał o ogrodzenie, wszystkie polecenia wydane przed ogrodzeniem zostaną wykonane, ponieważ sterownik jądra lub blok sprzętowy wykonuje polecenia w kolejności.

Platforma synchronizacji umożliwia wielu odbiorcom i producentom sygnalizowanie, że 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 przesyłane ze przestrzeni jądra do przestrzeni użytkownika. 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 zostanie wysłany sygnał o ogrodzenie, producenci obrazów wiedzą, że obaj konsumenci skończyli korzystać z tego zasobu.

Podobnie jak wartości sync_pt, ogrodzenia są aktywne na początku i zmieniają stan na podstawie stanu punktów. Jeśli wszystkie wartości sync_pt zostaną zgłoszone, zgłoszona zostanie również wartość sync_fence. Jeśli jeden element sync_pt znajdzie się w stanie błędu, cały element sync_fence będzie w stanie błędu.

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

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

  • Podsystem związany z przestrzenią jądra, który wdraża platformę synchronizacji dla określonego sterownika sprzętowego. Sterowniki, które muszą być świadome ogrodzenia, to w ogóle wszystko, co uzyskuje dostęp do sterownika sprzętowego lub z nim się komunikuje. Kluczowe pliki obejmują:
    • Główna 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 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: implementacja sterownika wyświetlacza

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. 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 platformy synchronizacji funkcja display_buffer jest bardziej złożona. Podczas wyświetlania bufora jest on powiązany z ogrodzeniem, które wskazuje, kiedy będzie gotowy. Możesz ustawić zadanie w kolejce i zainicjować jego wykonywanie, gdy zniknie blokada.

Dodanie do kolejki i rozpoczęcie pracy po usunięciu bariery nie powoduje blokowania niczego. Natychmiast zwracasz własne ogrodzenie, co gwarantuje, że bufor zniknie z ekranu. Gdy umieszczasz bufory w kolejce, jądro wyświetla zależności ze platformą 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 w przestrzeni użytkownika jako deskryptory plików.

Konwencje integracji

Postępuj zgodnie z konwencjami interfejsu HAL Androida:

  • Jeśli interfejs API udostępnia deskryptor pliku, który odwołuje się do sync_pt, sterownik dostawcy lub interfejs HAL korzystający z interfejsu API musi zamknąć 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 interfejs HAL muszą powielać deskryptor.

Obiekt ogrodzenia jest przemianowany za każdym razem, gdy przechodzi przez kolejkę buforową. 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. Jest to przydatne podczas debugowania, aby zidentyfikować źródło blokady, ponieważ nazwy pojawiają się w wyjściu /d/sync i raportach o błędach.

Integracja z NativeWindow

Okno typu ANativeWindow jest świadome ogrodzenia. dequeueBuffer, queueBuffer i cancelBuffer mają parametry ogrodzenia.

Integracja z OpenGL ES

Synchronizacja OpenGL ES opiera się na 2 rozszerzeniach EGL:

  • EGL_ANDROID_native_fence_sync umożliwia pakowanie i tworzenie natywnych deskryptorów plików Android fence w obiektach EGLSyncKHR.
  • EGL_ANDROID_wait_sync umożliwia blokowanie po stronie GPU, a nie po stronie procesora, dzięki czemu GPU oczekuje EGLSyncKHR. Rozszerzenie EGL_ANDROID_wait_sync jest takie samo jak rozszerzenie EGL_KHR_wait_sync.

Aby korzystać z tych rozszerzeń niezależnie, wdróż 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 natywnych ogrodzeń 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 fence opakowuje istniejący deskryptor natywnego pliku Android fence w obiekcie EGLSyncKHR.
  • -1 tworzy natywny deskryptor pliku ogrodzenia na Androida na podstawie obiektu EGLSyncKHR.

Aby wyodrębnić obiekt EGLSyncKHR z rodzinnego deskryptora pliku ogrodzenia Androida, użyj wywołania funkcji DupNativeFenceFD(). Daje to ten sam wynik co zapytanie atrybutu zbioru, ale jest zgodne z konwencją, że odbiorca zamyka nawias (stąd duplikacja operacji). Na koniec zniszczenie obiektu EGLSyncKHR zamyka atrybut ogrodzenia wewnętrznego.

Integracja z Composerem

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

  • Zakresy zabezpieczeń są przekazywane wraz z buforami danych wejściowych do wywołań setLayerBuffersetClientTarget. Reprezentują one oczekujący zapis w buforze i muszą sygnalizować, zanim SurfaceFlinger lub HWC spróbuje odczytać dane z powiązanego bufora w celu wykonania kompozycji.
  • Ogrodzenia zwalniające są pobierane po wywołaniu presentDisplay za pomocą wywołania getReleaseFences. Te linie reprezentują oczekujące odczyty z poprzedniego bufora na 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, które zostaną zastąpione podczas bieżącej kompozycji. Zanim aplikacja zapisze nowe treści do zwróconego do niej bufora, musi poczekać na sygnały z ogrodzenia.
  • Obecne ogrodzenia są zwracane w ramach wywołania funkcji presentDisplay, po jednym na każdy obraz. Obecne ogrodzenia wskazują, kiedy kompozycja tej klatki została zakończona lub kiedy wynik kompozycji poprzedniej klatki nie jest już potrzebny. 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.