image

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

Вот почему мы создали Figma, командный инструмент работы над дизайном интерфейсов, как облачный сервис, распространяемый в виде веб-приложения.

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



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

Несколько примеров:


Ситуация с отсутствием общих примитивов в вебе начинает меняться, и сейчас есть такие технологии, как WebGL и asm.js, дающие разработчикам инструменты работы с железом напрямую, минуя движок браузера. Это завоевание, которое, наконец, делает высокопроизводительные графические веб-приложения чем-то реалистичным и уместным на практике. Разработчикам больше не нужно ждать, пока необходимая им фича будет реализована в браузерах, они могут создавать такую функциональность самостоятельно!

Emscripten


Наш редактор написан на C++ и кросс-компилирован в JavaScript с помощью кросс-компилятора emscripten. Компилятор emscripten нацелен на asm.js, подмножество JavaScript поддерживаемое во всех современных браузерах, которое позволяет получить от JIT-компилятора JavaScript предсказуемый, компактный машинный код.

Такой подход имеет ряд преимуществ:

  • Мы полностью контролируем структуру памяти и можем использовать компактные 32-битные числа с плавающей точкой или даже байты, когда это целесообразно, вместо 64-битных double в JavaScript. Это очень важно для приложений вроде нашего, использующих большие объемы данных.
  • Сгенерированный код полностью контролирует выделение памяти, что значительно упрощает задачу получить рендеринг UI в 60 кадров в секунду, избегая пауз от сборки мусора. Все C++ объекты размещаются в зарезервированных диапазонах в предварительно выделенном типизированном масиве, так что сборщику мусора JavaScript тут просто нечего делать.
  • Сгенерированный код предварительно оптимизируется с использованием продвинутого компилятора LLVM. Комбинируя это с C++ template specialization мы получаем очень эффективный код, который всего лишь до 2х раз медленнее нативного.
  • Так же, гарантируется, что код asm.js свободен от точек деоптимизации, так что JIT-компилятор может произвести AoT-компиляцию и предоставить прогнозируемую производительность. Обычный JavaScript-код проходит через эвристические алгоритмы JIT-компилятора, поэтому его производительность может иногда дичайше варьироваться между последовательными прогонами того же самого кода.

Я это не к тому, что emscripten идеален. Как и с любой новой фиговиной, на пути бывали и затыки. Одной из проблем для нас стало то, что некоторые конфигурации браузеров не могли выделять большие диапазоны непрерывного адресного пространства для огромного типизированного массива, который содержит все адресное пространство emscripten. Худшим случаем был 32-битный Хром для Windows, который иногда не мог выделить даже типизированный массив в 256Мб, т.к. ASLR фрагментировал адресное пространство. С тех пор это уже было пофикшено.

Еще одна фишка, которая помогает, это использование для больших ресурсов хендлов для выноса из кучи вещей типа буферов для картинок и геометрических объектов. Мы создали внутренний API «IndirectBuffer» (заопенсорсили тут), который позволяет ссылаться на внешний типизированный массив и делает его доступным в C++. Вынос больших объектов из главной кучи снижает проблемы фрагментации памяти для долгоиграющих сессий, позволяя нам использовать больше ограниченного адресного пространства в 32-битных браузерах, и позволяя нам преодолеть ограничение в 31 бит на размер типизированного массива в 64-битных браузерах.

Сейчас asm.js уже весьма неплохо поддерживается, но на подходе немало крутых штук.
WebAssembly — это попытка реализации двоичного формата для кода asm.js, чтобы значительно снизить время парсинга, к которой примкнули все основные браузеры. Прямо сейчас, единственной формой многопоточности является использование веб-worker'ов с передачей сообщений, но спецификация общего типизированного массива (на подходе), сделает мультипоточность с общей памятью реальностью.

Рендеринг


