Storage Buffers и Uniform Buffers

Работа современных графических API строится на передаче данных из приложения в шейдеры через специальные области памяти — буферы. В контексте Babylon.js основную роль играют два типа буферов:

  • Uniform Buffers
  • Storage Buffers

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

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

Babylon.js абстрагирует низкоуровневые особенности WebGL и WebGPU, предоставляя единый API для работы с такими буферами.


Uniform Buffers

Назначение

Uniform Buffer представляет собой блок параметров, который передаётся в шейдеры в виде структуры. Такие буферы позволяют сгруппировать связанные данные и отправлять их на GPU одним вызовом вместо передачи множества отдельных uniform-переменных.

Основные преимущества:

  • уменьшение количества вызовов WebGL/WebGPU API;
  • повышение производительности при большом количестве объектов;
  • единая структура данных для CPU и GPU.

Uniform Buffers широко применяются для передачи:

  • матриц трансформации;
  • параметров освещения;
  • настроек материалов;
  • глобальных параметров сцены.

Ограничения Uniform Buffers

Несмотря на удобство, Uniform Buffers имеют ряд ограничений, обусловленных архитектурой GPU:

  • ограниченный максимальный размер блока;
  • данные являются только для чтения в шейдере;
  • структура должна соответствовать строгим правилам выравнивания памяти (std140).

Эти ограничения делают Uniform Buffers неэффективными для работы с большими массивами данных.


Создание UniformBuffer в Babylon.js

Babylon.js предоставляет класс UniformBuffer, который управляет выделением памяти и синхронизацией данных.

Создание буфера:

const uniformBuffer = new BABYLON.UniformBuffer(engine);

uniformBuffer.addUniform("world", 16);
uniformBuffer.addUniform("viewProjection", 16);
uniformBuffer.addUniform("time", 1);

uniformBuffer.create();

Метод addUniform определяет имя и размер переменной.

Здесь:

  • 16 — количество элементов для матрицы 4×4
  • 1 — скалярное значение

После объявления структуры вызывается create().


Обновление данных

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

uniformBuffer.updateMatrix("world", worldMatrix);
uniformBuffer.updateMatrix("viewProjection", viewProjMatrix);
uniformBuffer.updateFloat("time", performance.now() * 0.001);

uniformBuffer.update();

Метод update() синхронизирует буфер с GPU.

Babylon.js использует lazy update, поэтому фактическая отправка данных происходит только при необходимости.


Использование в шейдерах

В GLSL структура Uniform Buffer выглядит следующим образом:

layout(std140) uniform SceneData {
    mat4 world;
    mat4 viewProjection;
    float time;
};

Доступ к данным осуществляется как к обычным переменным:

gl_Position = viewProjection * world * vec4(position, 1.0);

Babylon.js автоматически связывает UniformBuffer с соответствующим блоком в шейдере.


Совместное использование между объектами

Uniform Buffers часто используются как глобальные контейнеры параметров сцены.

Пример:

  • один буфер содержит параметры освещения;
  • несколько материалов используют один и тот же буфер.

Это существенно снижает количество операций передачи данных на GPU.


Оптимизация обновлений

Babylon.js реализует внутреннюю систему отслеживания изменений. Если значение uniform не изменилось, повторная отправка данных не происходит.

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

  • уменьшить нагрузку на CPU;
  • минимизировать синхронизацию GPU.

Storage Buffers

Общая характеристика

Storage Buffer — более гибкий тип буфера, поддерживаемый WebGPU. Он позволяет хранить большие массивы данных и предоставляет произвольный доступ к этим данным из шейдеров.

Основные возможности:

  • чтение и запись внутри шейдера;
  • хранение больших структур данных;
  • использование в compute-шейдерах;
  • индексированный доступ к элементам массива.

Storage Buffers применяются в:

  • системах частиц;
  • симуляциях;
  • GPU-вычислениях;
  • обработке геометрии.

Отличия от Uniform Buffers

Свойство Uniform Buffer Storage Buffer
Размер ограниченный значительно больше
Доступ только чтение чтение и запись
Выравнивание строгие правила std140 более гибкое
Использование параметры сцены вычисления и массивы

Создание StorageBuffer

Babylon.js предоставляет класс StorageBuffer, предназначенный для работы с WebGPU.

Создание буфера:

const storageBuffer = new BABYLON.StorageBuffer(engine, 1024);

Параметры:

  • engine — экземпляр движка
  • 1024 — размер буфера в байтах

Заполнение буфера данными

Для передачи данных используется типизированный массив:

const data = new Float32Array([
    0, 1, 2, 3,
    4, 5, 6, 7
]);

storageBuffer.update(data);

Babylon.js копирует массив в память GPU.


Использование в compute-шейдерах

Storage Buffers особенно полезны при работе с Compute Shaders, доступными в WebGPU.

Пример WGSL:

struct Data {
    values : array<f32>
};

@group(0) @binding(0)
var<storage, read_write> data : Data;

Чтение и запись:

data.values[index] = data.values[index] * 2.0;

GPU может изменять содержимое буфера напрямую.


Доступ к данным после выполнения вычислений

Babylon.js позволяет считывать данные обратно с GPU.

const result = await storageBuffer.read();

Метод возвращает ArrayBuffer, который можно преобразовать:

const values = new Float32Array(result);

Этот механизм используется для:

  • результатов GPU-вычислений;
  • симуляций;
  • генерации данных.

Использование Storage Buffers в системах частиц

Одним из распространённых сценариев является GPU-симуляция частиц.

В буфере может храниться структура:

position.xyz
velocity.xyz
lifetime

Compute-шейдер обновляет состояние частиц:

position += velocity * deltaTime

После чего вершинный шейдер использует те же данные для рендеринга.

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


Выравнивание данных

Несмотря на более гибкие правила, Storage Buffers всё ещё требуют корректного выравнивания.

Например:

vec3 → выравнивается до 16 байт

Поэтому структуры обычно выравниваются следующим образом:

struct Particle {
    vec4 position;
    vec4 velocity;
}

Это предотвращает ошибки доступа и повышает производительность GPU.


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

Babylon.js поддерживает обновление части данных:

storageBuffer.update(data, offset);

offset задаёт смещение в байтах.

Этот механизм используется при изменении только части массива.


Синхронизация CPU и GPU

GPU выполняет операции асинхронно, поэтому чтение буфера требует ожидания завершения вычислений.

Babylon.js оборачивает этот процесс в Promise:

await storageBuffer.read();

Такой подход предотвращает блокировку основного потока.


Практические сценарии использования

Параметры сцены

Uniform Buffers используются для передачи:

  • матриц камеры
  • параметров освещения
  • глобальных коэффициентов материалов

Это стандартная практика большинства графических движков.


Массовые данные

Storage Buffers применяются для:

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

GPU-вычисления

В связке с Compute Shaders Storage Buffers позволяют выполнять на GPU:

  • сортировку данных;
  • симуляции жидкостей;
  • обработку изображений;
  • генерацию ландшафтов.

Это превращает графический процессор в универсальный вычислительный инструмент.


Архитектурная роль буферов в Babylon.js

Babylon.js строит систему управления данными на трёх уровнях:

  1. CPU-структуры JavaScript
  2. GPU-буферы
  3. шейдерные структуры

Uniform Buffers используются для компактных наборов параметров, которые часто читаются шейдерами. Storage Buffers служат контейнерами для больших массивов данных и активно применяются в вычислительных задачах.

Разделение этих механизмов позволяет эффективно использовать возможности GPU, минимизировать передачу данных между CPU и GPU и создавать высокопроизводительные графические и вычислительные приложения.