Krita/redisplay

From KDE Community Wiki

Note:

15:40:57 < boud> slangkamp: we really should try to run the recomposition in a background thread. I've got some
                ideas for that, but I'm not sure I'll have time to implement them.
15:42:02 < CyrilleB> that would be a grand project :) (but not sure my brain can currently support the stress of
                    multithreading debugging) 
15:42:14 < slangkamp> I noticed that for reading single pixels the randomaccessor has an advantage over readBytes
15:42:15 < boud> CyrilleB: actually, it might be pretty easy:
15:42:40 < boud> a) make the setDirty() on the nodes defer to KisImage
15:42:51 < boud> b) make KisImage queue the jobs
 15:43:01 < boud> c) make the jobs call back setNodeInternal or something like that on the layers 
15:43:46 < boud> which then calls the TopDown recomposition strategy


  • Redisplay

Redisplay is the traditional term for the bit of code that paints the view on the document. It originates in text editors coding, notably The Craft of Text Editing, or, Emacs for the Modern World by Craig Finseth. This article explains how Krita recomputes its display.

The redisplay in Krita is defined by the KisProjectionUpdateStrategy pure abstract class. There are two implementations as of writing: TopDown and BottomUp. TopDown is not multi-threaded yet, BottomDown is, but crashes in QRegion.

TopDown

Every time a node is marked dirty, it updates its projection; after the projection is updated, it notifies its parent that it has updated a region. The parent then updates the same region and notifies its parent. The parent knows which layer caused the dirtiness and can use the stack projection cache in adjustment layers to start recomposition from there, if there's one under the dirtying node in the stack.


BottomUp

Krita caches the current state of the fully rendered document in a projection. The projection is a paintdevice that belongs to the root layer, which is a group layer that can contain other layers.

As soon as a layer is modified, for instance by painting on it, moving it in the image or filtering it, the affected area is setdirty. When (part of) a layer is set dirty, it notifies its parent in the layer hierarchy, which notifies its parent, and so on, all the way to the root layer.

The root layer then notifies the image which tells the KisProjection instance that an area of the image needs to be recomposited. KisProjection divides the dirty area into a number of blocks (the size of which you can change using the updaterectsize configuration parameter) and starts a number of threads (set with the maxprojectionthreads configuration parameter). The blocks are given to the running threads (using ThreadWeaver) and every block is updated using the KisMergeVisitor class.

As soon as a block is completely updated, the gui thread is notified and a signal is sent to the canvas. The canvas then updates a QImage representation of the image and notifies the canvas widget which paints the updated block on the widget -- if it's visible.

A possible optimization is to let KisProjection know which area of the image is currently visible and prioritize threads that handle blocks that overlap the visible area. KisProjection already has the api for this (setRegionOfInterest). We only need to make the canvases use this. Complication: if there is a thumbnail of the image visible, the region of interest will always be the whole image. (possible optimizations: pyramids, per-pixel recomposition (when using nearest-neighbouring for the thumbnails).

Krita has several layer types: paintdevice, adjustment, group and shape. Shape layers work differently from the other layers.

If a flake shape is added or modified, it directly asks for a canvasupdate. In the KisCanvas2::canvasUpdate() method, we check whether the current layer is a shape layer, and if so, we ask the kisshapelayer to repaint itself. The updated shapelayers sets the relevant area dirty, which means the projection gets recomposited and finally an update for the widget scheduled.