Architektura renderowania NG

Chris Harrelson
Chris Harrelson

Zobaczysz tu, jak są skonfigurowane elementy komponentu RenderingNG i jak przepływa przez nie potok renderowania.

Początkowo renderowane są te zadania:

  1. Renderuj zawartość ekranu w pikselach na ekranie.
  2. Uruchamiaj efekty wizualne w różnych stanach treści.
  3. Przewiń w odpowiedzi na dane wejściowe.
  4. Sprawnie kieruj dane wejściowe we właściwe miejsca, aby skrypty programistyczne i inne podsystemy mogły na nie reagować.

Zawartość do wyrenderowania to drzewo ramek każdej karty przeglądarki oraz jej interfejs. 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 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 stosowana do bitmapy, np. przewijanie, przekształcanie, przycinanie, filtr, przezroczystość lub mieszanie.

Komponenty architektury

W RenderingNG zadania te są logicznie podzielone na kilka etapów i komponentów kodu. Komponenty te trafiają do różnych procesów procesora, wątków i podkomponentów wewnątrz tych wątków. Każdy z nich odgrywa ważną rolę w zapewnianiu niezawodności, skalowalności i elastyczności w odniesieniu do wszystkich treści internetowych.

Struktura potoku renderowania

Schemat potoku renderowania.
Strzałki wskazują dane wejściowe i wyjściowe każdego etapu. Etapy są oznaczane kolorami, aby pokazać, który wątek lub proces jest wykonywany. W niektórych przypadkach, zależnie od sytuacji, etapy mogą być realizowane w wielu miejscach, dlatego niektóre mają dwa kolory. Zielone etapy procesu renderowania to główny wątek procesu renderowania, żółty to kompozytory procesu renderowania, a pomarańczowe etapy to proces wizualizacji.

Renderowanie odbywa się w potoku z licznymi etapami i artefaktami utworzonymi po drodze. 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:

  1. Animuj:zmieniaj style obliczone i mutuj drzewa właściwości w czasie na podstawie deklaratywnych osi czasu.
  2. Styl: stosowanie CSS do DOM i tworzenie stylów obliczonych.
  3. Układ: określ rozmiar i pozycję elementów DOM na ekranie oraz utwórz drzewo z niezmiennymi fragmentami.
  4. Przed malowaniem: oblicz drzewa właściwości i unieważnij istniejące listy wyświetlanych list i fragmenty tekstur GPU.
  5. Przewijanie: aktualizowanie przesunięcia dokumentów i przewijanych elementów DOM za pomocą mutacji drzew właściwości.
  6. Paint: oblicz listę wyświetlania, która opisuje sposób rastrowania fragmentów tekstur GPU z DOM.
  7. Zatwierdzenie: skopiuj drzewa właściwości i listę wyświetlania do wątku kompozytora.
  8. Warstwa: podział listy wyświetlanych na listę warstw skomponowanych na potrzeby niezależnej rasteryzacji i animacji.
  9. Raster, dekodowanie i malowanie instancji: przekształcaj odpowiednio listy wyświetlania, zakodowane obrazy i maluj kod instancji odpowiednio w kafelki tekstur GPU.
  10. Aktywuj:pozwala utworzyć ramkę kompozytora reprezentującą sposób rysowania i umieszczania kafelków GPU na ekranie oraz wszelkich efektów wizualnych.
  11. Agregacja: połącz ramki kompozytora ze wszystkich widocznych ramek kompozytora w pojedynczą, globalną ramkę kompozytora.
  12. Draw: powoduje wykonanie zagregowanej ramki kompozytora w GPU, aby utworzyć piksele na ekranie.

Etapy potoku renderowania można pominąć, jeśli nie są potrzebne. Na przykład animacje efektów wizualnych i przewijanie mogą pomijać układ, wstępne wyrenderowanie i wyrenderowanie. Dlatego animacje i przewijanie są na diagramie oznaczone żółtymi i zielonymi punktami. Jeśli w przypadku efektów wizualnych można pominąć układ, wstępne wyrenderowanie i malowanie, można je uruchomić w całości w wątku kompozytora i pominąć główny wątek.

