Kluczowe struktury danych 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

Spójrzmy na kluczowe struktury danych, czyli dane wejściowe i wyjściowe do potoku renderowania.

Takie struktury danych to:

  • Drzewa ramek składają się z lokalnych i zdalnych węzłów, które wskazują, w którym procesie renderowania i którym mechanizmie renderowania Blink znajdują się dokumenty internetowe.
  • Drzewo fragmentów stałych reprezentuje dane wyjściowe algorytmu ograniczenia układu (i dane wejściowe do niego).
  • Drzewa właściwości reprezentują hierarchie przekształceń, klipów, efektów i przewijania dokumentu internetowego. Są one używane w całym potoku.
  • Listy wyświetlania i fragmenty renderowania to dane wejściowe dla algorytmów rastrowania i warstwowania.
  • Ramki kompozycji zawierają powierzchnie, powierzchnie renderowania i kafelki tekstur GPU, które są używane do rysowania przy użyciu GPU.

Przed omówieniem tych struktur danych poniższy przykład opiera się na przeglądzie architektury. Ten przykład jest używany w całym dokumencie, ale pokazuje, jak struktury danych mają do niego zastosowanie.

<!-- Example code -->
<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>

Oprawianie drzew

Chrome może czasami zdecydować się na renderowanie ramki z innej domeny w innym procesie renderowania niż ramka nadrzędna.

W przykładowym kodzie widać łącznie 3 klatki:

Ramka nadrzędna foo.com zawierająca dwa elementy iframe.

W przypadku izolacji witryn Chromium używa 2 procesów renderowania do renderowania tej strony internetowej. Każdy proces renderowania przedstawia własną reprezentację drzewa ramek dla danej strony internetowej:

2 drzewa ramek reprezentujące 2 procesy renderowania.

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

W przeciwieństwie do tego ramka lokalna reprezentuje ramkę, która przechodzi przez standardowy potok renderowania. Ramka lokalna zawiera wszystkie informacje potrzebne do przekształcenia jej danych (np. drzewa DOM i danych o stylu) w element, który można wyrenderować i wyświetlić.

Potok renderowania działa na szczegółowości fragmentu drzewa ramek lokalnego. Rozważ bardziej skomplikowany przykład z klatką foo.com w głównej ramce:

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

Oraz ta ramka podrzędna bar.com:

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

Chociaż nadal istnieją tylko 2 mechanizmy renderowania, widoczne są teraz 3 fragmenty drzewa ramki, z 2 w procesie renderowania dla interfejsu foo.com i 1 w procesie renderowania bar.com:

Reprezentacja 2 renderowań i 3 fragmentów drzewa ramek.

Aby utworzyć jedną ramkę kompozytora dla strony internetowej, Viz jednocześnie żąda ramki kompozytora z ramki głównej każdego z trzech lokalnych drzew ramek, a następnie agreguje te dane. Zapoznaj się też z sekcją dotyczącą ramek kompozytora.

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 klatki nadal mają jednak niezależne cykle życia dokumentów, ponieważ są częścią różnych fragmentów lokalnego drzewa ramek. Z tego powodu nie można wygenerować jednej ramki kompozytora dla obu w ramach jednej aktualizacji. Proces renderowania nie ma wystarczających informacji, aby umieścić ramkę kompozytora wygenerowaną dla elementu 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 wyłączenie jego części z 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 rdzeniem każdego lokalnego fragmentu drzewa ramki powiązany jest obiekt widżetu. Wizualne aktualizacje właściwości są przekazywane do widżetu ramki głównej przed przeniesieniem do pozostałych widżetów (od góry do dołu).

Na przykład gdy rozmiar widocznego obszaru się zmieni:

Schemat procesu omawianego we wcześniejszym 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 ramek lokalnych prześlą ramkę kompozytora z bieżącym tokenem synchronizacji. Ten proces pozwala uniknąć mieszania ramek kompozytora o różnych właściwościach wizualnych.

Drzewo stałych fragmentów

Drzewo z zmiennymi fragmentami to dane wyjściowe etapu układu potoku renderowania. Reprezentuje ona pozycję i rozmiar wszystkich elementów na stronie (bez przekształceń).

Przedstawienie fragmentów w każdym drzewie, z którym 1 fragment jest oznaczony jako wymagający układu.

