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

Причина сложности этой задачи заключалась в том, что веб не был создан как вычислительная платформа общего назначения. Веб начинался как документно-ориентированная технология, к которой в последствии добавилась целая куча всякого добра для разработки приложений.
Это добро обычно являлось в форме специфических API достаточно узкой направленности, вместо того, чтобы давать примитивы общего назначения, которые можно было бы использовать для реализации всякой всячины.
Несколько примеров:
- CSS предоставляет ряд прекрасных алгоритмов рендеринга/раскладки текста, но не дает настроить их, или получить результат того, что сделал браузер, дабы использовать алгоритм раскладки текста как часть другого алгоритма.
- Все браузеры предоставляют высокопроизводительный ГПУ-компоновщик, но в веб-среде нет никакой возможности прицепиться внутри алгоритма рендеринга и изменить поведение компоновщика, чтобы добавить оптимизации производительности или особые режимы смешивания.
- В браузерах есть встроенные высокооптимизированные декодеры изображений, которые декодят их асинхронно, вне UI-потока, используя возможности железа, но нет никакого API, чтобы передать в декодеры параметры позволяющие, например, учесть ориентацию по EXIF или предотвратить установку некорректного пространства цветов при использовании drawImage и getImageData.
Ситуация с отсутствием общих примитивов в вебе начинает меняться, и сейчас есть такие технологии, как 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. Автор перевода — Сергей Дурнов.
