RequestAnimationFrame

Цикл отрисовки в MapLibre GL JS построен вокруг механизма кадровой синхронизации браузера и функции requestAnimationFrame, которая задаёт базовый ритм обновления сцены карты. Этот подход обеспечивает согласованность визуального состояния с частотой обновления экрана и позволяет минимизировать лишние вычисления при отсутствии изменений.

requestAnimationFrame (rAF) вызывает переданную функцию непосредственно перед следующим перерисовыванием страницы. В контексте WebGL-рендеринга это означает, что MapLibre получает возможность обновить матрицы, пересчитать тайлы и отрисовать слой строго в момент, когда браузер готов вывести новый кадр.

Внутренний цикл рендеринга карты опирается на идею «кадра как единицы состояния»:

  • состояние камеры (центр, zoom, bearing, pitch);
  • состояние источников данных (tiles, GeoJSON, vector sources);
  • состояние стиля (layers, paint/layout properties);
  • состояние анимаций (transition, ease, custom animations).

Любое изменение этих параметров маркирует карту как требующую перерисовки.

Рендер-петля и управление перерисовкой

В MapLibre GL JS отсутствует классический бесконечный цикл, активно работающий в каждый момент времени. Вместо этого используется условная схема:

  • если изменений нет — кадры не запрашиваются;
  • если есть изменения — активируется requestAnimationFrame.

Это позволяет экономить ресурсы CPU/GPU.

Упрощённая модель выглядит следующим образом:

function frame() {
    if (map._needsRender) {
        map._render();
    }

    if (map._needsRender) {
        requestAnimationFrame(frame);
    }
}

Флаг _needsRender (внутренний механизм) выставляется при:

  • вызове map.setCenter, map.setZoom, map.setBearing, map.setPitch;
  • изменении стиля через setStyle;
  • загрузке новых тайлов;
  • изменении источников данных;
  • запуске анимаций переходов.

Координация камеры и матриц преобразования

Одним из ключевых этапов каждого кадра является пересчёт матриц:

  • projection matrix (перспектива);
  • view matrix (камера);
  • tile matrix (позиционирование тайлов).

Эти матрицы формируют цепочку преобразования:

географические координаты → мировые координаты → экранные координаты.

requestAnimationFrame обеспечивает, что эти вычисления происходят синхронно с визуальным обновлением экрана, исключая рассинхронизацию между логикой и отрисовкой.

Интеграция с WebGL контекстом

MapLibre использует WebGL как низкоуровневый слой рендеринга. В каждом кадре:

  1. очищается буфер кадра;
  2. активируются шейдеры для текущих слоёв;
  3. передаются uniform-параметры (матрицы, zoom, time);
  4. выполняется отрисовка тайлов и векторных данных.

Ключевой момент — WebGL вызовы выполняются строго внутри rAF-цикла, что гарантирует минимальные задержки между расчётом и отображением.

Триггеры перерисовки и оптимизация нагрузки

Система оптимизации рендеринга строится на событии «dirty state». Карта помечается как требующая обновления только при фактических изменениях.

Типовые триггеры:

  • изменение источника (source.setData);
  • добавление/удаление слоя;
  • изменение фильтров (setFilter);
  • анимации свойств (transition).

Если ни один триггер не активен, rAF-цепочка останавливается, и браузер возвращается к idle-состоянию.

Это принципиально отличает MapLibre от постоянных игровых циклов, где кадры генерируются непрерывно.

Анимации и интерполяция состояния

Анимации камеры и свойств слоёв реализуются через интерполяцию между начальным и конечным состоянием, где каждый кадр rAF предоставляет временную метку timestamp.

На основе этой метки вычисляется прогресс:

  • easing-функции (linear, cubic, custom bezier);
  • временные интервалы transitions;
  • плавное изменение zoom/center/bearing.

Пример логики:

function animate(timestamp) {
    const t = (timestamp - startTime) / duration;

    const eased = easeInOutCubic(t);

    map.setCenter(interpolate(start, end, eased));

    if (t < 1) {
        requestAnimationFrame(animate);
    }
}

Пользовательские слои и внешний рендеринг

Custom layer API также привязан к rAF-циклу. Каждый пользовательский слой получает вызовы:

  • onAdd(map, gl) — инициализация;
  • render(gl, matrix) — отрисовка в каждом кадре;
  • onRemove() — очистка ресурсов.

Метод render вызывается строго в момент кадра, синхронно с основным pipeline MapLibre. Это позволяет интегрировать собственные WebGL-шейдеры без нарушения общей синхронизации.

Взаимодействие с событиями карты

События render и idle тесно связаны с rAF:

  • render — вызывается после каждого кадра, где произошла отрисовка;
  • idle — вызывается, когда rAF-цепочка завершилась и карта не требует обновлений.

Таким образом, requestAnimationFrame становится центральным механизмом перехода между активным и пассивным состоянием карты.

Механизмы принудительного обновления

В случаях, когда внешняя система изменяет состояние, но не вызывает стандартные методы API, используется принудительный запуск цикла:

  • установка флага перерисовки;
  • вызов внутреннего trigger;
  • запуск rAF, если он не активен.

Псевдомеханизм:

map.triggerRepaint = function () {
    this._needsRender = true;
    if (!this._frameRequested) {
        this._frameRequested = true;
        requestAnimationFrame(this._renderFrame);
    }
};

Ограничение частоты и производительность

Несмотря на привязку к rAF, реальная частота отрисовки зависит от:

  • загрузки GPU;
  • сложности слоёв;
  • количества тайлов;
  • включённых эффектов (blur, fade, extrusion).

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

Взаимодействие с асинхронной загрузкой данных

Загрузка тайлов и ресурсов происходит вне rAF-цикла, но их интеграция в сцену синхронизируется при следующем кадре. Это предотвращает «разрывы» состояния, когда новые данные могли бы появиться между рендерами.

Каждый загруженный тайл:

  • помещается в кэш;
  • маркируется как ready;
  • включается в сцену на следующем rAF-кадре.

Согласованность состояния сцены

Главная задача rAF-архитектуры — обеспечение атомарности кадра:

  • все изменения применяются до отрисовки;
  • ни одно промежуточное состояние не попадает в экран;
  • каждый кадр представляет согласованный снимок сцены.

Это критично для картографических систем, где несогласованность приводит к визуальным артефактам при панорамировании и зуме.