В основе архитектуры Deck.gl лежит концепция передачи данных в виде атрибутов, которые напрямую загружаются в GPU и используются в шейдерах для отрисовки геометрии. Атрибуты в этом контексте — это не просто свойства объектов, а строго типизированные буферы, синхронизированные между CPU и GPU, определяющие поведение каждой вершины или экземпляра геометрии.
Каждый слой (layer) в Deck.gl описывает, какие данные ему нужны для рендеринга. Эти данные преобразуются в атрибуты:
Ключевая идея заключается в том, что атрибуты существуют не как
массив 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 — тип данных WebGLaccessor — функция извлечения значения из входных
данныхnormalized,
divisor, shaderAttributesDeck.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] }
];
Но на уровне атрибутов этот массив превращается в:
Такое разделение позволяет GPU работать с данными без промежуточных преобразований.
AttributeManager отвечает за создание, обновление и
переиспользование буферов. Его жизненный цикл включает несколько
стадий:
updateTriggersВажный аспект — ленивое обновление. Атрибуты пересчитываются только при изменении зависимых данных.
updateState({ props, oldProps, changeFlags }) {
if (changeFlags.dataChanged) {
this.attributeManager.invalidateAll();
}
}
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. Вместо отрисовки каждой геометрии отдельно используется один набор вершин и набор атрибутов на экземпляр.
attributes: {
instancePositions: {
size: 3,
divisor: 1
}
}
divisor: 1 означает, что атрибут применяется на уровень
экземпляра, а не вершины.
Это позволяет:
Все атрибуты приводятся к строго определённым форматам:
Float32Array — координаты, размерыUint8Array — цветаUint16Array — индексыПример преобразования:
const positions = new Float32Array(data.length * 3);
Каждый объект данных разворачивается в плоский массив, что критично для производительности GPU pipeline.
Deck.gl использует систему updateTriggers для контроля
пересчёта атрибутов.
getColor: d => d.value,
updateTriggers: {
getColor: [data]
}
Если зависимость не изменилась, атрибут не пересчитывается. Это позволяет избегать дорогостоящих операций перерасчёта при каждом рендере.
После вычисления атрибутов данные загружаются в WebGL буферы. Каждый
атрибут соответствует WebGLBuffer.
Процесс включает:
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}
}
}
});
Это используется для:
Для оптимизации можно обновлять только часть буфера:
attribute.update({
value: newData,
startIndices: [0, 1000]
});
Это уменьшает объем передаваемых данных и снижает задержки при интерактивной визуализации.
Основные ограничения системы атрибутов:
Оптимальная стратегия заключается в:
Атрибуты в Deck.gl предполагают иммутабельную модель данных: любое изменение входного массива рассматривается как сигнал к пересборке буферов. Это упрощает синхронизацию состояния между CPU и GPU, но требует строгого контроля над изменениями данных.
Изменение объекта без смены ссылки часто приводит к отсутствию обновления атрибутов, что делает важным управление ссылочной целостностью данных.
Каждый слой Deck.gl можно рассматривать как декларацию набора атрибутов и правил их построения. Геометрия слоя вторична по отношению к данным: именно атрибуты определяют форму, цвет, размер и поведение объектов на сцене.
В результате вся система визуализации строится вокруг трансформации данных в GPU-совместимые структуры, где атрибуты являются центральным звеном между JavaScript-логикой и графическим конвейером WebGL.