Renderowanie przyspieszone w Chrome

Model warstw

Tom Wiltzius
Tom Wiltzius

Wprowadzenie

Dla większości programistów stron internetowych podstawowym modelem strony jest DOM. Renderowanie to często niejasny proces przekształcania tej reprezentacji strony w obraz na ekranie. W ostatnich latach nowoczesne przeglądarki zmieniły sposób działania renderowania, aby wykorzystać karty graficzne: często określa się to ogólnie jako „przyspieszenie sprzętowe”. Co tak naprawdę oznacza ten termin w przypadku zwykłej strony internetowej (czyli nie Canvas2D ani WebGL)? Z tego artykułu dowiesz się, na czym polega podstawowy model, na którym opiera się renderowanie treści internetowych przyspieszone sprzętowo w Chrome.

Big, Fatty Caveats

Mówimy tu o WebKit, a ściślej o portowaniu WebKit do Chromium. Ten artykuł zawiera szczegółowe informacje o wdrożeniu Chrome, a nie o funkcjach platformy internetowej. Platforma internetowa i standardy nie określają tego poziomu szczegółów implementacji, więc nie ma gwarancji, że cokolwiek w tym artykule będzie działać w innych przeglądarkach. Znajomość wewnętrznych mechanizmów może jednak być przydatna do zaawansowanego debugowania i optymalizacji wydajności.

Pamiętaj też, że cały ten artykuł dotyczy kluczowego elementu architektury renderowania Chrome, który zmienia się bardzo szybko. W tym artykule staramy się uwzględnić tylko te kwestie, które prawdopodobnie się nie zmienią, ale nie możemy zagwarantować, że wszystko będzie obowiązywać za 6 miesięcy.

Warto wiedzieć, że od jakiegoś czasu Chrome ma 2 różne ścieżki renderowania: ścieżkę z przyspieszeniem sprzętowym i starszą ścieżkę oprogramowania. W momencie pisania tego tekstu wszystkie strony korzystają z akceleracji sprzętowej w systemach Windows, ChromeOS i Chrome na Androida. Na komputerach Mac i Linux tylko strony, które wymagają kompozytowania niektórych elementów, korzystają z ścieżki przyspieszonej (poniżej znajdziesz więcej informacji o tym, co wymaga kompozytowania), ale wkrótce wszystkie strony będą korzystać z ścieżki przyspieszonej.

Na koniec przyjrzymy się bliżej mechanizmowi renderowania i sprawdzimy, które jego funkcje mają duży wpływ na wydajność. Aby poprawić wydajność swojej witryny, warto poznać model warstw, ale łatwo też można się na tym przejechać: warstwy to przydatne konstrukcje, ale tworzenie ich w dużej liczbie może zwiększyć obciążenie całego stosu graficznego. Uważaj się za ostrzeżonego!

Z DOM na ekran

Warstwy

Po załadowaniu i przetworzeniu strona jest reprezentowana w przeglądarce jako struktura, która jest dobrze znana wielu deweloperom stron internetowych: DOM. Podczas renderowania strony przeglądarka ma jednak serię pośrednich reprezentacji, które nie są bezpośrednio udostępniane deweloperom. Najważniejsza z tych struktur to warstwa.

W Chrome jest kilka różnych typów warstw: warstwy renderowania, które odpowiadają za poddrzewa DOM, oraz warstwy graficzne, które odpowiadają za poddrzewa warstw renderowania. Ta ostatnia jest dla nas najbardziej interesująca, ponieważ warstwy graficzne są przesyłane do procesora graficznego jako tekstury. Odtąd będę używać słowa „warstwa” w znaczeniu „GraphicsLayer”.

Krótkie omówienie terminologii związanej z procesorem graficznym: czym jest tekstura? Wyobraź sobie, że jest to obraz bitmapowy przenoszony z głównej pamięci (np. RAM) do pamięci wideo (np. VRAM na karcie graficznej). Gdy jest ona na GPU, możesz ją przypisać do geometrii siatki. W grach wideo lub programach CAD ta technika służy do „zasłonięcia” szkieletu modelu 3D. Chrome używa tekstur do przesyłania fragmentów treści strony internetowej na GPU. Tekstury można łatwo mapować na różne pozycje i przekształcenia, stosując je do bardzo prostej sześciennej siatki. Tak działa CSS 3D, który jest też świetny do szybkiego przewijania. Więcej informacji o tych funkcjach znajdziesz poniżej.

Przyjrzyjmy się kilku przykładom, które obrazują pojęcie warstwy.

