Весь веб на 60+ FPS: как новый рендерер в Firefox избавился от рывков и подтормаживаний

До релиза Firefox Quantum остаётся всё меньше времени. Он принесёт множество улучшений в производительности, в том числе сверхбыстрый движок CSS, который мы позаимствовали у Servo.

Но есть ещё одна большая часть технологии Servo, которая пока не вошла в состав Firefox Quantum, но скоро войдёт. Это WebRender, часть проекта Quantum Render.

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

При разработке WebRender мы поставили задачу, чтобы все приложения работали на 60 кадрах в секунду (FPS) или лучше, независимо от размера дисплея или от размера анимации. И это сработало. Страницы, которые пыхтят на 15 FPS в Chrome или нынешнем Firefox, летают на 60 FPS при запуске WebRender.

Как WebRender делает это? Он фундаментальным образом меняет принцип работы движка рендеринга, делая его более похожим на движок 3D-игры.

Разберёмся, что это значит. Но сначала…

Что делает рендерер?

В статье по Stylo я объясняла, как браузер переходит от разбора HTML и CSS к пикселям на экране, и как большинство браузеров делают это в пять этапов.

Эти пять этапов можно разделить на две части. Первая из них — это, по сути, составление плана. Для составления плана браузер разбирает HTML и CSS, учитывая информацию вроде размера области просмотра, чтобы точно выяснить, как должен выглядеть каждый элемент — его ширина, высота, цвет и т.д. Конечным результатом становится то, что называется «дерево фреймов» (frame tree) или «дерево визуализации» (render tree).

Во второй части — отрисовке и компоновке — вступает в работу рендерер. Он берёт этот план и превращает его в пиксели на экране.

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

Даже если на странице ничего не меняется — например, вы просто делаете прокрутку или выделяете текст — браузер всё равно должен выполнять операции рендеринга, чтобы нарисовать новые пиксели на экране.

Чтобы скроллинг и анимация были плавными, они должны обновляться на 60 кадрах в секунду.

Вы могли раньше слышать эту фразу — кадры в секунду (FPS) — будучи неуверенными, что она означает. Я представляю их как флипбук. Это как книжка со статичными картинками, которые можно быстро пролистать, так что создаётся иллюзия анимации.

Чтобы анимация в таком флипбуке выглядела плавной, нужно просматривать 60 страничек в секунду.

Странички во флипбуке сделаны из миллиметровки. Там много-много маленьких квадратиков, а каждый квадратик может содержать только один цвет.

Задача рендерера — заполнить квадратики в миллиметровке. Когда все они заполнены, то рендеринг фрейма закончен.

Конечно, в вашем компьютере нет настоящей миллиметровки. Вместо этого в компьютере есть область памяти под названием кадровый буфер. Каждый адрес памяти в кадровом буфере — это как квадратик на миллиметровке… он соответствует пикселю на экране. Браузер заполняет каждую ячейку числами, которые соответствуют значениям RGBA (красный, зелёный, синий и альфа).

Когда экрану нужно обновиться, он обращается к этой области памяти.

Большинство компьютерных дисплеев обновляются 60 раз в секунду. Вот почему браузеры стараются выдавать 60 кадров в секунду. Это значит, что у браузера есть всего 16,67 миллисекунды на всю работу: анализ стилей CSS, вёрстку, отрисовку — и заполнение всех слотов в кадровом буфере числами, которые соответствуют цветам. Этот промежуток времени между двумя фреймами (16,67 мс) называется бюджетом фрейма.

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

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

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

Краткая история отрисовки и компоновки

Примечание. Отрисовка и компоновка — это та часть, где движки в рендеринга в браузерах сильнее всего отличаются друг от друга. Одноплатформенные браузеры (Edge и Safari) работают немного не так, как мультиплатформенные (Firefox и Chrome).

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

Процесс вычисления, что изменилось, а затем обновления только изменившихся элементов или пикселей, называется инвалидацией.

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

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

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

Появление слоёв и компоновки

Использование слоёв сильно помогает при изменении больших частей страницы… по крайней мере, в некоторых случаях.

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

Долгое время в браузерах использовались слои, но они не всегда ускоряли рендеринг. Поначалу их использовали просто чтобы гарантировать правильную отрисовку элементов. Они осуществляли так называемый «позиционный контекст» (stacking context).

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

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

