Skip to main content

GPU Tables

@luma.gl/gpgpu/gpu-data provides primitive GPU chunks and vectors, while the private experimental @luma.gl/experimental/gpu-tables subpath represents row-aligned application data as typed GPU columns. A GPUTable is useful when the same data must feed rendering, transforms, or compute without first converting every row into JavaScript objects or one model-specific buffer structure.

Use a GPU table when:

  • application data already has a columnar shape, commonly from Apache Arrow;
  • columns must remain aligned while source record batches stream independently;
  • one model may consume columns as attributes while another consumes storage buffers;
  • optional style columns should be replaceable by one constant value;
  • GPU buffer ownership, packing, and destruction must be explicit.

Use ordinary buffers or geometry when the input is already one fixed mesh. Use uniforms for model-wide values that are not part of a tabular input contract. Keep using an Arrow Table when the CPU still needs Arrow queries or row access: a GPUTable is a GPU representation, not a CPU dataframe.

Object Model​

ObjectMeaning
GPUTableLogical schema, columns, row count, and preserved physical batches.
GPUVectorOne varying logical column over ordered GPUData chunks.
GPUConstantOne fixed-width value shared by every logical table row.
GPURecordBatchOne physical row range containing batch-local GPUData.
GPUDataOne owned or borrowed GPU buffer range and its row layout.
GPUInputSchemaA model's mapping from logical columns to shader inputs.
GPUTableShaderBindingsBackend-specific, draw-ready resources prepared from a table.

The central distinction is logical versus physical state. table.gpuColumns contains both vectors and constants. table.batches[n].gpuData contains only physical varying chunks. Constants do not pretend to be repeated GPUData and do not appear in table.bufferLayout.

Constructing A Table​

import {GPUConstant} from '@luma.gl/gpgpu/gpu-data';
import {GPUTable} from '@luma.gl/experimental/gpu-tables';

const table = new GPUTable({
columns: {
positions,
colors: new GPUConstant({
format: 'unorm8x4',
value: new Uint8Array([60, 150, 255, 220])
}),
radii: new GPUConstant({
format: 'float32',
value: new Float32Array([4])
})
}
});

The varying positions vector determines numRows. If every column is constant, provide it explicitly:

const table = new GPUTable({
columns: {color: constantColor},
numRows: 10_000
});

GPUConstant stores raw bytes in the declared VertexFormat. Normalized formats therefore use integer payloads, such as Uint8Array for unorm8x4.

Declaring Shader Inputs​

A model publishes one GPUInputSchema independent of the backend:

const pointInputs = [
{
columnName: 'positions',
attributeName: 'positions',
storageBindingName: 'pointPositions',
kind: 'positions',
required: true,
formats: ['float32x2']
},
{
columnName: 'colors',
attributeName: 'colors',
storageBindingName: 'pointColors',
kind: 'colors',
required: false,
formats: ['unorm8x4']
}
] as const satisfies GPUInputSchema;

Required inputs must be varying vectors. Optional fixed-width inputs may be vectors, constants, or absent. attributeName and storageBindingName allow the same logical table to feed attribute-backed and storage-backed shaders.

Composite inputs use attributeNames to map one logical column to several attribute views. For example, the same matrix column can serve portable vertex attributes or a WebGPU storage binding:

const transformInputs = [
{
columnName: 'instanceModelMatrix',
attributeNames: [
'instanceModelMatrixCol0',
'instanceModelMatrixCol1',
'instanceModelMatrixCol2',
'instanceModelMatrixCol3'
],
storageBindingName: 'instanceModelMatrix',
kind: 'matrices',
required: true,
formats: ['float32x4']
}
] as const satisfies GPUInputSchema;

The vector's BufferLayout describes the four offsets and shared row stride. Binding preparation keeps the buffer shared: it does not copy or split matrix columns.

Preparing Bindings​

const prepared = new GPUTableShaderBindings(device, {
table,
gpuInputSchema: pointInputs,
shaderLayout
});

for (const batch of prepared.batches) {
model.setBufferLayout(prepared.bufferLayout);
model.setAttributes(batch.attributes);
model.setBindings(batch.bindings);
model.setConstantAttributes(prepared.constantAttributes);
model.draw(renderPass);
}

prepared.destroy();
table.destroy();

GPUTableModel performs this preparation and batch rebinding automatically when given gpuInputSchema.

Backend Behavior​

Input pathVarying columnConstant column
WebGL attributeEnabled vertex attribute array.Disabled array plus the context constant attribute value.
WebGPU attributeOrdinary vertex buffer layout.One payload buffer with arrayStride: 0.
WebGPU storageBatch-local storage range indexed by row.One-row storage buffer indexed with row multiplier 0.

WebGPU storage shaders use the generated module:

let color = pointColors[
gpuTable_getRowIndex(
rowIndex,
gpuTableColumns.pointColorsRowMultiplier
)
];

The multiplier is 1 for a varying column and 0 for a constant. Shader code is therefore identical for both table representations.

Interleaving And Constants​

Interleaving is physical layout, while a constant is a logical column choice. Binding preparation removes constant attributes from an interleaved varying layout, drops empty layouts, and creates backend-specific constant state. Compatible WebGPU attribute constants are packed into zero-stride buffers by step mode.

Do not encode constants by setting a GPUData.byteStride to zero. GPUData describes physical varying chunks. Constant semantics belong to GPUConstant and are lowered only after a shader contract and backend are known.

Streaming, Packing, And Updates​

Arrow conversion preserves source record batches. Drawing therefore binds one prepared batch at a time. Call table.packBatches() explicitly when fewer, larger GPU batches are preferable. Table-level constants survive packing unchanged.

GPUTableShaderBindings.updateBindings(nextTable) replaces table-derived resources without changing its GPUInputSchema or ShaderLayout. Compatible owned allocations are reused. Construct a new bindings object when the shader contract changes.

Ownership And Memory​

  • GPUData owns or borrows one buffer.
  • GPURecordBatch owns its retained GPUData chunks.
  • GPUTable owns its retained batches, but constants contain CPU bytes only.
  • GPUTableShaderBindings owns zero-stride, one-row storage, and multiplier buffers.
  • Destroy bindings before or with the model; destroy the table when its physical data is no longer needed.

A constant column has the logical length of the table but one payload row. Memory metrics should distinguish logical expansion from physical allocation. For 10,000 unorm8x4 colors, a varying vector uses 40,000 payload bytes; a constant uses four payload bytes plus any backend alignment or multiplier-buffer overhead.

Diagnostics​

  • Mismatched rows: varying chunks at the same batch index must have equal lengths.
  • Wrong constant type: the typed-array constructor and byte length must exactly match the declared vertex format.
  • Constant required input: change the schema declaration to optional only when the model genuinely supports constant semantics.
  • Missing storage binding: optional absence still needs a model-specific fallback if the shader statically declares and reads the binding.
  • Stale layout: call updateBindings() or GPUTableModel.setProps({table}) after replacing a table.
  • Growing memory: destroy superseded binding objects and tables; neither relies on garbage collection for GPU resources.

See the GPUTable, GPUConstant, and GPUTableShaderBindings references for exact contracts.