Kluczowe struktury danych i ich role w RenderingNG

Chris Harrelson
Chris Harrelson
Daniel Cheng
Daniel Cheng
Philip Rogers
Philip Rogers
Koji Ishi
Koji Ishi
Ian Kilpatrick
Ian Kilpatrick
Kyle Charbonneau
Kyle Charbonneau

Wcześniejsze posty z tej serii zawierały omówienie celów, kluczowych właściwości i wysokich elementów składowych architektury RenderingNG. Przyjrzyjmy się teraz kluczowym strukturom danych, czyli danych wejściowych i wyjściowych trafiających do potoku renderowania.

Struktury danych to:

  • Drzewa ramek, które składają się z lokalnych i zdalnych węzłów, które określają, w którym procesie renderowania występują dokumenty internetowe i który mechanizm renderowania Blink.
  • Stałe drzewo fragmentów reprezentuje dane wyjściowe algorytmu ograniczeń układu (i dane wejściowe).
  • Drzewa właściwości, które reprezentują hierarchie przekształcania, przycinania, efektów i przewijania dokumentu internetowego. Są używane w całym potoku.
  • Listy wyświetlane i fragmenty renderowania to dane wejściowe algorytmów rastrowania i warstwy.
  • Ramki kompozytora obejmują powierzchnie, powierzchnie renderowania i kafelki tekstur GPU, które są używane do rysowania za pomocą GPU.

Zanim omówimy te struktury danych, chcę zobaczyć prosty przykład oparty na danych z poprzedniego posta. Wykorzystam ten przykład kilka razy w tym poście, aby pokazać, jak mają do niego zastosowanie struktury danych.

<html>
  <div style="overflow: hidden; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
      id="one" src="foo.com/etc"></iframe>
  </div>
  <iframe style="top:200px;
    transform: scale(1.1) translateX(200px)"
    id="two" src="bar.com"></iframe>
</html>

Drzewa ramkowe

Chrome może czasami renderować ramkę z innych domen w procesie renderowania innym niż jej ramka nadrzędna.

We wstępie widać 3 klatki łącznie:

Nadrzędna ramka foo.com zawierająca dwa elementy iframe.

Po włączeniu izolacji witryn Chromium wyrenderuje tę stronę internetową za pomocą 2 procesów renderowania. Każdy proces renderowania ma własną reprezentację drzewa ramek dla danej strony internetowej:

Dwa ramki reprezentujące 2 procesy renderowania.

Ramka wyrenderowana w innym procesie jest reprezentowana jako ramka zdalna. Zdalna ramka zawiera minimalną ilość informacji potrzebnych do działania jako obiekt zastępczy w renderowaniu, np. jej wymiary. Zdalna ramka nie zawiera żadnych informacji potrzebnych do wyrenderowania jej rzeczywistej zawartości.

W przeciwieństwie do tego ramka lokalna reprezentuje ramkę, która przechodzi przez standardowy potok renderowania opisany w poprzednich postach. Ramka lokalna zawiera wszystkie informacje niezbędne do przekształcenia danych z tej ramki (np. drzewa DOM i danych o stylu) w elementy, które można wyrenderować i wyświetlić.

Potok renderowania działa na szczegółowości fragmentu drzewa ramki lokalnej. Przeanalizujmy bardziej skomplikowany przykład, w którym główna ramka to foo.com:

<iframe src="bar.com"></iframe>

Oraz ta ramka podrzędna bar.com:

<iframe src="foo.com/etc"></iframe>

Chociaż nadal są tylko 2 mechanizmy renderowania, istnieją teraz 3 lokalne fragmenty drzewa ramki – dwa są w procesie renderowania dla foo.com i jeden w procesie renderowania bar.com:

Reprezentacja 2 wyrenderowanych elementów i 3 fragmentów drzewa ramki.

Aby utworzyć jedną ramkę kompozytora dla strony internetowej, Viz jednocześnie wysyła żądanie ramki kompozytora z ramki głównej każdej z 3 lokalnych drzew ramek, a następnie agreguje je. Zapoznaj się też z sekcją ramek kompozytora w dalszej części tego posta.

