Offscreen rendering

Offscreen rendering в MapLibre GL JS представляет собой подход к отрисовке карты вне основного DOM-контекста браузера или вне видимого canvas-элемента. Этот механизм используется для задач, где необходимо генерировать карту без прямого отображения пользователю: создание изображений на сервере, генерация превью, рендеринг в Web Worker, работа с OffscreenCanvas, а также интеграция в headless-окружения.


Архитектурная основа offscreen rendering

Внутренний рендеринг MapLibre GL JS построен вокруг WebGL-контекста, который управляется через слой абстракции canvas. При offscreen-рендеринге ключевым становится разрыв между:

  • источником состояния карты (style, sources, layers)
  • контекстом выполнения рендера (DOM или не-DOM окружение)
  • поверхностью вывода (visible canvas, OffscreenCanvas, buffer)

Рендерер MapLibre не привязан жёстко к DOM, что позволяет перенаправлять вывод в альтернативные цели при наличии совместимого canvas API.

Ключевые элементы:

1. Render loop

  • состояние карты обновляется через map._render()
  • кадры инициируются через requestAnimationFrame или его эмуляцию

2. Style pipeline

  • загрузка стиля JSON
  • разрешение источников (vector tiles, raster tiles, GeoJSON)
  • сборка слоёв в GPU-структуры

3. GPU abstraction

  • WebGL1/WebGL2 backend
  • использование буферов вершин и текстур тайлов

OffscreenCanvas как основной механизм

В современных браузерах offscreen rendering реализуется через OffscreenCanvas, который может быть передан в Web Worker.

Базовая схема работы

  • главный поток создаёт OffscreenCanvas
  • передаёт его в worker через transferControlToOffscreen()
  • MapLibre инициализируется внутри worker
  • рендеринг полностью изолирован от UI-потока

Пример инициализации

// main thread
const canvas = document.createElement('canvas');
const offscreen = canvas.transferControlToOffscreen();

worker.postMessage({
  type: 'init-map',
  canvas: offscreen
}, [offscreen]);
// worker
import maplibregl from 'maplibre-gl';

self.onmess age = (e) => {
  if (e.data.type === 'init-map') {
    const map = new maplibregl.Map({
      container: e.data.canvas,
      style: 'https://demotiles.maplibre.org/style.json',
      center: [0, 0],
      zoom: 2
    });

    map.on('load', () => {
      map.render();
    });
  }
};

Особенности жизненного цикла карты в offscreen-режиме

В обычном DOM-режиме карта привязана к событиям браузера. В offscreen-режиме поведение изменяется:

Отсутствие layout-событий

Нет:

  • resize observer DOM-элемента
  • scroll-based invalidation
  • CSS-driven resizing

Вместо этого используется:

  • явное map.resize()
  • ручная синхронизация размеров canvas

Контроль рендеринга

Offscreen rendering часто переводит карту в режим ручного управления:

  • map.triggerRepaint() — принудительный пересчёт кадра
  • map.once('render', ...) — контроль завершения кадра
  • map.isMoving() — проверка активности анимации

Headless rendering вне браузера

Offscreen rendering широко используется в Node.js окружениях для генерации статических изображений карт.

Типичный стек:

  • Node.js
  • canvas / node-canvas / skia-canvas
  • MapLibre GL JS
  • headless WebGL (или emulation)

Пример генерации изображения

import { createCanvas } from 'canvas';
import maplibregl from 'maplibre-gl';

const canvas = createCanvas(800, 600);

const map = new maplibregl.Map({
  container: canvas,
  style: 'https://demotiles.maplibre.org/style.json',
  center: [30, 50],
  zoom: 4,
  interactive: false
});

map.once('idle', () => {
  const buffer = canvas.toBuffer('image/png');
  console.log(buffer);
});

Синхронизация кадров и событие idle

В offscreen rendering важную роль играет определение момента завершения рендера.

idle-состояние

Событие idle возникает, когда:

  • нет активных анимаций
  • завершена загрузка тайлов
  • WebGL очередь пуста
  • нет pending repaint
map.once('idle', () => {
  // карта полностью отрисована
});

Это критично для headless рендеринга, где необходимо получить финальный кадр.


Производительность и GPU контекст