Każdy fragment reprezentuje część elementu DOM. Zwykle dla każdego elementu pojawia się tylko 1 fragment, ale może być ich więcej, jeśli zostanie podzielony na różne strony podczas drukowania lub na poszczególne kolumny w kontekście wielu kolumn.

Po układzie każdy fragment staje się niezmienny i nigdy nie jest zmieniany. Co ważne, nakładamy też kilka dodatkowych ograniczeń. Czego nie robimy:

  • Zezwalaj na wszystkie odwołania do góry w drzewie. (Element podrzędny nie może mieć wskaźnika do elementu nadrzędnego).
  • „dymek” z drzewa (dziecko odczytuje informacje tylko od swoich elementów podrzędnych, a nie od elementu nadrzędnego).

Te ograniczenia pozwalają na ponowne użycie fragmentu w kolejnym układzie. Bez tych ograniczeń musielibyśmy często odnawiać całe drzewo, co jest bardzo kosztowne.

Większość układów to zazwyczaj aktualizacje przyrostowe. Przykładem może być aplikacja internetowa aktualizująca niewielki fragment interfejsu w odpowiedzi na kliknięcie elementu przez użytkownika. Najlepiej, gdyby układ działał proporcjonalnie do tego, co rzeczywiście zmieniło się na ekranie. Możemy to osiągnąć, ponownie wykorzystując jak najwięcej części poprzedniego drzewa. Oznacza to, że (zwykle) musimy odbudować tylko grzbiet drzewa.

W przyszłości taka stała konstrukcja mogłaby w razie potrzeby umożliwić nam na przykład przekazywanie drzewa z niezmiennymi fragmentami przez granice wątków (aby wykonać kolejne fazy w innym wątku), generowanie wielu drzew w celu płynnej animacji układu lub wykonywanie równoległych układów spekulacyjnych. Rozwiązanie to również łączy się z potencjałem wielowątkowości w układzie.

Wbudowane elementy z fragmentami

Treść w tekście (głównie stylizowany tekst) wygląda trochę inaczej. Zamiast drzewa z polami i wskaźnikami wyświetlamy treści w tekście na płaskiej liście reprezentującej drzewo. Główną zaletą jest to, że w przypadku elementów w tekście reprezentacja płaskiej listy jest szybka, przydatna do badania wbudowanych struktur danych lub wysyłania dotyczących ich zapytań, a także oszczędza pamięć. Jest to niezwykle ważne dla wydajności renderowania stron internetowych, ponieważ renderowanie tekstu jest bardzo złożone i może stać się najwolniejszym elementem procesu, o ile nie zostanie dobrze zoptymalizowana.

Płaska lista jest tworzona dla każdego tekstu formatowania wbudowanego w kolejności od najbardziej szczegółowych wyników wyszukiwania w drzewie jego układu wbudowanego. Każdy wpis na liście jest krotką (obiektu, liczby elementów podrzędnych). Weźmy na przykład taki DOM:

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

Właściwość width ma wartość 0, więc wiersz zaczyna się między słowami „Hi” i „tam”.

Gdy kontekst formatowania wbudowanego w tej sytuacji jest przedstawiony w postaci drzewa, wygląda to tak:

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

Lista kartotekowa wygląda tak:

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

Taka struktura danych ma wielu użytkowników: interfejsy API ułatwień dostępu i interfejsy API geometrii, np. getClientRects i contenteditable. Każdy z nich ma inne wymagania. Komponenty te uzyskują dostęp do płaskiej struktury danych za pomocą podręcznego kursora.

Kursor ma interfejsy API, takie jak MoveToNext, MoveToNextLine czy CursorForChildren. Ta reprezentacja kursora sprawdza się bardzo dobrze w przypadku treści tekstowych z wielu powodów:

  • Kolejne iteracje w kolejności wyszukiwania z najwyższym poziomem szczegółowości są bardzo szybkie. Jest on używany bardzo często, ponieważ przypomina ruchy kursora. Ponieważ jest to płaska lista, wyszukiwanie uzależnione od głębokości tylko zwiększa przesunięcie tablicy, co zapewnia szybkie iteracje i lokalizację pamięci.
  • Umożliwia wyszukiwanie najszersze, co jest potrzebne np. przy malowaniu tła w polach wierszy i wbudowanych pól.
  • 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 nieruchomości

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

