Как рисует браузер. Доклад Яндекса

в 9:02, , рубрики: Блог компании Яндекс, браузерный движок, браузеры, интерфейсы, Клиентская оптимизация, отрисовка, отрисовка ui компонентов, яндекс.браузер

До недавнего времени я работал в команде Яндекс.Браузера и по следам этого опыта сделал доклад на конференции YaTalks. Доклад был о том, что у браузера под капотом и как ваши странички превращаются в пиксели на экране. Минимум фронтенда, только внутренности браузера, только хардкор.

Как рисует браузер. Доклад Яндекса - 1

— Всем привет, меня зовут Костя. Удивительно — сейчас я работаю в команде виртуальной сети Яндекс.Облака. До этого я пять с лишним лет проработал в команде Браузера, так что сегодня буду говорить о вещах, общих для нас с вами.

Как можно догадаться, я не очень хорошо понимаю во фронтенде. Если вы будете говорить со мной о React или еще о чем-нибудь в этом духе, я, скорее всего, вас не пойму. Но я делал очень много вещей в браузере: декодирование видео, бизнес-логику. В том числе я провел много времени, делая разные вещи в отрисовке браузера. Сегодня у нас будет такой, скажем, ликбез о внутреннем устройстве браузера. Я постараюсь пройтись по наиболее интересным вещам, которые мы делали в Яндекс.Браузере или Google в Chromium.

Как рисует браузер. Доклад Яндекса - 2

Если говорить про отрисовку в браузере, то это очень сложная штука, которая состоит из большого количества компонентов. В первую очередь вы должны загрузить ресурсы, чтобы показать их. Потом вы должны их распарсить, построить DOM-дерево, стили, лейауты и т. д. Первые три пункта вам, скорее всего, знакомы. Мой доклад будет больше посвящен другим трем частям: Painting, Rasterization и Compositing — тому, что происходит под капотом, когда верстку вы уже написали. Только по словам может показаться, что это примерно об одном и том же — на самом деле это совершенно разные компоненты.

Как рисует браузер. Доклад Яндекса - 3

Начнем с Painting и Compositing. Что это вообще такое? Давайте вернемся на много-много лет назад, когда веб был не таким сложным, как сейчас, когда не было всяких 3D, CSS-анимации и прочих штук. Как тогда рисовал браузер? Представьте, что у вас есть ваша страница, на ней какие-то чудесные элементы, картиночки и т. д. Браузер все это рисовал на одну здоровенную текстуру, в большой блок памяти. Он знал, как нарисовать каждый элемент, и если у нас случались какие-то изменения, тут они выделены желтым, то происходило примерно следующее.

Браузер агрегировал их в области, которые здесь обозначены синим цветом. В этой области произошло какое-то изменение, давайте ее перерисуем. Все, что в этой области находилось, просто перерисовывалось и копировалось на текстуру.

Это вполне себе работало. Потом умные люди придумали 3D CSS-анимацию, другие вещи. У нас могло происходить очень много отрисовок в разных местах. Если мы крутим один спиннер, перерисовывать весь пирог элементов, которые под ним расположены, не очень эффективно.

Как рисует браузер. Доклад Яндекса - 4

Тогда другие умные люди решили это все немного переделать. У нас есть какое-то DOM-дерево, мы построили его в памяти. Это плюсовые объекты, они сравниваются с тем, какую верстку вы написали.

Как рисует браузер. Доклад Яндекса - 5

А потом начинает происходить магия. Браузер преобразует все DOM-дерево в дерево Render Object. Эта штука знает, как нарисовать каждый конкретный DOM-элемент. То есть она знает, что нужно сделать, чтобы на экране появилось что-то вместо вашего дерева или P-элемента.

Как рисует браузер. Доклад Яндекса - 6

Следующее дерево — дерево слоев. Что это? Каждый наш элемент может быть ассоциирован с каким-то одним слоем, причем один Render Layer может содержать сразу несколько объектов. Для чего так сделано? Здесь это очень хорошо показано. Мы генерируем набор слоев, на каждом из них есть определенные элементы. Теперь, предположим, на одном из слоев что-то меняется — происходит анимация, пролетает марки-элемент. Тогда мы перерисовываем только один слой, а остальные, например бэкграунд, остаются неизменными. Мы их потом просто склеиваем в композиты и на выходе получаем итоговую картинку — текущий кадр анимации.

Как рисует браузер. Доклад Яндекса - 7