Przydatnym narzędziem podczas badania warstw w Chrome jest flaga „Pokaż krawędzie złożonej warstwy” w ustawieniach (czyli w panelu Narzędzia programisty pod ikoną małej zębatki) w sekcji „Renderowanie”. W prosty sposób wyróżnia warstwy na ekranie. Włączmy to. Wszystkie zrzuty ekranu i przykłady pochodzą z najnowszej wersji Chrome Canary, czyli Chrome 27 w momencie pisania tego tekstu.

Rysunek 1. Pojedyncza warstwa

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
Zrzut ekranu z renderowaniem krawędzi złożonej warstwy wokół podstawowej warstwy strony
Zrzut ekranu z renderowaniem krawędzi złożonej warstwy wokół podstawowej warstwy strony

Ta strona ma tylko jedną warstwę. Niebieska siatka to kafelki, które można traktować jako podjednostki warstwy. Chrome używa ich do przesyłania do GPU części dużych warstw. Nie są one tu tak ważne.

Rysunek 2. Element w osobnej warstwie

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
Zrzut ekranu pokazujący obramowania renderowania obracanej warstwy
Zrzut ekranu z renderowanymi krawędziami obracanej warstwy

Umieszczanie właściwości CSS 3D na obiekcie <div>, który obraca się wokół własnej osi, pozwala zobaczyć, jak wygląda element, gdy ma własną warstwę. Zwróć uwagę na pomarańczową obwódkę, która w tym widoku wyznacza obrys warstwy.

Kryteria tworzenia warstw

Co jeszcze ma własną warstwę? Heurystyka Chrome ewoluowała z czasem i nadal się rozwija, ale obecnie tworzenie warstw wyzwalaczy może być wywołane przez:

  • Właściwości CSS transformacji 3D lub perspektywy
  • elementów <video> przy użyciu akcelerowanego dekodowania wideo.
  • elementy <canvas> z kontekstem 3D (WebGL) lub przyspieszonym kontekstem 2D;
  • Wtyczki złożone (np. Flash)
  • elementy z animowaną przezroczystością lub z animowanym przekształceniem;
  • Elementy z przyspieszonymi filtrami CSS
  • Element ma potomka, który ma warstwę kompozytową (czyli element ma element podrzędny na własnej warstwie).
  • Element ma element nadrzędny o niższym z-indexie, który ma warstwę kompozytową (czyli jest renderowany na warstwie złożonej).

Wnioski praktyczne: animacja

Możemy też przenosić warstwy, co jest bardzo przydatne w przypadku animacji.

Rysunek 3. Warstwy animowane

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div>I am a strange root.</div>
</body>
</html>

Jak wspomnieliśmy wcześniej, warstwy są bardzo przydatne do przemieszczania się po statycznych treściach internetowych. W przypadku podstawowym Chrome przekształca zawartość warstwy w bitmapę oprogramowania, zanim prześle ją do GPU jako teksturę. Jeśli te treści nie ulegną zmianie w przyszłości, nie trzeba ich ponownie malować. To dobra rzecz: ponowne malowanie wymaga czasu, który można poświęcić na inne czynności, np. uruchamianie JavaScriptu, a jeśli jest długi, powoduje opóźnienia w animacji.

Na przykład ten widok osi czasu w Narzędziach dla programistów: podczas obracania tej warstwy w górę i w dół nie ma żadnych operacji związanych z malowaniem.

Zrzut ekranu pokazujący oś czasu w Narzędziach dla programistów podczas animacji
Zrzut ekranu z osi czasu w Narzędziach dla programistów podczas animacji

Nieprawidłowy. Odmalowywanie

Jeśli jednak treść warstwy ulegnie zmianie, musi zostać ponownie namalowana.

Rysunek 4. Ponowne malowanie warstw

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div id="foo">I am a strange root.</div>
  <input id="paint" type="button" value="repaint">
  <script>
    var w = 200;
    document.getElementById('paint').onclick = function() {
      document.getElementById('foo').style.width = (w++) + 'px';
    }
  </script>
</body>
</html>

Za każdym razem, gdy użytkownik klika element wejściowy, element obracający się staje się szerszy o 1 piksel. Powoduje to ponowne rozmieszczenie i ponowne narysowanie całego elementu, który w tym przypadku jest całą warstwą.

Dobrym sposobem na sprawdzenie, co jest malowane, jest użycie narzędzia „Pokaż prostokąty malowania” w Narzędziach dla programistów, które znajduje się również w sekcji „Renderowanie” w ustawieniach Narzędzi dla programistów. Po włączeniu zauważysz, że po kliknięciu przycisku animowany element i przycisk migają na czerwono.

Zrzut ekranu z polem wyboru Pokaż migające prostokąty renderowania
Zrzut ekranu z polem wyboru pokazywania prostokątów rysowania

