В основе высокопроизводительной визуализации в WebGL лежит минимизация затрат на передачу данных между JavaScript и GPU. Традиционные структуры на основе объектов JavaScript оказываются слишком тяжёлыми: они требуют сериализации, занимают больше памяти и приводят к лишним преобразованиям при подготовке атрибутов вершин. В этом контексте бинарные данные и типизированные массивы становятся ключевым механизмом эффективной работы.
Deck.gl изначально проектировался с расчётом на работу с большими
наборами геопространственных данных, поэтому его внутренняя модель
данных тесно связана с ArrayBuffer и
TypedArray. Основная цель — хранить и передавать данные в
виде непрерывных блоков памяти, которые напрямую могут быть использованы
WebGL-буферами.
ArrayBuffer представляет собой фиксированный блок
памяти, который не содержит логики интерпретации. Он лишь описывает
«сырой» участок памяти, а интерпретация задаётся через
представления:
Float32ArrayUint8ArrayUint32ArrayInt16Arrayи другие типизированные массивы.
В Deck.gl именно ArrayBuffer используется как контейнер
для хранения вершинных атрибутов, таких как координаты, цвета, индексы и
дополнительные пользовательские свойства.
Ключевая особенность:
ArrayBuffer может иметь несколько
представленийWebGL работает с буферами GPU, которые ожидают данные в строго определённом формате. Например:
Float32ArrayUint8Array или нормализованный
Uint8ArrayUint16Array или Uint32ArrayDeck.gl использует типизированные массивы как промежуточный слой между JavaScript и GPU.
Типичный поток данных:
bufferDataНаиболее затратная операция — конвертация структурированных объектов в плоские массивы.
Пример исходных данных:
[
{ position: [10, 20], value: 5, color: [255, 0, 0] },
{ position: [15, 25], value: 10, color: [0, 255, 0] }
]
В бинарном представлении Deck.gl формирует несколько параллельных буферов:
positions: [10, 20, 15, 25] →
Float32Arrayvalues: [5, 10] →
Float32Array или Int32Arraycolors: [255, 0, 0, 0, 255, 0] →
Uint8ArrayТакой подход называется structure-of-arrays (SoA) и является предпочтительным для GPU, в отличие от array-of-structures (AoS).
[
{x: 1, y: 2},
{x: 3, y: 4}
]
Проблема:
x = [1, 3]
y = [2, 4]
Преимущества:
Deck.gl внутренне стремится к SoA при любой трансформации данных.
Внутренний механизм Deck.gl для управления атрибутами основан на
AttributeManager. Он отвечает за:
Каждый слой (Layer) описывает, какие атрибуты ему нужны:
getPositiongetColorgetElevationAttributeManager вызывает соответствующие функции и заполняет бинарные буферы.
Deck.gl позволяет напрямую возвращать бинарные данные из accessor-функций, минуя промежуточную обработку.
Пример:
getPosition: d => new Float32Array([d.x, d.y, d.z])
Однако более эффективный вариант:
getPosition: (d, {index, target}) => {
target[0] = d.x;
target[1] = d.y;
target[2] = d.z;
}
Здесь target — заранее выделенный участок TypedArray.
Это позволяет:
Deck.gl использует концепцию packed attributes, когда несколько логически разных значений упаковываются в один буфер.
Пример:
Могут храниться как:
[x, y, z, intensity, category, x, y, z, intensity, category, ...]
или раздельно, в зависимости от оптимизации слоя.
Packed формат снижает количество WebGL buffer bindings, но требует аккуратного описания stride и offset.
Interleaving — это хранение нескольких атрибутов в одном TypedArray с фиксированным шагом.
[x, y, r, g, b, x, y, r, g, b]
Параметры:
WebGL использует:
gl.vertexAttribPointer(
location,
size,
type,
normalized,
stride,
offset
)
Deck.gl автоматически вычисляет эти параметры при создании атрибутов.
Индексные массивы позволяют переиспользовать вершины.
[0, 1, 2, 2, 3, 0]
Типичный тип:
Uint16Array для небольших геометрийUint32Array для больших наборов данныхDeck.gl использует индексы в слоях:
PolygonLayerGeoJsonLayerPathLayerИндексные буферы уменьшают объём памяти и ускоряют рендеринг за счёт уменьшения количества вершин.
При работе с внешними источниками Deck.gl часто использует бинарные форматы напрямую:
Преимущество:
ArrayBufferПример: GeoJSON → бинарная трансформация
В высокопроизводительных сценариях Deck.gl стремится к zero-copy обработке:
ArrayBuffer создаётся один разПример:
const buffer = new ArrayBuffer(1024);
const positions = new Float32Array(buffer);
const colors = new Uint8Array(buffer);
Разные представления позволяют интерпретировать одну память по-разному, но требуют строгого контроля смещений.
Основные факторы влияния:
Оптимизации Deck.gl:
При изменении данных Deck.gl не пересоздаёт все бинарные буферы.
Используются флаги:
Если изменяется только часть данных:
bufferSubDataФинальный этап — передача TypedArray в GPU:
gl.bufferData(gl.ARRAY_BUFFER, float32Array, gl.DYNAMIC_DRAW)
или
gl.bufferSubData(gl.ARRAY_BUFFER, offset, subArray)
Deck.gl автоматически выбирает стратегию:
При миллионах точек критичны следующие аспекты:
Типичная оптимизация:
Float32ArrayUint8ArrayUint16ArrayКаждый слой предъявляет разные требования:
ScatterplotLayer: позиции + радиус + цветPathLayer: массивы линий в бинарной формеPolygonLayer: индексы + треугольная триангуляцияHexagonLayer: агрегация в GPU-friendly buffersВо всех случаях конечная цель — свести данные к компактным TypedArray структурам, пригодным для прямой загрузки в WebGL буферы.