Renderowanie interfejsu przeglądarki nie zostało tu pokazane bezpośrednio, ale można je traktować jako uproszczoną wersję tego samego potoku (a w rzeczywistości jego implementacja współdzieli znaczną część kodu). Wideo (również nieprzedstawione bezpośrednio) jest zwykle renderowane za pomocą niezależnego kodu, który dekoduje klatki na kafelki tekstur GPU, które są następnie umieszczane w ramkach kompozytora i na etapie rysowania.

Proces i struktura wątków

Procesy procesora

Stosowanie wielu procesów na procesorach zapewnia izolację wydajności i bezpieczeństwa witryn i stanu przeglądarki, a także stabilność i bezpieczeństwo w związku ze sprzętem GPU.

Schemat różnych części procesów procesora

  • Proces renderowania renderuje, animuje, przewija i trasuje dane wejściowe w ramach 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. Obraz przesuwa się w górę i rysuje za pomocą GPU. Jeden proces Viz.

Różne witryny zawsze podlegają innym procesom renderowania.

Kilka kart lub okien przeglądarki z tą samą witryną zwykle jest przetwarzanych w różnych procesach renderowania, chyba że karty są ze sobą powiązane, np. gdy otwiera się drugi. 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 zawsze są renderowane w różny sposób, ale ramki z tej samej witryny są zawsze objęte tym samym procesem renderowania. Z punktu widzenia renderowania ważną zaletę wielu procesów renderowania jest to, że elementy iframe i karty pochodzące z innych witryn zapewniają odizolowanie wydajności od siebie nawzajem. Poza tym źródła mogą uzyskać jeszcze większą izolację.

Dla całej przeglądarki Chromium istnieje dokładnie jeden proces Viz, ponieważ zwykle wystarczy użyć tylko jednego GPU i 1 ekranu.

Rozdzielenie usługi Viz do własnego procesu zapewnia stabilność w przypadku błędów sprzętowych lub sterowników GPU. Dobrze sprawdza się również w przypadku izolacji zabezpieczeń, która jest ważna w przypadku interfejsów API GPU, takich jak Vulkan, i ogólnych zabezpieczeń.

Przeglądarka może mieć wiele kart i okien, a każdy z nich zawiera piksele interfejsu. Można się zastanawiać, dlaczego istnieje dokładnie jeden proces przeglądarki. Dzieje się tak, ponieważ w danym momencie aktywna jest tylko jedna z nich, a niewidoczne karty przeglądarki są w większości dezaktywowane i w ogóle zużywają pamięć GPU. Jednak złożone funkcje renderowania UI przeglądarki są coraz częściej wdrażane także w procesach renderowania (nazywanych WebUI). Nie wynika to z izolacji wydajności, ale z uwagi na łatwość obsługi silnika renderowania internetowego w 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 również udostępniany aplikacji umieszczonej na stronie, a komponent WebView używa tylko jednego procesu renderowania.

Niekiedy istnieje też proces narzędziowy do dekodowania chronionych treści wideo. Proces ten nie został zaprezentowany na poprzednich diagramach.

Wątki

Wątki pomagają uzyskać izolację wydajności i responsywność pomimo powolnych zadań, równoległego buforowania potoku i wielokrotnego buforowania.

Schemat procesu renderowania.

  • W wątku głównym są uruchamiane skrypty, pętla zdarzeń renderowania, cykl życia dokumentu, testowanie działań, wysyłanie zdarzeń skryptu oraz analizowanie formatów danych HTML, CSS i innych.
    • Asystenty wątków głównych wykonują zadania takie jak tworzenie obrazowych map bitowych i obiektów blob, które wymagają kodowania lub dekodowania.
    • Web Workers uruchamiają skrypt oraz pętlę zdarzeń renderowania dla OffscreenCanvas.
  • Wątek kompozytora przetwarza zdarzenia wejściowe, wykonuje przewijanie i animacje treści internetowych, oblicza optymalną warstwę warstwową treści z internetu oraz koordynuje dekodowanie obrazów, zadania malowania i zadania rastrowania.
    • Pomocnicy wątków kompozytora koordynują zadania rastrowania Viz oraz wykonują zadania związane z dekodowaniem obrazów, zadaniami malowania i rastrem zastępczym.
  • Wątki multimedialne, demuksery i wątki wyjściowe audio dekodują, przetwarzają i synchronizują strumienie wideo i audio. Pamiętaj, że film jest uruchamiany równolegle z głównym potokiem renderowania.

