Task 5 of 5

The Payoff: Photo to Screen, Zero Readbacks

Time to cash in the whole module. In the finale of Data In, Data Out, a two-kernel chain hauled the luminance map down to JavaScript and back up again — two transfers it didn't need. This pipeline does more work with fewer transfers: photo → luminance3×3 blurpainted canvas, and after the photo is uploaded, nothing comes back. The graphical kernel eats the blur texture and writes pixels; readbacks: zero.

The missing piece is the blur. Each cell averages its 3×3 neighbourhood — two little loops over dy/dx, indices clamped to 0…63 so the edges don't read out of bounds. When it works, hit Benchmark and watch what keeping data on the card does to the gap.

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].

Goal: implement the 3×3 box blur so the full three-pass pipeline — two texture passes and a graphical finale — runs with zero readbacks.

Requirements

Hint 1 — the neighbourhood loops

Two nested loops with fixed bounds are fine in a kernel: for (let dy = -1; dy <= 1; dy++) and the same for dx. Accumulate into a sum, return sum / 9.

Hint 2 — clamping the edges

Compute let yy = this.thread.y + dy; then push it back in range:

if (yy < 0) yy = 0;
if (yy > 63) yy = 63;

Same for xx. Corner cells just count some neighbours twice.

Hint 3 — the whole body

let sum = 0; then inside the loops sum += map[yy][xx]; and finally return sum / 9; — the clamped yy/xx from hint 2 do the rest.

Same idea elsewhere

You just built what engine programmers call a render graph: named passes, explicit dependencies, all resources resident on the GPU — the architecture behind Frostbite's frame graph, CUDA Graphs' pre-recorded launch chains, and a Metal command buffer full of encoder passes. Real engines are this task with more boxes.

All tasks in Pipelines & Textures

  1. Flip On the Pipeline
  2. Chain Kernels, Skip the Round Trip
  3. toArray() Is a Tollbooth
  4. Feedback Loops: immutable Textures
  5. The Payoff: Photo to Screen, Zero Readbacks

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.