описание графической архитектуры браузера Android
Часть I - Аппаратное ускорение
В этой статье описывается старая графическая архитектура браузера Android, замененная в 2012 году Chrome на Android (в JellyBean, Android 4.2). Хотя ему всего несколько лет, его графическая архитектура в конечном итоге была довольно продвинутой и позволила сделать несколько интересных обходных путей. Если вы когда-нибудь задумывались, как внутри реализован графический конвейер современного мобильного веб-браузера или как развиваются архитектуры программного обеспечения, эта статья может дать вам некоторое представление о связанных с этим проблемах. Часть I в основном описывает этап и описывает переход к аппаратному ускорению, Часть II расширяет некоторые расширенные функции.
Когда-то у Google было два веб-браузера: Google Chrome, конечно, но еще и браузер Android, который был совершенно отдельной кодовой базой и командой. Как ни странно, и Chrome, и браузер Android были основаны на WebKit, движке рендеринга HTML, который Apple использовала для Safari, который сам изначально был основан на KHTML, более раннем движке, пришедшем из проекта KDE.
Причины, по которым у Google было два браузера, были историческими и техническими: Chrome был ориентирован на использование настольных компьютеров и веб-приложения, имел сложную модель безопасности и многопроцессорную архитектуру, в то время как браузер Android должен был хорошо работать на мобильных устройствах, гораздо менее мощная платформа. , и в значительной степени просто использовал WebKit по назначению. Приоритеты разошлись.
Таким образом, от Android 1.0 до Android 4.2 (2012) веб-браузер Android был просто Android-приложением, построенным непосредственно на основе виджета веб-просмотра платформы. Фактически, веб-просмотр реализован - он должен был! - полноценный движок рендеринга HTML, который может встраивать любое приложение, при этом браузер является его первым клиентом.
Я познакомился с Android и веб-браузером Android где-то в 2008 году. Я работал в лондонском офисе Google над мобильным портом Google Gears, плагина для браузера, который добавлял различные мощные новые API-интерфейсы javascript. Мы запустили его на Windows Mobile 5 и 6 (не особо увлекательное занятие), а с приближением выпуска Android 1.0 я и Джон Рипли (в основном Джон, если я помню!) Портировали Gears в браузер Android. Это, в свою очередь, побудило меня позже добавить поддержку некоторых из этих причудливых новых API-интерфейсов HTML5, и, прежде чем я узнал об этом, я работал полный рабочий день над браузером Android. В начале 2010 года я переехал в Калифорнию, официально присоединившись к остальной команде разработчиков Android-браузера. Я оставался в команде до конца 2012 года, где ненадолго переключился на что-то иное, чем базовый фреймворк.
По мере того, как устройства становились все более мощными и Chrome начал ориентироваться на мобильные устройства, старый браузер Android был в конечном итоге заменен Chrome-on-Android в 2012 году, а веб-просмотр платформы был заменен совершенно новой версией (обновляемой для загрузки!) основан на кодовой базе Chrome около 2013 года, с Android 4.3. Наличие одной и той же внутренней кодовой базы значительно упрощает поддержание актуальности Webkit или поддержку новых функций на разных платформах - долгожданное улучшение для наших пользователей и разработчиков.
Хотя кодовая база Chrome является заметным улучшением по сравнению со старым веб-просмотром, в графической архитектуре старого веб-просмотра было немало интересных идей. Я сделал презентацию об этом в 2012 году на Google IO, хотя она была больше ориентирована на разработчиков Android. Эта статья, с другой стороны, имеет то преимущество по сравнению с приведенным выше видео, что вам не придется мучиться из-за моего акцентированного английского языка. Я опишу эволюцию архитектуры рендеринга веб-просмотра, а также некоторые причины этого.
Старый Свет
Типичный цикл рендеринга
Прежде чем продолжить, давайте посмотрим, как «должно» работать в обычном браузере. Основы относительно просты:
- Получает запрос на закрашивание области веб-просмотра (скажем, после прокрутки обнаруживается неокрашенная область или подумайте о анимированном гифке)
- Перейдите в webkit, который пройдет по дереву html, чтобы выяснить, какие элементы нужно раскрасить, а затем использовать их для раскраски ...
- Обновите экран, добавив на экран только что нарисованный контент
Конечно, в реальности все становится немного сложнее. На самом деле веб-просмотр Android работал совершенно по-другому. Помните, что мы работали на оборудовании, которое не было таким мощным, и помимо использования памяти, время автономной работы также было критически важным ресурсом.
Путь Android
Браузеру (обратите внимание, что я буду свободно использовать термины browser и webview как взаимозаменяемые в остальной части этой статьи) требовалось что-то, что обеспечило бы гораздо более быстрый цикл перерисовки, чем возврат к webkit и постоянно перемещаться по документу - это было важно для обеспечения быстрого взаимодействия с пользователем (прокрутка, масштабирование…), и обычный цикл перерисовки не собирался его сокращать. Поскольку Webkit написан на C ++, а пользовательский интерфейс браузера - на Java, постоянные вызовы JNI с java на native также не сильно помогли бы.
Итак, вместо того, чтобы напрямую просить webkit нарисовать экран, у команды появилась идея получше. Они решили изменить Webkit так, чтобы он записывал список инструкций по рисованию для всей страницы, но не рисовал напрямую - в основном, используя модель списка отображения. Список команд рисования был, например, нарисовать этот текст здесь или нарисовать это изображение там.
Например, когда пользователю нужно было увеличить масштаб, не нужно было просить Webkit перерисовать экран - вместо этого веб-просмотр мог просто воспроизвести уже существующий список отображения, изменяя только коэффициент масштабирования. Это также дало довольно приятный побочный эффект, заключающийся в том, что экран всегда был резким - отсутствие размытости текста при увеличении, отсутствие пропущенного контента, поскольку контент всегда перерисовывался «в правильном направлении».
Вы можете думать об этом списке отображения как о способе иметь векторное представление всей страницы, которое вы можете масштабировать по запросу и перерисовывать - разница между иллюстратором и фотошопом.
Список отображения
Таким образом, производительность цикла перерисовки была ограничена только временем, затраченным на отрисовку списка отображения на экране, что было достаточно быстрым, поскольку разрешения экрана были относительно низкими, а библиотека рисования, используемая в веб-просмотре, оказалась той, которая использовалась для всего Android. платформа Skia, причем достаточно оптимизированная. Фактически, сам список отображения был просто записью встроенных графических инструкций skia, которые webkit использовал бы, если бы рисовал экран напрямую и хранил в структуре данных SkPicture.
Еще одним особенно приятным побочным эффектом наличия такого векторного представления страницы, списка отображения, с которым можно было работать, было то, что он содержал кучу полезной информации, которая могла быть использована пользовательским интерфейсом. Например :
- навигация по клавиатуре была реализована таким образом
- найти текст на странице
- выделение текста
- позволяя пользователям нажимать на адрес на странице и перенаправляться на Google Maps и т. д.
Сгенерированный список отображения обычно мог содержать всю страницу для рисования, поэтому, если контент не менялся, все можно было полностью обработать на стороне пользовательского интерфейса, без необходимости возвращаться в Webkit.
В (довольно распространенном) случае, когда веб-страница хотела обновить, а части страницы нужно было бы перерисовать, нам нужно было вернуться к webkit, чтобы восстановить список отображения - дорогостоящая операция для всей страницы.
Чтобы избежать регенерации всего списка отображения, мы регенерировали («перекрашивали») только те части списка отображения, которые соответствуют обновленной области. Таким образом, список отображения обычно содержал большой SkPicture, за которым следовали более мелкие и связанные границы. В приведенном выше примере область, отмеченная 1, изменилась, поэтому нам пришлось вернуться к webkit, регенерировав только SkPicture, покрывающую область 1. SkPicture 2 и последующие SkPictures будут добавлены к общему PictureSet и будут использоваться вместо области 1, когда покраска экрана.
Соты
Архитектура веб-просмотра оставалась примерно такой, как описано от Android 1.0 до Gingerbread - Android 2.3. Затем мы начали работу над выпуском Honeycomb, который в конечном итоге был нацелен на планшетные устройства (все наши предыдущие выпуски были нацелены только на телефоны).
Дела шли неважно.
Совсем не хорошо.
Разрешение планшета было намного выше, чем у наших предыдущих телефонов. Производительность цикла перерисовки ужасная - в однозначных кадрах в секунду. Что-то нужно было изменить. Быстро.
Интерлюдия
В то время я работал над добавлением поддержки 3D-анимации CSS:
Это была недавно введенная функция в webkit, которая позволила вам указать трехмерное преобразование и применить его к элементу ‹div› на вашей HTML-странице. Предположительно, вы могли бы использовать его для создания гораздо более причудливых веб-сайтов с анимацией и элементами, летающими вокруг вас влево и вправо.
Выглядело круто.
Это также означало, что для того, чтобы это работало, мне пришлось переместить рисование элементов на отдельные поверхности, чтобы я мог применить трехмерное преобразование.
Это имело несколько интересных последствий. Во-первых, если вам нужно изменить положение контента, который был размещен на слое, вам не нужно обновлять базовый слой, то есть нет необходимости возвращаться в Webkit, чтобы повторно создать список отображения для страницы. Во-вторых, CSS-анимации, будучи декларативными, было довольно легко полностью перемещать на стороне пользовательского интерфейса (то есть оценивать их в цикле рендеринга на стороне пользовательского интерфейса), без необходимости связываться с webkit во время воспроизведения анимации. Наконец, ничто не помешало вам применить двухмерное преобразование для получения тех же преимуществ, а это означает, что вы можете сделать веб-интерфейс более гибким и отзывчивым.
Добавление поддержки составных слоев, а также CSS-анимации, оцениваемой на стороне пользовательского интерфейса, привело к значительному ускорению в определенных случаях, даже при рендеринге этих слоев в программном обеспечении, поскольку мы экономили непрерывные циклы обращения к webkit (см. Падающие листья demo »для примера анимации, в которой используется концепция слоев даже в 2D). Карты Google для мобильных устройств были еще одним кандидатом на эту функцию.
С появлением CSS3D архитектура веб-просмотра Android изменилась с одного списка отображения на множество списков отображения (один для корневого слоя, один для любого слоя CSS3D).
Я начал праздно думать о переходе на архитектуру с полностью аппаратным ускорением, поскольку текущая архитектура начинала показывать некоторые проблемы с масштабируемостью.
Новый мир
Как объяснялось ранее, существующая архитектура, основанная на списках отображения, имеет определенные преимущества. Но все также начинало выглядеть… тревожным в отношении производительности .
Самая большая очевидная проблема заключалась в том, что, хотя подход со списком отображения был быстрее, чем при непосредственной отрисовке webkit, и спасал нас от бесчисленных обращений к webkit, скорость, с которой мы могли рисовать экран, по-прежнему полностью зависела от сложности контента. мы хотели отобразить. Это имеет смысл - больше контента означает больше инструкций по рисованию, больше вещей для рисования. Обращение к webkit происходило асинхронно, но сама отрисовка все еще была синхронной.
В результате скорость взаимодействия с пользователем, в частности масштабирования и прокрутки, изменялась в зависимости от отображаемого веб-сайта. На одном сайте все было бы гладко, на другом… не очень.
Когда мы начали работать над Honeycomb и попробовали браузер на планшете, более высокое разрешение сразу дало понять, что существующая архитектура просто не масштабируется. Времени, потраченного на рисование экрана, было слишком много.
Внезапно праздные мысли об использовании архитектуры CSS3D для продвижения браузера к аппаратному ускорению не были чем-то хорошим, а тем, что мы должны были сделать Если бы мы хотели отправить.
Итак, аппаратное ускорение было.
Аппаратное ускорение
Аппаратное ускорение может означать много разных вещей с точки зрения веб-браузера. Идея состоит в том, чтобы использовать специализированное оборудование (например, графический процессор) для ускорения операции, в нашем случае обычно отрисовки. В целом мы могли сделать две вещи:
- Растеризовать (раскрасить) весь список отображения прямо на видеокарте
- Используйте мозаичный подход: сегментируйте свой контент на несколько текстур, организованных в плитки
Вариант №1 казался привлекательным на бумаге, но также требовал гораздо большей работы по рефакторингу и развитию - времени у нас не было. Обеспечить точный рендеринг (аналогично тому, как у нас был программный рендеринг) также будет непросто. Это также (в то время на имеющемся у нас оборудовании) могло привести к аналогичным проблемам с производительностью, от которых мы пытались отказаться, т.е. производительность по-прежнему будет зависеть от контента, хотя надежда заключалась в том, что растеризация контента через графический процессор будет достаточно быстрой, чтобы не стать практической проблемой. Наконец, здесь мы не имели дело с браузером изолированно, мы также были веб-просмотром фреймворка - виджетом, который может использоваться любым приложением. Веб-просмотр должен был использовать графический процессор совместно с остальной частью системы - фактически, Ромен Гай был занят в то время добавлением аппаратного ускорения в структуру пользовательского интерфейса Android по тем же причинам. Было бы неплохо, если бы веб-просмотр не перегружал весь графический процессор ...
В итоге мы выбрали вариант №2. Это было не только проще интегрировать - мы могли повторно использовать большую часть существующего графического конвейера веб-просмотра - это не было слишком утомительным для GPU. Основное отличие от предыдущего конвейера программного рендеринга заключалось в том, что вместо прямой растеризации списка отображения на экране у нас был бы промежуточный набор плиток, покрывающих экран, на котором мы рисовали бы. Затем графический процессор просто рисовал плитки на экране.
Что еще более важно, имея эти промежуточные плитки, мы могли полностью отделить поведение браузера при прокрутке и масштабировании от рисования, что привело к идеальной прокрутке и масштабированию со скоростью 60 кадров в секунду. По крайней мере, так было задумано!
Прокрутка
Для данного окна просмотра - окна с содержимым нашей страницы - мы должны сгенерировать набор покрывающих его тайлов. Затем мы раскрашиваем плитки, используя список отображения из Webkit. Рендеринг плиток на экране происходил на графическом процессоре (каждая плитка представляет собой текстуру), что позволило нам рисовать их практически мгновенно на экране, а затем мы могли очень быстро перемещать плитки в любое положение.
Поскольку мы могли перемещать плитки мгновенно, мы могли перемещать их синхронно с пользователем, прокручивающим страницу - добавляя новые плитки, покрывающие новый контент, по мере необходимости - и вуаля! Прокрутка 60 FPS.
Мы также сохранили набор плиток, которые мы постоянно повторно использовали и перекрашивали в фоновом потоке (как показано на предыдущей диаграмме, когда плитка выходит за пределы области просмотра, ее можно вернуть в наш пул плиток и повторно использовать для рисования плитки. который скоро будет показан пользователю), сохраняя разумное использование памяти.
Масштабирование
Масштабирование также выполняется намного быстрее, поскольку плитки рисуются графическим процессором. Уменьшить масштаб, как показано на диаграмме выше, так же просто, как уменьшить текущий набор плиток (набор плиток A). Затем в фоновом режиме мы начинаем рендеринг нового набора плиток, которые будут покрывать новое окно просмотра (набор плиток B). Когда набор плиток B готов к отображению, мы можем переключаться между ними с плавным переходом.
Проблемы с плиткой
Предостережение с этим подходом к мозаике заключалось в том, что мы потеряли бы нашу прекрасную, идеально резкую во все времена визуализацию. Действительно, хотя отрисовка тайлов на экране с помощью графического процессора происходит очень быстро, перерисовка содержимого самих тайлов все еще может быть медленной; и если плитки перекрашиваются слишком медленно, пользователь получит недостающие плитки (при прокрутке) или размытое содержимое (при масштабировании).
Тем не менее, это было намного предпочтительнее мучительно медленной простой программной визуализации на дисплеях с высоким разрешением; и преимущество этого подхода состояло в том, что мы по-прежнему могли бы использовать наличие списка отображения, чтобы рисовать вещи быстрее, чем просто через webkit.
Итог: Мы можем отправить!
Слои
Теперь, когда нам удалось добиться хороших характеристик планшета, пришло время оглянуться на интеграцию композитных слоев. В конце концов, мы все время работали в контексте OpenGL - полная поддержка 3D-слоев CSS была намного проще, и мы могли бы завершить реализацию к выпуску Honeycomb.
Примерно в то время произошел важный архитектурный поворот в нашем веб-просмотре. Все началось с малого.
Составные слои, реализованные в WebKit в то время, работали только в нескольких случаях:
- если вы применили преобразование css к элементу
- если бы у вас было видео, оно было бы перемещено в отдельный слой (помогает с композицией)
Почему мы не могли расширить поддержку слоев на другие случаи?
Помните, что по сравнению с «обычным» механизмом рендеринга webkit у нас было несколько преимуществ и недостатков:
- Нам удалось быстрее перерисовать уже имеющийся контент, так как мы могли просто воспроизвести список отображения.
- … Но мы медленнее рисовали новый контент, так как у нас было промежуточное создание списка отображения плюс рисование, а теперь и загрузка в текстуры GL.
У нас был один особенно болезненный пример поведения HTML, которое было ужасно медленным с нашей существующей архитектурой на основе списка отображения.
По сути, фиксированные элементы располагаются не относительно документа, а относительно области просмотра - окна. Это означает, что они останутся на месте, даже если вы прокручиваете страницу. Отличная функция, используемая на многих-многих веб-сайтах. Особенно мобильные сайты.
Это был наихудший сценарий для нашей архитектуры рендеринга. Это означало, что каждый раз, когда пользователь прокручивал веб-страницу с таким элементом, нам приходилось возвращаться к webkit, запрашивать новый список отображения для всей страницы, возвращаться и восстанавливать все плитки, покрывающие экран. Промыть и повторить.
Но… теперь у нас в наборе инструментов есть составные слои. Мы решили переместить такие фиксированные элементы на их собственный составной слой. Это полностью решило проблему медлительности - фиксированным элементам больше не нужно было перерисовывать всю страницу. Все, что им нужно, - это позиционировать непосредственно из потока пользовательского интерфейса, и у нас будет идеально плавное, маслянистое поведение при прокрутке со скоростью 60 кадров в секунду, даже с этими надоедливыми фиксированными элементами на странице.
… Как и следовало ожидать, позиционирование оказалось не таким простым, как казалось поначалу - если я помню, потребовалась пара релизов, чтобы сгладить некоторые угловые случаи. Тем не менее, распространение составных слоев на другие варианты использования HTML в целом увенчалось успехом.
Интеграция с фреймворком: GLFunctor
На раннем этапе разработки поддержки аппаратного ускорения в веб-просмотре возникла необходимость интегрировать ее с остальной частью работы по ускорению фреймворка Android, которую Ромен Гай выполнял для выпуска Honeycomb.
Мы очень старались избежать ненужного повторного копирования текстур - пропускная способность была ограничена. Мы могли бы создать полностью отдельный контекст GL для работы веб-просмотра, но это было бы неоптимально. Итак ... после быстрого обсуждения мы представили GLFunctor - функцию, которая просто вызывала средство визуализации OpenGL webview непосредственно из фреймворка, разделяя тот же контекст GL, что и остальная часть приложения.
Поскольку фреймворк и веб-представление знали, что они изменяют, с точки зрения GL, структура могла сохранять и восстанавливать состояние GL до и после вызова веб-просмотра. Изящная особенность этого трюка заключалась в том, что GLFunctor был, по сути, прямым вызовом функции для веб-представления, плюс набор необходимых параметров, который идет с ним - матрица, применяемая к представлению, и т. Д. Структура пользовательского интерфейса могла тогда напрямую хранить GLFunctor напрямую. в свои собственные списки отображения.
Трудно быть более эффективным.
Прокрутка, улучшенная
Как описано ранее, у тайлинга есть некоторые недостатки - в основном, рисование тайлов иногда может быть слишком медленным, чтобы все тайлы были готовы для текущего окна просмотра. Это приводит к отсутствию плиток, что не является оптимальным для пользователя.
Не считая улучшения качества рисования, кажется, это просто то, с чем вам нужно жить. Или ты?
Помните, в нашем распоряжении был список отображения. Одна вещь, для которой мы можем использовать список отображения, - это перерисовать тот же контент намного быстрее, чем если бы нам пришлось вернуться к webkit. Как мы могли это использовать?
Что ж, мы могли бы сгенерировать второй набор плиток с более низким разрешением, таким образом покрывая большую область документа за ту же цену. И отображать эти плитки, когда плитки с правильным разрешением недоступны.
Зачем нам вообще это нужно? Разве мы не должны не хотеть размытых участков? Мы определенно не хотим размытых участков, это правда. Однако при прокрутке произойдут две вещи:
- вы не очень быстро прокручиваете. Покраска плитки может догнать, все хорошо.
- вы очень быстро прокручиваете - кидаете. Покраска плитки будет безнадежно позади.
Для № 2 идеальным решением является наличие второго набора размытых плиток: они будут покрывать гораздо большую площадь, и ваш глаз будет предпочитать размытый контент, проходящий быстро, чем пустые области или узоры шахматной доски там, где должна быть плитка. Фактически, во многих случаях было трудно увидеть, что быстрая прокрутка контента не отображалась с правильным разрешением. Подделайте это, пока не сделаете это!
Еще одна уловка, которую мы использовали для улучшения пользовательского опыта при прокрутке, заключалась в том, что мы сгенерировали больше плиток, чем необходимо для покрытия области просмотра, и предварительно отрисовали плитки, которые двигались в направлении прокрутки.
Отправим его!
После нескольких изнурительных месяцев работы над этим мы отправили эту версию браузера в Honeycomb. У нас работало аппаратное ускорение, прокрутка и масштабирование работали хорошо. У нас была некоторая поддержка составных слоев (я считаю, что в первой версии не было мозаичных слоев). Перед нами еще много дел. Обзоры были довольно хорошими, улучшения пользовательского интерфейса также добавили много (особенно вкладки, режим инкогнито (ах!) И круговые меню).
На тот момент мы не были радикально более инновационными, чем другие конвейеры рендеринга браузеров - мозаика стала очевидным решением для многих платформ. Однако мы начали работать довольно быстро благодаря подходу со списком отображения. Мы стали быстрее. Намного быстрее :)
Во второй части будут описаны более сложные части конвейера рендеринга и магии списка отображения.