Для создания новых слоев есть фиксированный набор причин. Например, слой создается, чтобы вынести на него 3D CSS-анимацию, canvas, видео-элемент — в общем, нечто связанное с тяжелой анимацией, пригодное для перерисов отдельно от всего остального содержимого.

Как рисует браузер. Доклад Яндекса - 8

Но такой подход имеет несколько проблем. Сейчас нужно включить мыслетопливо. Подумайте, что здесь будет изображено? Тут всего два элемента.

Как рисует браузер. Доклад Яндекса - 9

Вот. Хотя, казалось бы, элементы расположены один за одним. Почему canvas вылетает наверх? У нас же они последовательно расположены, я не задавал никакой порядок.

Как рисует браузер. Доклад Яндекса - 10

Давайте усложним. Тут у нас появляется еще один div, вот такой.

Как рисует браузер. Доклад Яндекса - 11

В общем, ожидаемое поведение. У нас div поверх div, но canvas почему-то сверху. Магия! Хорошо, давайте снова усложним этот пример.

Как рисует браузер. Доклад Яндекса - 12

Изменилась ровно одна строка, я добавил transform.

Как рисует браузер. Доклад Яндекса - 13

И теперь у нас все расположено правильно — по крайней мере, в терминах canvas и div. Но вот этот div все еще располагается внизу, хотя он был следующим элементом в нашей верстке.

Это так называемый фундаментальный баг композитинга. Если вы поищете по трекеру Chromium, то увидите кучу багов, которые слинкованы с одним древнющим. Он так и называется.

Как рисует браузер. Доклад Яндекса - 14

Так что произошло? Как я уже говорил, некоторые элементы выносятся на Render Layer, некоторые не выносятся. Какие-то рисуются совместно с другими. Здесь произошло следующее: div-элементы остаются в том же слое, что и бэкграунд. Canvas вылетает на отдельный слой. А z-ordering осуществляется только между слоями. Из-за того, что у нас бэкграунд и div в одном слое, а canvas в другом, получается баг: canvas перекрывает div.

Но как только мы выносим этот div-элемент на отдельный слой и он начинает нормально использовать z-order, он также начинает понимать, кто за кем расположен. И тут уже все отрисовывается «нормально».

Как рисует браузер. Доклад Яндекса - 15

И одна из последних инициатив, развивающихся уже несколько лет, — так называемая Slimming Paint, которая должна это починить. Ее смысл в том, что нам нужно отделить Painting от вынесения на слои, то есть понимание, что нужно сделать для отрисовки этих элементов, от того, как их потом композитить друг с другом. Если у нас вот такая простая верстка, она превращается во что-то подобное. Есть простой список команд, которые нужно сделать, чтобы получить контент страницы. И если мы вернемся к этому примеру, она будет выглядеть примерно так.

Как рисует браузер. Доклад Яндекса - 16

То есть мы сказали: вот тебе Paint, вот тебе контент — пожалуйста, дай мне что-нибудь. Он дает список для отрисовки, который уходит в Compositor, и Compositor понимает, как нужно разбить весь контент на слои, чтобы они были нормально расположены друг относительно друга.

И если вы не заметили, это скриншот из Chrome. Я сделал его где-то недели две назад, то есть баг все еще живой. Проект еще не закончен, он сейчас в процессе разработки.

Как рисует браузер. Доклад Яндекса - 17

То есть Compositor по этому списку и по каким-то тайным знаниям, которые передает plink, может понять, как правильно разбить это все на слои.

Как рисует браузер. Доклад Яндекса - 18

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

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

Давайте перейдем к другой теме: Rasterization. Как раз в Rasterization в Яндекс.Браузере было сделано много всего, и я этим тоже занимался. В чем смысл растеризации? На выходе предыдущего этапа, когда мы сделали Paint, есть список команд, которые мы должны осуществить, чтобы получилась какая-то картинка. Растеризация — это превращение списка команд в реальные пиксели.

Как рисует браузер. Доклад Яндекса - 19

Как рисует браузер. Доклад Яндекса - 20

Если вы откроете в инспекторе браузера вкладку More tools → Rendering, то там есть галочка Layer borders. И вы видите вот такую сетку. Что здесь происходит? Когда браузер рисует страницу, он на самом деле теперь не делает ее целиком. Каждый слой браузер берет и разбивает на какое-то количество вот таких квадратиков. Исторически сложилось, что они размером 256 на 256 пикселей. То есть каждый слой разбивается на вот такое количество отдельных текстур. На каждую текстуру потом рисуется контент текущего тайла, и потом они все склеиваются в одну большую текстуру.