Именно так поступили браузеры. Они начали сохранять слои, обновляя только изменившиеся. А в некоторых случаях слои вообще не менялись. Их нужно лишь слегка переместить — например, если анимация перемещается по экрану или в случае прокрутки элемента.

Такой процесс совместной расстановки слоёв называется компоновкой. Компоновщик работает со следующими объектами:

  • исходные растровые изображения: фон (включая пустое окно, где должен прокручиваться контент) и сам прокручивающийся контент;
  • целевое растровое изображение — то, что демонстрируется на экране.

Сначала компоновщик копирует фон на целевое растровое изображение.

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

Это снижает объём отрисовки в основном потоке. Но основной поток по-прежнему много времени тратит на компоновку. И есть много процессов, которые борются за ресурсы в основном потоке.

Я приводила такой пример раньше: основной поток похож на full-stack разработчика. Он отвечает за DOM, вёрстку и JavaScript. И он также отвечает за отрисовку и компоновку.

Каждая миллисекунда, потраченная в основном потоке на отрисовку и компоновку — это время, которое отняли у JavaScript или вёрстки.

Но у нас есть другое аппаратное обеспечение, которое сидит здесь и почти ничего не делает. И оно специально создано для графической обработки. Речь идёт о GPU, который игры с 90-х годов используют для быстрой отрисовки кадров. А с тех пор графические процессоры стали больше и мощнее.

Компоновка с аппаратным ускорением

Так что разработчики браузеров начали передавать работу GPU.

Теоретически, графическому ускорителю можно передать две задачи:

  1. Отрисовка слоёв.
  2. Компоновка слоёв друг с другом.

Отрисовку бывает сложно передать GPU. Так что обычно мультиплатформенные браузеры оставляют эту задачу на CPU.

Однако GPU может очень быстро выполнять компоновку, и эту задачу легко на него повесить.

Некоторые браузеры ещё больше форсируют параллелизм, добавляя поток компоновщика на CPU. Он становится менеджером всей работы по компоновке, которая выполняется на GPU. Это значит, что если основной поток чем-то занят (например, выполняет JavaScript), о поток компоновщика по-прежнему активен и выполняет работу, видимую для пользователя, типа прокрутки контента.

То есть вся работа по компоновке уходит из основного потока. Впрочем, там по-прежнему остаётся много всего. Каждый раз, когда нужно перерисовать слой, это делает основной поток, а затем передаёт слой в GPU.

Некоторые браузеры переместили и отрисовку в дополнительный поток (сейчас мы в Firefox тоже работаем над этим). Но быстрее будет передать и этот последний кусок вычислений — отрисовку — сразу на GPU.

Отрисовка с аппаратным ускорением

Итак, браузеры начали передавать на графический процессор и отрисовку тоже.

Этот переход всё ещё продолжается. Некоторые браузеры выполняют всю отрисовку на GPU, а в других такое возможно только на определённых платформах (например, только на Windows или только на мобильных устройствах).

Отрисовка на GPU привела к нескольким последствиям. Она позволила CPU уделять всё время задачам вроде JavaScript и вёрстки. К тому же, GPU гораздо быстрее рисуют пиксели, чем CPU, так что ускорился весь процесс отрисовки. Также уменьшилось количество данных, которые нужно передавать с CPU на GPU.

Но поддержание такого разделения между отрисовкой и компоновкой по-прежнему требует определённых расходов, даже если оба процесса выполняются на GPU. Это разделение ещё и ограничивает вас в вариантах оптимизаций для ускорения работы GPU.

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

Другими словами, мы хотели не просто ускорить рендеринг фреймов… мы хотели, чтобы они рендерились более стабильно, без рывков и подтормаживаний. И даже если нужно отрисовать много пикселей, как в шлемах виртуальной реальности WebVR с разрешением 4K, мы по-прежнему хотим плавное воспроизведение.

Почему анимация так тормозит в современных браузерах?

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

После разбивки страниц на слои количество таких «идеальных» сценариев увеличилось. Если вы можете просто нарисовать несколько слоёв, а потом просто двигать их относительно друг друга, то архитектура «отрисовка + компоновка» отлично справляется.

Но у слоёв есть недостатки. Они занимают много памяти, а иногда могут замедлить рендеринг. Браузеры должны сочетать слои там, где это имеет смысл… но сложно определить точно, где это имеет смысл, а где нет.

