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. Автор перевода — Сергей Дурнов.