Accessing Neighbor Thread Values in GPU.js
In GPU.js, you cannot directly access the output values being calculated by neighboring threads within a single execution step. Because GPU.js relies on WebGL fragment shaders under the hood, each thread executes in parallel isolation to calculate the value of its assigned index. However, you can read neighboring indices from input datasets passed into the kernel, or achieve step-by-step neighbor dependency using a multi-pass pipeline.
Why Direct Neighbor Thread Access Is Impossible
GPU.js compiles your JavaScript functions into GLSL fragment shaders. In the WebGL rendering pipeline, each shader instance (or "thread") computes the color or data for an individual output element independently.
To maintain high throughput across thousands of cores, GPUs
intentionally isolate these executions. There is no shared memory or
inter-thread communication mechanism available in GPU.js that allows
Thread A to pause and read the runtime output generated by Thread B
during the same cycle. A thread can only write to its designated
coordinate: this.thread.x, this.thread.y, and
this.thread.z.
Reading Neighboring Values from Input Data
While you cannot read what a neighboring thread is actively computing, you can read neighboring values from an input array or texture passed into the kernel.
Inside the kernel function, you can offset indices to query adjacent data from the previous state:
const kernel = gpu.createKernel(function(grid) {
const x = this.thread.x;
const y = this.thread.y;
// Access static or pre-existing values of neighbors
const left = grid[y][x - 1];
const right = grid[y][x + 1];
const top = grid[y + 1][x];
const bottom = grid[y - 1][x];
return (left + right + top + bottom) / 4;
}).setOutput([512, 512]);When reading neighbors from input arrays, ensure you implement boundary checks to prevent out-of-bounds errors along the edges of the grid.
Solving Dependencies with Multi-Pass Kernels
If your algorithm requires data to propagate across neighbors dynamically—such as in cellular automata (like Conway's Game of Life), image convolution, or physical simulations—you must break the computation into multiple execution steps:
- Step 1: Run a kernel where threads read from the initial input grid, evaluate neighbor conditions, and output the updated state into a new texture.
- Step 2: Pass the resulting texture from Step 1 back into the kernel as the new input for the subsequent generation.
By chaining kernel runs in a loop or using
gpu.combineKernels(), you allow threads in iteration
N to read the neighbor calculations finalized by iteration
N - 1.