Значит, если на странице двигается много разных объектов, придётся создавать кучу слоёв. Слои отнимают слишком много памяти, а передача в компоновщик отнимает слишком много времени.

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

То есть усилия по отрисовке удаиваются: каждый пиксель обрабатывается дважды без какой-либо необходимости. Быстрее было бы просто напрямую рендерить страницу, минуя этап компоновки.

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

Даже если большинство фреймов вписываются в оптимальный сценарий — то есть отнимают только малую часть бюджета фрейма — движение объектов по-прежнему может остаться прерывистым. Чтобы воспринять на глаз рывки и подтормаживания, достаточно потери всего пары кадров, которые вписались в худший сценарий.

Эти сценарии называются обрывами производительности. Приложение работает как будто нормально, пока не столкнётся с одним из этих худших сценариев (вроде анимированного фона) — и фреймрейт внезапно падает до предела.

Но можно избавиться от таких обрывов.

Как это сделать? Последуем примеру игровых 3D-движков.

Использование GPU как игрового движка

Что если мы перестанем гадать, какие слои нам нужны? Что если удалить этот промежуточный этап между отрисовкой и компоновкой и просто вернуться к отрисовке каждого пикселя в каждом фрейме?

Может, это покажется нелепой идеей, но кое-где используется такая система. В современных видеоиграх перерисовывается каждый пиксель, и они держат уровень 60 кадров в секунду надёжнее, чем браузеры. Они делают это необычным способом… вместо создания этих прямоугольников для инвалидации и слоёв, которые минимизируют площадь для перерисовки, просто обновляется весь экран целиком.

Будет ли рендеринг веб-страницы таким способом намного медленнее?

Если мы делаем отрисовку на CPU, то да. Но GPU специально спроектированы для такой работы.

GPU построены с максимальным параллелизмом. Я рассказывала о параллелизме в своей последней статье о Stylo. Благодаря параллельной обработке компьютер выполняет несколько задач одновременно. Количество одновременно выполняемых задач ограничено количеством ядер в процессоре.

В CPU обычно от 2 до 8 ядер, а в GPU как минимум несколько сотен, а часто и больше 1000 ядер.

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

Это именно то, что нам нужно при заполнении пикселей. Все пиксели можно раздать разным ядрам. Поскольку GPU работает с сотнями пикселей одновременно, то заполнение пикселей он выполняет значительно быстрее, чем CPU… но только если все ядра загружены работой.

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

Первым делом нужно указать GPU, что именно отрисовать. Это значит передать им формы объектов и инструкции по их заполнению.

Для этого следует разбить весь рисунок на простые формы (обычно треугольники). Эти формы находятся в 3D-пространстве, так что некоторые из них могут заслонять другие. Затем вы берёте вершины всех треугольников — и вносите в массив их координаты x, y, z.

Затем отправляете команду GPU на отрисовку этих форм (draw call).

С этого момента GPU начинает работать. Все ядра будут выполнять одну задачу одновременно. Они сделают следующее:

  1. Определят углы всех фигур. Это называется закрашиванием вершин (vertex shading).
  2. Установят линии, которые соединяют вершины. Теперь можно определить, какие пиксели включает в себя фигуры. Это называется растеризацией.
  3. Когда мы знаем, какие пиксели принадлежат каждой фигуре, можно пройтись по каждому пикселю и присвоить ему цвет. Это называется закрашиванием пикселей (pixel shading).

Последний шаг выполняется по-разному. Чтобы выдать конкретные инструкции, с GPU работает специальная программа, которая называется «пиксельный шейдер». Закрашивание пикселей — один из немногих элементов функциональности GPU, который вы можете программировать.

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

Но бывают более сложные шейдеры, например, в фоновом изображении. Здесь придётся выяснить, какие части изображения соответствуют какому пикселю. Это можно сделать тем же способом, как художник масштабирует изображение, увеличивая или уменьшая его… поместите поверх картинки сетку с квадратиками для каждого пикселя. Затем возьмите образцы цветов внутри каждого квадратика — и определите итоговый цвет пикселя. Это называется наложением текстуры (texture mapping), поскольку здесь изображение (оно называется текстурой) накладывается на пиксели.

GPU будет обращаться к пиксельному шейдеру по поводу каждого пикселя. Разные ядра параллельно работают над разными пикселями, но всем им нужен один и тот же пиксельный шейдер. Когда вы инструктируете GPU отрисовать формы объектов, вы одновременно указываете, какой использовать пиксельный шейдер.

