Arrow Table Conversion
Universal Arrow-to-GPU Planning
planArrowGPUConversion() and convertArrowToGPUVector() are the common
schema-to-storage boundary for Arrow-backed GPU inputs. The planner is side-effect
free. It reports the selected semantic family, physical strategy, execution
backend, source and target formats, preserved chunk count, estimated bytes and
passes, ownership, reasons, and lossy-conversion warnings before any GPU resource
is allocated.
import {convertArrowToGPUVector, planArrowGPUConversion} from '@luma.gl/arrow';
const plan = planArrowGPUConversion(device, sourceVector, {
semantic: 'color',
format: 'unorm8x4'
});
const prepared = await convertArrowToGPUVector(device, sourceVector, {
name: 'instanceColors',
semantic: 'color',
format: 'unorm8x4'
});
// prepared.vector is ready for a GPU table or model.
prepared.destroy();
The planner selects among:
- zero-copy borrowing when an input
GPUVectoralready has the exact format; - direct Arrow upload when the physical bytes already match;
- reinterpret upload when only GPU normalization metadata changes, such as
Uint8x4tounorm8x4; - chunk-preserving numeric conversion for bit-packed Boolean, 64-bit numeric, normalized, and format-changing scalar/list inputs;
- semantic color, temporal, and matrix conversion using their specialized conversion implementations.
Use policy: 'require-zero-copy' or policy: 'require-direct' to turn a
performance expectation into a checked invariant. prefer-gpu and prefer-cpu
control converter families that offer both paths. Unsupported types, incompatible
shapes, and nullable matrix or temporal inputs are rejected during planning.
Caller-owned GPU inputs are never destroyed by a borrowed prepared result.
On WebGPU, directly uploadable numeric casts run through the reusable GPGPU
castData() operation instead of converting every value on the CPU. Float64 is
handled specially: Arrow's binary64 bytes upload unchanged and Xiaoji's
fround() operation splits each value into adjacent high and residual-low
Float32 lanes in one compute pass. prepared.vector is a strided view of the
high lanes and prepared.residualVector is a zero-copy view of the low lanes.
Both views preserve Arrow chunks and null/readback metadata; destroying the
prepared result releases their shared allocation. Non-WebGPU devices, or
policy: 'prefer-cpu', use the equivalent chunk-preserving CPU fallback.
planArrowTableGPUConversion() and convertArrowTableToGPUTable() apply the same
decisions to an ordered output schema. Each output column names an Arrow source
path and its semantic/format contract. The result preserves Arrow record-batch
boundaries, source row offsets, field metadata, null metadata, and one owned GPU
allocation per output chunk; it never implicitly packs streaming batches.
convertArrowRecordBatchesToGPURecordBatches() accepts sync or async record-batch
streams and yields independently owned GPU batches. It preserves batch/row source
identity and carries the first resolved temporal origin into later batches, so a
long timestamp stream does not silently change coordinate systems at each batch.
const prepared = await convertArrowTableToGPUTable(device, arrowTable, {
columns: [
{name: 'instancePositions', source: 'geometry.position'},
{
name: 'instanceColors',
source: 'style.color',
semantic: 'color',
format: 'unorm8x4'
},
{name: 'eventTime', source: 'event.timestamp', semantic: 'temporal'}
]
});
The direct makeGPUVectorFromArrow() and makeGPUTableFromArrowTable() factories
remain useful when the source layout is already known to be GPU-compatible. Use
the planner boundary when schemas vary, diagnostics matter, or a semantic
conversion may be required.
Arrow Vector Factories
Fixed-Size-List Storage Columns
makeGPUVectorFromArrow() and makeGPUTableFromArrowTable() upload numeric
Arrow FixedSizeList columns wider than four elements as ordinary
GPUVector<'fixed-size-list<format,size>'> values. The vector remains aligned
with its containing table: length counts source rows, valueLength counts
flattened elements, and original Arrow record batches remain separate GPU
record batches.
When calling makeGPUVectorFromArrow() directly, provide
{format: 'fixed-size-list<float32,768>'} to retain that exact fixed-list
generic in TypeScript; Arrow's runtime FixedSizeList type cannot otherwise
encode the numeric width in its compile-time type.
import {makeGPUTableFromArrowTable} from '@luma.gl/arrow';
const table = makeGPUTableFromArrowTable(device, arrowTable, {
shaderLayout: {
attributes: [],
bindings: [
{name: 'embedding', type: 'read-only-storage', group: 0, location: 0},
{name: 'sourceIds', type: 'read-only-storage', group: 0, location: 1}
]
},
validityColumns: {embedding: 'embeddingValidity'}
});
The optional validityColumns mapping materializes a named, table-owned Uint32
column that combines nullable parent rows and nullable child values. Stable row
identifiers remain ordinary non-null sibling columns. Use
fixedSizeListColumns: ['embedding'] when a short one-to-four-element column
should intentionally retain fixed-size-list storage semantics instead of its
default vertex format. For physically padded rows, specify
validityColumns: {embedding: {name: 'embeddingValidity', dimensions: 3}} to
ignore nullable padding after the three meaningful coordinates.
List Columns
Arrow vector factories also support variable-length Arrow list columns whose nested elements contain one to four numeric components. This covers scalar lists plus tuple-style data such as XY, XYZ, and XYZM coordinates, while copying only compact list-offset metadata needed for readback instead of retaining the uploaded Arrow value arrays.
Path Columns
Path Vectors are list of coordinates that are interpreted as a open paths or closed paths. Closed paths are often used to represent polygon outlines.
closeArrowPaths()
closeArrowPaths() normalizes Float32 path rows before rendering or expansion:
closed paths whose first and last vertices differ by more than an epsilon receive
an appended copy of their first vertex, while already-closed, open, empty, and
single-point paths remain unchanged.
The Float32 rows can be absolute path coordinates or origin-relative deltas produced from Float64 path conversion. WebGPU uses compute classification and scatter passes; other devices use equivalent CPU fallback semantics.
ArrowPathRenderer.convertToGPUVectors() and convertArrowPathToGPUVectors() are the
path-attribute conversion boundary. Float32 XY, XYZ, and XYZM path rows upload
unchanged unless closure is requested. Float64 path rows convert on the CPU into
stable per-row Float32 deltas from the first point; CPU Float64 origins are kept
separately, and updateViewOrigins() refreshes the per-row view-origin buffers
when the view or model matrix changes.
ArrowPathRenderer.convertToGPUVectors(..., {model: 'storage'}) and
convertArrowPathStorageToGPUVectors() provide the storage-only WebGPU boundary:
Float64 rows upload temporarily, convert once into Float32 deltas with
sub_fp64u32_to_f32, then release the transient Float64 GPU payload before
returning storage path props.
PathAttributeModel consumes prepared path props and expands one logical path row
into packed per-segment render records. PathStorageModel is the WebGPU
storage-backed counterpart. It expands nested path rows into compact indexed
segment records through compute using the GPU-resident prepared path values plus
copied list-offset metadata. Render shaders fetch coordinates from path-value
storage and add per-row view origins, while per-path color and width rows remain
storage-buffer bindings instead of being duplicated per generated segment.
Default storage records use three u32 words per segment
(segmentStartPointIndex, segmentFlags, and globalRowIndex) plus one
persistent vec4<u32> path range per source row. Shaders that request legacy
end, previous, or next segment index attributes automatically keep the older
six-word record layout.
Reusable storage state can be built separately with createArrowPathStorageState.
PathTripsStorageModel layers Trips-style temporal filtering over that
storage path surface by reading prepared List<Float32> timestamps aligned with
path vertices.
Color Columns
canConvertColors(), convertColors(), convertArrowColors(), and
convertArrowColorsToArrow() normalize
fixed-width RGB and RGBA rows into the canonical
GPUVector<'unorm8x4'> representation used by renderers. Supported Arrow
sources are FixedSizeList<Uint8 | Float16 | Float32, 3 | 4>; equivalent
fixed-width GPU vectors can enter through convertColors() without an Arrow
round trip.
convertArrowColorsToArrow() performs the same lowering and returns canonical
Arrow Uint8 RGBA rows for CPU-side conversion pipelines.
Uint8 channels are preserved, while Float16 and Float32 channels are clamped to
[0, 1], scaled to [0, 255], and rounded. Three-channel inputs receive an
opaque alpha channel. This is a normalized display/style-color conversion, not
a lossless scene-linear or HDR conversion for values outside [0, 1].
Conversion preserves source chunk boundaries and nullable row validity. The returned vector owns distinct output buffers; caller-owned input buffers remain borrowed. WebGPU performs the conversion with compute, while WebGL devices use the equivalent CPU/readback fallback.
Arrow path, polygon, and text layers apply this normalization automatically to
fixed-width per-row colors. Specialized nested per-vertex or per-character
vertex-list<unorm8x4> inputs already satisfy the renderer contract and pass
through without fixed-width normalization.
Matrix Columns
Matrix Arrow columns can carry explicit vis.gl matrix shape, order, and physical
layout metadata through makeArrowMatrixVector() and the shape-specific matrix
builders.
convertArrowMatrixToGPUVector() accepts metadata-tagged
FixedSizeList<Float32> or FixedSizeList<Float64> rows, then returns one
canonical Float32 column-major wgsl-storage GPU vector. Canonical Float32 GPU
vectors pass through, raw Arrow Float64 rows truncate to Float32 during CPU
conversion, and non-canonical GPU-resident rows normalize with WebGPU compute.
Arrow source chunks remain separate prepared GPU chunks.
The same prepared column can lower into matrix attribute columns or bind as
array<matCxR<f32>>.
Time Columns
Temporal Arrow columns can be converted with convertArrowTemporalToGPUVector() or
convertArrowTemporalToGPUVectors(). Supported v1 leaves are Date, Time,
Timestamp, and Duration, including List<...> leaves for Trips-style
streams. Conversion emits relative Float32 values in the original Arrow unit,
persists the selected origin metadata on the prepared field, chooses the first
valid absolute value when no origin is stored, and uses origin 0 for
durations. Raw Arrow inputs use CPU fallback where WebGPU is unavailable;
GPU-resident temporal inputs use WebGPU compute.
Global Grid Columns
DGGS helpers keep compact global grid keys on the GPU. convertDggsCellIdsToGPUKeys()
parses UTF-8 geohash, quadkey, S2, A5, or H3 cell IDs into Uint64 GPU keys on
WebGPU, while convertDggsCellKeysToGPUPaths() expands prepared Uint64 keys into
closed Float32 cell boundary paths for path-style rendering. Its optional
coordinateFormat: 'fp64-split' mode emits
[longitudeHigh, latitudeHigh, longitudeLow, latitudeLow] Float32 components,
but DGGS boundary math still runs in Float32 today, so the low components are
zero until true higher-precision decode math is added. The dggs shader module
provides the matching WGSL Uint64 word and DGGS boundary helpers.
Columnar Geometries
ArrowTableGeometry and makeGPUGeometryFromArrow() can convert
loaders.gl-compatible Mesh Arrow tables through the local structural
ArrowMeshTable type. Mesh Arrow attributes are uploaded as table-backed GPU
geometry, defaulting to one interleaved vertex buffer plus a separate optional
index buffer.
For lower-level table pipelines, import GPUTableBufferPlanner from
@luma.gl/experimental/gpu-tables to produce deterministic GPU allocation plans from column
descriptors while respecting device vertex and storage buffer limits.
GPU Input Programming Guidelines
Keep the final GPU contract separate from Arrow conversion policy:
GPUInputSchema
-> validates final GPUVector props
ArrowInputSchema
-> resolves Arrow columns
-> converts Arrow vectors
-> generates internal vectors
-> validates the result against GPUInputSchema
GPUInputSchema belongs to @luma.gl/experimental/gpu-tables. It declares required and optional
prepared vector names, accepted GPUVector.format values, semantic kinds, and
which inputs are internal. It deliberately does not mention Arrow DataType
objects, source column paths, temporal origins, tessellation, text dictionaries,
or fallback-vector generation.
ArrowInputSchema belongs to @luma.gl/arrow. It combines an Arrow-free
gpuInputSchema with adapter-owned resolveSourceVectors,
convertToGPUVectors, and getGPUInputVectors functions. The
prepareArrowInput() helper runs that pipeline and calls
validateGPUInputVectors() before returning the prepared result.
Use a simple Arrow input schema for direct one-column uploads. Keep model-family
adapters for cross-column conversions such as path normalization, polygon
tessellation, temporal conversion, and text glyph preparation. Do not make
@luma.gl/gpgpu or @luma.gl/experimental depend on Apache Arrow to describe those policies.
BufferLayouts
The module can derive GPU BufferLayout entries from Arrow schemas and create primitive GPU data
and experimental table objects from compatible Arrow columns through
makeGPUDataFromArrowData(), makeGPUVectorFromArrow(), makeGPURecordBatchFromArrowRecordBatch(), and
makeGPUTableFromArrowTable(). Primitive storage is documented in the
GPGPU data reference, while the higher-level object model is
documented in Experimental GPU Tables and
GPU Table Structure.
Arrow upload helpers now produce format-first GPU objects:
- fixed Arrow columns become
GPUVector.formatvalues such asfloat32x3orunorm8x4; - list-of-vertex Arrow columns become
vertex-list<...>formats; GPUSchemarecords the selected GPU fields;- Arrow
DataTypevalues remain adapter metadata for migration and readback.
Streaming helpers preserve batch and buffer boundaries by creating one immutable
GPURecordBatch per incoming Arrow record batch. Each Arrow data chunk is
uploaded into a new GPUData with its own buffer, and GPUTable aggregate
vectors expose the logical column through GPUVector.data[].
instead of merging previous batches into one larger buffer.