Ramy synchronizacji

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_strpt_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 niezmiennych sync_fencesync_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 ani sync_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
  • Dostawca musi podać odpowiednie bariery synchronizacji jako parametry funkcji validateDisplay()presentDisplay() w HAL.
  • 2 rozszerzenia GL związane z barierkami (EGL_ANDROID_native_fence_syncEGL_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-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 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, queueBuffercancelBuffer 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 obiektach EGLSyncKHR.
  • EGL_ANDROID_wait_sync umożliwia wstrzymanie po stronie GPU, a nie po stronie CPU, co powoduje, że GPU czeka na EGLSyncKHR. Rozszerzenie EGL_ANDROID_wait_sync jest takie samo jak rozszerzenie EGL_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ń setLayerBuffersetClientTarget. 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łania getReleaseFences. 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 funkcja presentDisplay 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.