Почти для всех веб-страниц разным частям страницы требуются разные пиксельные шейдеры.

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

Вот так GPU распределяет работу по сотням или тысячам ядер. Всё из-за исключительного параллелизма при рендеринге каждого фрейма. Но даже с таким исключительным параллелизмом по-прежнему остаётся много работы. К постановке задач нужно подходить с умом, чтобы добиться достойной производительности. Вот здесь вступает в дело WebRender…

Как WebRender работает с GPU

Давайте вспомним, какие шаги предпринимает браузер для рендеринга страницы. Здесь произошло два изменения.

  1. Больше нет разделения между отрисовкой и компоновкой… оба процесса выполняются на одном этапе. GPU делает их одновременно, руководствуясь полученными командами от графического API.
  2. Вёрстка теперь даёт нам для рендеринга другую структуру данных. Раньше это было нечто под названием дерева фреймов (или дерево визуализации в Chrome). А теперь она передаёт список отображения (display list).

Список отображения — это набор высокоуровневых инструкций отрисовки. Он указывает, что нужно отрисовать, не используя специфические инструкций для конкретного графического API.

Как только нужно отрисовать что-то новое, основной поток передаёт список отображения в RenderBackend — это код WebRender, работающий на CPU.

Задача RenderBackend — взять список высокоуровневых инструкций отрисовки и преобразовать его в команды для GPU, которые объединяются в пакеты для более быстрого выполнения.

Затем RenderBackend передаёт эти пакеты в поток компоновщика, которые передаёт их далее в GPU.

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

Удаление лишних фигур из списка (ранняя выбраковка)

Лучший способ сэкономить время — вообще не работать.

Первым делом RenderBackend сокращает список отображения. Он определяет, какие элементы списка реально будут отображаться на экране. Для этого он смотрит, насколько далеко от окна в списке прокрутки находится элемент.

Если фигура попадает в пределы окна, её включают в список отображения. А если ни одна часть фигуры не попадает сюда, то её исключают из списка. Этот процесс называется ранней выбраковкой (early culling).

Минимизация количества промежуточных структур (дерево задач для рендеринга)

Теперь наше дерево содержит только нужные формы. Это дерево организовано в позиционных контекстах, о которых мы говорили ранее.

Эффекты вроде фильтров CSS и позиционные контексты несколько усложняют дело. Например, у вас есть элемент с прозрачностью 0,5, а у него есть дочерний элемент. Вы можете подумать, что все дочерние элементы тоже прозрачные… но в реальности прозрачна вся группа.

Из-за этого нужно сначала вывести группу на текстуру, с полной прозрачностью каждого квадрата. Затем, помещая её в родительский объект, можно изменить прозрачность всей текстуры.

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

Выделение пространства для этих текстур дорого обходится. Мы хотели бы максимально вместить все объекты на одной и той же промежуточной структуре.

Чтобы помочь GPU справиться с задачей, создаём дерево задач для рендеринга. В нём указано, какие текстуры нужно создать раньше других текстур. Любые текстуры, независимые от других, могут быть созданы в первом проходе, то есть их можно потом объединить на одной промежуточной текстуре.

Так что в вышеупомянутом примере с полупрозрачными квадратами мы первым проходом раскрасили бы один угол квадрата. (На самом деле всё немного сложнее, но суть в этом).

Вторым проходом можем продублировать этот угол для всего квадрата и закрасить его. Затем выполнить рендеринг группы непрозрачных квадратов.

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

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

Дерево задач также помогает объединять задачи в пакеты.

Группировка команд на отрисовку (пакетная обработка)

Как мы уже говорили, нужно создать небольшое количество пакетов с большим количеством фигур в каждом из них.

Тщательное формирование пакетов позволяет сильно ускорить рендеринг. Нужно втиснуть в пакет как можно больше объектов. Такое требование выдвигается по нескольким причинам.

Во-первых, когда бы CPU не отдавал GPU команду на отрисовку, у CPU всегда есть много других задач. Ему нужно заботиться о таких вещах, как настройка GPU, загрузка программы шейдера и проверка на разные аппаратные баги. Вся эта работа скапливается, и пока CPU её делает, GPU может простаивать.