Ramka główna foo.com i ramka podrzędna foo.com/other-page są częścią tego samego drzewa ramek i są renderowane w tym samym procesie. Te 2 ramki nadal mają jednak niezależne cykle życia dokumentów, ponieważ są częścią różnych lokalnych fragmentów drzewa ramek. Z tego powodu nie można wygenerować jednej ramki kompozytora dla obu w ramach jednej aktualizacji. W trakcie renderowania nie ma wystarczającej ilości informacji, aby skomponować ramkę kompozytora wygenerowaną dla interfejsu foo.com/other-page bezpośrednio do ramki kompozytora dla ramki głównej foo.com. Na przykład ramka nadrzędna bar.com poza procesem może wpływać na wyświetlanie elementu iframe foo.com/other-url przez przekształcenie elementu iframe za pomocą CSS lub zasłanianie części elementu iframe innymi elementami w elemencie DOM.

Kaskada aktualizacji właściwości wizualnych

Właściwości wizualne, takie jak współczynnik skali urządzenia i rozmiar widocznego obszaru, wpływają na renderowane dane wyjściowe i muszą być synchronizowane między fragmentami lokalnego drzewa ramek. Z katalogiem głównym każdego fragmentu lokalnego drzewa ramki jest powiązany obiekt widżetu. Aktualizacje właściwości wizualnych są przekazywane do widżetu ramki głównej, a następnie do pozostałych widżetów, zaczynając od góry do dołu. Jeśli na przykład zmieni się rozmiar widocznego obszaru:

Schemat procesu omówiony w poprzednim tekście.

Ten proces nie jest natychmiastowy, więc zreplikowane właściwości wizualne również zawierają token synchronizacji. Kompozytor Viz używa tego tokena synchronizacji, aby poczekać, aż wszystkie fragmenty drzewa ramki lokalnej prześlą ramkę kompozytora z bieżącym tokenem synchronizacji. Ten proces pozwala uniknąć mieszania ramek kompozytora o różnych właściwościach wizualnych.

Stałe drzewo fragmentów

Stałe drzewo fragmentów to dane wyjściowe etapu układu potoku renderowania. Odzwierciedla położenie i rozmiar wszystkich elementów na stronie (bez przekształceń).

Reprezentacja fragmentów każdego drzewa, przy czym jeden fragment jest oznaczony jako wymagający układu.

Każdy fragment reprezentuje część elementu DOM. Zwykle każdy element zawiera tylko 1 fragment, ale może być ich więcej, jeśli podczas drukowania jest on podzielony na różne strony lub w przypadku kolumn wielokolumnowych.

Po zastosowaniu układu każdy fragment staje się stały i nigdy się nie zmienia. Co ważne, stosujemy też kilka dodatkowych ograniczeń. Czego nie robimy:

  • Zezwalaj na wszystkie odwołania typu „up” w drzewie. Dziecko nie może mieć wskaźnika do elementu nadrzędnego.
  • „bąbel” z drzewa (dziecko odczytuje informacje tylko od swoich elementów podrzędnych, a nie od elementu nadrzędnego).

Ograniczenia te pozwalają nam ponownie wykorzystać dany fragment w kolejnym układzie. Bez tych ograniczeń musielibyśmy często ponownie wygenerować całe drzewo, co jest kosztowne.

Większość układów to zwykle aktualizacje przyrostowe, np. aplikacja internetowa aktualizuje małą część interfejsu w odpowiedzi na kliknięcie elementu przez użytkownika. Idealnie układ powinien działać proporcjonalnie do zmian na ekranie. Możemy to osiągnąć, ponownie wykorzystując jak najwięcej części poprzedniego drzewa. Oznacza to (zazwyczaj) tylko, że musimy odbudować kręgosłup drzewa.

Ten niezmienny projekt pozwoli nam w przyszłości robić ciekawe rzeczy, np. przekazywać stałe drzewo fragmentów przez granice wątków (aby wykonać kolejne fazy w innym wątku), generować wiele drzew na potrzeby płynnej animacji układu lub wykonywać równoległe układy spekulacyjne. Daje nam to również potencjał w postaci układu wielowątkowego.

