webpost.ing

How data moves between the shaders in webpaint.ing

By releases ·

webpaint.ing is the drawing app that sits beside this site. This note follows the data: which pictures and buffers exist, who writes and reads each, in what order and in which frame, and where data crosses between the graphics card and ordinary memory. Where something is only planned, it says so.

One rule that shapes everything

A shader cannot read the picture it is writing. And a picture drawn in one frame cannot be relied on by another picture drawn in that same frame, so it is read in a later one. Everything below follows from those two facts.

Only two things in the app are compute shaders: the flood behind Fill and Select by colour, and the export enlarger. The rest of the card's work is drawing shaders. The turn-taking pattern appears in both worlds: the flood uses it with compute passes, and the Watercolour brush uses it with drawing shaders, keeping its record of how much each pixel is covered in one of two pictures that each new stroke reads and writes in turn.

The flood's data, in order

Three kinds of picture matter for a fill, all on the card. The layer's own picture, drawn in an earlier frame. Two state pictures the size of the sheet, called A and B here, which take turns. And a tiny flags picture, one pixel for each of the sixteen sweeps. Each state pixel holds three numbers: whether it is in the region so far, the layer pixel's opacity, and whether it is close enough in colour to the pressed pixel. A few whole numbers (the pressed position, the tolerance, the sheet's size, the sweep's direction) are handed to each pass directly.

All of the passes are recorded in one batch, in one frame, with a barrier between passes so that each starts only when the one before it has finished.

The fill then stores its pixels as a list of runs, made from the copy in ordinary memory.

How a compute result reaches a drawing shader

A compute shader writes into a picture. A drawing shader samples pictures. Nothing says these must be different pictures, so the app wraps state picture A, the card's own object, as an ordinary texture that the drawing side can use. The fill preview is then one small drawing shader that paints the fill colour wherever that texture says "in the region". No pixels move: the same memory on the card is first written by compute and then sampled by drawing. It works from the frame after the batch was recorded (the later-frame rule again) and stays good until the next fill reuses A. It is drawn into the buffer a stroke in progress uses, and ends when the fill's real operation lands on the layer.

Where data crosses to ordinary memory

The boundary is crossed rarely because the card answers late and the copies are large: one picture of a 2048 by 2048 sheet is 32 MB. Today there are these crossings.

Everything else stays on the card: strokes, the live stroke buffer, and the small undo patches. A patch is the rectangle an operation changed, kept as it was before, cut from a second copy of the layer that runs one operation behind. Undoing puts a patch back with no trip through ordinary memory.

The export enlarger is a separate flow

Enlarging an export by 2, 4 or 8 is the second compute shader, and it shares no pictures with the flood. It belongs to the page, not to the drawing engine, and uses the browser's own WebGPU device, which the page asks the browser for once and keeps.

Planned, not built

Links