# Life on the Edge

*Task 4 of 6 · [Thinking in Parallel](https://gpu.rocks/learn/thinking-in-parallel-c3876efb.md) · GPU.js Learn*

The moment a gather reads a *neighbor*, the edges bite. Take the forward
difference — `out[i] = signal[i+1] − signal[i]`, "how much does the signal jump
here?". Thread 63 asks for `signal[64]`, which does not exist.

What comes back is **whatever the backend decides** — and the three this
course runs on decide three different things. Run the starter, then switch
**Mode** and run it again: the CPU backend gives you `NaN`, WebGL
gives you a garbage texel from elsewhere in the texture, and WebGPU quietly
*clamps* the index and gives you `signal[63]` — a perfectly plausible
number that you never asked for. That last one is the dangerous one: nothing looks wrong,
so nothing gets fixed.

Reading off the end isn't *wrong*, it is **undefined**: every
platform is free to answer differently, and they do. So never rely on the read. Decide what
the edge *means*, and write that down. The usual convention — the one every image
filter uses — is **replicate**: the last cell repeats the last real
difference, `signal[63] − signal[62]`. You get it by clamping the index you
start *from*, so the pair you read is always a pair that exists.

## Figures

- **signal[64] doesn't exist — clamp before you knock**

## Goal

**Goal:** compute the forward difference with a clamped *base*
index, so the last cell repeats the last real difference —
`signal[63] − signal[62]` — instead of depending on what this backend happens to
do with a read past the end.

## Requirements

- Clamp the index you read *from*: `Math.min(this.thread.x, this.constants.n - 2)`
- Interior cells still return `signal[i+1] − signal[i]`
- The last cell repeats the one before it — never a value read past the end

## Hint 1 — which read is out of range

Only thread 63 misbehaves: `this.thread.x + 1` is 64, one past the
end. Every other thread's pair is fine, so the fix has to leave 0 … 62 exactly as they
are and hand 63 a pair that exists.

Careful which index you pin. Clamping the *neighbor* —
`Math.min(this.thread.x + 1, n - 1)` — makes thread 63 read itself twice and
return 0, which is the answer WebGPU was already inventing for you. Clamp the
*base* instead.

## Hint 2 — the clamped base

```js
const i = Math.min(this.thread.x, this.constants.n - 2);
return signal[i + 1] - signal[i];
```

For thread 63, `i` is 62, so the answer is
`signal[63] - signal[62]` — the last real jump, repeated. Cells 62 and 63
come back holding the same number, which is exactly what "replicate" means.

## Same idea elsewhere

Edge conventions are shipped as sampler settings on real hardware —
`clamp-to-edge` address mode in WebGPU and Metal,
`cudaAddressModeClamp` on CUDA texture objects, with `repeat` and
`mirror` sitting beside them as the alternatives. Reading a raw buffer instead
of a texture? Then you pick the convention by hand, exactly like here — and you *do*
pick one, because an unguarded read past the end is undefined everywhere: CUDA will happily
hand you another allocation's memory, and WGSL leaves out-of-range buffer access loose
enough that two implementations can disagree. The three answers you just got from three
backends are that fact, one level up.

## Starter code

```js
// Forward difference: out[i] = signal[i + 1] - signal[i].
const gpu = new GPU({ mode });

const delta = gpu.createKernel(function (signal) {
  // TODO: thread 63 reads signal[64] — one past the end, and what comes
  // back is undefined: NaN on cpu, a garbage texel on webgl, a silently
  // clamped signal[63] on webgpu. Clamp the index you read FROM so the
  // last cell repeats the last real difference instead.
  return signal[this.thread.x + 1] - signal[this.thread.x];
}, {
  output: [64],
  constants: { n: 64 },
});

const result = await delta(signal);
console.log('last two deltas (they should match):', result[62], result[63]);
```

---

Interactive version: https://gpu.rocks/learn/thinking-in-parallel-c3876efb/4

[Previous task](https://gpu.rocks/learn/thinking-in-parallel-c3876efb/3.md) · [Next task](https://gpu.rocks/learn/thinking-in-parallel-c3876efb/5.md)
