Struktura synchronizacji

Struktura synchronizacji wyraźnie opisuje zależności między różnymi operacjami asynchronicznymi w systemie graficznym Androida. Struktura udostępnia interfejs API, który umożliwia komponentom wskazywanie, kiedy bufory są zwalniane. Struktura umożliwia także przekazywanie prymitywów synchronizacji pomiędzy sterownikami z jądra do przestrzeni użytkownika oraz pomiędzy samymi procesami przestrzeni użytkownika.

Na przykład aplikacja może kolejkować pracę do wykonania w GPU. Procesor graficzny rozpoczyna rysowanie tego obrazu. Choć obraz nie został jeszcze wciągnięty do pamięci, wskaźnik bufora przekazywany jest do kompozytora okna wraz z płotkiem wskazującym, kiedy zakończy się praca GPU. Kompozytor okienny rozpoczyna przetwarzanie z wyprzedzeniem i przekazuje pracę do kontrolera wyświetlania. W podobny sposób praca procesora jest wykonywana z wyprzedzeniem. Po zakończeniu pracy procesora graficznego kontroler wyświetlacza natychmiast wyświetla obraz.

Struktura synchronizacji umożliwia także wdrażającym wykorzystanie zasobów synchronizacji w ich własnych komponentach sprzętowych. Wreszcie platforma zapewnia wgląd w potok graficzny, co ułatwia debugowanie.

Jawna synchronizacja

Jawna synchronizacja umożliwia producentom i konsumentom buforów graficznych sygnalizowanie zakończenia korzystania z bufora. Jawna synchronizacja jest implementowana w przestrzeni jądra.

Korzyści wynikające z jawnej synchronizacji obejmują:

  • Mniejsze różnice w zachowaniu pomiędzy urządzeniami
  • Lepsza obsługa debugowania
  • Ulepszone metryki testowania

Struktura synchronizacji ma trzy typy obiektów:

  • sync_timeline
  • sync_pt
  • sync_fence

synchronizacja_osi czasu

sync_timeline to monotonnie rosnąca oś czasu, którą dostawcy powinni wdrożyć dla każdej instancji sterownika, takiej jak kontekst GL, kontroler wyświetlania lub blitter 2D. sync_timeline zlicza zadania przesłane do jądra dla konkretnego elementu sprzętu. sync_timeline zapewnia gwarancje dotyczące kolejności operacji i umożliwia implementacje specyficzne dla sprzętu.

Podczas wdrażania sync_timeline postępuj zgodnie z tymi wytycznymi:

  • Podaj przydatne nazwy wszystkich sterowników, osi czasu i barier, aby uprościć debugowanie.
  • Zaimplementuj operatory timeline_value_str i pt_value_str na osiach czasu, aby zwiększyć czytelność wyników debugowania.
  • Zaimplementuj fill driver_data aby w razie potrzeby zapewnić bibliotekom przestrzeni użytkownika, takim jak biblioteka GL, dostęp do prywatnych danych na osi czasu. data_driver umożliwia dostawcom przekazywanie informacji o niezmiennych parametrach sync_fence i sync_pts w celu tworzenia na ich podstawie wierszy poleceń.
  • Nie zezwalaj przestrzeni użytkownika na jawne tworzenie lub sygnalizowanie ogrodzenia. Jawne tworzenie sygnałów/ogrodzeń skutkuje atakiem typu „odmowa usługi”, który wstrzymuje funkcjonalność potoku.
  • Nie uzyskuj jawnego dostępu do elementów sync_timeline , sync_pt ani sync_fence . API zapewnia wszystkie wymagane funkcje.

synchronizacja_pt

sync_pt to pojedyncza wartość lub punkt na sync_timeline . Punkt ma trzy stany: aktywny, sygnalizowany i błąd. Punkty rozpoczynają się w stanie aktywnym i przechodzą do stanów sygnalizowanych lub błędów. Na przykład, gdy odbiorca obrazu nie potrzebuje już bufora, sygnalizowany jest sygnał sync_pt , dzięki czemu producent obrazu wie, że można ponownie zapisać w buforze.

synchronizacja_ogrodzenia

sync_fence to zbiór wartości sync_pt , które często mają różne elementy nadrzędne sync_timeline (na przykład dla kontrolera wyświetlania i procesora graficznego). sync_fence , sync_pt i sync_timeline to główne elementy podstawowe używane przez sterowniki i przestrzeń użytkownika do komunikowania swoich zależności. Kiedy ogrodzenie zostanie zasygnalizowane, wszystkie polecenia wydane przed ogrodzeniem zostaną wykonane, ponieważ sterownik jądra lub blok sprzętowy wykonuje polecenia w określonej kolejności.