Oddzielenie wątku głównego i kompozytora jest niezwykle ważne ze względu na izolację wydajności animacji i przewijania od wątku głównego.

Na proces renderowania przypada tylko 1 wątek główny, nawet jeśli wiele kart lub klatek z tej samej witryny może zakończyć się tym samym procesem. Wydajność jest jednak odizolowana od pracy wykonywanej przy użyciu różnych interfejsów API przeglądarek. 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 instancji roboczej wykona działanie w trybie zastępczym procesora.

Liczba wątków instancji roboczych kompozytora zależy od możliwości urządzenia. Na przykład komputery stacjonarne zwykle używają większej liczby wątków, ponieważ mają więcej rdzeni procesora i są mniej ograniczone baterią niż urządzenia mobilne. To przykład skalowania w górę i w dół.

Architektura z wątkami procesu renderowania to zastosowanie 3 różnych wzorców optymalizacji:

  • Wątki pomocnicze: wysyłaj długotrwałe podzadania do dodatkowych wątków, aby wątek nadrzędny odpowiadał na inne, jednoczesne żądania. Dobrymi przykładami tej techniki są główne wątki pomocnicze wątków oraz kompozytora.
  • Buforowanie wielokrotne: wyświetlanie wcześniej wyrenderowanych treści podczas renderowania nowych treści pozwala ukryć opóźnienie renderowania. Wątek kompozytora używa tej techniki.
  • Równoległość potoku: możesz uruchomić potok renderowania w wielu miejscach jednocześnie. W ten sposób przewijanie i animacja mogą być szybkie. Nawet jeśli odbywa się aktualizacja renderowania głównego wątku, przewijanie i animacja mogą działać równolegle.

Proces przeglądarki

Diagram procesu przeglądarki przedstawiający relację między renderowaniem a wątkiem komponowania oraz pomocnikiem wątku renderowania i komponowania.

  • Wątek renderowania i komponowania odpowiada na dane wejściowe w interfejsie przeglądarki, kieruje inne dane wejściowe do właściwego procesu renderowania, umieszcza i maluje interfejs przeglądarki.
  • Asystenty renderowania i komponowania wątków wykonują zadania związane z dekodowaniem obrazów oraz rastrem i dekodowaniem zastępczym.

Renderowanie i komponowanie wątku w przeglądarce są podobne 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ż z założenia nie ma potrzeby izolacji wydajności od długich zadań z wątkiem głównym.

Proces Viz

Proces Viz obejmuje wątek główny GPU i wątek kompozytora wyświetlania.

  • Wątek główny GPU przesyła listy wyświetlania i klatki wideo do kafelków tekstur GPU, a także rysuje na ekranie ramki kompozytora.
  • Wątek kompozytora displayowego agreguje i optymalizuje komponowanie z każdego procesu renderowania (oraz z procesu przeglądarki) w jedną ramkę kompozytora, która jest prezentowana 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 komponencie WebView Androida jest osobny wątek renderowania na poziomie systemu operacyjnego, ponieważ komponenty WebView są wbudowane w aplikację natywną. Na innych platformach prawdopodobnie w przyszłości pojawi się taki wątek.

Kompozytor displayowych znajduje się w innym wątku, ponieważ musi zawsze odpowiadać i nie blokować żadnego możliwego źródła spowolnienia w głównym wątku GPU. Jedną z przyczyn spowolnienia w głównym wątku GPU są wywołania kodu spoza Chromium, np. sterowników GPU specyficznych dla dostawcy, które mogą działać wolniej w trudno do przewidzenia.

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 głównego wątku procesu renderowania

Schemat mechanizmu renderowania Blink.

W mechanizmie renderowania Blink:

  • Fragment lokalnego drzewa ramek reprezentuje drzewo lokalnych ramek i elementu DOM w ramkach.
  • Komponent DOM i Canvas API zawiera implementacje wszystkich tych interfejsów API.
  • Uruchamiający cykl życia dokumentu wykonuje kroki potoku renderowania aż do etapu zatwierdzenia włącznie.
  • 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 pętli zdarzeń renderowania i sposób uruchamiający określa, co i kiedy ma zostać uruchomione w pętli zdarzenia. Zaplanuje renderowanie z częstotliwością odpowiadającą wyświetlaczowi urządzenia.

