# Feedback Loops: immutable Textures

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

Simulations don't run once — they **step**: the output of step
*n* is the input of step *n*+1. With pipelines that means feeding a
kernel its own texture back. Try it naively and gpu.js stops you cold — the kernel would
be reading the very storage it is writing to, and every backend refuses. WebGL puts it as
*"Source and destination … are the same. Use immutable = true"*; WebGPU says the
argument *"is this kernel's own output buffer"*. Same crime, and the same fix.

`immutable: true` is the fix: each call renders to a *fresh*
texture instead of recycling one, so last step's output is safe to read while this
step writes. (In long-running sims you'd call `texture.delete()` on old
steps to recycle their memory — at 128 cells here, we'll let them slide.)

Below is a 1D heat field: 128 cells, all cold except one hot spike. One diffusion
step moves each cell toward its neighbours. Twelve steps stay entirely on the GPU —
one upload at the start, one download at the end.

## Figures

- **immutable: true — a fresh texture per step makes feedback legal**

## Goal

**Goal:** make the feedback loop legal — the `step` kernel
needs `immutable: true` — and run 12 diffusion steps without the heat ever
visiting JavaScript.

## Requirements

- Add `immutable: true` to the `step` kernel (keep `pipeline: true`)
- Keep the loop feeding `step`'s output straight back in — no readbacks inside it
- After 12 steps, download once and log the peak at cell 64

## Hint 1 — read the error message

Run the starter as-is. The error names the crime: this kernel's input is its
own output storage. On WebGL it names the sentence too — `immutable = true`;
on WebGPU it only tells you the buffer is the kernel's own, and
`immutable: true` is still the fix. Either way, the second step of the loop
is where it fires: the first step reads `upload`'s texture, which is somebody
else's.

## Hint 2 — why upload() exists

The tiny `upload` kernel copies the seed array into a texture
once, so `step` always sees texture inputs from its very first call.
Keeping argument types stable means the kernel compiles exactly once.

## Hint 3 — the one-word diff

In `step`'s settings:

```js
{ output: [128], pipeline: true, immutable: true }
```

The loop is already correct.

## Same idea elsewhere

Every GPU API solves read-write hazards the same way gpu.js just made you do:
ping-pong buffering. WebGPU compute passes swap two storage buffers each dispatch,
CUDA stencil codes swap `in`/`out` device pointers, Metal
simulations flip between two textures. `immutable: true` is ping-ponging
with the bookkeeping done for you.

## Starter code

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

// Upload pass — copies the seed array into a texture, once.
const upload = gpu.createKernel(function (seed) {
  return seed[this.thread.x];
}, { output: [128], pipeline: true });

// One diffusion step: each cell relaxes toward its neighbours.
// Edge cells hold their value.
const step = gpu.createKernel(function (heat) {
  const x = this.thread.x;
  if (x === 0 || x === 127) {
    return heat[x];
  }
  return 0.25 * heat[x - 1] + 0.5 * heat[x] + 0.25 * heat[x + 1];
}, {
  output: [128],
  pipeline: true,
  // TODO: this kernel reads its own previous output — run it and
  // let the error message tell you the missing setting.
});

let state = await upload(field);
for (let i = 0; i < 12; i++) {
  state = await step(state); // output straight back in — a feedback loop
}

const heat = state.toArray ? await state.toArray() : state;
console.log('peak after 12 steps:', heat[64]);
```

---

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

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