Struktura synchronizacji umożliwia wielu konsumentom lub producentom sygnalizowanie zakończenia korzystania z bufora, przekazując informacje o zależnościach za pomocą jednego parametru funkcji. Ogrodzenia są wspierane przez deskryptor pliku i przekazywane z przestrzeni jądra do przestrzeni użytkownika. Na przykład ogrodzenie może zawierać dwie wartości sync_pt , które oznaczają, kiedy dwóch oddzielnych odbiorców obrazu zakończy odczytywanie bufora. Kiedy płot zostaje zasygnalizowany, producenci obrazu wiedzą, że obaj konsumenci mają już dość konsumpcji.

Ogrodzenia, podobnie jak wartości sync_pt , zaczynają być aktywne i zmieniają stan w zależności od stanu swoich punktów. Jeśli wszystkie wartości sync_pt zostaną zasygnalizowane, sync_fence zostanie zasygnalizowane. Jeśli jeden sync_pt wpadnie w stan błędu, cały sync_fence będzie miał stan błędu.

Członkostwo w sync_fence jest niezmienne po utworzeniu ogrodzenia. Aby uzyskać więcej niż jeden punkt w ogrodzeniu, przeprowadza się scalanie, w którym punkty z dwóch różnych ogrodzeń są dodawane do trzeciego ogrodzenia. Jeśli jeden z tych punktów został zasygnalizowany w ogrodzeniu źródłowym, a drugi nie, trzeci płot również nie będzie w stanie zasygnalizowanym.

Aby zaimplementować jawną synchronizację, podaj następujące informacje:

  • Podsystem przestrzeni jądra, który implementuje strukturę synchronizacji dla określonego sterownika sprzętowego. Sterowniki, które muszą uwzględniać zabezpieczenia, to zazwyczaj wszystko, co uzyskuje dostęp do narzędzia Hardware Composer lub komunikuje się z nim. Kluczowe pliki obejmują:
    • Podstawowa implementacja:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Dokumentacja w kernel/common/Documentation/sync.txt
    • Biblioteka do komunikacji z przestrzenią jądra w platform/system/core/libsync
  • Dostawca musi zapewnić odpowiednie zabezpieczenia synchronizacji jako parametry funkcji validateDisplay() i presentDisplay() w warstwie HAL.
  • Dwa rozszerzenia GL związane z ogrodzeniem ( EGL_ANDROID_native_fence_sync i EGL_ANDROID_wait_sync ) oraz obsługa ogrodzenia w sterowniku graficznym.

Studium przypadku: Implementacja sterownika ekranu

Aby skorzystać z interfejsu API obsługującego funkcję synchronizacji, opracuj sterownik ekranu posiadający funkcję bufora wyświetlania. Zanim istniał framework synchronizacji, ta funkcja odbierała obiekty dma-buf , umieszczała te bufory na wyświetlaczu i blokowała, gdy bufor był widoczny. Na 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 środowisku synchronizacji funkcja display_buffer jest bardziej złożona. Podczas wystawiania bufora zostaje on powiązany z płotem, który wskazuje, kiedy bufor będzie gotowy. Możesz ustawić się w kolejce i rozpocząć pracę po oczyszczeniu ogrodzenia.

Kolejkowanie i rozpoczynanie pracy po przeczyszczeniu płotu niczego nie blokuje. Od razu zwracasz własne ogrodzenie, co gwarantuje, kiedy bufor zniknie z wyświetlacza. Podczas ustawiania buforów w kolejce jądro wyświetla listę zależności ze strukturą 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śniono, jak zintegrować platformę synchronizacji przestrzeni jądra z częściami przestrzeni użytkownika platformy Android i sterownikami, które muszą się ze sobą komunikować. Obiekty przestrzeni jądra są reprezentowane jako deskryptory plików w przestrzeni użytkownika.

Konwencje integracyjne

Postępuj zgodnie z konwencjami interfejsu Android HAL:

  • Jeśli interfejs API udostępnia deskryptor pliku odwołujący się do sync_pt , sterownik dostawcy lub warstwa HAL korzystająca z interfejsu API musi zamknąć deskryptor pliku.
  • Jeśli sterownik dostawcy lub warstwa HAL przekaże deskryptor pliku zawierający sync_pt do funkcji API, sterownik dostawcy lub warstwa HAL nie może zamknąć deskryptora pliku.
  • Aby nadal używać deskryptora pliku ogrodzenia, sterownik dostawcy lub warstwa HAL musi zduplikować deskryptor.