Elementy wbudowane w fragment

Treść poboczna (głównie stylizowana) ma nieco inną reprezentację. Zamiast struktury drzewa z pudełkami i wskaźnikami pokazujemy zawartość w tekście na płaskiej liście reprezentującej drzewo. Główną zaletą jest taka, że płaska reprezentacja dla elementów wewnętrznych jest szybka i przydatna przy badaniu tych struktur danych lub wysyłaniu do nich zapytań, a także zajmuje mało pamięci. Jest to niezwykle ważne w przypadku wydajności renderowania stron internetowych, ponieważ renderowanie tekstu jest bardzo złożone i może stać się najwolniejszym elementem potoku, jeśli nie zostanie wysoce zoptymalizowany.

Ciekawe jest to, że model DOM był w przeszłości bardzo podobny do tego, jak Internet Explorer prezentował swój model DOM (początkowo podobny do edytora tekstu).

Lista płaska jest tworzona dla każdego kontekstu formatowania wbudowanego w kolejności, w której najpierw przeszukiwane są dane w poddrzewie układu wbudowanego. Każda pozycja na liście jest kropką (obiekt, liczba elementów podrzędnych). Weźmy na przykład ten DOM:

<div style="width: 0;">
  <span style="color: blue; position: relative;">Hi</span> <b>there</b>.
</div>

(Zwróć uwagę, że właściwość width jest ustawiona na 0, więc wiersz zawija się między słowami „Cześć” i „tam”). Jeśli kontekst formatowania wbudowanego w tej sytuacji jest przedstawiony w postaci drzewa, wygląda on tak:

{
  "Line box": {
    "Box <span>": {
      "Text": "Hi"
    }
  },
  "Line box": {
    "Box <b>": {
      "Text": "There"
    }
  },
  {
    "Text": "."
  }
}

