Compute shaders

Визуализация больших массивов геопространственных и временных данных в Deck.gl опирается на перенос вычислительно тяжёлых операций с CPU на GPU. Основной подход заключается в использовании шейдеров не только для отрисовки, но и для предварительной агрегации, фильтрации и трансформации данных. Такой подход устраняет узкие места, связанные с передачей данных по шине CPU–GPU и серийной обработкой в JavaScript.

GPU в этой архитектуре рассматривается как потоковый вычислитель, где каждая вершина или фрагмент выполняет одинаковую операцию над своим подмножеством данных. Это позволяет реализовать поведение, близкое к compute shaders, даже в WebGL2, где полноценные compute shaders отсутствуют.


WebGL2 как основа вычислений

В Deck.gl вычислительные паттерны реализуются через WebGL2, используя несколько ключевых механизмов:

  • vertex shaders для потоковой обработки входных данных
  • transform feedback для записи результатов обратно в буферы
  • framebuffer objects (FBO) для промежуточных вычислений
  • texture-based data storage для больших массивов
  • instanced rendering для массовой обработки сущностей

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


Transform Feedback как замена compute shaders

Transform feedback является ключевым механизмом, позволяющим реализовать GPU-вычисления в WebGL2.

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

  • vertex shader выполняет произвольные вычисления
  • результат записывается в буфер без этапа растеризации
  • выход одного прохода становится входом следующего

Это создаёт цепочку вычислительных проходов, напоминающую compute pipeline.

Типичный поток выглядит следующим образом:

  1. загрузка массива данных в vertex buffer
  2. запуск shader pass с вычислениями
  3. запись результатов в transform feedback buffer
  4. повторное использование буфера как входа

В Deck.gl этот механизм используется для агрегации данных на GPU и подготовки данных для визуальных слоёв.


GPU Aggregation Layers

Одно из основных применений вычислений на GPU — агрегация точек в сетки, гексагоны или пиксельные кластеры.

ScreenGridLayer

В этом слое данные проецируются в экранное пространство, после чего выполняется агрегация по пикселям. Каждый входной объект вносит вклад в соответствующую ячейку сетки.

Вычислительный процесс:

  • проекция координат в screen-space
  • вычисление индекса ячейки сетки
  • атомарное накопление значений через multiple render targets или ping-pong buffers
  • нормализация результата для визуализации

Ключевая особенность — отсутствие CPU-агрегации, что критично при миллионах точек.


GPUGridLayer

Этот слой реализует пространственную сетку, где каждая ячейка соответствует географической области.

На GPU выполняется:

  • биннинг точек по координатам
  • суммирование значений внутри ячеек
  • вычисление статистик (count, sum, min, max)

Фактически каждая вершина становится независимым вычислительным агентом, который определяет свою принадлежность к ячейке.


HexagonLayer (GPU ускорение)

Хотя классическая версия HexagonLayer может использовать CPU-агрегацию, GPU-реализация переносит:

  • пространственное разбиение на H3-подобную сетку
  • агрегацию значений по индексам ячеек
  • подготовку геометрии для рендеринга

Ключевой аспект — предварительное вычисление индексов на GPU, что снижает стоимость перерасчёта при интерактивном зуме.


Модель вычислений через luma.gl

Deck.gl использует luma.gl как низкоуровневый слой, который управляет WebGL ресурсами.

Вычисления описываются через:

  • ShaderModule
  • Model abstraction
  • Attribute system
  • Uniform binding

Vertex shader в таком контексте превращается в функцию:

f(vertex) → output attributes

Каждый attribute буфер рассматривается как поток данных, а модель — как вычислительный контейнер.


Псевдопараллелизм и потоковая модель

GPU вычисления в Deck.gl базируются на SIMD-модели:

  • одна инструкция выполняется над множеством элементов
  • отсутствует произвольное ветвление (или оно минимизируется)
  • данные обрабатываются пакетами (draw calls)

Это требует преобразования алгоритмов:

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

Ping-pong buffers и многопроходные вычисления

Для задач, требующих итеративных вычислений, используется техника ping-pong buffering.

Схема:

  • Buffer A — входные данные
  • Buffer B — результаты прохода
  • следующий шаг: B становится входом, A — выходом

