Введение
Для большинства веб-разработчиков фундаментальным представлением веб-странницы является DOM. В то время как процесс преобразования этого представления в изображение на экране (далее рендеринг) часто покрыто пеленой непонимания. В последние годы разработчики браузеров активно оптимизируют этот процесс, перекладывая часть работы на плечи графических процессоров: то что называется “аппаратным ускорением (hardware acceleration)”. Мы рассмотрим рендеринг в контексте обычных страниц, исключая Canvas2D и WebGL. Эта статья попытается пролить свет на фундаментальную концепцию использования аппаратного ускорения при генерации изображения веб-контента в браузере Chrome.
Предупреждение
Мы здесь будем говорить только о движке WebKit, а если быть более точным о его форке в Chromium’е. Эта статья покрывает детали реализации в Chrome’е, которые не коем образом не задокументированы в стандартах. Поэтому нет гарантий в том, что, что-либо описанное в этой статье будет применимо к другим браузерам. Тем не менее, знание этих тонкостей поможет вам провести тонкую отладку и повысить производительность.
Также, примите во внимание, что движок рендеринга в Chrome’е развивается стремительными шагами. И нет гарантий в том, что всё здесь описанное будет справедливо так же через полгода.
Важно иметь в виду, что на данный момент Chrome поддерживает два механизма рендеринга: аппаратный и старый программный. На момент написания этой статьи все страницы рендерятся с привлечением аппаратного ускорения в Windows’е, ChromeOS и Chrome’е на Android. В MacOS и Linux только страницы, содержащие композиционные элементы сваливаются в этот режим (ниже мы поговорим об этом), но совсем скоро это исправят.
В конечном счёте мы рассмотрим лишь верхушку движка рендерера, уделяя внимания тем вещам, которые призваны существенно повысить производительность. Практика на вашем собственном сайте поможет вам лучше понять модель слоёв. Но не стоит бездумно плодить их количество, это может внести лишние накладные расходы на весь графический стек.
Путь от DOM’а на экран
Концепция слоёв
Когда страница загружена и обработана в браузере, она превращается в то, что всем известно как DOM. Процесс рендеринга страницы протекает через серию промежуточных преобразований непосредственно недоступных разработчику. Важнейшая из порождаемых в процессе структур — слой.
Слои в Chrom’е бывают двух типов: Слои Преобразования (RenderLayers), которые содержат поддеревья DOM, и Графические Слои (GraphicsLayers), которые содержат поддеревья предыдущих. Сейчас последние для нас представляют больший интерес, так как Графические Слои это то что отправляется графическому процессору в качестве текстур. Далее по тексту везде, где встречается слово “слой”, я подразумеваю именно Графический Слой.
Слегка отвлечёмся на GPU терминологию и попробуем разобраться, что такое текстура?
Думайте о ней как о порции битового отображения графического объекта (bitmap), которая передаётся из основной памяти (RAM) в память графического процессора (VRAM). Когда текстура попадает в руки GPU, он может натянуть её на полигональную сетку — в видео играх это техника используется для натяжки “кожи” на трёхмерные объекты. Chrome использует текстуры для передачи порций контента страницы графическому процессору, что бы тот впоследствии мог наложить их на простую квадратную сетку и вытворять с ней любые трёхмерные преобразования. Именно так работает 3D CSS и также это прекрасно сказывается на производительности прокрутки страницы — но обо всём по порядку.
Давайте рассмотрим пару примеров для более наглядной демонстрации концепции слоёв.
Есть очень полезный инструмент для понимания слоёв в Chrome’е — опция “show composited layer borders” в разделе “rendering” окна настроек панели инструментов разработчика. Эта опция просто подсвечивает границы расположения слоёв на экране оранжевым цветом. Давайте её включим. Все скриншоты для примеров были сделаны в Chrome Canary, Chrome 27 на момент написания данной статьи.
Пример 1: Однослойная страница
<html>
<body>
<div>I am a strange root.</div>
</body>
</html>
Эта страница имеет единственный слой. Голубая сетка представляет собой границы тайлов — частей слоя, которые Chrome использует для разбивки большого слоя и отправки их GPU. Сейчас они не представляют для нас большого интереса.
Пример 2: Элемент в своём собственном слое
<html>
<body>
<div style="-webkit-transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
I am a strange root.
</div>
</body>
</html>
Задавая 3D CSS свойство элементу, которое поворачивает его, мы можем видеть, что ему выделяется его собственный слой: обратите внимание на оранжевую рамку, которая оборачивает его на скриншоте выше. (Это делается для того, что бы можно было рендерить каждый слой независимо от других)
Условия создания слоёв
Какие ещё элементы могут заполучить свой слой? Эвристика Chrome’а развивается в этом направление последнее время, но в настоящий момент любое из следующих условий приводит к созданию слоя:
- наличие у элемента CSS свойств трёхмерных трансформаций
- элемент
<video>
использующий аппаратное ускорение при декодировании видео <canvas>
в 3D(WebGL) контексте или ускоренном 2D- сторонние плагины (т.ч. Flash)
- элементы, использующие CSS анимацию прозрачности или анимацию трансформаций
- элементы, использующие CSS фильтры задействующие аппаратное ускорение
- элементы, имеющие потомков со своими композиционными слоями
- наличие у элемента брата (сестринского элемента) с более низким z-index’ом, который сам при этом обладает своим слоем (иными словами, если элемент рендерится поверх композиционного)
Практическое применение: Анимация
Мы знаем, что посредством анимации CSS трансформации элемент свалится в свой собственный слой. А слои очень хороши для анимации.
Пример 3: Анимированные слои
<html>
<head>
<style type="text/css">
div {
-webkit-animation-duration: 5s;
-webkit-animation-name: slide;
-webkit-animation-iteration-count: infinite;
-webkit-animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@-webkit-keyframes slide {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div>I am a strange root.</div>
</body>
</html>
В простейшем случае Chrome’е программно отрисует содержимое слоя и отправит его на отмашку графическому процессору в качестве текстуры. Если это содержимое не будет изменяться в будущем, то нет нужды его перерисовывать. И это хорошо: перерисовка отнимает время, которое может быть потрачено, например, на отработку скриптов. И если перерисовка занимает много времени, это может привести к досадным дефектам в виде падения частоты кадров и задержек в анимации.
Можете убедится, что на временной шкале панели разработчиков нет ни каких операций перерисовки, пока слой вращается туда — сюда.
Осторожно! Repainting
Но если содержимое слоя изменить, это приведёт к его перерисовке.
Пример 4: Перерисовка слоёв
<html>
<head>
<style type="text/css">
div {
-webkit-animation-duration: 5s;
-webkit-animation-name: slide;
-webkit-animation-iteration-count: infinite;
-webkit-animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@-webkit-keyframes slide {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div id="foo">I am a strange root.</div>
<input id="paint" type="button" onclick="" value=”repaint”>
<script>
var w = 200;
document.getElementById('paint').onclick = function() {
document.getElementById('foo').style.width = (w++) + 'px';
}
</script>
</body>
</html>
Каждый раз, когда кнопка будет нажата, вращающийся элемент будет прибавлять пиксель в ширине. Это приведёт к перерисовке всего элемента, который в данном случае находится в отдельном слое.
Простой способ, увидеть какие части страницы перерисовываются включить опцию “show paint rects” всё в том же разделе “Rendering” окна настроек панели разработчиков. После включения обратите внимание как анимированный элемент, и кнопка мерцают красным при клике по кнопке.
События перерисовки можно также заметить на временной шкале. Зоркий глаз заметит сдвоение событий перерисовки: одно собственно для слоя, другое для смены отображения состояния кнопки.
Заметьте, что Chrome’е не всегда перерисовывает слой полностью. Он пытается быть выборочным и перерисовывать только те фрагменты слоя (те самые тайлы отмеченные голубыми границами) в которых DOM подвергся изменению. В нашем случае только один элемент на весь слой, но в большинстве реальных проектов элементов на слой приходится гораздо больше.
Напрашивается следующий вопрос: что приводит к устареванию DOM’а и его перерисовке? Сложно в нескольких предложения охватить все случаи, которые могут привести к невалидности DOM’а. В большинстве случаев это порча DOM’а посредством изменения CSS стилей и динамическая генерация видимого контента в рантайме. Тони Гентилкор описывал их в своём блоге, также Стоян Стояноф писал очень детализированную статью, но она ограничивается описанием перерисовки без деталей этой новой штуки, о которой мы пытаемся говорить — композиционирование посредством видео подсистемы.
Складываем всё вместе: DOM в экран
Так как же Chrome’е превращает DOM в изображение на экране? Концептуально можно выделить следующие шаги:
- Берёт DOM и разбивает его на слои, следуя изложенным выше критериям
- Программно рисует каждый из слоёв по отдельности
- Отправляет их GPU в качестве текстур
- На графической подсистеме компонует все слои в финальное изображение
Это то, что происходит при прорисовке первого кадра страницы. Но есть несколько способов сократить накладные расходы на рендеринг каждого следующего кадра:
- Не все изменения CSS стилей приводят к перерисовке. Chome может всего лишь перекомпоновать существующие слои уже находящиеся в памяти GPU. Этого можно добиться изменением только композиционных свойств (т.к. трансформации, прозрачность, фильтры).
- Если часть слоя изменена она будет перерисована и заново отправлена GPU. Если контент оставить неизменным, а менять только композиционные свойства (трансформацию или прозрачность) Chrome’е может оставить их в видео памяти и перекомпоновать для генерации следующего кадра.
Как нам должно было стать понятным, основанная на слоях композиционная модель даёт большое преимущество для производительности рендеринга. Композиционирование более недорогой способ рендеринга в сравнение с полной перерисовкой. Поэтому попытка избежать перерисовки слоя, дёргая только композиционные свойства элементов — неплохой способ оптимизации производительности. Здравомыслящие разработчики возьмут на заметку список свойств активирующих композиционирование, что бы в подходящий момент мочь форсировать создание слоёв. Но будьте аккуратны в создание слоёв — это не бесплатно: им нужна системная память и память видео подсистемы (которая ограниченна на мобильных устройствах).
На этом всё!
Автор: lolenko