Zobaczysz tu, jak są skonfigurowane elementy komponentu RenderingNG i jak przepływa przez nie potok renderowania.
Początkowo renderowane są te zadania:
- 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.
- Sprawnie kieruj dane wejściowe we właściwe miejsca, aby skrypty programistyczne 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. Strumień nieprzetworzonych zdarzeń wejściowych 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 i jego adres URL. Strona internetowa wczytywana na karcie przeglądarki ma ramkę najwyższego poziomu, ramki podrzędne każdego elementu iframe uwzględnione w dokumencie najwyższego poziomu oraz ich rekurencyjne elementy podrzędne elementu iframe.
Efekt wizualny to operacja graficzna zastosowana do bitmapy, taka jak przewijanie, przekształcenie, przycinanie, filtrowanie, krycie lub mieszanie.
Komponenty architektury
W RenderingNG zadania te 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 1 dobrze zdefiniowane zadanie w ramach renderowania. Artefakty to struktury danych, które są danymi wejściowymi lub wyjściowymi etapami.
Etapy to:
- Animacja: zmiana obliczanych stylów i mutacje drzewa właściwości w czasie na podstawie deklaratywnych harmonogramów.
- Styl: stosowanie CSS do DOM i tworzenie stylów obliczeniowych.
- Układ: określ rozmiar i pozycję elementów DOM na ekranie oraz utwórz drzewo z niezmiennymi fragmentami.
- Przed malowaniem: oblicz drzewa właściwości i invalidate istniejące listy wyświetlanych list i fragmenty tekstur GPU.
- 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 zarasteryzować elementy tekstury GPU z DOM.
- Commit: 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ąć ze względu na efekty wizualne, można je wykonać całkowicie na wątku kompozytora, pomijając wątek główny.
Renderowanie interfejsu przeglądarki nie jest tutaj bezpośrednio przedstawione, ale można je traktować jako uproszczoną wersję tego samego potoku (w rzeczywistości jego implementacja zawiera wiele kodu). Film (również nie pokazany bezpośrednio) jest zwykle renderowany za pomocą niezależnego kodu, który dekoduje klatki w płytki tekstur GPU, a potem przesyła je do kompozycji klatek i do kroku rysowania.
Proces i struktura wątków
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 trafia dane wejściowe dla interfejsu przeglądarki (w tym pasek adresu, tytuły kart i ikony), a potem kieruje wszystkie pozostałe dane wejściowe do odpowiedniego procesu renderowania. Występuje jeden proces przeglądarki.
- Proces Viz łączy kompozycje z wielu procesów renderowania oraz 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 Chromium może umieścić wiele kart z tej samej witryny w tym samym procesie renderowania, nawet jeśli nie są one 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. Poza tym źródła mogą 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.
Rozdzielenie usługi Viz do własnego procesu zapewnia stabilność w przypadku błędów sprzętowych lub sterowników GPU. Jest to też dobre rozwiązanie w zakresie 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 UI przeglądarki są coraz częściej wdrażane także w procesach renderowania (nazywanych 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 stron Chromium.
Na starszych urządzeniach z Androidem procesy renderowania i przeglądarki są wspólne, gdy są używane w komponencie WebView (nie dotyczy to ogólnie 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 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.
- Pomocnicy wątków kompozytora koordynują zadania rastrowania Viz oraz wykonują zadania związane z dekodowaniem obrazów, zadaniami malowania i rastrem zastępczym.
- 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 obrazów bitmap i blobów w interfejsie Canvas API jest uruchamiane w wątku pomocniczym wątku głównego.
Podobnie na proces renderowania występuje tylko 1 wątek kompozytora. Zasadniczo nie jest problemem, że jest tylko jeden, ponieważ wszystkie bardzo kosztowne operacje na wątku kompozytora są delegowane albo do wątków instancji roboczych kompozytora, albo do procesu Viz. Tę pracę można wykonywać równolegle z kierowaniem danych wejściowych, przewijaniem lub animacją. Wątki instancji roboczych kompozytora koordynują zadania wykonywane w procesie Viz, ale akceleracja GPU we wszystkich miejscach może zakończyć się niepowodzeniem z powodów pozostających poza kontrolą Chromium, takich jak błędy sterownika. 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ładem tej techniki są wątki pomocnicze głównego wątku 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 odbywa się 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.
- Pomocnicze wątki renderowania i kompletowania 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.
Raster i rysowanie zazwyczaj odbywają się w tym samym wątku, ponieważ oba te elementy opierają się na zasobach GPU, przez co trudno jest w niezawodny sposób wykorzystać wielowątkowy układ GPU (łatwiejszy wielowątkowy dostęp do GPU 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 głównym lub kompozycyjnym wątku procesu renderowania występują logiczne komponenty oprogramowania, które współdziałają ze sobą w uporządkowany sposób.
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 i interfejsy API Canvas zawiera implementacje wszystkich tych interfejsów API.
- Program do obsługi cyklu życia dokumentu wykonuje kroki potoku renderowania aż do kroku zatwierdzania.
- Komponent testowanie i wysyłanie działań związanych ze zdarzeniem wejściowym wykonuje testy działań, aby ustalić, na który element DOM jest kierowane zdarzenie, oraz uruchamia algorytmy wysyłające zdarzenia wejściowe i działania domyślne.
Harmonogram i wykonywacz pętli zdarzeń do renderowania decydują, co i kiedy ma być wykonywane w pętli zdarzeń. Zaplanuje 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 ramki iframe podrzędne, które są w niej zawarte. Ramka jest lokalna dla procesu 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.
Fragment drzewa ramek lokalnego to połączony komponent tego samego koloru w drzewie ramki. Na obrazie widać 4 drzewa lokalnych ramek: 2 dla witryny A, 1 dla witryny B i 1 dla witryny C. Każde lokalne drzewo ramek otrzymuje własny komponent mechanizmu 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
Komponenty kompozytora procesu renderowania obejmują:
- 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, łączenie, rasterowanie, 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>
Proces, wątki i struktura komponentów na tych kartach wyglądają tak:
Przyjrzyjmy się poszczególnym przykładom 4 głównych zadań związanych z renderowaniem. Przypomnienie:
- Wyświetla treści w pikselach na ekranie.
- Animate efekty wizualne na treściach, aby przechodziły one z jednego stanu w inny.
- Przewiń 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.
- Mechanizm renderowania Blink informuje kompozytora, że wymaga wyrenderowania.
- Kompozytor informuje Viz, że musi nastąpić renderowanie.
- Viz sygnalizuje rozpoczęcie renderowania do kompozytora.
- Kompozytor przekazuje sygnał startowy do mechanizmu 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 rastrowania są wysyłane do Viz w celu wykonania prac rastrowych (często istnieje więcej niż jedno z nich).
- Viz rasteryzuje zawartość na karcie graficznej.
- Wizualizacja potwierdza wykonanie zadania związanego z pobraniem. 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 animować przejście transformacji CSS na drugiej karcie:
- Wątek kompozytora procesu renderowania bar.com zaznacza animację w pętli zdarzeń kompozytora, modyfikują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 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 przewinąć stronę internetową na karcie trzeciej:
- 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ściowe jest odsyłane z powrotem do procesu przeglądarki.
- Proces przeglądarki przekształca go w gest 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). - Ramka kompozytora jest wysyłana do Viz.
- Viz agreguje ramki kompozytora na potrzeby procesu renderowania foo.com, procesu renderowania bar.com i interfejsu przeglądarki.
- Viz planuje losowanie.
- Viz pobiera na ekran zagregowaną ramkę 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. Jeśli nie słyszysz słowapreventDefault
, nastąpi przejście 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 przyswojenie 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. Komponenty te zostały następnie podzielone na równoległe procesy i wątki w celu maksymalizacji skalowalności i elastyczności.
Każdy komponent odgrywa kluczową rolę w zapewnianiu wydajności i funkcji nowoczesnych aplikacji internetowych.
Poczytaj o kluczowych strukturach danych, które są dla RenderingNG równie ważne jak komponenty kodu.
Ilustracje: Una Kravets