Diagram drzewa ramek.

Fragmenty lokalnych drzew ramek są nieco skomplikowane. Zwróć uwagę, że drzewo ramek to rekurencyjna strona główna i jej podrzędne elementy iframe. Ramka jest lokalna dla procesu renderowania, jeśli jest renderowana w tym procesie. W przeciwnym razie jest zdalna.

Można sobie wyobrazić kolorowanie ramek zgodnie z procesem renderowania. Na poprzednim obrazie zielone okręgi to wszystkie klatki w ramach jednego procesu renderowania; pomarańczowe – w drugiej, a niebieskie w trzeciej.

Fragment drzewa ramek lokalnego to połączony komponent tego samego koloru w drzewie ramki. Na zdjęciu widać 4 lokalne drzewa z ramkami: 2 z miejsca A, 1 z miejscem B i 1 z miejscem C. Każde lokalne drzewo ramek otrzymuje własny komponent mechanizmu renderowania Blink. Mechanizm renderowania Blink lokalnego drzewa ramek może, ale nie musi, być renderowany w tym samym procesie renderowania co inne lokalne drzewa ramek. Zależy to od sposobu wybierania procesów renderowania, jak opisaliśmy wcześniej.

Struktura wątków kompozytora procesu renderowania

Diagram przedstawiający komponenty kompozytora procesu renderowania.

Komponenty kompozytora procesu renderowania obejmują:

  • Moduł obsługi danych, który obsługuje listę skomponowanych warstw, listy wyświetlania i drzewa właściwości.
  • Uruchamiający cykl życia, który uruchamia animowanie, przewijanie, komponowanie, rastrowanie oraz dekodowanie oraz aktywowanie kroków potoku renderowania. Pamiętaj, że animowanie i przewijanie mogą mieć miejsce zarówno w wątku głównym, jak i w kompozytorze.
  • Moduł obsługi danych wejściowych i testów trafień wykonuje przetwarzanie danych wejściowych i testowanie trafień w rozdzielczości skomponowanych warstw, aby określić, czy w wątku kompozytora można wykonać gesty przewijania i na które testy trafień procesu renderowania powinny być kierowane.

Przykładowa architektura w praktyce

W tym przykładzie znajdują się 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:

Schemat procesu korzystania z kart.

Omówmy po jednym przykładzie każdego z 4 głównych zadań renderowania. Przypomnienie:

  1. Renderuj treści na ekranie w piksele.
  2. animuj efekty wizualne, przechodząc z jednego stanu do drugiego.
  3. Przewiń w odpowiedzi na dane wejściowe.
  4. Sprawnie kieruj dane wejściowe w odpowiednie miejsca, aby skrypty programistyczne i inne podsystemy mogły na nie zareagować.

Aby wyrenderować zmieniony DOM na pierwszej karcie:

  1. Skrypt programisty zmienia DOM w procesie renderowania na potrzeby foo.com.
  2. Mechanizm renderowania Blink informuje kompozytora, że wymaga wyrenderowania.
  3. Kompozytor informuje zespół Viz, że wymaga wyrenderowania.
  4. Usługa Viz sygnalizuje początek renderowania z powrotem do kompozytora.
  5. Kompozytor przekazuje sygnał startowy do mechanizmu renderowania Blink.
  6. Uruchamiający pętlę zdarzeń głównego wątku uruchamia cykl życia dokumentu.
  7. Wątek główny wysyła wynik do wątku kompozytora.
  8. Uruchamiający pętlę zdarzeń kompozytora uruchamia cykl życia komponowania.
  9. Wszystkie zadania rastrowania są wysyłane do Viz w celu wykonania prac rastrowych (często istnieje więcej niż jedno z nich).
  10. Viz rasteryzuje treści w GPU.
  11. Usługa Viz potwierdza wykonanie zadania rastrowego. Uwaga: Chromium często nie czeka na zakończenie działania rastru i zamiast niego używa tzw. tokenu synchronizacji, który musi zostać rozwiązany przez zadania rastrowania przed wykonaniem kroku 15.
  12. Ramka kompozytora jest wysyłana do Viz.
  13. Viz gromadzi ramki kompozytora na potrzeby procesu renderowania foo.com, procesu renderowania iframe bar.com i interfejsu przeglądarki.
  14. Viz planuje losowanie.
  15. Viz pobiera na ekran zagregowaną ramkę kompozytora.