Prosta lista wygląda tak:

  • (Pole tekstowe, 2)
  • (Pole <span>, 1)
  • (Wyślij SMS-a: „Cześć”, 0).
  • (Pole tekstowe, 3)
  • (Pole <b>, 1)
  • (Tekst „tam”, 0)
  • (Tekst „.", 0)

Tę strukturę danych ma wielu użytkowników: interfejsy API ułatwień dostępu i interfejsy API geometrii, takie jak getClientRects i contentedit. Każdy z nich ma inne wymagania. Te komponenty uzyskują dostęp do płaskiej struktury danych za pomocą wygodnego kursora.

Kursor zawiera interfejsy API takie jak MoveToNext, MoveToNextLine i CursorForChildren. Sposób przedstawiania kursora jest bardzo skuteczny w przypadku treści tekstowych z wielu powodów:

  • Wykonanie iteracji w kolejności przede wszystkim wyszukiwania jest bardzo szybkie. Stosuje się je bardzo często, ponieważ przypomina ruchy kursora. Ponieważ jest to lista płaska, wyszukiwanie najpierw na poziomie głębokości zwiększa tylko przesunięcie tablicy, zapewniając szybkie iteracje i lokalizację pamięci.
  • Zapewnia wyszukiwanie zaawansowane, co jest konieczne np. przy malowaniu tła pól liniowych i wbudowanych.
  • Znajomość liczby elementów podrzędnych przyspiesza przejście do kolejnego elementu równorzędnego (po prostu zwiększ przesunięcie tablicy o tę liczbę).

Drzewa posesji

DOM to drzewo elementów (plus węzły tekstowe), a CSS mogą stosować do nich różne style.

Występują one głównie na 4 rodzaje efektów:

  • Układ: dane wejściowe algorytmu ograniczenia układu.
  • Farba: sposób malowania i rastrowania elementu (ale nie jego elementów potomnych).
  • Obraz: efekty rastrowe i rysunkowe zastosowane do poddrzewa DOM, takie jak przekształcenia, filtry i przycinanie.
  • Przewijanie: wyrównanie do osi i zaokrąglenie narożnika oraz przewijanie zawartego w nim poddrzewa.

Drzewa właściwości to struktury danych, które wyjaśniają, w jaki sposób efekty wizualne i przewijane odnoszą się do elementów DOM. Zapewniają one odpowiedzi na takie pytania jak: gdzie w stosunku do ekranu znajduje się dany element DOM, biorąc pod uwagę jego rozmiar i położenie? Jakiej sekwencji operacji GPU należy użyć, aby zastosować efekty wizualne i przewijanie?

Efekty wizualne i przewijane w internecie są bardzo skomplikowane w pełnej okazałości. Najważniejszą rzeczą, jaką odgrywają drzewa właściwości, jest przekształcenie tej złożoności w jedną strukturę danych, która dokładnie odzwierciedla ich strukturę i znaczenie, a jednocześnie eliminuje resztę złożoności DOM i CSS. Dzięki temu możemy z większą pewnością wdrożyć algorytmy komponowania i przewijania. W szczególności:

  • Geometrię i inne obliczenia narażone na błędy można zebrać w jednym miejscu.
  • Złożoność tworzenia i aktualizowania drzew właściwości można zebrać w jednym etapie potoku renderowania.
  • Wysyłanie drzew właściwości do innych wątków i procesów jest znacznie łatwiejsze i szybsze niż w przypadku pełnego stanu DOM, dzięki czemu można ich używać w wielu przypadkach.
  • Im więcej przypadków użycia, tym więcej korzyści możemy uzyskać z umieszczonego na górze buforowania geometrii, ponieważ użytkownicy mogą ponownie wykorzystywać pamięci podręczne innych osób.

RenderingNG wykorzystuje drzewa właściwości do wielu celów, takich jak:

  • Oddzielenie komponowania od farby i komponowanie z wątku głównego.
  • Określenie optymalnej strategii komponowania / rysowania.
  • pomiar geometrii IntersectionObserver;
  • Unikaj pracy w przypadku elementów poza ekranem i kafelków tekstur GPU.
  • Efektywnie i dokładnie unieważnia farbę i rastrę.
  • Pomiar zmiany układu i największego wyrenderowania treści w podstawowych wskaźnikach internetowych.

Każdy dokument internetowy ma 4 osobne drzewa właściwości: przekształcenie, klip, efekt i przewijanie.(*) Drzewo przekształcone reprezentuje przekształcenia i przewijanie CSS. Przekształcenie przewijania jest przedstawiane jako macierz przekształceń 2D. Drzewo klipów reprezentuje klipy nadmiarowe. Drzewo efektów prezentuje wszystkie pozostałe efekty wizualne: przezroczystość, filtry, maski, tryby mieszania oraz inne rodzaje klipów, takie jak clip-path. Drzewo przewijania zawiera informacje o przewijaniu, takie jak sposób przewijania łańcucha. Jest ono potrzebne do przewijania wątku kompozytora. Każdy węzeł w drzewie właściwości reprezentuje przewijanie lub efekt wizualny stosowany przez element DOM. Jeśli tak się stanie, w każdym drzewie może istnieć więcej niż 1 węzeł drzewa właściwości dla tego samego elementu.

Topologia każdego drzewa jest jak rzadka reprezentacja modelu DOM. Jeśli na przykład są 3 elementy DOM z klipami nadmiarowymi, zostaną utworzone 3 węzły drzewa klipów, a struktura drzewa klipów będzie przebiegać zgodnie z relacją bloku między tymi klipami. Między drzewami znajdują się także linki. Połączenia te wskazują względną hierarchię DOM, a tym samym kolejność aplikacji węzłów. Jeśli np. przekształcenie elementu DOM znajduje się pod innym elementem DOM z filtrem, to oczywiście przekształcenie następuje przed filtrem.

Każdy element DOM ma stan drzewa właściwości w postaci 4 kropek (przekształcanie, klip, efekt, przewijanie), który wskazuje najbliższy klip nadrzędny, przekształcenie i węzły drzewa efektów, które mają zastosowanie do tego elementu. To bardzo wygodne, bo dzięki tej informacji znamy dokładnie listę klipów, przekształceń i efektów, które mają zastosowanie do danego elementu, i w jakiej kolejności. To pokazuje, gdzie znajduje się on na ekranie i jak go narysować.

Przykład

(źródło).

<html>
  <div style="overflow: scroll; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
  id="one" srcdoc="iframe one"></iframe>
  </div>
  <iframe style="top:200px;
      transform: scale(1.1) translateX(200px)" id=two
      srcdoc="iframe two"></iframe>
</html>

W poprzednim przykładzie (który nieco różni się od tego przedstawionego we wprowadzeniu) poniżej przedstawiamy kluczowe elementy wygenerowanych drzew właściwości:

Przykład różnych elementów drzewa właściwości.

Wyświetlanie list i fragmentów farb

Element wyświetlany zawiera polecenia rysowania niskiego poziomu (patrz tutaj), które można zrastrować za pomocą Skia. Wyświetlane elementy są zwykle proste i wymaga tylko kilku poleceń rysowania, takich jak obramowanie lub tło. Drzewo farb przesuwa się po drzewie układu i powiązanych z nim fragmentów zgodnie z kolejnością malowania CSS, aby utworzyć listę wyświetlanych elementów.

Na przykład:

Niebieskie pole z napisami „Witaj świecie” w zielonym prostokącie.

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="blue" style="width:100px;
  height:100px; background:blue;
  position:absolute;
  top:0; left:0; z-index:-1;">
</div>

Kod HTML i CSS wygeneruje następującą listę wyświetlania, w której każda komórka jest elementem displayowym:

Tło widoku Tło: #blue Tło: #green #green tekst w tekście
drawRect o wymiarach 800 x 600 i kolorze białym. drawRect o wymiarach 100 x 100 na pozycji 0,0 i kolorze niebieskim. drawRect o wymiarach 80 x 18 na pozycji 8,8 i kolorze zielonym. drawTextBlob na pozycji 8,8 i tekst „Hello world”.

Lista produktów w wyświetlanej kolejności jest uporządkowana od tyłu. W powyższym przykładzie zielony element div znajduje się przed niebieskim elementem div w kolejności DOM, ale kolejność malowania CSS wymaga, by niebieski element div o ujemnym z-indeksie nakładał się przed (krok 3) zielonym elementem div (krok 4.1). Wyświetlane elementy odpowiadają w przybliżeniu atomom specyfikacji kolejności wyświetlania farb w CSS. Pojedynczy element DOM może odpowiadać kilku elementom wyświetlania, na przykład #green zawiera element wyświetlania dla tła i inny element wyświetlania dla tekstu wbudowanego. Ten poziom szczegółowości jest ważny, ponieważ pokazuje pełną złożoność specyfikacji kolejności malowania CSS, np. przeplatania wynikającego z ujemnej marży:

Zielony prostokąt z częściowo nałożonym szarym polem i napisem „Hello, world”.

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="gray" style="width:35px; height:20px;
  background:gray;margin-top:-10px;"></div>

Spowoduje to utworzenie takiej listy wyświetlania, w której każda komórka jest elementem do wyświetlania:

Tło widoku Tło: #green Tło: #gray #green tekst w tekście
drawRect o wymiarach 800 x 600 i kolorze białym. drawRect o wymiarach 80 x 18 na pozycji 8,8 i kolorze zielonym. drawRect o wymiarach 35 x 20 w pozycji 8,16 z kolorem szarym. drawTextBlob na pozycji 8,8 i tekst „Hello world”.

Wyświetlana lista elementów jest przechowywana i używana ponownie na potrzeby późniejszych aktualizacji. Jeśli obiekt układu nie zmienił się podczas spaceru po drzewie, jego wyświetlane elementy są kopiowane z poprzedniej listy. Dodatkowa optymalizacja korzysta z właściwości specyfikacji kolejności malowania CSS: grupowanie kontekstów jest malowane atomowo. Jeśli żaden obiekt układu nie zmienił się w kontekście nakładania się, funkcja spacer po drzewie pomija kontekst nakładania i kopiuje całą sekwencję wyświetlanych elementów z poprzedniej listy.

Bieżący stan drzewa właściwości jest utrzymywany podczas spaceru po drzewie właściwości, a lista wyświetlanych elementów jest podzielona na „fragmenty” elementów displayowych, które mają ten sam stan drzewa właściwości. Widać to w tym przykładzie:

Różowe pudełko z przechylonym pomarańczowym pudełkiem.

<div id="scroll" style="background:pink; width:100px;
   height:100px; overflow:scroll;
   position:absolute; top:0; left:0;">
    Hello world
    <div id="orange" style="width:75px; height:200px;
      background:orange; transform:rotateZ(25deg);">
        I'm falling
    </div>
</div>

Spowoduje to utworzenie takiej listy wyświetlania, w której każda komórka jest elementem do wyświetlania:

Tło widoku Tło: #scroll #scroll tekst w tekście Tło: #orange #orange tekst w tekście
drawRect o wymiarach 800 x 600 i kolorze białym. drawRect o wymiarach 100 x 100 na pozycji 0,0 i kolorze różowym. drawTextBlob z pozycją 0,0 i tekstem „Hello world”. drawRect o rozmiarze 75 x 200 na pozycji 0,0 i kolorze pomarańczowym. drawTextBlob z pozycją 0,0 i tekstem „Spadam”.

Drzewo właściwości przekształcenia i fragmenty farby miałyby wtedy postać (uproszczoną dla zwięzłości):

Obraz przedstawiający poprzednią tabelę, pierwsze 2 komórki we fragmencie 1, trzeci we fragmencie 2, ostatnie 2 komórki we fragmencie 3.

Uporządkowana lista fragmentów renderowania, które są grupami wyświetlanych elementów i stanem drzewa właściwości, to dane wejściowe etapu warstwowego potoku renderowania. Cała lista fragmentów renderowania można połączyć w 1 skomponowaną warstwę i zrastrować razem, ale wymagałoby to kosztownej rasteryzacji przy każdym przewijaniu przez użytkownika. Dla każdego fragmentu wyrenderowanego materiału można utworzyć skomponowaną warstwę i zrasteryzować ją pojedynczo, aby uniknąć ponownej rasteryzacji, ale spowoduje to szybkie wyczerpanie pamięci GPU. Etap nakładania warstw musi znaleźć kompromis między ilością pamięci GPU i obniżeniem kosztów w razie zmian. Dobrym ogólnym podejściem jest domyślne scalanie fragmentów, a nie scalanie fragmentów renderowania, które mają stany drzewa właściwości, które powinny zmieniać się w wątku kompozytora, na przykład przewijanie wątku kompozytora lub animacje przekształcania wątku kompozytora.

Poprzedni przykład powinien w idealnym przypadku uzyskać 2 skomponowane warstwy:

  • Skomponowana warstwa 800 x 600 zawierająca polecenia do rysowania:
    1. drawRect o wymiarach 800 x 600 i kolorze białym
    2. drawRect o rozmiarze 100 x 100 na pozycji 0,0 i kolorze różowym
  • Skomponowana warstwa 144 x 224 zawierająca polecenia do rysowania:
    1. drawTextBlob z pozycją 0,0 i tekstem „Hello world”
    2. przetłumacz 0,18
    3. rotateZ(25deg)
    4. drawRect o rozmiarze 75 x 200 na pozycji 0,0 i kolorze pomarańczowym
    5. drawTextBlob z pozycją 0,0 i tekstem „Spadam”

Jeśli użytkownik przewinie stronę #scroll, druga skomponowana warstwa zostanie przeniesiona, ale rasteryzacja nie będzie konieczna.

W tym przykładzie z poprzedniej sekcji dotyczącej drzew właściwości jest 6 fragmentów farby. Razem ze stanami drzewa właściwości (przekształcenie, klip, efekt, przewijanie) są one:

  • Tło dokumentu: przewijanie dokumentu, klip do dokumentu, katalog główny, przewijanie dokumentu.
  • Poziomy, pionowy i przewijany narożnik elementu div (3 osobne fragmenty tekstu): przewijanie dokumentu, klip z dokumentu, rozmycie #one, przewijanie dokumentu.
  • Element iframe #one: obrót #one, klip z przewijaniem przewijania, rozmycie #one, przewijanie div.
  • Element iframe #two: skala #two, klip do dokumentu, element główny, przewijanie dokumentu.

Ramki kompozytora: powierzchnie, powierzchnie renderowania i kafelki tekstur GPU

Jak wspomnieliśmy w poprzednim poście (przykład zastosowania znajdziesz tutaj), procesy przeglądarki i renderowania zarządzają rasteryzacją treści, a następnie przesyłają ramki kompozytora do procesu Viz w celu prezentacji na ekranie. Ramki kompozytora to sposób, w jaki RenderingNG przedstawia sposób łączenia zrastrowanych treści i efektywnego rysowania za pomocą GPU.

Karty

Teoretycznie kompozytor procesu renderowania lub przeglądarki mógłby rasteryzować piksele w pojedynczą teksturę o pełnym rozmiarze widocznego obszaru renderowania i przesłać ją do Viz. Aby ją wyświetlić, kompozytor wyświetlania musiałby skopiować piksele z tej pojedynczej tekstury do odpowiedniego miejsca w buforze klatek (na przykład na ekranie). Jeśli jednak ten kompozytor chciałby zaktualizować choćby 1 piksel, musiałby ponownie zrastrować cały widoczny obszar i przesłać do Viz nową teksturę.

Zamiast tego jest on podzielony na kafelki. Osobny kafelek tekstury GPU jest tył każdego kafelka z zrastrowanymi pikselami stanowiącymi część widocznego obszaru. Renderowanie może potem aktualizować poszczególne kafelki, a nawet zmieniać ich pozycję na ekranie. Na przykład podczas przewijania strony pozycja istniejących kafelków zmienia się w górę i niekiedy w przypadku treści znajdujących się niżej na stronie musi być zrasteryzowana nowa karta.

Cztery kafelki.

Powyższa grafika przedstawia słoneczny dzień złożony z 4 kafelków. Po przewinięciu strony pojawia się piąty kafelek. Jeden z kafelków ma tylko jeden kolor (niebieski), na górze znajdują się film i element iframe. A to prowadzi do następnego tematu.

Czwórki i powierzchnie

Kafelki tekstur GPU to specjalny rodzaj quad, który jest bardzo wymyślną nazwą dla jednej lub drugiej kategorii tekstur. Kwadrat identyfikuje teksturę wejściową i wskazuje, jak ją przekształcić i zastosować do niej efekty wizualne. Na przykład zwykłe kafelki z zawartością mają przekształcenie wskazujące ich pozycję x i y w siatce.

Kafelki tekstur GPU.

Te zrasteryzowane kafelki są umieszczone w karnetu renderowania, czyli liście czworokątów. Renderowanie nie zawiera żadnych informacji o pikselach. Zamiast tego dostępne są instrukcje, gdzie i jak narysować każdy kwadrat w celu uzyskania oczekiwanych rezultatów w pikselach. Dla każdego kafelka tekstury GPU istnieje czwórka rysowania. Musi tylko powtórnie przeglądać listę czworokątów z zastosowaniem określonych efektów wizualnych do każdego z nich, aby uzyskać pikselowy wynik operacji renderowania. Komponowanie czworokątów do rysowania na potrzeby renderowania można efektywnie wykonywać w GPU, ponieważ dozwolone efekty wizualne są starannie wybierane, aby odpowiadały bezpośrednio funkcjom GPU.

Poza rastrowanymi kafelkami dostępne są też inne typy czworokątów. Istnieją na przykład czworokąty do rysowania w jednolitym kolorze, które w ogóle nie są obsługiwane przez teksturę, lub czworoboki do rysowania tekstur w przypadku tekstur niebędących kafelkami, takich jak filmy czy odbitki na płótnie.

W ramce kompozytora można też osadzić inną ramkę kompozytora. Na przykład kompozytor przeglądarki tworzy ramkę kompozytora z interfejsem przeglądarki i pusty prostokąt, w którym zostanie umieszczona treść kompozytora renderowania. Innym przykładem są elementy iframe izolowane od witryny. Osadzanie odbywa się za pomocą powierzchni.

Gdy kompozytor przesyła ramkę kompozytora, towarzyszy jej identyfikator nazywany identyfikatorem platformy, który umożliwia innym ramkam kompozytora umieszczenie jej przez odwołanie. Najnowsza ramka kompozytora przesłana z określonym identyfikatorem powierzchni jest przechowywana przez Viz. Inna ramka kompozytora może się później odwoływać do niej za pomocą kwadratu do rysowania powierzchni, dzięki czemu Viz wie, co narysować. (Pamiętaj, że czworokąty do rysowania powierzchni zawierają tylko identyfikatory powierzchni, a nie tekstury).

Średnie karty renderowania

Niektóre efekty wizualne, takie jak wiele filtrów lub zaawansowane tryby mieszania, wymagają narysowania co najmniej 2 czworokątów do tekstury pośredniej. Następnie tekstura pośrednia jest rysowana do bufora docelowego w GPU (lub innej tekstury pośredniej), jednocześnie stosując efekt wizualny. Aby to umożliwić, ramka kompozytora faktycznie zawiera listę przebiegów renderowania. Zawsze występuje przejście do renderowania głównego, które jest rysowane jako ostatnie, a miejsce docelowe odpowiada buforowi ramki. Może ich też być więcej.

Możliwość wielokrotnych kart renderowania wyjaśnia tę nazwę. Każda karta musi być wykonywana sekwencyjnie na GPU, w ramach wielu „karnetów”, podczas gdy 1 przepustka może zostać wykonana w ramach 1 masowo równoległych obliczeń GPU.

Agregacja

Do Viz przesyłanych jest wiele ramek kompozytu, które trzeba rysować razem na ekranie. Jest to możliwe na etapie agregacji, w którym wartości są konwertowane w pojedynczą, zagregowaną ramkę kompozytora. Agregacja zastępuje czworokąty rysowania na powierzchni przez określone przez nich ramki kompozytora. To także okazja, by zoptymalizować pozbycie się zbędnych, pośrednich tekstur i treści spoza ekranu. Na przykład w wielu przypadkach ramka kompozytora dla elementu iframe izolowanego od witryny nie potrzebuje własnej tekstury pośredniej i można ją rysować bezpośrednio do bufora ramki za pomocą odpowiednich czworokątów. Na etapie agregacji ustalamy takie optymalizacje i stosujemy je na podstawie globalnej wiedzy, której nie są dostępne dla poszczególnych kompozytorów renderowania.

Przykład

Oto rzeczywiste ramki kompozytora, które reprezentują przykład od początku tego posta.

  • Platforma foo.com/index.html: id=0
    • Renderowanie 0: rysuj na wyjściu.
      • Renderowanie z nagraniem czworokątnym: rysuj z rozmyciem 3 pikseli i przycinaj w ramach przejścia renderowania o wartości 0.
        • Render Pass 1:
          • Narysuj czworokąty zawartości kafelków w elemencie iframe #one, podając pozycje x i y dla każdego elementu.
      • Powierzchniowy czworokąt: o identyfikatorze 2 narysowany ze skalą i przekształceniem.
  • Interfejs przeglądarki: ID=1
    • Renderowanie 0: rysuj na wyjściu.
      • Rysuj czworokąty w interfejsie przeglądarki (również sąsiadująco)
  • Platforma bar.com/index.html: ID=2
    • Renderowanie 0: rysuj na wyjściu.
      • Narysuj czworokąty dla zawartości elementu iframe #two, podając pozycje x i y dla każdego elementu.

Podsumowanie

Dziękujemy za uwagę! Wraz z 2 poprzednimi postami omówiliśmy RenderingNG. W następnej kolejności zagłębimy się w zagadnienia i zagadnienia związane z technologią, które występują w wielu podskładnikach procesu renderowania, od początku do końca. Wkrótce je udostępnimy.

Ilustracje: Una Kravets.