# Chain Kernels, Skip the Round Trip

*Task 2 of 5 · [Pipelines & Textures](https://gpu.rocks/learn/pipelines-and-textures-9f4aeaa5.md) · GPU.js Learn*

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.

**Array layout in gpu.js**
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]`.

## Figures

- **kernel to kernel by texture — javascript never sees the middle**

## Goal

**Goal:** finish the `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.

## Requirements

- Keep `luminance` a pipeline kernel — its result never touches JavaScript
- Pass the returned texture directly into `contrast` (already wired up)
- In `contrast`, return `(l - 0.5) * 2 + 0.5` clamped with `Math.min` / `Math.max`

## Hint 1 — textures index like arrays

Inside `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.

## Hint 2 — the clamp

```js
return Math.min(Math.max((l - 0.5) * 2 + 0.5, 0), 1);
```

## Same idea elsewhere

Handing a texture from kernel to kernel is what CUDA does when consecutive
launches read and write the same device pointers, and what a WebGPU compute pass does
when one dispatch's storage buffer becomes the next dispatch's binding. On Metal it's
two encoders sharing an `MTLBuffer`. Nobody copies to the CPU in between.

## Starter code

```js
const gpu = new GPU({ mode });

// Pass 1 — luminance map, kept on the GPU as a texture.
const luminance = gpu.createKernel(function (photo) {
  const pixel = photo[this.thread.y][this.thread.x];
  return 0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2];
}, { output: [64, 64], pipeline: true });

// Pass 2 — contrast stretch. Final stage, so it returns plain numbers.
const contrast = gpu.createKernel(function (map) {
  const l = map[this.thread.y][this.thread.x];
  // TODO: stretch around the midpoint — (l - 0.5) * 2 + 0.5 —
  // clamped to 0–1 with Math.min / Math.max
  return l;
}, { output: [64, 64] });

const mapTexture = await luminance(photo); // a texture — still on the GPU
const result = await contrast(mapTexture); // and straight back in it goes
console.log('center cell:', result[32][32]);
```

---

Interactive version: https://gpu.rocks/learn/pipelines-and-textures-9f4aeaa5/2

[Previous task](https://gpu.rocks/learn/pipelines-and-textures-9f4aeaa5/1.md) · [Next task](https://gpu.rocks/learn/pipelines-and-textures-9f4aeaa5/3.md)