Как рисует браузер. Доклад Яндекса - 21

Это помогает во многом. Прежде всего, мы можем перерисовывать только те тайлы, которые изменились. Но еще это позволяет нам приоритизировать отрисовку. То есть в первую очередь должны быть нарисованы те тайлы, которые видит пользователь, так называемые viewport. Затем мы должны нарисовать soon border, это то, что вокруг viewport. Дальше — направление скролла: если ее скроллят вниз, мы прорисовываем как можно больше вниз. Если вверх — прорисовываем как можно больше вверх. Если у нас осталась квота памяти, мы нарисуем что-нибудь еще, но не факт, что она останется к этому моменту.

Как рисует браузер. Доклад Яндекса - 22

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

Как рисует браузер. Доклад Яндекса - 23

То есть зеленые тайлы — это которые остаются от предыдущей отрисовки, красные — это которые мы перерисовали. Но у такого подхода есть и другие преимущества.

Как рисует браузер. Доклад Яндекса - 24

Мы можем сделать — и в Chrome это сделали — так называемую оптимизацию маленьких перерисовок. Предположим, у вас есть какой-то throbber, курсор или еще что-то в этом духе, выполняющее небольшую перерисовочку в маленьком прямоугольнике. Тогда нам нет необходимости перерисовывать весь квадрат. Это логично. Например, если у нас моргает курсор, то перерисовывается только он. Это существенно экономит CPU.

Как рисует браузер. Доклад Яндекса - 25

Следующая оптимизация, которую они сделали. Где тут может быть неэффективность? Тайлы сдвинуты? Хорошая идея, но я клоню к другому. Вот просто белый прямоугольник. Это белый тайл, на котором не нарисовано ни единого пикселя. Но это текстура. Она занимает память 256 на 256 на четыре байта.

Как рисует браузер. Доклад Яндекса - 26

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

Мы в Яндексе пошли немножко дальше и решили сделать более специфичный эксперимент. Как вы думаете, где тут еще можно сэкономить?

У нас есть тайл. Контент на нем расположен в какой-то крохотной области — полоска, слово «Яндекс». Зачем нам рисовать целиком, если контента очень мало, а все остальное одноцветное?

Как рисует браузер. Доклад Яндекса - 27

Что мы сделали? Этим занимался конкретно я. Мы разбили каждый тайл на пять тайлов. Если контент в нем только в серединке, то мы выделяем текстуру для этого контента только под то, что сделано в серединке. Вот красная область. Все остальное мы кодируем так же — размер, координаты и цвет.

Как рисует браузер. Доклад Яндекса - 28

То есть конкретно на данной странице все эти области теперь стали не текстурой. Используются не байты в памяти, а просто команды о том, что нам нужно тут нарисовать, залить одним цветом. Это дало нам экономию примерно в 40% по памяти GPU в среднем на пользователя.

Как рисует браузер. Доклад Яндекса - 29

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

Если вы сейчас включите эту галочку, то увидите не такую сетку из прямоугольников, а вот такую.

Как рисует браузер. Доклад Яндекса - 30

Что это такое, почему тайлы здесь такие широкие, и почему их мало? Смысл здесь в следующем. В Chrome подумали: почему бы нам не сделать не только хардварный композитинг, но и хардварную отрисовку. Что они делают? У нас есть список команд о том, что нужно сделать: нарисовать прямоугольник, залить цветом и т. д. Все это уходит на GPU, и GPU рисует такую текстуру. Перерисовка происходит очень быстро, поэтому тайлы тоже можно сделать большими. Вот немножко шакалистое видео, но оно очень хорошо показывает преимущество, которое случилось на телефонах как раз за счет того, что отрисовка стала хардварно ускоренной. Я думаю, разница тут очень-очень заметная.

Общение разработчиков браузера с фронтендерами мне кажется очень полезным. Оно случается не очень часто, но дает много пользы. Поэтому когда к нам приходят наши коллеги из других отделов и спрашивают, как нам сделать верстку побыстрее и получше, то мы стараемся им помочь и рассказать о местах, где что-то не оптимально и можно ускориться.

И я не устану повторять свои советы. (Не буду приводить здесь конспект, на эту тему был отдельный большой доклад. — прим. автора.)

Здесь я собрал набор полезных ссылок, про отрисовку и не только, а также немножко про Яндекс.Браузер. Спасибо.

Автор: Константин Крамлих

Источник


* - обязательные к заполнению поля