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. Pozwala ona też przekazywać prymitywne mechanizmy 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 ustawić w kolejce zadania do wykonania na karcie graficznej. GPU zaczyna rysować obraz. Chociaż obraz nie został jeszcze narysowany w pamięci, wskaźnik bufora jest przekazywany do kompozytora okna wraz z ograniczeniem, które wskazuje, kiedy zakończy się praca na karcie graficznej. Okno kompozytorski rozpoczyna przetwarzanie z wyprzedzeniem i przekazuje zadanie sterownikowi wyświetlania. W podobny sposób praca procesora jest wykonywana z wyprzedzeniem. Gdy GPU zakończy pracę, 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. Framework zapewnia też widoczność potoku graficznego, co ułatwia debugowanie.
Wyraźna synchronizacja
Wyraźna synchronizacja umożliwia producentom i konsumentom buforów graficznych sygnalizowanie, kiedy skończą korzystać z bufora. Wyraźna synchronizacja jest implementowana w przestrzeni jądra.
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 implementować w przypadku każdego wystąpienia sterownika, np. kontekstu GL, kontrolera wyświetlacza lub blittera 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 ze sprzętem.
Podczas implementacji sync_timeline
przestrzegaj tych wskazówek:
- Aby ułatwić debugowanie, nadaj przydatne nazwy wszystkim czynnikom, ścieżkom czasowym i ogrodzeniem.
- Wprowadzaj operatory
timeline_value_str
ipt_value_str
w osi czasu, aby ułatwić odczytywanie danych debugowania. - 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 userspace na jawne tworzenie lub sygnalizowanie ogrodzenia. Wyraźne tworzenie sygnałów lub barier powoduje atak typu odmowy usługi, który zatrzymuje działanie 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 sygnalizowania lub błędu. Jeśli na przykład konsument obrazu nie potrzebuje już bufora, sygnał sync_pt
sygnalizuje producentowi obrazu, że może ponownie zapisać dane w buforze.
sync_fence
sync_fence
to zbiór wartości sync_pt
, które często mają różnych sync_timeline
rodziców (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 obrazu.
Podobnie jak wartości sync_pt
, ogrodzenia są aktywne od początku i zmieniają stan w zależności od 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ż 1 punkt w ogrodzeniu, przeprowadzane jest scalenie, w którym punkty z 2 oddzielnych ogrodzeń są dodawane do 3 ogrodzenia.
Jeśli jeden z tych punktów został zgłoszony w pierwotnym ogrodzeniu, a drugi nie, trzeci ogrodzeniu również nie będzie zgłoszony.
Aby wdrożyć jawną synchronizację, podaj te informacje:
- Podsystem w przestrzeni jądra, który wdraża mechanizm synchronizacji dla konkretnego 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.
Najważniejsze pliki to:
- 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 z 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 zadania do kolejki i jego uruchomienie po usunięciu bariery nie powoduje blokowania niczego. Od razu zwracasz własny płot, co gwarantuje, że bufor nie będzie widoczny na ekranie. Gdy umieszczasz bufory w kolejce, 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 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 w przestrzeni jądra są reprezentowane jako deskryptory plików w przestrzeni użytkownika.
Konwencje integracji
Stosuj się do konwencji 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 HAL musi powielić 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. Parametry bariery: dequeueBuffer
, queueBuffer
i cancelBuffer
.
Integracja z OpenGL ES
Synchronizacja OpenGL ES opiera się na 2 rozszerzeniach EGL:
EGL_ANDROID_native_fence_sync
umożliwia tworzenie lub owijanie natywnych opisów pliku ogrodzenia Androida w obiektachEGLSyncKHR
.EGL_ANDROID_wait_sync
pozwala na blokowanie GPU, a nie procesora, co powoduje oczekiwanie GPU naEGLSyncKHR
. RozszerzenieEGL_ANDROID_wait_sync
jest takie samo jak rozszerzenieEGL_KHR_wait_sync
.
Aby używać tych rozszerzeń niezależnie, zaimplementuj rozszerzenie EGL_ANDROID_native_fence_sync
wraz z powiązanym wsparciem jądra. Następnie włącz rozszerzenie EGL_ANDROID_wait_sync
w sterowniku. Rozszerzenie EGL_ANDROID_native_fence_sync
składa się z osobnego typu obiektu bariery natywnej 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że być ustawiony w jednym z 2 sposobów:
- Prawidłowy deskryptor pliku ogrodzenia zawiera w obiekcie
EGLSyncKHR
istniejący natywny deskryptor pliku ogrodzenia Androida. - -1 tworzy natywny deskryptor pliku ogrodzenia na Androida na podstawie obiektu
EGLSyncKHR
.
Aby wyodrębnić obiekt EGLSyncKHR
z natywnego 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
powoduje zamknięcie atrybutu wewnętrznego ogrodzenia.
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ące zapisanie do bufora i mu wysłać sygnał, zanim SurfaceFlinger lub HWC spróbuje odczytać z powiązanego bufora, aby wykonać kompozycję. - 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 ograniczające uwalnianie, gdy HWC nie używa już poprzedniego bufora, ponieważ bieżący bufor zastąpił poprzedni bufor na wyświetlaczu. Ograniczenia wersji są przekazywane z powrotem do aplikacji wraz z poprzednimi buforami, które zostaną zastąpione podczas bieżącego tworzenia. Zanim aplikacja zapisze nowe treści w zwróconym buforze, musi poczekać na sygnał z ogrodzenia. - Obecne ogrodzenia są zwracane w ramach wywołania funkcji
presentDisplay
, po jednym na każdy kadr. 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.