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
ipt_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 niezmiennychsync_fence
isync_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
anisync_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_pt
i sync_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
- Główna implementacja:
- Dostawca musi podać odpowiednie bariery synchronizacji jako parametry funkcji
validateDisplay()
ipresentDisplay()
w interfejsie HAL. - 2 rozszerzenia GL związane z fence (
EGL_ANDROID_native_fence_sync
iEGL_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 obiektachEGLSyncKHR
.EGL_ANDROID_wait_sync
umożliwia blokowanie po stronie GPU, a nie po stronie procesora, dzięki czemu GPU oczekujeEGLSyncKHR
. RozszerzenieEGL_ANDROID_wait_sync
jest takie samo jak rozszerzenieEGL_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ń
setLayerBuffer
isetClientTarget
. 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łaniagetReleaseFences
. 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 funkcjapresentDisplay
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.