Работа с атрибутами

В основе архитектуры Deck.gl лежит концепция передачи данных в виде атрибутов, которые напрямую загружаются в GPU и используются в шейдерах для отрисовки геометрии. Атрибуты в этом контексте — это не просто свойства объектов, а строго типизированные буферы, синхронизированные между CPU и GPU, определяющие поведение каждой вершины или экземпляра геометрии.

Базовая модель атрибутов

Каждый слой (layer) в Deck.gl описывает, какие данные ему нужны для рендеринга. Эти данные преобразуются в атрибуты:

  • позиция (position)
  • цвет (color)
  • радиус (radius)
  • углы поворота (angle)
  • пользовательские параметры

Ключевая идея заключается в том, что атрибуты существуют не как массив JavaScript-объектов, а как непрерывные бинарные буферы (TypedArray), загружаемые в GPU.

Внутри слоя атрибуты регистрируются через механизм AttributeManager.

this.attributeManager.add({
  positions: {
    size: 3,
    accessor: 'getPosition'
  },
  colors: {
    size: 4,
    accessor: 'getColor',
    type: GL.UNSIGNED_BYTE,
    normalized: true
  }
});

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

  • size — количество компонентов (например, vec2, vec3, vec4)
  • type — тип данных WebGL
  • accessor — функция извлечения значения из входных данных
  • дополнительные параметры: normalized, divisor, shaderAttributes

Связь атрибутов и данных слоя

Deck.gl разделяет данные и их GPU-представление. Исходный массив данных может выглядеть как массив объектов:

const data = [
  { position: [37.78, -122.41], value: 10, color: [255, 0, 0] },
  { position: [37.79, -122.42], value: 20, color: [0, 255, 0] }
];

Но на уровне атрибутов этот массив превращается в:

  • Float32Array для позиций
  • Uint8Array для цветов
  • отдельные буферы для каждого свойства

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

AttributeManager и жизненный цикл атрибутов

AttributeManager отвечает за создание, обновление и переиспользование буферов. Его жизненный цикл включает несколько стадий:

  1. Инициализация атрибутов при создании слоя
  2. Построение буферов при изменении данных
  3. Обновление при изменении updateTriggers
  4. Очистка или переиспользование памяти

Важный аспект — ленивое обновление. Атрибуты пересчитываются только при изменении зависимых данных.

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

Accessor и трансформация данных

Accessor-функции являются мостом между пользовательскими данными и GPU-атрибутами. Они извлекают значения из объектов данных.

getPosition: d => d.coordinates,
getColor: d => d.intensity > 0.5 ? [255, 0, 0] : [0, 0, 255]

На этапе сборки атрибутов Deck.gl вызывает accessor для каждого элемента массива и формирует бинарный буфер.

Критически важно, что accessor выполняется на CPU, а результат кешируется в GPU-буфере.

Instanced rendering и divisor

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

attributes: {
  instancePositions: {
    size: 3,
    divisor: 1
  }
}

divisor: 1 означает, что атрибут применяется на уровень экземпляра, а не вершины.

Это позволяет:

  • резко сократить объем draw calls
  • уменьшить нагрузку на CPU
  • эффективно отрисовывать тысячи объектов

TypedArray и формат хранения данных

Все атрибуты приводятся к строго определённым форматам:

  • Float32Array — координаты, размеры
  • Uint8Array — цвета
  • Uint16Array — индексы

Пример преобразования:

const positions = new Float32Array(data.length * 3);

Каждый объект данных разворачивается в плоский массив, что критично для производительности GPU pipeline.

updateTriggers и контроль пересчёта атрибутов

Deck.gl использует систему updateTriggers для контроля пересчёта атрибутов.

getColor: d => d.value,
updateTriggers: {
  getColor: [data]
}

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

GPU-буферы и синхронизация

После вычисления атрибутов данные загружаются в WebGL буферы. Каждый атрибут соответствует WebGLBuffer.

Процесс включает:

  1. Создание buffer object
  2. Привязку к атрибуту shader program
  3. Upload данных через bufferData или bufferSubData

В шейдере атрибут объявляется как:

attribute vec3 positions;
attribute vec4 colors;

GPU затем интерпретирует эти буферы как входные потоки данных для вершинного шейдера.

Взаимодействие с шейдерами

Атрибуты напрямую маппятся на переменные GLSL. Deck.gl автоматически связывает JavaScript-описание атрибутов с shader attributes.

Пример vertex shader:

attribute vec3 instancePositions;
attribute vec4 instanceColors;

void main(void) {
  gl_Position = project_position(instancePositions);
  vColor = instanceColors;
}

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

Пользовательские атрибуты

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

this.attributeManager.add({
  elevation: {
    size: 1,
    accessor: 'getElevation',
    shaderAttributes: {
      elevation: {divisor: 1}
    }
  }
});

Это используется для:

  • построения 3D визуализаций
  • генерации динамических эффектов
  • вычисления параметров освещения

Частичное обновление атрибутов

Для оптимизации можно обновлять только часть буфера:

attribute.update({
  value: newData,
  startIndices: [0, 1000]
});

Это уменьшает объем передаваемых данных и снижает задержки при интерактивной визуализации.

Производительность и узкие места

Основные ограничения системы атрибутов:

  • частые пересоздания TypedArray
  • избыточные updateTriggers
  • неэффективные accessor-функции
  • отсутствие батчинга данных

Оптимальная стратегия заключается в:

  • минимизации пересчётов
  • предвычислении данных
  • использовании instancing
  • сокращении числа атрибутов

Детерминированность и иммутабельность

Атрибуты в Deck.gl предполагают иммутабельную модель данных: любое изменение входного массива рассматривается как сигнал к пересборке буферов. Это упрощает синхронизацию состояния между CPU и GPU, но требует строгого контроля над изменениями данных.

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

Роль атрибутов в архитектуре слоёв

Каждый слой Deck.gl можно рассматривать как декларацию набора атрибутов и правил их построения. Геометрия слоя вторична по отношению к данным: именно атрибуты определяют форму, цвет, размер и поведение объектов на сцене.

В результате вся система визуализации строится вокруг трансформации данных в GPU-совместимые структуры, где атрибуты являются центральным звеном между JavaScript-логикой и графическим конвейером WebGL.