Ускоренный рендеринг в Chrome

Модель слоя

Том Вильциус
Tom Wiltzius

Введение

Для большинства веб-разработчиков фундаментальной моделью веб-страницы является DOM. Рендеринг — это часто неясный процесс превращения представления страницы в изображение на экране. В последние годы современные браузеры изменили способ работы рендеринга, чтобы воспользоваться преимуществами видеокарт: это часто неопределенно называют «аппаратным ускорением». Когда речь идет об обычной веб-странице (т. е. не о Canvas2D или WebGL), что на самом деле означает этот термин? В этой статье объясняется базовая модель, лежащая в основе аппаратного ускорения рендеринга веб-контента в Chrome.

Большие и жирные предостережения

Здесь мы говорим о WebKit, а точнее, о порте WebKit на Chromium. В этой статье рассматриваются детали реализации Chrome, а не функции веб-платформы. Веб-платформа и стандарты не кодифицируют этот уровень детализации реализации, поэтому нет никаких гарантий, что что-либо из этой статьи будет применимо к другим браузерам, но знание внутреннего устройства, тем не менее, может быть полезно для расширенной отладки и настройки производительности.

Также обратите внимание, что вся эта статья обсуждает основную часть архитектуры рендеринга Chrome, которая меняется очень быстро. В этой статье делается попытка охватить только те вещи, которые вряд ли изменятся, но нет никаких гарантий, что все это будет применяться через шесть месяцев.

Важно понимать, что Chrome уже некоторое время использует два разных пути рендеринга: путь с аппаратным ускорением и старый программный путь. На момент написания этой статьи все страницы обрабатываются с помощью аппаратного ускорения в Windows, ChromeOS и Chrome для Android. В Mac и Linux по ускоренному пути идут только страницы, часть контента которых требует компоновки (подробнее о том, для чего потребуется компоновка, см. ниже), но вскоре все страницы тоже перейдут по ускоренному пути.

Наконец, мы заглянем под капот движка рендеринга и рассмотрим его особенности, которые оказывают большое влияние на производительность. При попытке улучшить производительность вашего собственного сайта может быть полезно понять модель слоев, но также легко выстрелить себе в ногу: слои — это полезные конструкции, но создание их большого количества может привести к увеличению нагрузки на весь графический стек. Считайте себя предупрежденным!

От DOM к экрану

Представляем слои

После загрузки и анализа страницы она представляется в браузере в виде структуры, знакомой многим веб-разработчикам: DOM. Однако при рендеринге страницы браузер имеет ряд промежуточных представлений, которые не доступны разработчикам напрямую. Наиболее важной из этих структур является слой.

В Chrome на самом деле существует несколько разных типов слоев: RenderLayers, отвечающие за поддеревья DOM, и GraphicsLayers, отвечающие за поддеревья RenderLayers. Последнее для нас наиболее интересно, потому что GraphicsLayers — это то, что загружается в графический процессор в виде текстур. С этого момента я буду говорить просто «слой», имея в виду GraphicsLayer.

Небольшое отступление от терминологии графического процессора: что такое текстура? Думайте об этом как о растровом изображении, которое перемещается из основной памяти (т. е. ОЗУ) в видеопамять (т. е. VRAM на вашем графическом процессоре). Как только он окажется на графическом процессоре, вы можете сопоставить его с геометрией сетки — в видеоиграх или программах САПР этот метод используется для придания «кожи» скелетным 3D-моделям. Chrome использует текстуры для передачи фрагментов содержимого веб-страницы на графический процессор. Текстуры можно легко сопоставить с различными положениями и преобразованиями, применив их к действительно простой прямоугольной сетке. Именно так работает 3D CSS, а также он отлично подходит для быстрой прокрутки, но об этом подробнее позже.

Давайте рассмотрим пару примеров, иллюстрирующих концепцию слоев.

Очень полезным инструментом при изучении слоев в Chrome является флаг «показать границы составных слоев» в настройках (т. е. маленький значок шестеренки) в Dev Tools под заголовком «рендеринг». Он очень просто подчеркивает расположение слоев на экране. Давайте включим его. Все эти скриншоты и примеры взяты из последней версии Chrome Canary, Chrome 27 на момент написания этой статьи.

Рисунок 1. Однослойная страница.

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
Снимок экрана: границы рендеринга составного слоя вокруг базового слоя страницы
Снимок экрана: границы рендеринга составного слоя вокруг базового слоя страницы

Эта страница имеет только один слой. Синяя сетка представляет собой плитки, которые можно рассматривать как части слоя, которые Chrome использует для одновременной загрузки частей большого слоя в графический процессор. Они здесь не особо важны.

Рисунок 2. Элемент на отдельном слое.

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
Снимок экрана: границы рендеринга повернутого слоя
Снимок экрана: границы рендеринга повернутого слоя

Поместив в <div> свойство 3D CSS, которое его вращает, мы можем увидеть, как он выглядит, когда элемент получает собственный слой: обратите внимание на оранжевую рамку, которая очерчивает слой в этом представлении.

Критерии создания слоя

Что еще получает свой собственный слой? Эвристика Chrome здесь со временем развивалась и продолжает развиваться, но в настоящее время создается любой из следующих триггерных слоев:

  • Свойства CSS трехмерного или перспективного преобразования
  • Элементы <video> с использованием ускоренного декодирования видео
  • Элементы <canvas> с 3D-контекстом (WebGL) или ускоренным 2D-контекстом.
  • Составные плагины (например, Flash)
  • Элементы с анимацией CSS для их непрозрачности или с использованием анимированного преобразования.
  • Элементы с ускоренными CSS-фильтрами
  • У элемента есть потомок, у которого есть слой композиции (другими словами, если у элемента есть дочерний элемент, который находится в его собственном слое).
  • У элемента есть родственный элемент с меньшим z-индексом, который имеет составной слой (другими словами, он отображается поверх составного слоя).

