Can GPU.js Kernels Return Arrays per Thread?

In GPU.js, a kernel function cannot return an arbitrary, dynamically-sized array of values from an individual thread. By default, each thread must return a single scalar number. However, GPU.js does support returning small, fixed-size vectors of up to four numerical values (representing RGBA-style channels), and multiple outputs can be achieved using sub-kernels or by expanding the output dimensions.

The Underlying WebGL Constraint

GPU.js compiles JavaScript functions into WebGL fragment shaders. In the GPU's execution model, each thread maps directly to an execution unit writing to a specific memory address (historically a pixel in a texture target). Because GPU fragment shaders must output to fixed memory layouts defined before execution, a thread cannot dynamically allocate memory or return a variable-length JavaScript array.

Returning Small Vectors (2 to 4 Elements)

While arbitrary arrays are unsupported, GPU.js allows returning fixed-size vectors containing two, three, or four numbers per thread. This is typically used for graphical outputs or vector math:

const kernel = gpu.createKernel(function() {
    return [1.0, 2.0, 3.0, 4.0];
}).setOutput([100]);

This output utilizes the standard four-channel vector (vec4) supported natively by graphics hardware.

Workarounds for Multi-Value Outputs

If an algorithm requires generating multiple distinct values per logical entity, two primary architectural patterns are used:

1. Expanding the Kernel Dimensions

Instead of having one thread return an array of \(M\) values, the kernel's output dimensions are increased to accommodate the extra data. For example, if \(N\) items each require \(M\) values, set the output to [M, N]:

const kernel = gpu.createKernel(function(data) {
    const itemIndex = this.thread.y;
    const valueIndex = this.thread.x;
    return computeSpecificValue(itemIndex, valueIndex);
}).setOutput([M, N]);

2. Sub-Kernels (createKernelMap)

When each thread needs to compute a fixed set of different named values simultaneously, sub-kernels can be used via combineKernels or createKernelMap. This leverages Multiple Render Targets (MRT) on supported hardware to write to multiple output buffers in a single pass:

const kernel = gpu.createKernelMap({
    firstResult: function() { return 1.0; },
    secondResult: function() { return 2.0; }
}, function() {
    return subKernelFunction();
}).setOutput([100]);

A standard GPU.js kernel must return either a scalar float or a fixed-length vector of up to four components; any requirement for larger or variable data per thread must be handled by restructuring the dimension space or splitting outputs across multiple targets.