Nazwa obiektu ogrodzenia zmienia się za każdym razem, gdy przechodzi przez BufferQueue. Obsługa ogrodzenia jądra umożliwia ogrodom posiadanie ciągów nazw, więc struktura synchronizacji używa nazwy okna i indeksu bufora, który jest umieszczany w kolejce, aby nadać nazwę ogrodzeniu, na przykład SurfaceView:0 . Jest to pomocne podczas debugowania w celu zidentyfikowania źródła zakleszczenia, ponieważ nazwy pojawiają się w wynikach polecenia /d/sync i raportach o błędach.

Integracja z ANativeWindow

ANativeWindow obsługuje ogrodzenie. dequeueBuffer , queueBuffer i cancelBuffer mają parametry ogrodzenia.

Integracja z OpenGL ES

Integracja synchronizacji OpenGL ES opiera się na dwóch rozszerzeniach EGL:

  • EGL_ANDROID_native_fence_sync umożliwia zawijanie lub tworzenie natywnych deskryptorów plików ogrodzenia systemu Android w obiektach EGLSyncKHR .
  • EGL_ANDROID_wait_sync pozwala na przestoje po stronie GPU, a nie po stronie procesora, powodując, ż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, zaimplementuj rozszerzenie EGL_ANDROID_native_fence_sync wraz z powiązaną obsługą jądra. Następnie włącz rozszerzenie EGL_ANDROID_wait_sync w swoim sterowniku. Rozszerzenie EGL_ANDROID_native_fence_sync składa się z odrębnego natywnego typu obiektu ogrodzenia EGLSyncKHR . W rezultacie rozszerzenia mające zastosowanie do istniejących typów obiektów EGLSyncKHR niekoniecznie mają zastosowanie do obiektów EGL_ANDROID_native_fence , co pozwala uniknąć niepożądanych interakcji.

Rozszerzenie EGL_ANDROID_native_fence_sync wykorzystuje odpowiedni natywny atrybut deskryptora pliku ogrodzenia, który można ustawić tylko w czasie tworzenia i nie można go bezpośrednio odpytywać z istniejącego obiektu synchronizacji. Ten atrybut można ustawić na jeden z dwóch trybów:

  • Prawidłowy deskryptor pliku ogrodzenia otacza istniejący natywny deskryptor pliku ogrodzenia systemu Android w obiekcie EGLSyncKHR .
  • -1 tworzy natywny deskryptor pliku ogrodzenia systemu Android z obiektu EGLSyncKHR .

Użyj wywołania funkcji DupNativeFenceFD() , aby wyodrębnić obiekt EGLSyncKHR z natywnego deskryptora pliku ogrodzenia systemu Android. Daje to taki sam rezultat, jak zapytanie o ustawiony atrybut, ale jest zgodne z konwencją, że odbiorca zamyka ogrodzenie (stąd zduplikowana operacja). Na koniec zniszczenie obiektu EGLSyncKHR zamyka atrybut wewnętrznego ogrodzenia.

Integracja z programem Hardware Composer

Hardware Composer obsługuje trzy typy ogrodzeń synchronizacji:

  • Ogrodzenia Acquire są przekazywane wraz z buforami wejściowymi do wywołań setLayerBuffer i setClientTarget . Reprezentują one oczekujący zapis do bufora i muszą sygnalizować, zanim SurfaceFlinger lub HWC podejmie próbę odczytu z powiązanego bufora w celu wykonania kompozycji.
  • Ograniczenia zwolnień są pobierane po wywołaniu metody presentDisplay za pomocą wywołania getReleaseFences . Reprezentują one oczekujący odczyt z poprzedniego bufora na tej samej warstwie. Ogranicznik zwolnienia sygnalizuje, że HWC nie używa już poprzedniego bufora, ponieważ bieżący bufor zastąpił poprzedni bufor na wyświetlaczu. Ograniczenia wydania są przekazywane z powrotem do aplikacji wraz z poprzednimi buforami, które zostaną zastąpione podczas bieżącej kompozycji. Aplikacja musi poczekać na sygnał blokady wydania, zanim zapisze nową zawartość do bufora, który został do niej zwrócony.
  • W ramach wywołania metody presentDisplay zwracane są obecne ogrodzenia , po jednym na klatkę. Obecne płoty wskazują, kiedy kompozycja tej klatki została ukończona lub alternatywnie, kiedy wynik kompozycji poprzedniej klatki nie jest już potrzebny. W przypadku wyświetlaczy fizycznych presentDisplay zwraca obecne ogrodzenia, gdy bieżąca ramka pojawia się na ekranie. Po zwróceniu obecnych ogrodzeń można bezpiecznie ponownie zapisać w buforze docelowym SurfaceFlinger, jeśli ma to zastosowanie. W przypadku wyświetlaczy wirtualnych obecne ogrodzenia są zwracane, gdy można bezpiecznie odczytać z bufora wyjściowego.