До выхода Firefox Quantum остается все меньше и меньше времени.
Это принесет множество улучшений производительности, в том числе супер быстрый CSS-движок , который мы позаимствовали у Servo.
Но есть еще одна важная часть технологии Servo, которая еще не включена в Firefox Quantum, но скоро будет. Это WebRender, часть проекта Quantum Render.
WebRender известен своей исключительной скоростью.
Но главная цель — не ускорить рендеринг, а сделать его более плавным.
При разработке WebRender мы поставили цель, чтобы все приложения работали со скоростью 60 кадров в секунду (FPS) или выше, независимо от размера дисплея или размера анимации.
И это сработало.
Страницы, которые работают со скоростью 15 кадров в секунду в Chrome или текущей версии Firefox летать со скоростью 60 кадров в секунду при запуске WebRender .
Как WebRender это делает? Это фундаментально меняет способ работы движка рендеринга, делая его более похожим на движок 3D-игры.
Давайте разберемся, что это значит. Но сначала… Что делает рендерер? В статья Стило Я объяснил, как браузер переходит от анализа HTML и CSS к пикселям на экране, и как большинство браузеров делают это за пять шагов.
Эти пять этапов можно разделить на две части.
По сути, первый из них — это составление плана.
Чтобы создать план, браузер анализирует HTML и CSS, принимая во внимание такую информацию, как размер области просмотра, чтобы точно определить, как должен выглядеть каждый элемент — его ширина, высота, цвет и т. д. Конечным результатом является то, что называется « дерево фрейма» или «дерево рендеринга».
Вторая часть — рендеринг и композиция — это то, где в игру вступает рендерер.
Он берет этот проект и превращает его в пиксели на экране.
Но браузеру недостаточно сделать это всего один раз.
Ему приходится повторять операцию снова и снова для одной и той же веб-страницы.
Каждый раз, когда что-то меняется на странице — например, если с помощью переключателя открывается элемент div, — браузеру приходится повторять все шаги заново много раз.
Даже если на странице ничего не меняется (например, вы просто прокручиваете или выделяете текст), браузеру все равно приходится выполнять операции рендеринга для рисования новых пикселей на экране.
Чтобы прокрутка и анимация были плавными, они должны обновляться со скоростью 60 кадров в секунду.
Возможно, вы слышали эту фразу — количество кадров в секунду (FPS) — раньше, но не знали, что она означает. Я представляю их как флипбук.
Это как книга со статичными картинками, которые можно быстро перелистывать, чтобы создавалась иллюзия анимации.
Чтобы анимация в таком флипбуке выглядела плавной, нужно просматривать 60 страниц в секунду.
Страницы флипбука изготовлены из миллиметровой бумаги.
Есть много-много маленьких квадратов, и каждый квадрат может содержать только один цвет. Задача рендерера — заполнить квадраты на миллиметровке.
Когда они все заполнены, рендеринг кадра завершен.
Конечно, в вашем компьютере нет настоящей миллиметровки.
Вместо этого у компьютера есть область памяти, называемая буфером кадра.
Каждый адрес памяти в кадровом буфере подобен квадрату на миллиметровой бумаге.
он соответствует пикселю на экране.
Браузер заполняет каждую ячейку числами, соответствующими значениям RGBA (красный, зеленый, синий и альфа).
Когда экрану необходимо обновиться, он обращается к этой области памяти.
Дисплей большинства компьютеров обновляется 60 раз в секунду.
Вот почему браузеры пытаются выдавать 60 кадров в секунду.
Это означает, что у браузера есть всего 16,67 миллисекунды, чтобы выполнить всю работу: анализ стилей CSS, макет, рендеринг — и заполнение всех слотов в буфере кадра числами, соответствующими цветам.
Этот промежуток времени между двумя кадрами (16,67 мс) называется бюджетом кадра.
Возможно, вы слышали, как люди иногда упоминают пропущенные кадры.
Пропущенный кадр — это когда система выходит за рамки бюджета.
Дисплей пытается получить новый кадр из фреймбуфера до того, как браузер завершит его отображение.
В этом случае на дисплее снова отображается старая версия кадра.
Пропущенные кадры можно сравнить со страницей, вырванной из флипбука.
Анимация начинает зависать и дергаться, потому что вы потеряли ссылку с предыдущей страницы на следующую.
Таким образом, вам нужно поместить все пиксели в фреймбуфер, прежде чем дисплей проверит их снова.
Давайте посмотрим, как браузеры справлялись с этим и как технология менялась с течением времени.
Тогда мы сможем придумать, как ускорить этот процесс.
Краткая история рендеринга и верстки Примечание.
Рендеринг и композитинг — это то, в чем механизмы рендеринга браузера больше всего отличаются друг от друга.
Одноплатформенные браузеры (Edge и Safari) работают немного иначе, чем многоплатформенные браузеры (Firefox и Chrome).
Даже в самых ранних браузерах были внесены некоторые оптимизации для ускорения рендеринга страниц.
Например, при прокрутке страницы браузер пытался переместить уже отрисованные части страницы, а затем отрисовать пиксели на свободном пространстве.
Процесс расчета того, что изменилось, а затем обновления только изменившихся элементов или пикселей называется недействительным.
Со временем браузеры начали использовать более продвинутые методы инвалидации, такие как инвалидация прямоугольников.
При этом вычисляется наименьший прямоугольник вокруг измененной области экрана, а затем обновляются только пиксели внутри этих прямоугольников.
Это действительно уменьшает объем вычислений, если на странице меняется лишь небольшое количество элементов.
например, просто мигающий курсор.
Но это мало поможет, если изменяются большие части страницы.
Для таких случаев приходилось придумывать новые методики.
Внешний вид слоев и макет
Использование слоев очень помогает при изменении больших частей страницы.по крайней мере, в некоторых случаях.
Слои в браузерах похожи на слои в Photoshop или слои тонкой гладкой бумаги, которую раньше использовали для рисования мультфильмов.
Обычно вы рисуете разные элементы страницы на разных слоях.
Затем вы помещаете эти слои друг на друга.
Браузеры долгое время использовали слои, но они не всегда ускоряли рендеринг.
Сначала они использовались просто для того, чтобы убедиться, что элементы отображаются правильно.
Они реализовали так называемый «контекст стекирования».
Например, если на вашей странице есть полупрозрачный элемент, он должен находиться в своем собственном позиционном контексте.
Это означает, что у него есть собственный слой, поэтому его цвет можно смешивать с цветом нижележащего элемента.
Эти слои были удалены сразу после рендеринга кадра.
На следующем кадре слои пришлось рисовать заново.
Но некоторые элементы на этих слоях не меняются от кадра к кадру.
Например, представьте себе обычную анимацию.
Фон не меняется, даже если персонажи на переднем плане движутся.
Гораздо эффективнее сохранить фоновый слой и просто использовать его повторно.
Именно это и сделали браузеры.
Они начали сохранять слои, обновляя только те, которые изменились.
А в некоторых случаях слои вообще не менялись.
Их нужно лишь слегка переместить — например, если анимация движется по экрану или если элемент прокручивается.
Этот процесс объединения слоев называется макетом.
Компоновщик работает со следующими объектами:
- исходные растровые изображения: фон (включая пустое окно, в котором должен прокручиваться контент) и сам прокручиваемый контент;
- Целевое растровое изображение — это то, что отображается на экране.
Затем ему необходимо выяснить, какую часть прокручиваемого контента следует отображать.
Он скопирует эту часть поверх целевого растрового изображения.
Это уменьшает объем рендеринга в основном потоке.
Но основной поток по-прежнему тратит много времени на макетирование.
И есть много процессов, которые борются за ресурсы в основном потоке.
Я уже приводил этот пример: основной поток похож на Full-Stack разработчика.
Он отвечает за DOM, верстку и JavaScript. А еще он отвечает за рендеринг и верстку.
Каждая миллисекунда, потраченная на рендеринг и компоновку основного потока, — это время, отнимаемое у JavaScript или макета.
Но у нас есть другое оборудование, которое стоит здесь и почти ничего не делает. И он специально создан для графической обработки.
Речь идет о графическом процессоре, который игры с 90-х годов использовали для быстрого рендеринга кадров.
С тех пор графические процессоры стали больше и мощнее.
Аппаратное ускорение верстки
Поэтому разработчики браузеров начали передавать эту работу графическому процессору.Теоретически на графический ускоритель можно передать две задачи:
- Рисуем слои.
- Расположение слоев друг с другом.
Поэтому обычно многоплатформенные браузеры оставляют эту задачу процессору.
Однако графический процессор может очень быстро выполнять композицию, и ему легко поручить эту задачу.
Некоторые браузеры еще больше расширяют параллелизм, добавляя поток компоновщика в ЦП.
Он становится менеджером всей работы по макетированию, выполняемой на графическом процессоре.
Это означает, что если основной поток чем-то занят (например, выполнением JavaScript), поток компоновщика все еще активен и выполняет работу, видимую пользователю, например прокрутку контента.
То есть вся работа по верстке уходит из основного потока.
Однако там еще многое осталось.
Каждый раз, когда слой необходимо перерисовать, основной поток делает это, а затем передает слой в графический процессор.
Некоторые браузеры также перенесли рендеринг в дополнительный поток (сейчас мы работаем над этим и в Firefox).
Но быстрее будет перенести эту последнюю часть вычислений — рендеринг — непосредственно на графический процессор.
Аппаратное ускорение рендеринга
Итак, браузеры тоже начали передавать рендеринг на графический процессор.
Этот переход все еще продолжается.
Некоторые браузеры выполняют весь рендеринг на графическом процессоре, в то время как другие делают это только на определенных платформах (например, только на Windows или только на мобильных устройствах).
Рендеринг с помощью графического процессора имел несколько последствий.
Это позволило ЦП посвящать все свое время таким задачам, как JavaScript и верстка.
Кроме того, графические процессоры отрисовывают пиксели намного быстрее, чем центральные процессоры, поэтому ускоряется весь процесс рендеринга.
Объем данных, которые необходимо передать из ЦП в ГП, также был уменьшен.
Но поддержание такого разделения между рендерингом и композитингом по-прежнему требует некоторых затрат, даже если оба процесса выполняются на графическом процессоре.
Это разделение также ограничивает ваши возможности оптимизации для ускорения графического процессора.
Здесь в игру вступает WebRender. Это фундаментально меняет способ рендеринга, устраняя различие между рендерингом и композицией.
Это позволяет адаптировать производительность рендерера к требованиям современного Интернета и подготовить его к ситуациям, которые возникнут в будущем.
Другими словами, мы не просто хотели, чтобы кадры отображались быстрее.
мы хотели, чтобы они отображались более последовательно, без заиканий и заиканий.
И даже если требуется рендеринг большого количества пикселей, как в гарнитурах 4K WebVR, нам все равно нужно плавное воспроизведение.
Почему анимация в современных браузерах такая медленная? Вышеупомянутые оптимизации помогли в некоторых случаях ускорить рендеринг.
Когда меняются минимальные элементы страницы (например, просто мигают курсы), браузер выполняет минимально возможный объем работы.
После разбивки страниц на слои количество таких «идеальных» сценариев увеличилось.
Если вы можете просто нарисовать несколько слоев, а затем просто переместить их друг относительно друга, то архитектура рендер + макет отлично справляется со своей задачей.
Но у слоев есть недостатки.
Они занимают много памяти и иногда могут замедлять рендеринг.
Браузеры должны комбинировать слои там, где это имеет смысл.
но сложно точно определить, где это имеет смысл, а где нет. Это означает, что если на странице перемещается много разных объектов, вам придется создать кучу слоев.
Слои занимают слишком много памяти, а передача в наборщик занимает слишком много времени.
В других случаях у вас получится один слой там, где должно быть несколько.
Этот единственный слой будет постоянно перерисовываться и передаваться наборщику, который затем скомпонует его, ничего не меняя.
То есть усилия по рендерингу снимаются: каждый пиксель обрабатывается дважды без всякой необходимости.
Было бы быстрее просто визуализировать страницу напрямую, минуя этап верстки.
Во многих случаях слои просто бесполезны.
Например, если у вас анимированный фон, то весь слой все равно придется перерисовывать.
Эти слои помогают только с небольшим количеством свойств CSS. Даже если большинство кадров укладываются в оптимальный сценарий, то есть занимают лишь небольшую часть бюджета кадра, движение объектов все равно может оставаться прерывистым.
Чтобы на глаз воспринимать рывки и подтормаживания, достаточно потерять всего пару кадров, укладывающихся в худший вариант развития событий.
Эти сценарии называются скачками производительности.
Кажется, что приложение работает нормально, пока не столкнется с одним из этих наихудших сценариев (например, анимированным фоном), и частота кадров внезапно не упадет до предела.
Но избавиться от таких обрывов можно.
Как это сделать? Давайте последуем примеру 3D-игровых движков.
Использование графического процессора в качестве игрового движка Что, если мы перестанем гадать, какие слои нам нужны? Что, если мы удалим этот промежуточный этап между рендерингом и композицией и просто вернемся к рендерингу каждого пикселя в каждом кадре? Это может показаться нелепой идеей, но кое-где применяется такая система.
Современные видеоигры повторно отображают каждый пиксель и поддерживают частоту 60 кадров в секунду более надежно, чем браузеры.
Они делают это необычным способом.
вместо создания прямоугольников и слоев недействительности, минимизирующих область перерисовки, они просто обновляют весь экран.
Будет ли рендеринг веб-страницы таким образом намного медленнее? Если делать рендеринг на CPU, то да.
Но графические процессоры специально предназначены для такого рода работы.
Графические процессоры построены с максимальным параллелизмом.
я говорил о параллелизм в его последней статье на Stylo .
Параллельная обработка позволяет компьютеру выполнять несколько задач одновременно.
Количество одновременных задач ограничено количеством ядер процессора.
ЦП обычно имеет от 2 до 8 ядер, а графический процессор — как минимум несколько сотен, а часто и более 1000 ядер.
Однако эти ядра работают немного по-другому.
Они не могут функционировать полностью независимо, как ядра ЦП.
Вместо этого они обычно выполняют какую-то совместную задачу, выполняя одну и ту же инструкцию для разных фрагментов данных.
Это именно то, что нам нужно при заполнении пикселей.
Все пиксели могут быть распределены по разным ядрам.
Поскольку графический процессор работает с сотнями пикселей одновременно, он может заполнять пиксели гораздо быстрее, чем центральный процессор.
но только если все ядра заняты.
Поскольку ядра должны работать над одной задачей одновременно, графические процессоры имеют довольно ограниченный набор действий, а их программные интерфейсы очень ограничены.
Давайте посмотрим, как это работает. Первый шаг — сообщить графическому процессору, что именно рисовать.
Это означает предоставление им форм учреждения и инструкций по их заполнению.
Для этого следует разбить весь рисунок на простые фигуры (обычно треугольники).
Эти фигуры находятся в трехмерном пространстве, поэтому некоторые из них могут затмевать другие.
Затем вы берете вершины всех треугольников и добавляете в массив их координаты x, y, z.
Затем вы отправляете команду графического процессора для рисования этих фигур (вызов рисования).
С этого момента графический процессор начинает работать.
Все ядра будут выполнять одну задачу одновременно.
Они сделают следующее:
- Определите углы всех фигур.
Это называется вершинным затенением.
- Настройте линии, соединяющие вершины.
Теперь вы можете определить, какие пиксели включает в себя фигура.
Это называется растеризацией.
- Как только мы узнаем, какие пиксели принадлежат каждой фигуре, мы можем просмотреть каждый пиксель и назначить ему цвет. Это называется пиксельным затенением.
Для выдачи конкретных инструкций на графическом процессоре запускается специальная программа, называемая «пиксельным шейдером».
Окраска пикселей — одна из немногих функций графического процессора, которую можно запрограммировать.
Некоторые пиксельные шейдеры очень просты.
Например, если вся фигура окрашена одним цветом, шейдер должен просто назначить этот цвет каждому пикселю фигуры.
Но есть и более сложные шейдеры, например, в фоновом изображении.
Здесь вам предстоит разобраться, какие части изображения какому пикселю соответствуют. Это можно сделать так же, как художник масштабирует изображение, увеличивая или уменьшая его.
размещая поверх изображения сетку с квадратами для каждого пикселя.
Затем возьмите образцы цветов внутри каждого квадрата и определите окончательный цвет пикселя.
Это называется отображением текстуры, поскольку оно отображает изображение (называемое текстурой) на пиксели.
Графический процессор будет связываться с пиксельным шейдером попиксельно.
Разные ядра работают с разными пикселями параллельно, но всем им нужен один и тот же пиксельный шейдер.
Когда вы даете указание графическому процессору визуализировать формы объекта, вы также указываете, какой пиксельный шейдер использовать.
Почти для всех веб-страниц разные части страницы требуют разных пиксельных шейдеров.
Поскольку шейдер работает со всеми пикселями, указанными в команде рендеринга, обычно вам нужно разбить эти команды на несколько групп.
Их называют пакетами.
Чтобы максимально загрузить все ядра, необходимо создать небольшое количество пакетов с большим количеством фигур в каждом из них.
Именно так графический процессор распределяет работу между сотнями или тысячами ядер.
Это все благодаря исключительному параллелизму при рендеринге каждого кадра.
Но даже при таком исключительном параллелизме предстоит еще много работы.
Вам необходимо разумно ставить цели, чтобы добиться достойных результатов.
Здесь на помощь приходит WebRender. Как WebRender работает с графическим процессором Давайте вспомним, какие шаги предпринимает браузер для отображения страницы.
Здесь было два изменения.
- Больше нет разделения между рендерингом и композицией.
оба процесса происходят на одном и том же этапе.
Графический процессор делает их одновременно, руководствуясь командами, полученными от графического API.
- Макет теперь предоставляет нам другую структуру данных для рендеринга.
Раньше это называлось деревом фреймов (или деревом рендеринга в Chrome).
И теперь он проходит список отображения.
Он определяет, что необходимо нарисовать без использования специальных инструкций графического API. Как только необходимо отрисовать что-то новое, основной поток передает список отображения в RenderBackend — это код WebRender, работающий на ЦП.
Задача RenderBackend — взять список инструкций рендеринга высокого уровня и преобразовать его в команды для графического процессора, которые объединяются вместе для более быстрого выполнения.
Затем RenderBackend передает эти пакеты потоку компоновщика, который передает их графическому процессору.
RenderBackend хочет, чтобы команды рендеринга выполнялись на графическом процессоре с максимальной скоростью.
Для этого используется несколько различных техник.
Удаление ненужных фигур из списка (ранний отбор)
Лучший способ сэкономить время — вообще не работать.Первое, что делает RenderBackend — сокращает список отображения.
Он определяет, какие элементы списка будут фактически отображаться на экране.
Для этого он смотрит, насколько далеко элемент находится от окна в списке прокрутки.
Если фигура попадает в окно, она включается в список отображения.
И если сюда не попадает ни одна часть фигуры, то она исключается из списка.
Этот процесс называется ранней выбраковкой.
Минимизация количества промежуточных структур (дерево задач рендеринга)
Теперь наше дерево содержит только необходимые фигуры.Это дерево организовано в позиционных контекстах, о которых мы говорили ранее.
Такие эффекты, как CSS-фильтры и позиционные контексты, несколько усложняют ситуацию.
Например, у вас есть элемент с прозрачностью 0,5 и у него есть дочерний элемент. Вы можете подумать, что все дочерние элементы тоже прозрачны.
но на самом деле прозрачна вся группа.
Из-за этого вам необходимо сначала отобразить группу на текстуре, с полной прозрачностью каждого квадрата.
Затем, поместив его в родительский объект, вы сможете изменить прозрачность всей текстуры.
Позиционные контексты могут быть вложены друг в друга.
и родительский объект может принадлежать другому позиционному контексту.
То есть его нужно будет нарисовать на другой промежуточной текстуре и так далее.
За выделение места для этих текстур приходится платить.
Нам хотелось бы максимально разместить все объекты на одной промежуточной структуре.
Чтобы помочь графическому процессору справиться с задачей, мы создаем дерево задач рендеринга.
Он определяет, какие текстуры должны быть созданы раньше других текстур.
Любые текстуры, независимые от других, могут быть созданы в первый проход, то есть их потом можно объединить в одну промежуточную текстуру.
Итак, в приведенном выше примере с полупрозрачными квадратами нашим первым шагом будет окраска одного угла квадрата.
(На самом деле, это немного сложнее, но суть в этом).
Вторым проходом мы можем продублировать этот угол на весь квадрат и закрасить его.
Затем визуализируйте группу непрозрачных квадратов.
Наконец, остается только изменить прозрачность текстуры и разместить ее в соответствующем месте конечной текстуры, которая будет отображаться на экране.
Построив дерево задач рендеринга, мы высчитываем минимально возможное количество объектов рендеринга перед их отображением на экране.
Это хорошо, потому что я упоминал, что выделение места для этих текстур обходится дорого.
Дерево задач также помогает вам организовывать задачи в пакеты.
Группировка команд рисования (пакетная обработка)
Как мы уже говорили, вам необходимо создать небольшое количество пакетов с большим количеством фигур в каждом из них.Тщательная пакетная обработка может значительно ускорить рендеринг.
Вам нужно втиснуть в пакет как можно больше предметов.
Это требование выдвигается по нескольким причинам.
Во-первых, всякий раз, когда ЦП выдает команду рендеринга графическому процессору, у ЦП всегда есть много других задач.
Ему необходимо позаботиться о таких вещах, как настройка графического процессора, загрузка шейдерной программы и проверка различных аппаратных ошибок.
Вся эта работа накапливается, и пока процессор ее выполняет, графический процессор может простаивать.
Во-вторых, существуют определенные затраты на изменение государства.
Допустим, между пакетами вам нужно изменить состояние шейдера.
На обычном графическом процессоре вам придется подождать, пока все ядра завершат выполнение задачи текущего шейдера.
Это называется дренированием трубопровода.
Пока конвейер не будет очищен, остальные ядра будут переведены в режим ожидания.
Теги: #Firefox #Quantum #серво #fps #дерево кадров #дерево рендеринга #вершинное затенение #gpu #растеризация #растеризация #пиксельное затенение #пиксельный шейдер #отображение текстур #позиционный контекст #WebVR #Firefox #открытый исходный код #CSS #api #браузеры
-
Как Мы Продаем Программное Обеспечение
19 Oct, 24 -
Программирование ≠ Информатика
19 Oct, 24 -
Что Такое Точка Бифуркации?
19 Oct, 24