[Перевод] Ускоряем WebGL/Three.js с помощью OffscreenCanvas и веб-воркеров

Ускоряем WebGL/Three.js с помощью OffscreenCanvas и веб-воркеров

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

Статья основана на личном опыте, когда я добавил вращающуюся 3D-землю на свой сайт и это забрало 5 очков производительности в Google Lighthouse — слишком много для лёгких понтов.

Проблема


Three.js прячет кучу сложных моментов WebGL, но имеет серьёзную цену — библиотека добавляет 563 КБ в вашу JS-сборку для браузеров (да и архитектура библиотеки не позволяет эффективно работать тришейкингу).

Некоторые могут сказать, что картинки часто весят те же 500 КБ — и будут сильно неправы. Каждый КБ скрипта гораздо сильнее ударяет по производительности, чем КБ изображения. Чтобы сайт был быстрым, нужно думать не только о ширине канала и времени задержки — нужно так же думать о времени работы ЦПУ компьютера для обработки файлов. На телефонах и слабых ноутбуках обработка может идти дольше, чем загрузка.

Обработка 170 КБ JS идёт 3,5 секунды против 0,1 секунды для 170 КБ изображения
Обработка 170 КБ JS идёт 3,5 секунды против 0,1 секунды для 170 КБ изображения — Эдди Османи

Пока браузер будет исполнять 500 КБ Three.js, основной поток страницы будет заблокирован и пользователь будет видеть фриз интерфейса.

Веб-воркеры и Offscreen Canvas


У нас давно есть решение, чтобы не убирать фриз во время долгого исполнения JS — веб-воркеры, запускающие код в отдельном потоке.

Чтобы работа с веб-воркерами не превратилась в ад многопоточного программирования, веб-воркер не имеет доступа к DOM. Только основной поток работает с HTML страницы. Но как без доступа к DOM запустить Three.js, которая требует прямого доступа к ?

Для этого есть OffscreenCanvas — он позволяет передать в веб-воркер. Чтобы не открывать врата многопоточного ада, после передачи, основной поток теряет доступ к этому  — только один поток будет работать с ним.

Кажется мы близки к цели, но оказывается, что только Хром поддерживает OffscreenCanvas.

Только Хром поддерживает OffscreenCanvas
Поддержка OffscreenCanvas на апрель 2019 по данным Can I Use

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

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

Решение


Чтобы скрыть хаки под слоем сахара, я сделал маленькую JS-библиотеку offscreen-canvas в 400 байт (!). В примерах код будет использовать её, но я буду рассказывать, как она работает «под капотом».

Начнём с установки библиотеки:

npm install offscreen-canvas


Нам потребуется отдельный JS-файл для веб-воркера — создадим отдельный файл сборки в Вебпаке или Parcel:

  entry: {
    'app': './src/app.js',
+   'webgl-worker': './src/webgl-worker.js'
  }


Сборщики будут постоянно менять имя файла при деплое из-за кеш-бастеров — нам нужно будет записать имя в HTML с помощью preload-тега. Тут пример будет абстрактный, так как реальный код будет сильно зависеть от особенностей вашей сборки.

    
  


Теперь нам нужно в основном JS-файле получить DOM-узел для  и содержимое preload-тега.

import createWorker from 'offscreen-canvas/create-worker'

const workerUrl = document.querySelector('[rel=preload][as=script]').href
const canvas = document.querySelector('canvas')

const worker = createWorker(canvas, workerUrl)


createWorker при наличии canvas.transferControlToOffscreen загрузит JS-файл в веб-воркер. А при отсутствии этого метода — как обычный