Offscreen rendering позволяет разгрузить UI-поток, но вводит новые ограничения:

Преимущества

  • отсутствие блокировки UI
  • параллельная загрузка тайлов
  • изоляция WebGL контекста
  • возможность батчинга кадров

Ограничения

  • ограниченный доступ к DOM API
  • возможная деградация WebGL в worker (зависит от браузера)
  • стоимость передачи OffscreenCanvas между потоками
  • отсутствие прямого контроля над input events

Управление размером и DPI

OffscreenCanvas требует явного контроля разрешения:

const dpr = 2;

const canvas = new OffscreenCanvas(1024 * dpr, 768 * dpr);

map.resize();

В MapLibre важно учитывать:

  • pixelRatio
  • физический размер canvas
  • пересчёт матриц камеры

Поток рендеринга в offscreen режиме

Рендеринг проходит следующие стадии:

  1. Изменение состояния карты (camera / style)
  2. Планирование repaint
  3. Запрос тайлов из источников
  4. Компиляция шейдеров (при необходимости)
  5. WebGL draw calls
  6. Коммит буфера в OffscreenCanvas

Работа с тайлами и источниками данных

Offscreen rendering особенно чувствителен к источникам данных:

  • vector tiles требуют GPU декодирования
  • raster tiles используют текстуры
  • GeoJSON может пересчитываться в worker-потоке

Ключевая особенность — асинхронность загрузки:

map.on('data', (e) => {
  if (e.dataType === 'source' && e.isSourceLoaded) {
    map.triggerRepaint();
  }
});

Использование в batch-рендеринге

Offscreen rendering часто применяется для генерации множества карт:

  • тайловые изображения
  • статические превью
  • аналитические карты
  • отчёты

Типовой pipeline

  • очередь координат
  • инициализация карты
  • установка view (center/zoom)
  • ожидание idle
  • экспорт canvas
  • очистка контекста

Экспорт изображения

Методы зависят от окружения:

Browser

canvas.toBlob((blob) => {
  // загрузка или сохранение
});

Node

const buffer = canvas.toBuffer('image/png');

Типичные проблемы offscreen rendering

Утечки WebGL контекста

При многократном создании карт без destroy возникает деградация GPU памяти.

Несинхронизированные размеры

Canvas и viewport должны быть синхронизированы, иначе возникают артефакты.

Тайлы не успевают загрузиться

Ранний экспорт приводит к неполным изображениям.

Различия браузеров

  • Chrome: полноценная поддержка OffscreenCanvas
  • Firefox: частичная
  • Safari: ограниченная поддержка worker rendering

Роль repaint loop в offscreen режиме

MapLibre использует внутренний цикл:

  • requestAnimationFrame (или polyfill)
  • проверка dirty state
  • пересборка WebGL сцен

В worker-режиме этот цикл может быть эмулирован вручную:

function frame() {
  map._render();
  setTimeout(frame, 16);
}
frame();

Комбинация offscreen rendering и взаимодействия потоков

В сложных приложениях используется разделение:

  • UI thread: управление состоянием
  • Worker thread: рендеринг карты
  • Shared state: postMessage / Comlink

Это позволяет строить архитектуры:

  • live map preview
  • server-like rendering pipeline в браузере
  • параллельные карты для аналитики

Особенности работы с анимацией

Анимации камеры (zoom, rotate, flyTo) в offscreen режиме требуют:

  • явного контроля завершения
  • ожидания moveend
  • блокировки экспорта до стабилизации состояния
map.flyTo({ center: [10, 20], zoom: 5 });

map.once('moveend', () => {
  map.once('idle', exportImage);
});

Влияние отсутствия DOM на поведение карты

Без DOM:

  • отсутствует CSS sizing
  • нет pointer events
  • нет resize observer
  • нет scroll-driven interaction

Это приводит к более детерминированному рендерингу, но требует ручного управления всеми параметрами viewport.


Параллелизм и масштабирование

Offscreen rendering становится основой для горизонтального масштабирования:

  • несколько workers
  • пул карт
  • распределение задач рендеринга
  • кэширование style.json и tile cache

Такая архитектура характерна для систем:

  • генерации картографических тайлов
  • аналитических GIS сервисов
  • offline rendering pipelines