GPU Commands
Most luma.gl applications spend most of their time doing one of two things:
- calling immediate convenience methods such as
Buffer.write()orTexture.writeData() - recording explicit GPU work through
CommandEncoder,RenderPass, andComputePass
Both styles are valid. The right choice depends on whether you need simple data transfer, explicit ordering, or predictable batching.
Two command styles in luma.gl
Immediate convenience methods
These methods perform a complete operation from the call site:
Buffer.write()Buffer.mapAndWriteAsync()Texture.copyExternalImage()Texture.writeData()Texture.readBuffer()Texture.readDataAsync()(deprecated convenience wrapper)Texture.writeBuffer()
These APIs are convenient because they do not require you to create a CommandEncoder, finish it, or submit it yourself.
They are best when:
- your source data is on the CPU
- you are doing a one-off upload or readback
- you do not need to tightly coordinate the copy with render or compute work in the same command stream
Recorded command streams
Recorded command streams are built explicitly:
const commandEncoder = device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass({
framebuffer,
clearColor: [0, 0, 0, 1]
});
model.draw(renderPass);
renderPass.end();
const commandBuffer = commandEncoder.finish();
device.submit(commandBuffer);
This style is best when:
- the source and destination are already on the GPU
- multiple copies, passes, and query operations must happen in one ordered stream
- you want to control exactly when work is submitted
The main recorded-copy methods are:
copyBufferToBuffer()copyBufferToTexture()copyTextureToBuffer()copyTextureToTexture()resolveQuerySet()writeTimestamp()
WebGL vs WebGPU
The CommandEncoder API is portable, but the backend behavior is not identical.
WebGPU
On WebGPU, command encoding is truly deferred:
- commands record onto a specific
CommandEncoder finish()seals that encoder into oneCommandBuffersubmit()sends that finished work to the queue
This gives WebGPU applications a real command-stream abstraction. Ordering, batching, and pass ownership all follow the encoder you recorded into.
WebGL
On WebGL, luma.gl provides a best-effort compatibility layer.
- copy commands are recorded and replayed when you submit the command buffer
- render passes are still effectively immediate-mode
- state changes and clears happen as the pass is used, not as a native deferred GPU command stream
So the portable rule is:
- use
CommandEncoderwhen you want a cross-backend way to express copy work and pass structure - do not assume WebGL has the same deferred execution model as WebGPU
For rendering, WebGL is still conceptually immediate even though luma.gl exposes the same surface API.
Immediate APIs on WebGPU
Some luma.gl methods intentionally map to WebGPU's queue-style immediate operations rather than command-buffer recording.
Queue-style upload paths
Buffer.write()is the luma.gl equivalent of queue-driven buffer uploadTexture.writeData()closely matchesGPUQueue.writeTexture()Texture.copyExternalImage()closely matchesGPUQueue.copyExternalImageToTexture()
Use these when your source data starts on the CPU or in browser image objects.
Command-encoder copy paths
Texture.writeBuffer()Texture.readBuffer()CommandEncoder.copyBufferToTexture()CommandEncoder.copyTextureToBuffer()
These use GPU buffer-copy semantics instead of queue-style CPU upload semantics.
This matters on WebGPU because buffer-copy layout rules are stricter:
- row pitch must follow buffer-copy alignment rules
- in practice
bytesPerRowusually needs to be a multiple of256
If your data is tightly packed on the CPU, Texture.writeData() is usually the better upload path.
Choosing an approach
Recommended defaults
Use Buffer.write() when:
- you are updating a buffer from CPU memory
- you do not need to batch that upload with other GPU commands
Use Texture.writeData() when:
- you are uploading texels from a typed array
- the source is tightly packed CPU memory
- you want to avoid WebGPU buffer-copy row-alignment requirements
Use Texture.copyExternalImage() when:
- the source is an
ImageBitmap,ImageData, canvas, image, or video
Use CommandEncoder copy methods when:
- the source already lives in a GPU
BufferorTexture - multiple copies and passes must execute in a specific order
- you want one explicit submission boundary for a group of operations
Use Texture.readBuffer() when:
- you want a simple standalone readback helper with an explicit destination buffer
- you do not need that readback to be manually integrated into a larger command stream
- the source texture was created with
Texture.COPY_SRC
Use engine DynamicTexture.readAsync() when:
- you want a convenience readback helper that allocates a temporary buffer and returns CPU bytes directly
Performance guidance
Prefer immediate resource methods for CPU-to-GPU uploads
If your source data originates on the CPU, the immediate methods are usually the simplest and best first choice:
Buffer.write()Texture.writeData()Texture.copyExternalImage()
These avoid extra staging logic in application code and map well to the native fast paths of each backend.
Prefer command encoding for GPU-to-GPU work
If both ends of the operation already live on the GPU, command encoding is typically the better fit:
- buffer-to-buffer copies
- texture-to-buffer staging
- buffer-to-texture staging from reusable upload buffers
- texture-to-texture copies
- render + compute + copy pipelines in one ordered sequence
This is especially true on WebGPU, where the encoder is the real unit of command recording.
Avoid unnecessary buffer staging for texture uploads
On WebGPU, buffer-copy texture uploads are not the same as writeTexture() style uploads.
Use Texture.writeData() instead of writeBuffer() or copyBufferToTexture() when:
- the source data is a CPU typed array
- rows are tightly packed
- you do not already have the data in a reusable GPU buffer
Use writeBuffer() or copyBufferToTexture() when:
- the source is already in a GPU buffer
- you want to reuse a staging/upload buffer across many updates
- the extra layout control is intentional
WebGL recommendations
On WebGL, prefer the simpler API unless you specifically need the portable command surface:
- use resource methods for straightforward uploads and readbacks
- use
CommandEncoderfor copy operations and portable code structure - do not treat WebGL command encoding as a promise of native deferred execution