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 umieszczać zadania w kolejce do wykonania przez procesor graficzny. 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świetlania. 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 framework 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ńczyli 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 modułu do szybkiego kopiowania bloków danych 2D. sync_timeline
counts
jobs submitted to the kernel for a particular piece of hardware.
sync_timeline
zapewnia gwarancje dotyczące kolejności 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.
- W osiach czasu zaimplementuj operatory
timeline_value_str
ipt_value_str
, aby dane wyjściowe debugowania były bardziej czytelne. - 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 niezmiennychsync_fence
isync_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
anisync_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 sygnał zostanie wysłany, producenci obrazów wiedzą, że obaj konsumenci zakończyli korzystanie z nich.
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ą zasygnalizowane, zasygnalizowana zostanie wartość sync_fence
. Jeśli jeden z elementów sync_pt
jest w stanie błędu, cały element sync_fence
jest w stanie błędu.
Członkostwo w sync_fence
jest niezmienne po utworzeniu obszaru. Aby uzyskać więcej niż 1 punkt w obszarze, przeprowadza się scalanie, w ramach którego punkty z 2 różnych obszarów są dodawane do 3 obszaru.
Jeśli jeden z tych punktów został zasygnalizowany w pierwotnym obszarze, a drugi nie, 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 ogrodzenia, to zwykle wszystko, co ma dostęp do kompozytora sprzętowego lub się z nim komunikuje.
Kluczowe 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
- Podstawowa implementacja:
- Dostawca musi podać odpowiednie bariery synchronizacji jako parametry funkcji
validateDisplay()
ipresentDisplay()
w HAL. - 2 rozszerzenia GL związane z barierkami (
EGL_ANDROID_native_fence_sync
iEGL_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 struktura synchronizacji, ta funkcja otrzymywała dma-buf
obiekty, 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 umieszczania bufora na wyświetlaczu 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 sygnał, który gwarantuje, że bufor zostanie usunięty z wyświetlacza. Podczas kolejkowania buforów jądro wymienia zależności z 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 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_sync
umożliwia tworzenie lub opakowywanie natywnych deskryptorów plików ogrodzenia Androida w obiektachEGLSyncKHR
.EGL_ANDROID_wait_sync
umożliwia wstrzymanie po stronie GPU, a nie po stronie CPU, co powoduje, że GPU czeka naEGLSyncKHR
. RozszerzenieEGL_ANDROID_wait_sync
jest takie samo jak rozszerzenieEGL_KHR_wait_sync
.
Aby używać tych rozszerzeń niezależnie, wdróż 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żna ustawić w jednym z 2 trybów:
- Prawidłowy deskryptor pliku bariery opakowuje istniejący natywny deskryptor pliku bariery Androida w obiekt
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 komponentem sprzętowym
Komponent sprzętowy obsługuje 3 typy barier synchronizacji:
- Ogrodzenia są przekazywane wraz z buforami wejściowymi do wywołań
setLayerBuffer
isetClientTarget
. 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. - Release fences są pobierane po wywołaniu funkcji
presentDisplay
za pomocą wywołaniagetReleaseFences
. Oznacza to oczekujący odczyt z poprzedniego bufora na tej samej warstwie. Sygnał zwolnienia bufora 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 publikowania, 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 funkcjapresentDisplay
zwraca obecne 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 wyświetlaczy wirtualnych obecne bariery są zwracane, gdy można bezpiecznie odczytać dane z bufora wyjściowego.