Aby animować przejście przekształcenia CSS na drugiej karcie:

  1. Wątek kompozytora procesu renderowania bar.com zaznacza animację w pętli zdarzeń kompozytora, modyfikując istniejące drzewa właściwości. Spowoduje to ponowne uruchomienie cyklu życia kompozytora. Zadania rastrowania i dekodowania mogą być wykonywane, ale nie zostały one tutaj opisane.
  2. Ramka kompozytora jest wysyłana do Viz.
  3. Viz agreguje ramki kompozytora na potrzeby procesu renderowania foo.com, procesu renderowania bar.com i interfejsu przeglądarki.
  4. Viz planuje losowanie.
  5. Viz pobiera na ekran zagregowaną ramkę kompozytora.

Aby przewinąć stronę internetową na karcie trzeciej:

  1. Przeglądarka uruchamia sekwencję zdarzeń input (mysz, dotyk lub klawiatura).
  2. Każde zdarzenie jest kierowane do wątku kompozytora procesu renderowania baz.com.
  3. Kompozytor określa, czy wątek główny musi mieć dostęp do zdarzenia.
  4. W razie potrzeby zdarzenie jest wysyłane do wątku głównego.
  5. Wątek główny uruchamia detektory zdarzeń input (pointerdown, touchstar, pointermove, touchmove lub wheel), aby sprawdzić, czy detektory wywołają zdarzenie preventDefault w zdarzeniu.
  6. Wątek główny zwraca, czy do kompozytora wywołano funkcję preventDefault.
  7. W przeciwnym razie zdarzenie wejściowe jest odsyłane z powrotem do procesu przeglądarki.
  8. Proces przeglądarki przekształca go w gest przewijania, łącząc go z innymi ostatnimi zdarzeniami.
  9. Gest przewijania jest wysyłany ponownie do wątku kompozytora procesu renderowania baz.com.
  10. Przewijanie jest stosowane w tym miejscu, a wątek kompozytora procesu renderowania bar.com zaznacza animację w pętli zdarzeń kompozytora. Następnie zmienia przesunięcie przewijania w drzewach właściwości i ponownie uruchamia cykl życia kompozytora. Informuje on też wątek główny o uruchamianiu zdarzenia scroll (nieprzedstawionego tutaj).
  11. Ramka kompozytora jest wysyłana do Viz.
  12. Viz agreguje ramki kompozytora na potrzeby procesu renderowania foo.com, procesu renderowania bar.com i interfejsu przeglądarki.
  13. Viz planuje losowanie.
  14. Viz pobiera na ekran zagregowaną ramkę kompozytora.

Aby przekierować zdarzenie click w hiperlinku w elemencie iframe nr 2 na karcie 1:

  1. Zdarzenie input (mysz, dotyk lub klawiatura) przekazuje działanie przeglądarki. Wykonuje on przybliżony test działań, aby określić, czy proces renderowania elementu iframe bar.com powinien otrzymać kliknięcie, a następnie je tam wysyła.
  2. Wątek kompozytora dla bar.com kieruje zdarzenie click do wątku głównego dla bar.com i planuje przetworzenie tego zadania w pętli zdarzeń renderowania.
  3. Procesor zdarzeń wejściowych dla testów trafień w głównym wątku na bar.com określa, który element DOM w elemencie iframe został kliknięty, i wywołuje zdarzenie click, które ma obserwować skrypty. Jeśli nie słyszysz słowa preventDefault, nastąpi przejście do hiperlinku.
  4. Po wczytaniu strony docelowej hiperlinku generowany jest nowy stan z krokami podobnymi do poprzedniego przykładu „renderowania zmienionego DOM”. (kolejne zmiany nie zostały tu pokazane).

Na wynos

Zapamiętanie i przyswojenie działania renderowania może zająć dużo czasu.

Najważniejszym wnioskiem jest to, że potok renderowania przez staranną modularyzację i dbałość o szczegóły został podzielony na wiele niezależnych komponentów. 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 z nich odgrywa kluczową rolę w umożliwianiu 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 Uny Kravets.