Создание собственных слоев

Система слоёв в Deck.gl построена вокруг расширяемой модели, в которой каждый визуальный компонент инкапсулируется в класс, наследующий базовый функционал фреймворка. Пользовательский слой представляет собой класс, расширяющий Layer, и описывающий полный цикл подготовки данных, создания GPU-ресурсов и отрисовки.

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


Базовый класс Layer и структура наследования

Любой кастомный слой создаётся через наследование от Layer:

import {Layer} from '@deck.gl/core';

class CustomLayer extends Layer {
  initializeState() {}
  updateState({props, oldProps, changeFlags}) {}
  draw({uniforms}) {}
}

Базовый класс предоставляет инфраструктуру:

  • управление props и diff-алгоритмом изменений
  • lifecycle-хуки
  • доступ к WebGL контексту
  • интеграцию с AttributeManager
  • управление шейдерами

Жизненный цикл слоя

Каждый слой проходит фиксированные стадии:

initializeState

Вызывается один раз при создании слоя. Используется для:

  • инициализации AttributeManager
  • регистрации атрибутов
  • создания WebGL ресурсов
initializeState() {
  const attributeManager = this.getAttributeManager();

  attributeManager.add({
    positions: {size: 3, accessor: 'getPosition'},
    colors: {size: 4, accessor: 'getColor'}
  });
}

updateState

Вызывается при изменении props или данных.

updateState({props, oldProps, changeFlags}) {
  if (changeFlags.dataChanged) {
    this._updateGeometry();
  }
}

Типичные сценарии:

  • перерасчёт геометрии
  • обновление буферов
  • пересборка шейдеров

draw

Основная функция рендеринга. Получает униформы и вызывает WebGL пайплайн.

draw({uniforms}) {
  const {gl} = this.context;

  this.state.model.setUniforms({
    ...uniforms,
    opacity: this.props.opacity
  });

  this.state.model.draw(gl);
}

finalizeState

Освобождение ресурсов:

  • удаление буферов
  • очистка моделей
  • деаллокация памяти GPU

AttributeManager и работа с данными

Deck.gl использует AttributeManager для декларативного описания атрибутов вершин.

Каждый атрибут описывает:

  • размерность (size)
  • тип данных
  • accessor (функция извлечения данных)
  • update triggers
attributeManager.add({
  positions: {
    size: 3,
    accessor: 'getPosition',
    update: this.calculatePositions
  }
});

Принцип работы

  1. данные передаются через props
  2. accessor извлекает значения
  3. buffer заполняется Float32Array
  4. данные отправляются в GPU

Создание геометрии слоя

Геометрия может формироваться динамически в updateState:

_updateGeometry() {
  const {data} = this.props;

  const positions = [];
  const colors = [];

  for (const d of data) {
    positions.push(...d.position);
    colors.push(...d.color);
  }

  this.setState({positions, colors});
}

Модель и WebGL абстракция

Deck.gl использует Model как обёртку над WebGLProgram.

import {Model} from '@luma.gl/core';

this.state.model = new Model(gl, {
  vs: vertexShader,
  fs: fragmentShader,
  attributes: {
    positions: this.state.positionsBuffer
  }
});

Модель включает:

  • компиляцию шейдеров
  • привязку атрибутов
  • управление uniforms
  • draw calls

Шейдеры в пользовательских слоях

Vertex shader

Отвечает за позиционирование вершин:

attribute vec3 positions;
attribute vec4 colors;

uniform float opacity;

varying vec4 vColor;

void main(void) {
  vColor = colors * opacity;
  gl_Position = project_position_to_clipspace(positions, vec3(0.0), vec3(0.0));
}

Fragment shader

Отвечает за пиксельную окраску:

precision highp float;

varying vec4 vColor;

void main(void) {
  gl_FragColor = vColor;
}

Проекция координат

Deck.gl использует встроенные функции проекции:

  • project_position_to_clipspace
  • project_scale
  • project_normal

Эти функции автоматически учитывают:

  • zoom
  • pitch
  • bearing
  • projection mode (Mercator, LNGLAT и др.)

Работа с instancing

Instancing позволяет отрисовывать тысячи объектов одним draw call.

this.state.model = new Model(gl, {
  isInstanced: true,
  instanceCount: data.length
});

В шейдере:

attribute vec3 instancePositions;

void main(void) {
  vec3 pos = positions + instancePositions;
  gl_Position = project_position_to_clipspace(pos);
}

Управление состоянием слоя

State слоя хранит:

  • буферы атрибутов
  • модели WebGL
  • промежуточные вычисления
this.setState({
  model,
  positionsBuffer,
  colorsBuffer
});

Состояние отделено от props и не должно изменяться напрямую вне lifecycle методов.


Работа с props и diffing

Deck.gl автоматически сравнивает props:

  • изменение данных
  • изменение визуальных параметров
  • изменение функций доступа
updateState({changeFlags}) {
  if (changeFlags.propsChanged) {
    this._updateAttributes();
  }
}

Переиспользование слоя

Переиспользование достигается через:

  • кеширование моделей
  • reuse буферов
  • минимизацию пересборки шейдеров
if (!this.state.model) {
  this._createModel();
}

Подключение кастомного слоя к Deck.gl

import {Deck} from '@deck.gl/core';

const deckgl = new Deck({
  initialViewState: {
    longitude: 0,
    latitude: 0,
    zoom: 3
  },
  layers: [
    new CustomLayer({
      data: dataset,
      getPosition: d => d.position,
      getColor: d => d.color
    })
  ]
});

Обработка больших данных

При работе с большими массивами данных применяются:

  • typed arrays (Float32Array)
  • chunking
  • GPU instancing
  • memoization вычислений
const positions = new Float32Array(data.length * 3);

Оптимизация атрибутов

AttributeManager поддерживает стратегии обновления:

  • never
  • always
  • onDirty
positions: {
  size: 3,
  update: 'onDirty'
}

Использование uniforms

Uniforms позволяют передавать глобальные параметры в шейдер:

this.state.model.setUniforms({
  time: this.props.time,
  opacity: this.props.opacity
});

В GLSL:

uniform float time;
uniform float opacity;

Композиция слоёв

Кастомный слой может быть частью композиции:

  • слой поверх других слоёв
  • слой-обёртка
  • агрегирующий слой
renderLayers() {
  return [
    new CustomLayer({data}),
    new ScatterplotLayer({data})
  ];
}

Сложные слои и multi-model подход

Один слой может содержать несколько моделей:

this.state = {
  modelA,
  modelB
};
draw() {
  this.state.modelA.draw();
  this.state.modelB.draw();
}

Расширение функциональности слоя

Типичные расширения:

  • добавление picking (интерактивности)
  • кастомные события
  • постобработка
  • смешивание цветов (blending)

Picking реализуется через pickingColors:

attribute vec3 instancePickingColors;

void main(void) {
  picking_setPickingColor(instancePickingColors);
}

Интеграция с фреймворками

В React-экосистеме слой используется через DeckGL компонент:

<DeckGL layers={[new CustomLayer({data})]} />

Слой остаётся независимым от UI-слоя и не содержит React-логики.


Архитектурные принципы кастомных слоёв

  • разделение CPU и GPU логики
  • минимизация пересчётов
  • декларативное описание атрибутов
  • изоляция состояния
  • повторное использование WebGL ресурсов
  • ленивые вычисления при изменениях данных