Это позволяет реализовать:

  • сглаживание данных
  • диффузионные модели
  • итеративную агрегацию
  • приближённые физические симуляции

Каждый проход выполняется как отдельный draw call с другим framebuffer target.


Атрибутные вычисления вместо глобального состояния

Одно из ключевых ограничений GPU-подхода — отсутствие глобального изменяемого состояния.

Поэтому вычисления формулируются через:

  • per-instance attributes
  • varyings между vertex и fragment shader
  • текстуры как глобальные массивы

Например, агрегация реализуется через кодирование состояния в RGBA-каналы текстуры, где каждый канал хранит отдельную метрику.


Фрагментные шейдеры как вычислительные ядра

Хотя vertex shader чаще используется для transform feedback, fragment shader применяется для:

  • raster-based aggregation
  • image-space computation
  • heatmap generation
  • density estimation

Каждый фрагмент соответствует пикселю сетки, и вычисления выполняются параллельно для всех пикселей.

Это делает fragment shader аналогом compute kernel, работающего по регулярной сетке.


Ограничения WebGL compute-подхода

Несмотря на мощность, модель имеет ограничения:

  • отсутствие произвольного доступа к памяти (random write)
  • ограниченные атомарные операции
  • зависимость от текстурных обходов
  • дорогие синхронизации между проходами
  • ограниченный размер uniform buffers

Эти ограничения формируют стиль алгоритмов:

  • предпочтение редукций
  • локальные вычисления
  • минимизация зависимостей между элементами

WebGPU и эволюция compute shaders в Deck.gl

С переходом к WebGPU модель вычислений становится ближе к классическим compute shaders.

В этом контексте появляются:

  • workgroup-based execution
  • shared memory внутри групп
  • прямые compute passes без rasterization
  • WGSL-шейдеры вместо GLSL

Архитектура Deck.gl адаптируется к этому через абстракции, позволяющие:

  • переиспользовать вычислительные пайплайны
  • разделять логику агрегации и рендеринга
  • переносить существующие GPU-алгоритмы без переписывания бизнес-логики

Разделение вычислений и визуализации

В GPU-подходе Deck.gl вычисления и визуализация разделены:

  • compute stage: трансформация данных, агрегация, фильтрация
  • render stage: отрисовка геометрии или пикселей

Это разделение позволяет:

  • кэшировать промежуточные результаты
  • повторно использовать вычисленные структуры
  • уменьшать нагрузку при интерактивных изменениях камеры

Поток данных в GPU-пайплайне Deck.gl

Типичный pipeline выглядит следующим образом:

  • загрузка геоданных в buffer attributes
  • vertex shader: трансформация координат и предварительная обработка
  • transform feedback или framebuffer pass: агрегация
  • повторные passes для нормализации
  • финальный render pass

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


Оптимизация вычислительных проходов

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

  • минимизации draw calls
  • упаковки данных в плотные buffer structures
  • использования instanced rendering вместо индивидуальных объектов
  • снижения precision в shader вычислениях (mediump вместо highp)
  • предвычисления индексов и lookup-таблиц

Особое значение имеет снижение bandwidth между CPU и GPU, так как именно он часто становится узким местом при работе с миллионами объектов.


Практическая модель мышления при проектировании GPU-алгоритмов

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

  • каждый элемент обрабатывается независимо
  • результат зависит только от входного состояния
  • состояние хранится в буферах или текстурах
  • вычисления разбиваются на стадии

Такая модель ближе к функциональному программированию, чем к императивному стилю.


Итеративные вычисления и устойчивость результата

Многопроходные GPU-алгоритмы требуют контроля стабильности:

  • накопление ошибок при float-операциях
  • расхождение при итерациях
  • необходимость нормализации после каждого pass

Поэтому часто применяются:

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

Роль шейдерных модулей

Shader modules позволяют переиспользовать вычислительные блоки:

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

Это снижает дублирование логики между слоями и позволяет строить композиционные GPU-алгоритмы.


Масштабируемость GPU-вычислений

Основное преимущество GPU-подхода проявляется при росте данных:

  • 10⁴ объектов: CPU ещё эффективен
  • 10⁶ объектов: GPU становится необходим
  • 10⁷+ объектов: требуется агрегация и downsampling на GPU

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