Мы создали свой собственный движок рендеринга, чтобы быть уверенными, что контент рендерится быстро и единообразно на всех платформах. У браузеров есть великолепные механизмы работы с графикой, и мы изначально пытались использовать их, вместо того, чтобы создавать новый движок рендеринга. Без низкоуровневых API для доступа к дереву рендеринга браузера, доступными вариантами были HTML, SVG или 2D canvas.

Ни один из этих вариантов не был удовлетворительным по ряду причин:

  • HTML и SVG тащат за собой много лишнего багажа и часто работают намного медленнее, чем 2D canvas API из-за использования DOM. Они обычно оптимизированы для прокрутки, а не зума, и геометрия зачастую пересчитывается при каждом изменении масштаба.
  • Нет никаких гарантий того, что рендеринг пройдёт с аппаратным ускорением через GPU, и много вещей все еще рендерится на CPU.
  • Поддержка наложения масок, размытия и режимов смешивания в HTML и SVG дичайше варьируется от браузера к браузеру, и часто не производится сглаживание или на выходе имеем слишком низкое разрешение на дисплеях с высоким разрешением.
  • 2D canvas API является API реального времени, а не отложенного, так что все геометрические объекты должны быть перезалиты в GPU для каждого кадра. Такое поведение расточительно без всякой нужды, и может стать узким местом.
  • Раскладка текста неодинакова между разными браузерами, а мало того и между версиями одного и того же браузера для разных платформ.
  • Мы хотели иметь возможность добавлять такие функции, как рисование угольчатого градиента, который не поддерживается ни одним из перечисленных API рендеринга.

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

Наш код выглядит почти как браузер внутри браузера; у нас есть свой DOM, свой компоновщик, свой движок раскладки текста, и мы подумываем добавить дерево преобразований, прямо как то, что используется для отображения HTML у браузеров.

Браузеры


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

Перед тем, как я начал работать над Figma, кастомные high-DPI курсоры были реально сломаны в вебе. Мне пришлось самому чинить Хром, Лису и Вебкит, ибо все они были поломаны по-своему. До сих пор нет одного общего способа работать с этим (SVG в Лисе, -webkit-image-set в Хроме и Вебките, и доисторический .cur формат в ИЕ), но хотя бы теперь все работает.

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

Есть еще много всего, что веб-платформа могла бы улучшить, что бы помогло улучшить Figma:

  • Наша величайшая боль — отсутствие доступа к контурам символов и таблицам кернинга, которые в настоящий момент вообще никак нельзя получить. Одним из основных опасений является вопрос безопасности, но битва уже проиграна. Мы надеемся, что доступ к данным шрифта будет открыт хотя-бы в режиме запроса разрешения у пользователя, как и другие, чувствительные к личной информации API. Хромовцы предложили способ починить это, над чем они сейчас и работают, но на горизонте больше никого и не маячит.
  • Мы бы с радостью добавили поддержку вставки содержимого в популярных форматах из буфера обмена (.ai, .pdf, и т.д.), но веб не позволяет этого делать. Единственные форматы в спецификациях — text/plain и text/html (наша Figma использует text/html с двоичными данными, закодированными в HTML-комментариях).
  • Ещё одна проблема для нас — отсутствие поддержки жеста щипка для трэкпада в OS X. Хромовцы добавили малоизвестный хак, при котором жест «щипок» отправляет событие колеса мыши с зажатым ctrl и вызывает preventDefault(), что позволяет странице управляться с этим. Это круто, и делает увеличение и панорамирование в Figma нативным по ощущениям. Я пытался добавить это в Лису, но эта попытка пока что застряла. Щипок в Сафари работает как увеличение, что сбивает с толку пользователей и это невозможно отключить.

Заключение


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

Данная статья — перевод статьи 2015-го года из блога команды Figma. Хотя статья и не новая, но вполне ещё актуальна. Это первая статья из цикла переводов блога команды Figma. Автор перевода — Сергей Дурнов.

Комментарии (0)