Są one wyświetlane na 4 sposoby:

  • Układ: dane wejściowe algorytmu ograniczenia układu.
  • Malowanie: sposób malowania i rastrowania elementu (ale nie jego elementów potomnych).
  • Wizualne: efekty rastrowania/rysowania zastosowane do drzewa podrzędnego DOM, takie jak przekształcenia, filtry i przycinanie.
  • Przewijanie: wyrównane do osi i zaokrąglone narożniki przycięcia i przewijanie zawartego w drzewie poddrzewa.

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

Wizualne i przewijane efekty w internecie są w pełni skomplikowane. Dlatego najważniejszą rzeczą, jaką robią 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 usuwa resztę złożoności DOM i CSS. Dzięki temu możemy wdrożyć algorytmy, które skuteczniej komponują i przewijają. W szczególności:

  • Geometrię, która może być podatna na błędy, i inne obliczenia można scentralizować w jednym miejscu.
  • Złożoność tworzenia i aktualizowania drzew właściwości oddziela się w ramach jednego etapu potoku renderowania.
  • Dużo łatwiej i szybciej wysyłać drzewa właściwości do różnych wątków i procesów niż w stanie pełnego DOM, dzięki czemu można ich używać w wielu przypadkach.
  • Im więcej jest tych przypadków użycia, tym więcej korzyści uzyskujemy dzięki wbudowanemu buforowaniu geometrii, ponieważ mogą one ponownie wykorzystywać swoje pamięci podręczne.

RenderingNG wykorzystuje drzewa właściwości do różnych celów, m.in. do:

  • Oddzielenie komponowania od farby i komponowanie od wątku głównego.
  • Określenie optymalnej strategii komponowania / rysowania.
  • Pomiar geometrii IntersectionObserver.
  • Unikanie pracy z elementami poza ekranem i kafelkami tekstur GPU.
  • Efektywnie i dokładnie unieważnia renderowanie i rastrowanie.
  • Pomiar zmiany układu i największego wyrenderowania treści za pomocą podstawowych wskaźników internetowych.

Każdy dokument internetowy ma 4 osobne drzewa właściwości: przekształcenie, klip, efekt i przewijanie.(*). Drzewo przekształcenia przedstawia przekształcenia i przewijanie CSS. (Przekształcenie przewijane jest reprezentowane jako macierz przekształceń 2D). Drzewo klipów reprezentuje dodatkowe klipy. Drzewo efektów prezentuje wszystkie pozostałe efekty wizualne: przezroczystość, filtry, maski, tryby mieszania oraz inne rodzaje klipów, np. ścieżkę do klipu. Drzewo przewijania zawiera informacje o przewijaniu, takie jak sposób przewijania łańcucha razem; jest ono potrzebne do przewijania wątku kompozytora. Każdy węzeł w drzewie właściwości odpowiada przewijaniu lub efektowi wizualnemu zastosowanemu przez element DOM. Jeśli taki element ma wiele efektów, w każdym drzewie może być więcej niż 1 węzeł drzewa właściwości dla tego samego elementu.

Topologia każdego drzewa jest jak rozproszona reprezentacja DOM. Jeśli np. są 3 elementy DOM z rozszerzonymi klipami, zostaną utworzone 3 węzły drzewa klipu, a struktura drzewa klipów będzie zgodna z relacją blokową między klipami nadmiarowymi. Występują też połączenia między drzewami. Linki te wskazują względną hierarchię DOM, a tym samym kolejność zastosowań węzłów. Na przykład, jeśli przekształcenie elementu DOM znajduje się pod innym elementem DOM z filtrem, to oczywiście przekształcenie będzie stosowane przed filtrem.

Każdy element DOM ma stan drzewa właściwości, który składa się z 4 krotki (transform, klipu, efektu, przewijania), która wskazuje najbliższy klip nadrzędny, przekształcenie i węzły drzewa efektu, które mają wpływ na ten element. To bardzo wygodne rozwiązanie, ponieważ dzięki tym informacjom znamy dokładną listę klipów, przekształceń i efektów związanych z danym elementem, a także ich kolejność. Dzięki temu wiemy, gdzie znajduje się go 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 (nieco innym niż we wprowadzeniu) przedstawiamy kluczowe elementy wygenerowanych drzew właściwości:

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

Wyświetl listy i fragmenty renderowania

