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_timelinesync_ptsync_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_stript_value_strna 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_driverumożliwia dostawcom przekazywanie informacji o niezmiennychsync_fenceisync_ptsw 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_ptanisync_fencew 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.hkernel/common/drivers/base/sync.c
- Dokumentacja:
kernel/common/Documentation/sync.txt - Biblioteka do komunikacji z przestrzenią jądra w
platform/system/core/libsync
- Podstawowa implementacja:
- Dostawca musi podać odpowiednie bariery synchronizacji jako parametry funkcji
validateDisplay()ipresentDisplay()w warstwie abstrakcji sprzętowej (HAL). - 2 rozszerzenia GL związane z barierkami (
EGL_ANDROID_native_fence_synciEGL_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, queueBuffer i cancelBuffer mają parametry obszaru.
Integracja OpenGL ES
Integracja synchronizacji OpenGL ES opiera się na 2 rozszerzeniach EGL:
EGL_ANDROID_native_fence_syncumożliwia tworzenie lub opakowywanie natywnych deskryptorów plików bariery Androida w obiektachEGLSyncKHR.EGL_ANDROID_wait_syncumożliwia wstrzymanie po stronie GPU, a nie po stronie CPU, dzięki czemu GPU czeka naEGLSyncKHR. RozszerzenieEGL_ANDROID_wait_syncjest takie samo jak rozszerzenieEGL_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ń
setLayerBufferisetClientTarget. 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
presentDisplayza pomocą wywołaniagetReleaseFences. 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 funkcjapresentDisplayzwraca 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.