Tutaj dowiesz się, jak skonfigurować elementy RenderingNG i jak strumień renderowania przez nie przepływa.
Zadania związane z renderowaniem to:
- Wyświetlanie treści w pikselach na ekranie.
- Animuj efekty wizualne w treściach, aby przechodziły one z jednego stanu w drugi.
- Przewijanie w odpowiedzi na dane wejściowe.
- Przesyłaj dane wejściowe do odpowiednich miejsc, aby skrypty dla deweloperów i inne podsystemy mogły na nie reagować.
Treści do renderowania to drzewo ramek dla każdej karty przeglądarki oraz interfejs przeglądarki. a także strumień nieprzetworzonych zdarzeń wprowadzania danych z ekranów dotykowych, myszy, klawiatur i innych urządzeń.
Każda ramka zawiera:
- Stan DOM
- CSS
- Obszary robocze
- Zasoby zewnętrzne, takie jak obrazy, filmy, czcionki i pliki SVG
Ramka to dokument HTML z adresem URL. Strona internetowa wczytana w karcie przeglądarki ma ramkę najwyższego poziomu, ramki podrzędne dla każdego elementu iFrame zawartego w dokumencie najwyższego poziomu oraz ich rekurencyjne potomstwo.
Efekt wizualny to operacja graficzna zastosowana do bitmapy, taka jak przewijanie, przekształcenie, przycinanie, filtrowanie, krycie lub mieszanie.
Komponenty architektury
W RenderingNG te zadania są logicznie podzielone na kilka etapów i komponentów kodu. Komponenty trafiają do różnych procesów i wątków procesora oraz do podkomponentów w tych wątkach. Każdy z nich odgrywa ważną rolę w osiąganiu niezawodności, skalowalnej wydajności i rozszerzalności wszystkich treści internetowych.
Struktura potoku renderowania
Proces renderowania przebiega w ramach łańcucha przetwarzania, w którym powstaje wiele etapów i elementów. Każdy etap reprezentuje kod, który wykonuje jedno dobrze zdefiniowane zadanie w ramach renderowania. Artefakty to struktury danych, które są wejściami lub wyjściami etapów.
Etapy:
- Animacja: zmiana obliczanych stylów i mutacje drzewa właściwości w czasie na podstawie deklaratywnych harmonogramów.
- Styl: zastosuj CSS do DOM i utwórz obliczone style.
- Układ: określa rozmiar i położenie elementów DOM na ekranie oraz tworzy niezmienną strukturę fragmentu.
- Przed malowaniem: obliczanie drzew obiektów oraz invalidate istniejących list wyświetlania i płytek tekstury GPU w odpowiednich przypadkach.
- Przewijanie: aktualizowanie przesunięcia dokumentu i przewijanych elementów DOM przez zmianę drzewa właściwości.
- Paint: obliczanie listy wyświetlania, która opisuje, jak zastosować rastrowanie do elementów tekstury GPU z DOM.
- Zobowiązanie: skopiuj drzewa właściwości i listę wyświetlania do wątku kompozytora.
- Warstwowanie: podziel listę wyświetlania na listę złożonych warstw na potrzeby niezależnej rasteryzacji i animacji.
- Praca z rasterem, dekodowaniem i malowaniem: przekształcanie list wyświetlania, zakodowanych obrazów i kodu pracy w worklecie w płytki tekstury GPU.
- Aktywuj: utwórz ramkę kompozytora, która pokazuje, jak rysować i umieszczać na ekranie elementy GPU wraz z efektami wizualnymi.
- Sumowanie: łączenie klatek kompozytora ze wszystkich widocznych klatek kompozytora w jedną globalną klatkę kompozytora.
- Rysuj: wykonaj zespół klatek na karcie graficznej, aby utworzyć piksele na ekranie.
Etapy łańcucha przetwarzania można pominąć, jeśli nie są potrzebne. Na przykład animacje efektów wizualnych i przewijania mogą pomijać układ, wstępną i dodatkową obróbkę obrazu. Dlatego na diagramie animacja i przewijanie są oznaczone żółtymi i zielonymi kropkami. Jeśli układ, wstępną i dokładną obróbkę obrazu można pominąć w przypadku efektów wizualnych, można je uruchomić całkowicie na wątku kompozytora, pomijając wątek główny.
Renderowanie interfejsu użytkownika przeglądarki nie jest tutaj bezpośrednio przedstawione, ale można je traktować jako uproszczoną wersję tego samego potoku (a w rzeczy samej jego implementacja zawiera wiele kodu). Film (również nie pokazany bezpośrednio) jest zwykle renderowany za pomocą niezależnego kodu, który dekoduje klatki do płytek tekstur GPU, które są następnie przesyłane do klatek kompozytora i do kroku rysowania.
Proces i struktura wątku
Procesy procesora
Korzystanie z wielu procesów CPU zapewnia izolację pod względem wydajności i bezpieczeństwa między witrynami oraz stanem przeglądarki, a także izolację pod względem stabilności i bezpieczeństwa od sprzętu GPU.
- Proces renderowania renderuje, animuje, przewija i przekierowuje dane wejściowe w przypadku jednej kombinacji witryny i karty. Istnieje kilka procesów renderowania.
- Proces przeglądarki renderuje, animuje i przekierowuje dane wejściowe do interfejsu użytkownika przeglądarki (w tym paska adresu, tytułów kart i ikon) oraz przekazuje wszystkie pozostałe dane do odpowiedniego procesu renderowania. Jest 1 proces przeglądarki.
- Proces wizualizacji agreguje kompozycję z wielu procesów renderowania oraz z procesu przeglądarki. Wykonuje rasteryzację i rysowanie za pomocą GPU. Jest jeden proces wizualizacji.
Różne witryny zawsze kończą w różnych procesach renderowania.
Wiele kart lub okien przeglądarki z tą samą witryną zwykle jest renderowanych w różnych procesach, chyba że karty są powiązane, np. jedna otwiera drugą. W przypadku dużego obciążenia pamięci na komputerze stacjonarnym Chromium może umieścić wiele kart z tego samego witryny w tym samym procesie renderowania, nawet jeśli nie są ze sobą powiązane.
Na jednej karcie przeglądarki ramki z różnych witryn są zawsze renderowane w różnych procesach, ale ramki z tej samej witryny są zawsze renderowane w tym samym procesie. Z punktu widzenia renderowania ważną zaletą wielu procesów renderowania jest to, że iframe’y i karty w różnych witrynach są izolacyjne pod względem wydajności. Źródła mogą też uzyskać jeszcze większą izolację.
W przypadku Chromium jest dokładnie jeden proces Viz, ponieważ zwykle jest tylko jeden układ GPU i ekran, na których można wyświetlać obraz.
Oddzielenie wizualizacji do własnego procesu jest korzystne dla stabilności w przypadku błędów w sterownikach GPU lub sprzęcie. Jest to też dobre rozwiązanie pod kątem izolacji zabezpieczeń, co jest ważne w przypadku interfejsów API GPU, takich jak Vulkan, oraz zabezpieczeń w ogóle.
Ponieważ przeglądarka może mieć wiele kart i okienek, a wszystkie z nich mają piksele interfejsu użytkownika do wyświetlenia, możesz się zastanawiać, dlaczego jest tylko jeden proces przeglądarki. Dzieje się tak dlatego, że tylko jeden z nich jest aktywny w danym momencie. W fakcie niewidoczne karty przeglądarki są w większości dezaktywowane i nie używają pamięci GPU. Jednak złożone funkcje renderowania interfejsu przeglądarki są coraz częściej wdrażane w procesach renderowania (tzw. WebUI). Nie jest to spowodowane chęcią zapewnienia izolacji pod kątem wydajności, ale chęcią skorzystania z łatwości obsługi silnika renderowania Chromium.
Na starszych urządzeniach z Androidem proces renderowania i przeglądarki są współdzielone podczas korzystania z komponentu WebView (nie dotyczy to Chromium na Androidzie, tylko WebView). W komponencie WebView proces przeglądarki jest też udostępniany aplikacji do umieszczania, a komponent WebView ma tylko jeden proces renderowania.
Czasami jest też proces dekodowania chronionych treści wideo. Ten proces nie jest przedstawiony na poprzednich diagramach.
Wątki
Dzięki wątkom można uzyskać izolację wydajności i szybkość reakcji pomimo powolnych zadań, równoległego przetwarzania i wielokrotnego buforowania.
- Główny wątek obsługuje skrypty, pętlę zdarzeń renderowania, cykl życia dokumentu, testowanie trafień, rozsyłanie zdarzeń skryptu oraz analizowanie HTML, CSS i innych formatów danych.
- Pomocnicze wątki główne wykonują zadania takie jak tworzenie bitmap obrazów i blobów, które wymagają kodowania lub dekodowania.
- Workery w witrynie uruchamiają skrypt i pętlę zdarzeń renderowania dla OffscreenCanvas.
- Wątek kompozytora przetwarza zdarzenia wejściowe, przewija i animuje zawartość internetową, oblicza optymalną warstwizację zawartości internetowej oraz koordynuje dekodowanie obrazów, malowanie elementów roboczych i zadania rastrowe.
- Pomocnicze wątki kompozytora koordynują zadania dotyczące rastrowania wizualizacji, wykonują zadania dekodowania obrazu, malują elementy robocze i zastępczy raster.
- Media, demuxer lub wątki wyjścia audio dekodują, przetwarzają i synchronizują strumienie wideo i dźwięku. Pamiętaj, że film jest renderowany równolegle z głównym procesem renderowania.
Oddzielenie wątków głównego i kompozytora jest bardzo ważne dla izolacji wydajności animacji i przewijania od pracy wątku głównego.
W przypadku każdego procesu renderowania jest tylko 1 główny wątek, nawet jeśli wiele kart lub ramek z tej samej witryny może prowadzić do tego samego procesu. Istnieją jednak ograniczenia dotyczące wydajności w przypadku różnych interfejsów API przeglądarki. Na przykład generowanie bitmap obrazów i blobów w interfejsie Canvas API odbywa się w wątku pomocniczym wątku głównego.
Podobnie w przypadku każdego procesu renderowania jest tylko 1 wątek kompozytora. Ogólnie nie stanowi to problemu, że jest tylko jeden, ponieważ wszystkie naprawdę kosztowne operacje na wątku kompozytora są delegowane do wątków roboczych kompozytora lub procesu Viz, a ta praca może być wykonywana równolegle z przesyłaniem danych wejściowych, przewijaniem lub animacją. Wątek roboczy kompozytora koordynuje zadania wykonywane w procesie Viz, ale przyspieszanie przez procesor graficzny wszędzie może się nie udać z powodów niezależnych od Chromium, takich jak błędy sterowników. W takich sytuacjach wątek roboczy wykona zadanie w trybie awaryjnym na procesorze.
Liczba wątków kompozytora zależy od możliwości urządzenia. Na przykład komputery mają zwykle więcej wątków, ponieważ mają więcej rdzeni procesora i są mniej ograniczone pod względem baterii niż urządzenia mobilne. Oto przykład skalowania w górę i w dół.
Architektura wątków procesu renderowania to zastosowanie 3 różnych wzorów optymalizacji:
- Wątek pomocniczy: wysyłanie długotrwałych podzadań do dodatkowych wątków, aby zachować responsywność wątku nadrzędnego w przypadku innych równoczesnych żądań. Przykładami tej techniki są wątki pomocnicze wątku głównego i kompozytora.
- Wielokrotne buforowanie: wyświetlanie wcześniej wyrenderowanej treści podczas renderowania nowej treści, aby ukryć opóźnienie renderowania. Wątek kompozytora używa tej techniki.
- Równoległe przetwarzanie: przetwarzanie w ramach łańcucha w kilku miejscach jednocześnie. Dzięki temu przewijanie i animacje mogą być szybkie. Nawet jeśli trwa renderowanie głównego wątku, przewijanie i animacje mogą działać równolegle.
Proces przeglądarki
- Wątek renderowania i kompozycji reaguje na dane wejściowe w interfejsie przeglądarki, przekazuje inne dane wejściowe do odpowiedniego procesu renderowania oraz układa i rysuje interfejs przeglądarki.
- Pomocniki wątku renderowania i kompozycji wykonują zadania dekodowania obrazu i zastępczego rastrowania lub dekodowania.
Wątek renderowania i kompletowania procesu przeglądarki jest podobny do kodu i funkcji procesu renderowania, z tym że wątek główny i wątek kompozytora są połączone w jeden. W tym przypadku potrzebny jest tylko 1 wątek, ponieważ nie ma potrzeby oddzielania wydajności od długich zadań w wątku głównym, ponieważ z powodu konstrukcji nie ma takich zadań.
Proces wizualizacji
- Główny wątek GPU rasteryzuje listy wyświetlania i ramki wideo do płytek tekstur GPU oraz rysuje ramki kompozytora na ekranie.
- Wątek kompozytora wyświetlacza agreguje i optymalizuje kompozycję z każdego procesu renderowania oraz z procesu przeglądarki, tworząc pojedynczą ramkę kompozytora do wyświetlenia na ekranie.
Rasteryzacja i rysowanie odbywają się zwykle w tym samym wątku, ponieważ oba korzystają z zasobów GPU, a trudno jest niezawodnie korzystać z GPU w wielu wątkach (łatwiejszy dostęp do GPU w wielu wątkach jest jedną z motywacji do opracowania nowego standardu Vulkan). W WebView na Androida jest osobny wątek renderowania na poziomie systemu operacyjnego, ponieważ WebView jest wbudowany w natywną aplikację. Inne platformy prawdopodobnie będą miały taki wątek w przyszłości.
Kompozytor wyświetlania działa na innym wątku, ponieważ musi być zawsze gotowy do działania i nie może blokować żadnego możliwego źródła spowolnienia w głównym wątku procesora graficznego. Jednym z powodów spowolnienia wątku głównego procesora graficznego są wywołania kodu spoza Chromium, na przykład sterowników procesorów graficznych konkretnych producentów, które mogą działać wolno w trudny do przewidzenia sposób.
Struktura komponentu
W każdym wątku renderowania głównego lub wątku kompozytora znajdują się logiczne komponenty oprogramowania, które w sposób uporządkowany współdziałają ze sobą.
Komponenty wątku głównego procesu renderowania
W Blink Renderer:
- Fragment drzewa lokalnego elementu ramki reprezentuje drzewo lokalnych elementów ramki i DOM w elementach.
- Komponent DOM and Canvas APIs zawiera implementacje wszystkich tych interfejsów API.
- Program do obsługi cyklu życia dokumentu wykonuje kroki potoku renderowania aż do kroku zatwierdzania.
- Komponent testowania i przesyłania danych o wystąpieniu zdarzenia input event wykonuje testy dopasowania, aby ustalić, który element DOM jest kierowany przez zdarzenie, a następnie uruchamia algorytmy przesyłania zdarzeń input event i domyślne zachowania.
Harmonogram i wykonywacz pętli zdarzeń do renderowania decydują, co i kiedy ma być wykonywane w pętli zdarzeń. Zaplanuj renderowanie z częstotliwością odpowiadającą wyświetlaczowi urządzenia.
Fragmenty drzewa lokalnego kadru są nieco skomplikowane. Pamiętaj, że drzewo ramek to strona główna i jej ramki potomne, które są w nim zawarte. Klatka jest lokalna w procesie renderowania, jeśli jest renderowana w tym procesie, w przeciwnym razie jest zdalna.
Możesz sobie wyobrazić kolorowanie klatek zgodnie z ich procesem renderowania. Na powyższym obrazie zielone kółka to wszystkie klatki w jednym procesie renderowania, pomarańczowe kółka to klatki w drugim procesie, a niebieskie kółko to klatki w trzecim procesie.
Lokalny fragment drzewa klatek to połączony komponent tego samego koloru w drzewie klatek. Na obrazie widać 4 drzewa lokalnych ramek: 2 dla witryny A, 1 dla witryny B i 1 dla witryny C. Każde lokalne drzewo ramki ma własny komponent renderowania Blink. Renderowanie za pomocą silnika Blink może odbywać się w ramach tego samego procesu renderowania co inne lokalne drzewa klatek lub nie. Jest ona określana na podstawie sposobu wyboru procesów renderowania, jak opisano wcześniej.
Struktura wątku kompozytora procesu renderowania
Składniki kompozytora procesu renderowania to:
- element obsługi danych, który utrzymuje złożoną listę warstw, listy wyświetlania i drzewa właściwości.
- Procesor cyklu życia, który wykonuje animację, przewijanie, kompozycję, rasterowanie oraz dekodowanie i aktywowanie kroków procesu renderowania. (Pamiętaj, że animacja i przewijanie mogą występować zarówno w wątku głównym, jak i w komponatorze).
- Obsługa testów danych wejściowych i testów trafień wykonuje przetwarzanie danych wejściowych i testy trafień w rozdzielczości złożonych warstw, aby określić, czy gesty przewijania można wykonywać na wątku kompozytora oraz do których procesów renderowania mają być kierowane testy trafień.
Przykładowa architektura w praktyce
W tym przykładzie są 3 karty:
Karta 1: foo.com
<html>
<iframe id=one src="foo.com/other-url"></iframe>
<iframe id=two src="bar.com"></iframe>
</html>
Karta 2: bar.com
<html>
…
</html>
Karta 3. baz.com
html
<html>
…
</html>
Struktura procesów, wątków i komponentów na tych kartach wygląda tak:
Przyjrzyjmy się poszczególnym przykładom 4 głównych zadań związanych z renderowaniem. Przypomnienie:
- Wyświetla treści w pikselach na ekranie.
- Animuj efekty wizualne w treściach, aby przechodziły one z jednego stanu w drugi.
- Przewijanie w odpowiedzi na dane wejściowe.
- Przesyłaj dane do odpowiednich miejsc, aby skrypty deweloperów i inne podsystemy mogły na nie reagować.
Aby wyrenderować zmieniony DOM na pierwszej karcie:
- Skrypt dewelopera zmienia DOM w procesie renderowania witryny foo.com.
- Blinker informuje kompozytora, że musi nastąpić renderowanie.
- Kompozytor informuje Viz, że musi nastąpić renderowanie.
- Viz sygnalizuje rozpoczęcie renderowania do kompozytora.
- Kompozytor przekazuje sygnał startowy do renderowania Blink.
- pętla zdarzeń wątku głównego obsługuje cykl życia dokumentu.
- Główny wątek wysyła wynik do wątku kompozytora.
- Moduł obsługi pętli zdarzeń kompozytora wykonuje cykl życia funkcji kompozytowania.
- Wszystkie zadania rastrowe są wysyłane do Viz w celu przetwarzania rastrowego (zwykle jest ich więcej niż jedno).
- Viz rasteryzuje treści na karcie graficznej.
- Wizualizacja potwierdza wykonanie zadania związanego z pikselami. Uwaga: Chromium często nie czeka na zakończenie skanowania, lecz zamiast tego używa tzw. tokena synchronizacji, który musi zostać rozwiązany przez zadania skanowania przed wykonaniem kroku 15.
- Do Viz wysyłany jest element kompozytora.
- Wizualizacja agreguje ramki kompozytora dla procesu renderowania witryny foo.com, procesu renderowania iframe witryny bar.com i interfejsu przeglądarki.
- Wizualizacja planuje remis.
- Wizualizacja rysuje na ekranie zsumowany kadr kompozytora.
Aby animate przejście transformacji CSS na karcie 2:
- Wątek kompozytora w procesie renderowania bar.com odświeża animację w pętli zdarzeń kompozytora, zmieniając istniejące drzewa właściwości. Następnie ponownie uruchom cykl kompozytora. (mogą wystąpić zadania rasteryzacji i dekodowania, ale nie są one tutaj przedstawione).
- Do Viz wysyłany jest element kompozytora.
- Wizualizacja agreguje ramki kompozytora dla procesu renderowania witryny foo.com, procesu renderowania witryny bar.com i interfejsu przeglądarki.
- Wizualizacja planuje remis.
- Wizualizacja rysuje na ekranie zsumowany kadr kompozytora.
Aby przewinąć stronę internetową na karcie 3:
- Do procesu przeglądarki dociera sekwencja zdarzeń
input
(myszy, ekranu dotykowego lub klawiatury). - Każde zdarzenie jest kierowane do wątku kompozytora procesu renderowania baz.com.
- Kompozytor określa, czy wątek główny musi wiedzieć o zdarzeniu.
- W razie potrzeby zdarzenie jest wysyłane do wątku głównego.
- Główny wątek uruchamia detektory zdarzeń
input
(pointerdown
,touchstar
,pointermove
,touchmove
lubwheel
), aby sprawdzić, czy detektory wywołają funkcjępreventDefault
. - Główny wątek zwraca, czy kompozytor wywołał funkcję
preventDefault
. - W przeciwnym razie zdarzenie wejścia jest wysyłane z powrotem do procesu przeglądarki.
- Proces przeglądarki przekształca go w gesto przewijania, łącząc go z innymi ostatnimi zdarzeniami.
- Gest przewijania jest ponownie wysyłany do wątku kompozytora procesu renderowania baz.com,
- Przewijanie jest tam stosowane, a wątkiem kompozytora dla procesu renderowania bar.com jest animacja w pętli zdarzeń kompozytora.
Zmiana ta zmienia przesunięcie przewijania w drzewach usług i ponownie uruchamia kompozytor.
Informuje też wątek główny o wystąpieniu zdarzenia
scroll
(nie pokazanego na rysunku). - Do Viz wysyłany jest element kompozytora.
- Wizualizacja agreguje klatki kompozytora dla procesu renderowania witryny foo.com, procesu renderowania witryny bar.com i interfejsu przeglądarki.
- Wizualizacja planuje remis.
- Wizualizacja rysuje na ekranie zsumowany kadr kompozytora.
Aby przekierować zdarzenie click
na hiperlink w iframe #2 na karcie 1:
- Do procesu przeglądarki dociera zdarzenie
input
(mysz, dotyk lub klawiatura). Wykonuje przybliżony test trafienia, aby określić, czy proces renderowania iframe z adresu bar.com powinien otrzymać kliknięcie, i wysyła je tam. - Wątek kompozytora dla domeny bar.com przekazuje zdarzenie
click
do głównego wątku domeny bar.com i planuje zadanie pętli zdarzeń renderowania, aby je przetworzyć. - Przetwarzacz zdarzeń wejściowych w przypadku testów skuteczności głównego wątku strony bar.com, aby określić, który element DOM w ramce iframe został kliknięty, i uruchomić zdarzenie
click
, które mają obserwować skrypty. Słysząc „nie”,preventDefault
przejdzie do hiperlinku. - Po załadowaniu strony docelowej hiperlinku nowy stan jest renderowany. W tym celu należy wykonać czynności podobne do tych, które zostały opisane w poprzednim przykładzie „Renderowanie zmienionego DOM”. (nie pokazano tu kolejnych zmian).
Na wynos
Zapamiętanie i zrozumienie działania renderowania może zająć dużo czasu.
Najważniejsze jest to, że ścieżka renderowania została podzielona na kilka niezależnych komponentów dzięki starannej modularyzacji i dbałości o szczegóły. Następnie te komponenty zostały podzielone na równoległe procesy i wątki, aby zmaksymalizować skalowalną wydajność oraz możliwości rozszerzalności.
Każdy komponent odgrywa kluczową rolę w zapewnianiu wydajności i funkcji nowoczesnych aplikacji internetowych.
Czytaj dalej o kluczowych strukturach danych, które są dla RenderingNG równie ważne jak komponenty kodu.
Ilustracje: Una Kravets