Wyświetlany element zawiera niskopoziomowe polecenia rysowania (patrz tutaj), które można zrastrować za pomocą Skia. Elementy wyświetlane zazwyczaj są proste, wykonując jedynie kilka poleceń rysowania, takich jak obrysowanie obramowania lub tła. Drzewo Malowania wykonuje iterację po drzewie układu i powiązanych z nim fragmentach zgodnie z kolejnością renderowania CSS, aby utworzyć listę wyświetlanych elementów.

Na przykład:

Niebieskie pole ze słowami „Hello world” wewnątrz zielonego prostokąta.

<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>

Ten kod HTML i CSS spowoduje wygenerowanie następującej listy wyświetlanej, z której każda komórka to wyświetlany element:

Tło widoku #blue (tło) #green (tło) #green tekst wbudowany
drawRect w rozmiarze 800 x 600 i w kolorze białym. drawRect w rozmiarze 100 x 100 w pozycji 0,0 i w kolorze niebieskim. drawRect w rozmiarze 80 x 18 w pozycji 8,8 i w kolorze zielonym. drawTextBlob z pozycją 8 i 8 i tekstem „Hello world”.

Lista produktów jest uporządkowana od początku do końca. W przykładzie powyżej zielony element div znajduje się przed niebieskim elementem div w kolejności DOM, ale kolejność renderowania CSS wymaga, by niebieski element div o ujemnym z-indeksie został wymalowany przed (krok 3) zielonym elementem div (krok 4.1). Elementy displayowe odpowiadają poszczególnym etapom specyfikacji kolejności renderowania CSS. Pojedynczy element DOM może prowadzić do pojawienia się kilku elementów wyświetlania, np. #green ma element wyświetlany jako tło i drugi element displayowy dla tekstu w tekście. Ta szczegółowość jest ważna, ponieważ pozwala przedstawić pełną złożoność specyfikacji zamówienia renderowania CSS, np. przeplatanie utworzone z ujemną marżą:

Zielony prostokąt z częściowo nałożonym szarym polem i napisami „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>

Powoduje to utworzenie poniższej listy wyświetlanej, z której każda komórka odpowiada wyświetlanym elementom:

Tło widoku #green (tło) #gray (tło) #green tekst wbudowany
drawRect w rozmiarze 800 x 600 i w kolorze białym. drawRect w rozmiarze 80 x 18 w pozycji 8,8 i w kolorze zielonym. drawRect w rozmiarze 35 x 20 w pozycji 8,16 i w kolorze szarym. drawTextBlob z pozycją 8 i 8 i tekstem „Hello world”.

Wyświetlana lista elementów jest przechowywana i może być ponownie wykorzystywana w późniejszych aktualizacjach. Jeśli obiekt szablonu nie zmienił się podczas malowania po drzewie, jego wyświetlane elementy zostaną skopiowane z poprzedniej listy. Dodatkowa optymalizacja opiera się na właściwości specyfikacji zamówienia renderowania CSS: konteksty nakładania są atomowo malowane. Jeśli w kontekście grupowania nie zmienił się żaden obiekt szablonu, spacer drzewa malowania pomija kontekst nakładania i kopiuje całą sekwencję wyświetlanych elementów z poprzedniej listy.

Bieżący stan drzewa właściwości jest zachowywany podczas malowania po drzewie, a lista wyświetlanych elementów jest grupowana na „fragmenty” wyświetlanych elementów, które mają ten sam stan drzewa właściwości. Zostało to pokazane w tym przykładzie:

Różowe pudełko z pochyloną pomarańczową ramką.

<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>

Powoduje to utworzenie poniższej listy wyświetlanej, z której każda komórka odpowiada wyświetlanym elementom:

Tło widoku #scroll (tło) #scroll tekst wbudowany #orange (tło) #orange tekst wbudowany
drawRect w rozmiarze 800 x 600 i w kolorze białym. drawRect w rozmiarze 100 x 100 w pozycji 0,0 i w kolorze różowym. drawTextBlob z pozycją 0,0 i tekstem „Hello world”. drawRect w rozmiarze 75 x 200 w pozycji 0,0 i w kolorze pomarańczowym. drawTextBlob z pozycją 0,0 i tekstem „Upadam”.

Drzewo właściwości przekształcenia i fragmenty farby będą wyglądać tak (uproszczone w celu zachowania zwięzłości):