Wydarzenia związane z malowaniem pojawiają się też na osi czasu w Narzędziach dla deweloperów. Bystrzy czytelnicy mogą zauważyć, że są tam 2 zdarzenia paint: jedno dla warstwy, a drugie dla samego przycisku, który jest ponownie malowany, gdy zmienia się stan przycisku.

Zrzut ekranu przedstawiający ponowne narysowanie warstwy w Narzędziach dla programistów
Zrzut ekranu z narzędzi dewelopera: ponowne malowanie warstwy na osi czasu

Pamiętaj, że Chrome nie musi zawsze odświeżać całej warstwy. Stara się odświeżać tylko tę część DOM, która została unieważniona. W tym przypadku zmodyfikowany element DOM to rozmiar całej warstwy. W wielu innych przypadkach w warstwie będzie wiele elementów DOM.

Kolejne oczywiste pytanie brzmi: co powoduje unieważnienie i wymusza ponowne renderowanie? Trudno jest udzielić wyczerpującej odpowiedzi na to pytanie, ponieważ istnieje wiele szczególnych przypadków, które mogą spowodować unieważnienie. Najczęstszą przyczyną jest zanieczyszczenie DOM przez manipulowanie stylami CSS lub powodowanie ponownego układania. Tony Gentilcore napisał świetny post na blogu na temat tego, co powoduje przeformatowanie, a Stoyan Stefanov opublikował artykuł, w którym szczegółowo omawia malowanie (ale kończy się on na malowaniu, a nie na tych wszystkich skomplikowanych technikach kompozycyjnych).

Najlepszym sposobem na sprawdzenie, czy to narzędzie wpływa na coś, nad czym pracujesz, jest użycie narzędzia Linia czasu w Narzędziach dewelopera i pokazanie prostokątów wypełniania, aby sprawdzić, czy nie dochodzi do ponownego wypełniania, gdy nie chcesz. Następnie spróbuj zlokalizować, gdzie w DOM pojawiły się błędy tuż przed ponownym układem lub ponownym wypełnieniem. Jeśli malowanie jest nieuniknione, ale wydaje się, że zajmuje nieproporcjonalnie dużo czasu, przeczytaj artykuł Eberharda Gräthera na temat trybu ciągłego malowania w narzędziach dla deweloperów.

Łączenie: DOM i ekran

Jak Chrome przekształca DOM w obraz ekranu? Oznacza to, że:

  1. Przejmuje DOM i dzieli go na warstwy
  2. malowanie każdej z tych warstw niezależnie w bitmapach oprogramowania;
  3. przesyła je do GPU jako tekstury.
  4. Łączy różne warstwy w jeden obraz.

Wszystko to musi się zdarzyć, gdy Chrome po raz pierwszy wygeneruje ramkę strony internetowej. Ale w przyszłości możesz użyć skrótów:

  1. Jeśli zmienią się niektóre właściwości CSS, nie trzeba niczego ponownie malować. Chrome może ponownie zrekomponować istniejące warstwy, które są już na GPU jako tekstury, ale z innymi właściwościami kompozytowania (np.w innych pozycjach, z inną przezroczystością itp.).
  2. Jeśli część warstwy zostanie unieważniona, zostanie ponownie namalowana i przesłana. Jeśli zawartość pozostaje taka sama, ale zmieniają się jej atrybuty (np. jest przekształcana lub zmienia się jej przezroczystość), Chrome może pozostawić ją na karcie graficznej i ponownie ją złożyć, aby utworzyć nową klatkę.

Jak już wiesz, model kompozytowania oparty na warstwach ma duży wpływ na wydajność renderowania. Kompilowanie jest stosunkowo tanie, gdy nie trzeba nic malować, więc unikanie ponownego malowania warstw jest dobrym ogólnym celem podczas debugowania wydajności renderowania. Doświadczeni deweloperzy, którzy spojrzą na powyższą listę wyzwalaczy kompozytowania, szybko zrozumieją, że można łatwo wymusić tworzenie warstw. Nie twórz ich jednak bezmyślnie, ponieważ nie są one bezpłatne: zajmują pamięć w pamięci RAM systemu i na karcie graficznej (co jest szczególnie ograniczone na urządzeniach mobilnych), a duża ich liczba może powodować dodatkowe obciążenie logiki, która śledzi, które z nich są widoczne. Wiele warstw może też wydłużyć czas rastrowania, jeśli są duże i znacznie się na siebie nakładają, co powoduje zjawisko zwane „przerysowaniem”. Dlatego korzystaj z tych informacji rozważnie.

Na razie to wszystko. Zachęcamy do śledzenia naszych kanałów informacyjnych, aby dowiedzieć się więcej o praktycznych zastosowaniach modelu warstw.

Dodatkowe materiały