Flatten Multi-Dimensional Thread Index in gpu.js
This article explains how to convert multi-dimensional thread
coordinates into a single 1D memory offset when writing GPU kernels
using gpu.js. In GPU programming, tasks often run across 2D or 3D
coordinate grids, but underlying memory buffers and arrays are typically
stored linearly in 1D. You will learn the mathematical formulas and
practical code patterns required to map 2D and 3D thread coordinates
(this.thread.x, this.thread.y, and
this.thread.z) to standard 1D linear array indices.
Understanding gpu.js Thread Indexing
When defining a kernel in gpu.js with multi-dimensional outputs, gpu.js provides built-in thread properties:
- 2D Kernel Output
[width, height]:this.thread.xranges from0towidth - 1this.thread.yranges from0toheight - 1
- 3D Kernel Output
[width, height, depth]:this.thread.xranges from0towidth - 1this.thread.yranges from0toheight - 1this.thread.zranges from0todepth - 1
Because JavaScript TypedArrays and flat buffers are sequential 1D structures, each multi-dimensional thread must compute its exact position along that single axis.
Flattening a 2D Thread Index
In a standard row-major layout, the X coordinate moves fastest (along the row), and the Y coordinate steps from row to row.
Mathematical Formula
\[\text{Index}_{1D} = x + (y \times \text{width})\]
Implementation Example
Pass the row width directly into the kernel function or declare it within kernel constants.
const { GPU } = require('gpu.js');
const gpu = new GPU();
const width = 512;
const height = 512;
const kernel2D = gpu.createKernel(function(flatInput, width) {
// Compute the 1D index from 2D coordinates
const index = this.thread.x + (this.thread.y * width);
// Access flat 1D memory
return flatInput[index] * 2;
}).setOutput([width, height]);Flattening a 3D Thread Index
For 3D data volumes, the index calculation extends to incorporate the depth slice (\(Z\)). Each step along the Z-axis spans an entire 2D slice of size \((\text{width} \times \text{height})\).
Mathematical Formula
\[\text{Index}_{1D} = x + (y \times \text{width}) + (z \times \text{width} \times \text{height})\]
Implementation Example
const { GPU } = require('gpu.js');
const gpu = new GPU();
const width = 64;
const height = 64;
const depth = 64;
const kernel3D = gpu.createKernel(function(flatInput, width, height) {
// Compute the 1D index from 3D coordinates
const sliceSize = width * height;
const index = this.thread.x + (this.thread.y * width) + (this.thread.z * sliceSize);
// Read or process 1D buffer data
return flatInput[index] + 1.0;
}).setOutput([width, height, depth]);Using Kernel Constants for Optimization
Instead of passing dimensions as dynamic arguments on every kernel
execution, define them inside constants. This allows the
GPU compiler to optimize index calculations with fixed numeric
values:
const width = 1024;
const height = 768;
const fastKernel = gpu.createKernel(function(flatData) {
const index = this.thread.x + (this.thread.y * this.constants.width);
return flatData[index];
}, {
constants: { width: width },
output: [width, height]
});Handling Multi-Channel Data (e.g., RGBA)
If the flat buffer contains multiple interleaved elements per thread (such as RGBA pixel values), multiply the final 1D index by the channel count:
const channels = 4; // R, G, B, A
const baseIndex = (this.thread.x + (this.thread.y * width)) * channels;
const r = flatPixels[baseIndex];
const g = flatPixels[baseIndex + 1];
const b = flatPixels[baseIndex + 2];
const a = flatPixels[baseIndex + 3];