Obraz poprzedniej tabeli: dwie pierwsze komórki we fragmencie 1, trzecia we fragmencie 2 i 2 ostatnie 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 na etapie nakładania warstw w potoku renderowania. Całą listę fragmentów renderowania można scalić w jedną skomponowaną warstwę i zrasteryzować razem, ale wymagałoby to kosztownej rasteryzacji przy każdym przewijaniu przez użytkownika. Dla każdego fragmentu malowania można utworzyć skomponowaną warstwę i poddać ją rasteryzacji pojedynczo, aby uniknąć ponownej rasteryzacji, ale w ten sposób szybko wyczerpałaby się pamięć GPU. Na etapie tworzenia warstw musisz znaleźć kompromis między pamięcią GPU i obniżeniem kosztów, gdy sytuacja się zmieni. Dobrym ogólnym podejściem jest domyślne scalanie fragmentów, a nie scalania fragmentów renderowania, które mają stany drzewa właściwości, które powinny zmieniać się w wątku kompozytora, np. w przypadku przewijania wątku kompozytora lub animacji przekształcania wątku.

W poprzednim przykładzie najlepiej byłoby utworzyć dwie skomponowane warstwy:

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

Jeśli użytkownik przewinie stronę #scroll, druga skomponowana warstwa jest przenoszona, ale rasteryzacja nie jest wymagana.

W tym przykładzie jest to 6 fragmentów farby z poprzedniej sekcji poświęconej drzewom właściwości. Wraz z ich stanami drzewa właściwości (transform, klip, efekt, przewijanie) są to:

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

Ramki kompozytora: powierzchnie, powierzchnie renderowane i płytki tekstur GPU

Przeglądarka i procesy renderowania zarządzają rasteryzacją treści, a następnie przesyłają ramki kompozytora do procesu Viz, aby wyświetlić je na ekranie. Ramki kompozytora przedstawiają sposób łączenia zrastrowanych treści i efektywnego rysowania ich za pomocą GPU.

Karty

Teoretycznie proces renderowania lub kompozytor procesów przeglądarki może zrasteryzować piksele w jedną teksturę o pełnym rozmiarze widocznego obszaru mechanizmu renderowania i przesłać ją do Viz. Aby ją wyświetlić, kompozytor wyświetlania musiałby po prostu skopiować piksele z danej tekstury do odpowiedniej pozycji w buforze klatek (na przykład na ekran). Gdyby jednak ten kompozytor chciał zaktualizować nawet 1 piksel, musiałby ponownie zrasterować cały widoczny obszar i przesłać nową teksturę do Viz.

Zamiast tego widoczny obszar jest podzielony na kafelki. W przypadku każdego kafelka znajduje się osobny kafelek z teksturą GPU, w którym znajdują się zrasteryzowane piksele odpowiadające części widocznego obszaru. Mechanizm renderowania może wtedy aktualizować poszczególne kafelki, a nawet zmieniać pozycję istniejących kafelków na ekranie. Na przykład podczas przewijania witryny pozycja istniejących kafelków zmieniłaby się w górę i rzadko nowy kafelek musiałby zostać wyświetlony niżej.

Cztery kafelki.
Ten obraz przedstawia obraz przedstawiający słoneczny dzień z 4 kafelkami. Po przewinięciu pojawia się piąty kafelek. Tak się składa, że jeden z kafelków ma tylko jeden kolor (niebo błękitny), a na górze znajdują się obraz i element iframe.

Quady i powierzchnie

Kafelki tekstur GPU to specjalny rodzaj quad, który jest tylko wymyślną nazwą dla jednej lub innej kategorii tekstur. Czwórka wskazuje teksturę wejściową i wskazuje, jak ją przekształcić i zastosować do niej efekty wizualne. Na przykład zwykłe kafelki treści mają przekształcenie, które wskazuje ich pozycję na osi x i y w siatce kafelków.

Fragmenty tekstur GPU.

Te zrasteryzowane kafelki są zapakowane w zgodności z renderowaniem, która jest listą czworokątów. Nie zawiera informacji o pikselach. Są w nich tylko instrukcje, gdzie i jak narysować każdy czworokąt w celu uzyskania pożądanego efektu piksela. Każdy kafelek tekstury GPU obejmuje obszar rysowania. Kompozytor do wyświetlania reklam musi tylko iterować listę czworokątów, rysując każdy z nich z określonymi efektami wizualnymi, aby uzyskać oczekiwany piksel wyjściowy dla procesu renderowania. Komponowanie czworokątów renderowania w ramach przebiegu renderowania można sprawnie przeprowadzić w GPU, ponieważ dozwolone efekty wizualne są starannie wybierane tak, by mapowane bezpośrednio na funkcje GPU.

