Task 4 of 6
Deleting the seam is a splice on a CPU: for each row, remove one element and let the rest shuffle down. On a GPU there is no splice. A thread writes its own output cell and nothing else — it cannot push its neighbour along, which is the "no scatter" rule Thinking in Parallel makes a whole module of.
So turn the question round, which is what a gather always is. The output is one column
narrower than the input. The thread that owns output cell (x, y) asks: which
input pixel belongs here? Everything left of the seam has not moved. Everything
from the seam rightwards has slid one column left, so it comes from
x + 1. One if, no shuffling, and every row does its own thing at
the same time even though every row's seam is at a different column.
The output being narrower than the input is the reason for dynamicOutput —
carve.setOutput([w - 1, 72]) before each call, and the same kernel keeps
working all the way down.
carve so its output is plane
with the seam pixel removed from every row, and everything to its right pulled one column
left.seam[this.thread.y] — every row removes a different columnx comes from plane[y][x] when x < seam[y], and from plane[y][x + 1] otherwiseSay the seam is at column 3 in this row. Output cells 0, 1, 2 are input cells 0, 1, 2 — nothing moved. Output cell 3 is input cell 4: input cell 3 was the seam and is gone. Output cell 4 is input 5, and so on.
const x = this.thread.x;
const y = this.thread.y;
if (x < seam[y]) return plane[y][x];
return plane[y][x + 1];
Note the strict <. With <= the seam pixel survives and its
right-hand neighbour is deleted instead — the picture still narrows by one, so the
shapes all check out and the wrong pixel is gone.
thrust::remove_if and WebGPU compaction passes are the
same shape underneath.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.