GPU Table Structure
This page describes the @luma.gl/tables object model used by Arrow adapters,
table-backed rendering, and table-oriented compute helpers. The diagram is
adapted from the loaders.gl ArrowJS table-structure docs, but the focus here is
the GPU resource model: GPUTable, GPURecordBatch, GPUVector, and
GPUData.
GPUTable
Batch and Column Matrix
GPUData and Memory
Ownership Chain
Core Objects
| GPU object | Role | Important state |
|---|---|---|
GPUTable | Table-level owner and mutation surface. | schema, bufferLayout, gpuVectors, attributes, bindings, batches[] |
GPURecordBatch | One preserved GPU batch with batch-local vectors. | schema, numRows, bufferLayout, gpuVectors, attributes, bindings |
GPUVector | One logical typed column over one or more GPU data chunks. | name, format, length, valueLength, stride, byteStride, data[] |
GPUData | One GPU buffer plus typed row metadata. | buffer, format, length, valueLength, byteOffset, byteStride, rowByteLength, ownsBuffer |
GPUSchema | Plain selected-column schema metadata. | fields, metadata |
The table is intentionally batch-preserving. When an Arrow table has multiple
record batches, makeGPUTableFromArrowTable() creates one GPURecordBatch per source
batch. Table-level gpuVectors aggregate the batch-local chunks so code can
reason about one logical column, while batch-aware render and compute paths can
still bind one physical batch at a time.
Arrow JS Mapping
The API shape mirrors Arrow enough to make table adapters predictable, but the semantics are not identical.
| Arrow JS object | Similarity | GPU table difference |
|---|---|---|
Table | Like GPUTable, it is a schema plus row-aligned columns and batches. | GPUTable is a GPU resource owner. It has no row-object API and must be destroyed or detached intentionally. |
RecordBatch | Like GPURecordBatch, it preserves one row range across all selected columns. | GPURecordBatch also publishes model-ready attributes and WebGPU bindings. |
Vector | Like GPUVector, it is one logical column that can span chunks. | GPUVector may be backed by GPU buffers, dynamic appendable storage, interleaved rows, or aggregate chunk views. |
Data | Like GPUData, it is one typed chunk. | GPUData owns or borrows a GPU buffer, not a CPU typed-array view. It can only be read back through adapter-specific paths. |
Schema and Field | Both models retain selected-column metadata. | GPUSchema is a plain type with GPUField.format; Arrow-specific metadata is adapter-owned. |
The strongest similarity is structural: a table is a matrix of batches by
columns. In Arrow JS, each cell is a CPU Data chunk. In luma.gl, each cell is
a GPUData chunk that describes a GPU buffer.
The strongest difference is operational: Arrow JS optimizes for zero-copy CPU
views and immutable columnar data, while @luma.gl/tables optimizes for GPU
binding, explicit ownership, batch-local draw/dispatch, and deterministic
resource release.
GPU Memory Management
Arrow JS memory is mostly ordinary JavaScript memory. Tables, vectors, and data chunks can share typed-array buffers, and the JavaScript garbage collector eventually releases those buffers when no views remain.
GPU tables cannot rely on that model:
- GPU buffers are explicit resources and should be released with
destroy(). GPUDatais the storage-owning layer. It destroys itsBufferorDynamicBufferonly whenownsBufferis true.GPUVectorowns or borrows orderedGPUData[]chunks. It does not own raw buffers directly.GPURecordBatch.destroy()destroys the batch-local vectors it retains.GPUTable.destroy()destroys the preserved batches it still owns.- Borrowed external buffers are not destroyed unless ownership was explicitly requested or transferred.
detachVector()anddetachBatches()remove live GPU objects from a table and return ownership to the caller instead of destroying them.packBatches()allocates replacement packed buffers, swaps the table to those new batches, and destroys superseded owned batch storage.resetLastBatch()clears appendable logical rows while retaining GPU allocation capacity for reuse.
This ownership model is why GPUTable is a mutation and lifecycle surface, not
just a data view. The application decides when to preserve source batches, pack
them, detach live resources, append into dynamic storage, or destroy the current
GPU representation.
Single-Chunk Binding and Aggregate Vectors
Buffers are reached through GPUData, not directly through GPUVector.
Single-buffer consumers should explicitly require vector.data.length === 1 and
bind vector.data[0].buffer. Multi-batch aggregate vectors may contain many
GPUData chunks, so batch-aware code should use data[] or dispatch/draw one
GPURecordBatch at a time.
This distinction is deliberate. It lets high-level code treat a column as one logical vector while render and compute paths still bind the exact GPU buffer ranges that belong to the current batch.