Oprócz kafelków zrastrowanych dostępne są dodatkowe typy czworokątów rysowania. Istnieją na przykład czworokąty do rysowania w jednolitym kolorze, które w ogóle nie są poparte teksturą, lub czworokąty do rysowania tekstur w przypadku tekstur niebędących kafelkami, takich jak film czy obiekt canvas.

Inna ramka kompozytora może być też osadzona w ramce kompozytora. Na przykład kompozytor przeglądarki tworzy ramkę kompozytora z interfejsem przeglądarki i pusty prostokąt, w którym zostanie osadzona treść kompozytora renderowania. Innym przykładem są elementy iframe izolowane od witryny. Osadzanie się w ten sposób odbywa się za pomocą przestrzeni.

Gdy kompozytor przesyła ramkę kompozytora, towarzyszy mu identyfikator nazywany identyfikatorem powierzchni, który pozwala innym klatkom kompozytora umieścić ją 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łać za pomocą powierzchni czworokąta rysowania powierzchni, dzięki czemu Viz wie, co narysować. (Pamiętaj, że czworokąty do rysowania powierzchni zawierają tylko identyfikatory powierzchni, a nie tekstury).

Pośrednie karty renderowania

Niektóre efekty wizualne, np. filtry lub zaawansowane tryby mieszania, wymagają narysowania co najmniej dwóch czworokątów na teksturę pośrednią. Następnie tekstura pośrednia jest rysowana w buforze miejsca docelowego w GPU (lub innej tekstury pośredniej), stosując w tym samym czasie efekt wizualny. Aby to umożliwić, ramka kompozytora zawiera listę procesów renderowania. Zawsze istnieje główny przebieg renderowania, który jest rysowany jako ostatni, a miejsce docelowe odpowiada buforowi ramki. Może ich być też więcej.

To, że istnieje wiele kart renderowania – wyjaśnia więc, na czym polega „renderowanie”. Każda karta musi być wykonywana sekwencyjnie w ramach GPU w wielu „kartach”, podczas gdy 1 przepustka może być wykonywana w ramach jednego, niezwykle równoległego przetwarzania GPU.

Agregacja

Do Viz przesyłanych jest wiele klatek kompozytora i trzeba je narysować razem na ekranie. Jest to możliwe w fazie agregacji, która przekształca je w pojedynczą, zagregowaną ramkę kompozytora. Agregacja zastępuje czworokąty rysowania powierzchni przez określone ramki kompozytora. Można też zoptymalizować wyświetlanie zbędnych tekstur pośrednich i treści spoza ekranu. Na przykład w wielu przypadkach ramka kompozytora dla elementu iframe izolowanego z witryny nie wymaga własnej tekstury pośredniej i można ją narysować bezpośrednio w buforze ramki za pomocą odpowiednich czworokątów rysowania. Etap agregacji opracowuje takie optymalizacje i stosuje je na podstawie globalnej wiedzy, która nie jest dostępna dla poszczególnych kompozytorów renderowania.

Przykład

Oto ramki kompozytora reprezentujące przykład z początku tego posta.

  • Platforma foo.com/index.html: id=0
    • Renderowanie z przebiegiem 0: rysowanie na ekranie wyjściowym.
      • Renderowanie z płytką rysunkową: rysowanie z rozmyciem 3 pikseli i przypinanie do przebiegu 0 renderowania.
        • Renderowanie – przebieg 1:
          • Narysuj czworokąty dla zawartości kafelków elementu iframe #one, podając pozycje x i y dla każdego z nich.
      • Powierzchnia, rysowanie prostokąta o identyfikatorze 2, ze skalą i przekształceniem.
  • Interfejs użytkownika przeglądarki: ID=1
    • Renderowanie z przebiegiem 0: rysowanie na ekranie wyjściowym.
      • Rysuj kwadraty dla interfejsu przeglądarki (również na kafelkach)
  • bar.com/index.html platforma: ID=2
    • Renderowanie z przebiegiem 0: rysowanie na ekranie wyjściowym.
      • Narysuj czworokąty treści elementu iframe #two, podając pozycje x i y dla każdego z nich.

Ilustracje Uny Kravets.