Repository navigation
Design two stage brush
This question has been asked more than once and the answer has never been written down, which is the actual defect. Recorded 2026-08-27 on the owner's observation that "apparently it does not stick". If you are about to explain the two-stage brush from first principles again, explain it from here instead, and add what you learn.
Most brushes already are a single pass. The second stage exists for exactly
five things, and every one of them is a property of the whole mark rather
than of a dab. MainViewModel.Painting.NeedsLivePostProcess is the list:
- a simulated medium (watercolour, gouache, oil, ink wash)
- a wet edge
- a paper texture
- granulation
- the footprint ceiling of a soft brush
A hard round brush, a pencil, an eraser, an inking brush: NeedsLivePostProcess
is false, no pass is scheduled, the dabs go into the scratch and that is what
you see. The render report says so in as many words — "live tip not applicable
— no post-process pass ran". There is no double draw on those brushes at
all, and a capture taken with one says nothing about B322 for that reason.
Because they are retroactive: a dab laid down now changes pixels that were already laid down.
| effect | what a later dab does to earlier pixels |
|---|---|
| footprint ceiling | The ceiling is a running maximum over the whole stroke. Capping per dab feeds a clamped value back into the next dab's accumulation, so the mark darkens where it crosses itself — the exact fault Q157 settled and B294 is the fast-path version of. |
| wet edge | The rim belongs to the mark, not to a dab. Ink you laid a second ago was the rim then and is the interior now, so its pixels have to change. |
| granulation, paper texture | The field is indexed from the document corner so two strokes crossing the same patch sit on the same tooth. That is a property of where the mark is, not of the order its dabs arrived. |
| simulated medium | The fluid lattice redistributes pigment across the mark and reads the layer beneath for re-wetting. Pigment that moves has to come from somewhere already painted. |
Compute any of these forward, dab by dab, and the live mark stops matching the
commit — which LiveMatchesCommittedTests holds to within 1 part in 255, and
which is the promise that a stroke looks while you draw it the way it will look
when you let go.
They do not have this class of effect in the brush. A Photoshop round brush accumulates forward: a new dab never changes an old pixel, so a single pass is not an optimisation, it is simply what the definition allows. The difference is not they chose one pass and we chose two — it is where the effect is defined. Define the wet edge per dab and you get a single pass for free, and you also get a rim around every dab instead of around the mark, granulation that tiles per dab, and a soft brush that darkens where it crosses itself.
That is a decision about what the marks look like, not about efficiency. It is available, it has never been asked for, and it would be a different painting application.
The second pass is not slow in itself — a healthy capture puts it at 12.9 ms reading 2.2% of the mark (B331, 15:05). What hurts is that it runs on a worker thread and the screen shows the mark as of the last pass that finished, so during a fast stroke the newest dabs are missing. That is B322, and it is a latency problem rather than a throughput one: the same brush with the pass keeping up shows the preview under the nib the whole way.
Three things follow, and they are the ones worth keeping straight:
- Making the pass cheaper helps — B313 made it read only the band that moved, B331 is the case where that stops engaging.
- Making the preview cover the gap helps — B322's live tip.
- Merging the two stages helps nothing, because the gap is not the second stamp, it is the fact that the effects cannot be finished until the mark is.
The work is done twice over a stroke's life: once for the live preview, once at the commit. That is inherent to previewing anything and is not what the pass costs mid-stroke — the commit's copy happens at pen-up, where nobody is waiting on a pointer event. It is worth stating because it is the thing "double draw" most naturally describes, and it is the half that has never been a problem.
The question to answer first is not can we merge the passes but is the footprint ceiling still a running maximum. Everything else follows from that answer, because it is the effect whose whole-mark definition is load-bearing for the ordinary soft brush rather than for a specialist medium. Q157 is where that was decided, and the decision has held.
- Getting started
- Documents and projects
- Tools and strokes
- Brushes
- Colour
- Layers, selections and guides
- The timeline
- Onion skin, references and the camera
- Symbols
- Saving and recovery
- Exporting to a game engine
- AI assistance
- Keyboard, performance and what is planned
- Bones and rigging
- Effects — fire, smoke and water
- A simplified 3D space to draw in: planes, parallax first, orbit second
- Brush performance beyond 4k: what the GPU can and cannot buy us
- The add-on boundary
- Making the AI trustworthy
- What an AI request actually costs
- Bones and spline deformers for 2D — design
- Brush tips — a generated library, not a live calculation
- Cloud and SaaS: what the architecture already fits, and what it does not
- Colour mixing — paint that picks up what it is laid on
- DESIGN: Effects
- DESIGN: Fluid effects — fire, smoke and water as drawn line and fill
- Fluid and viscous media — notes for the next brush-engine pass
- Compositing on the GPU
- Decomposing the two big files: which pieces come out, and with which tool
- One memory limit for pictures
- .NET 10: what is safe, what is unverified, and what it narrows
- One picture cache: the paused canvas on tiles
- The paint path: one mark, made once, under the pen
- Pen dynamics: tilt and speed
- A pencil is graphite on the paper's tooth
- Performance: the ratchet and the map
- Playback's first loop
- Pressure curves — a shape, not an exponent
- Importing Procreate brushes
- A project as a game's asset set
- One hierarchy: folders scope everything
- Puppet pins — design and plan
- The raster checkpoint: making a painting cheap to reopen
- Scoped resources — palettes, references and everything else shared
- Sensitive topics, and the three-track flow
- Design: pose-assisted frame-by-frame animation (rigid bones + AI gap filling)
- Stroke groups: why a merge should never turn strokes into pixels
- The project window: what you do between drawings
- Reading the subject before drawing on it
- The ceiling of a swept soft mark (B349)
- Q171 — a symbol owns a layer stack
- Pillar 3 — reusable assets, as live symbols
- What a template is
- Text, and where fonts sit
- The tiled compositor, and getting it off the CPU
- Design tokens
- Transform tool design (M14)
- Why a brush stamps twice, and when it does not
- Migrating to the designed UI
- Undo stores only what changed
- Vector tooling: making the lines you already drew editable
- The smudge/blur family, and aligning brush settings with Photoshop
Generated from the repository. Edit the files under docs/, not these pages.