Практические последствия: анимация

Мы также можем перемещать слои, что делает их очень полезными для анимации.

Рисунок 3. Анимированные слои.

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

Как упоминалось ранее, слои действительно полезны для перемещения статического веб-контента. В базовом случае Chrome рисует содержимое слоя в программном растровом изображении перед загрузкой его в графический процессор в виде текстуры. Если это содержимое не изменится в будущем, его не нужно перекрашивать. Это хорошо: перерисовка требует времени, которое можно потратить на другие вещи, например, на запуск JavaScript, а если отрисовка длится долго, это приводит к сбоям или задержкам в анимации.

См., например, этот вид временной шкалы Dev Tools: при вращении этого слоя вперед и назад операции рисования не выполняются.

Скриншот временной шкалы Dev Tools во время анимации
Скриншот временной шкалы Dev Tools во время анимации

Неверный! Перекраска

Но если содержимое слоя изменится, его придется перекрасить.

Рисунок 4. Перерисовка слоев.

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

При каждом щелчке по элементу ввода вращающийся элемент становится на 1 пиксель шире. Это вызывает перерисовку и перерисовку всего элемента, который в данном случае представляет собой целый слой.

Хороший способ увидеть, что рисуется, — это использовать инструмент «показать прямоугольники рисования» в Dev Tools, который также находится в разделе «Визуализация» настроек Dev Tools. После включения обратите внимание, что анимированный элемент и кнопка мигают красным при нажатии кнопки.

Снимок экрана: флажок «Показать прямоугольники рисования»
Снимок экрана: флажок «Показать прямоугольники рисования»

События рисования также отображаются на временной шкале Dev Tools. Внимательные читатели могут заметить, что здесь есть два события рисования: одно для слоя и одно для самой кнопки, которая перерисовывается при переходе в нажатое состояние или обратно.

Снимок экрана: временная шкала Dev Tools, перерисовывающая слой
Снимок экрана: временная шкала Dev Tools, перерисовывающая слой

Обратите внимание, что Chrome не всегда нужно перерисовывать весь слой, он старается разумно перерисовывать только ту часть DOM, которая стала недействительной. В данном случае измененный нами элемент DOM имеет размер всего слоя. Но во многих других случаях в слое будет много элементов DOM.

Следующий очевидный вопрос: что вызывает аннулирование и необходимость перерисовки. На этот вопрос сложно ответить исчерпывающе, поскольку существует множество крайних случаев, которые могут привести к аннулированию. Наиболее распространенной причиной является загрязнение DOM путем манипулирования стилями CSS или выполнения ретрансляции. У Тони Джентилкора есть отличный пост в блоге о том, что вызывает переключение , а у Стояна Стефанова есть статья, в которой более подробно рассматривается рисование (но оно заканчивается просто рисованием, а не этой причудливой композицией).

Лучший способ выяснить, влияет ли это на что-то, над чем вы работаете, — использовать инструменты Dev Tools Timeline и Show Paint Rects, чтобы увидеть, перерисовываете ли вы, когда вам хотелось бы, чтобы это было не так, а затем попытаться определить, где вы испортили DOM прямо перед этой ретрансляцией/перерисовкой. Если рисование неизбежно, но кажется, что оно занимает неоправданно много времени, прочтите статью Эберхарда Гретера о режиме непрерывного рисования в Dev Tools.

Собираем все вместе: DOM на экран

Так как же Chrome превращает DOM в изображение на экране? Концептуально это:

  1. Берет DOM и разбивает его на слои.
  2. Красит каждый из этих слоев независимо в программные растровые изображения.
  3. Загружает их в графический процессор в виде текстур.
  4. Объединяет различные слои в окончательное изображение на экране.

Все это должно произойти, когда Chrome впервые создает фрейм веб-страницы. Но тогда может потребоваться несколько ярлыков для будущих кадров:

  1. Если некоторые свойства CSS изменяются, нет необходимости что-либо перерисовывать. Chrome может просто перекомпоновать существующие слои, которые уже находятся на графическом процессоре, в виде текстур, но с другими свойствами композиции (например, в разных положениях, с разной непрозрачностью и т. д.).
  2. Если часть слоя становится недействительной, она перерисовывается и повторно загружается. Если его содержимое остается прежним, но его составные атрибуты изменяются (например, оно транслируется или изменяется его непрозрачность), Chrome может оставить его в графическом процессоре и выполнить повторную компоновку для создания нового кадра.

Теперь должно быть ясно, что модель композитинга на основе слоев имеет глубокие последствия для производительности рендеринга. Композитинг сравнительно дешев, когда ничего не нужно рисовать, поэтому избегать перерисовки слоев — хорошая общая цель при попытке отладки производительности рендеринга. Опытные разработчики взглянут на приведенный выше список триггеров композитинга и поймут, что можно легко принудительно создавать слои. Но будьте осторожны, создавая их вслепую, поскольку они не бесплатны: они занимают память в системной оперативной памяти и на графическом процессоре (особенно ограничено на мобильных устройствах), а наличие большого количества из них может привести к другим накладным расходам в логике, отслеживающей видимые элементы. . Многие слои также могут увеличить время, затрачиваемое на растеризацию, если они большие и сильно перекрываются там, где этого не было раньше, что приводит к тому, что иногда называют «перерисовкой». Так что используйте свои знания с умом!

Это все на данный момент. Оставайтесь с нами, чтобы прочитать еще пару статей о практическом применении модели слоев.

Дополнительные ресурсы