Во-вторых, есть определённые расходы на изменение состояния. Скажем, между пакетами вам нужно изменить состояние шейдера. На обычном GPU придётся ждать, пока все ядра не закончат выполнение задания от текущего шейдера. Это называется очисткой конвейера (draining the pipeline). Пока конвейер не очищен, остальные ядра будут переведены в режим ожидания.

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

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

В данное время каждому типу примитивов требуется разный шейдер. Например, есть шейдер границ, текстовый шейдер и шейдер изображений.

Мы считаем, можно объединить многие из этих шейдеров, что позволит создавать ещё бóльшие пакеты, хотя они и сейчас неплохо сгруппированы.

Задачи практически готовы для отправки в GPU. Но есть ещё немножко работы, от которой можно избавиться.

Уменьшение работы по закрашиванию пикселей с помощью проходов по непрозрачности и альфа-каналу (Z-выбраковка)

Большинство веб-страниц содержит много фигур, перекрывающих друг друга. Например, текстовое поле находится поверх div (с фоном), которое находится поверх body (с другим фоном).

При определении цвета пикселя GPU мог бы вычислить цвет пикселя в каждой фигуре. Но показан будет только верхний слой. Это называется перекрашивание (overdraw), пустая трата времени GPU.

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

Правда, тут небольшая проблемка. Если фигура полупрозрачная, то нужно смешать цвета двух фигур. И чтобы всё выглядело правильно, рендеринг следует проводить снизу вверх.

Так что мы разделяем работу на два прохода. Сначала проход по непрозрачности. Рендерим сверху вниз все непрозрачные фигуры. Пропускаем рендеринг всех пикселей, которые закрыты другими.

Затем приступаем к полупрозрачным фигурам. Они отрисовываются снизу вверх. Если полупрозрачный пиксель находится поверх непрозрачного, то их цвета смешиваются. Если он находится позади непрозрачного, то не рассчитывается.

Разделение на два прохода — по непрозрачности и альфа-каналу — с дальнейшим пропуском вычислений ненужных пикселей называется Z-выбраковкой (Z-culling).

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

В данный момент мы подготовили кадр. Мы сделали всё возможное, чтобы удалить лишнюю работу.

…И мы готовы к отрисовке!

Графический процессор готов к настройке и рендерингу пакетов.

Оговорка: у нас пока не всё ушло на GPU

CPU по-прежнему выполняет часть работы по отрисовке. Например, мы по-прежнему рендерим на CPU символы (они называются глифами) в текстовых блоках. Существует возможность делать это на GPU, но трудно добиться попиксельного соответствия с глифами, которые компьютер рендерит в других приложениях. Так что люди могут запутаться при рендеринге шрифтов на GPU. Мы экспериментируем с перемещением рендеринга глифов на GPU в рамках проекта Pathfinder.

Но сейчас эти вещи отрисовываются в растровые изображения на CPU. Затем они загружаются в текстурный кэш на GPU. Этот кэш сохраняется от фрейма к фрейму, потому что обычно в нём не происходит изменений.

Даже хотя такая отрисовка остаётся на CPU, всё равно есть потенциал для её ускорения. Например, при отрисовке символов шрифта мы распределяем различные символы по всем ядрам. Это делается с помощью такой же техники, которую использует Stylo для распараллеливания вычисления стилей… перехват работы.

Будущее WebRender

В 2018 году мы планируем внедрить WebRender в Firefox в составе Quantum Render, через несколько релизов после первоначального выпуска Firefox Quantum. После этого существующие веб-страницы станут работать плавнее. А браузер Firefox будет готов к новому поколению дисплеев высокого разрешения 4K, поскольку производительность рендеринга крайне важна при увеличении количества пикселей на экране.

Но WebRender полезен не только для Firefox. Он также необходим в нашей работе над WebVR, где нужно рендерить разные фреймы для каждого глаза со скоростью 90 FPS на разрешении 4K.

Первая версия WebRender уже доступна в Firefox, если вручную активировать соответствующий флаг. Работ по интеграции продолжается, так что производительность пока не настолько высока, как будет в финальном релизе. Если хотите наблюдать за разработкой WebRender, следите за репозиторием GitHub или за твиттером Firefox Nightly, где еженедельно публикуются новости по всему проекту Quantum Render.

Об авторе: Лин Кларк — инженер группы Mozilla Developer Relations. Она работает с JavaScript, WebAssembly, Rust и Servo и любит рисовать программисткие картинки.

https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank/

https://habrahabr.ru/post/340176/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *