Task 2 of 5
Here's the payoff of textures: a texture returned by one kernel can be passed straight into the next kernel as an argument. gpu.js binds the texture as the input — no download, no re-upload, no JavaScript in the middle. The data makes the whole trip without ever leaving the card.
In Data In, Data Out you chained two kernels through JavaScript: the
luminance map came
back as arrays, then went up again for the second pass. Same chain below — except this
time luminance is a pipeline kernel, and the second pass eats its texture
directly.
Image data comes in row-major: image[y][x] is the pixel in row y,
column x, and each pixel is an [r, g, b, a] array with channels from
0 to 1. Mind the inversion that catches everyone — sizes are given width-first
(output: [width, height]), but indexing runs row-first, so this thread's own
pixel is image[this.thread.y][this.thread.x]. Swap those two and you read the
transpose of your image. Three-dimensional data follows the same rule:
output: [w, h, d] is indexed [z][y][x].
contrast kernel — stretch each
luminance value around the midpoint with (l − 0.5) × 2 + 0.5, clamped
to 0–1 — and keep the texture handoff intact.luminance a pipeline kernel — its result never touches JavaScriptcontrast (already wired up)contrast, return (l - 0.5) * 2 + 0.5 clamped with Math.min / Math.maxInside contrast, the texture argument reads exactly like the
2D arrays you already know: map[this.thread.y][this.thread.x].
The kernel doesn't care where the data lives.
return Math.min(Math.max((l - 0.5) * 2 + 0.5, 0), 1);MTLBuffer. Nobody copies to the CPU in between.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.