I still am feeling elated that the Commit-Digest has returned - and I feel quite chuffed that Danny has asked me to write a little feature article for the Commit-Digest. Now, I already do regular updates of development news on krita.org so I would like to present a technical overview of what has gone into what will become Krita 2.3 - the great core bits.
To me the most fun bit of Krita is seeing what actual artists can do with it. But what's even more important in some respects is that Krita is now a huge and relatively well-documented repository of technical information on how to create a 2D graphics application. And in some respects, Krita really is at the forefront of painting application technology.
For instance, Krita users could switch between a software canvas and a hardware-accelerated OpenGL canvas before Photoshop had that option. Krita is quite pervasively multithreaded. While we are still not completely satisfied with the performance, painting on a 6000x6000 pixel image with a brush with a 500 pixel diameter will give all your cores a sharp prod in their behinds, and make them sweat a bit. We've always had more colorspaces than any other application, but Krita is going new places there as well, supporting Little CMS 2 already and using a wickedly cool technology called OpenGTL as well to create new Color Model implementations with very little effort. And then, of course, since Krita is about painting, not about bragging, we've got an architecture that makes it easy to experiment with new kinds of brush engines.
But I have to choose, and I've written before about the brush engines, and besides, if you can read Slovak, there's Lukas' thesis that will explain it better than I can do. So, threading and OpenGTL are the main topics for today.
We have tried to use OpenGL in two places in Krita, and we are still using it in one. The main use of OpenGL is on the canvas. The other place has been an attempt to use GLSL to implement filters. We quit trying to develop GLSL-based filters when Cyrille Berger came up with OpenGL, though, and it is no longer relevant for Krita. We still use the OpenGL shader language for the canvas.
But the OpenGL canvas is still going strong and is, in fact, the recommended canvas type for Krita if your graphics card supports it. These days that is more and more often the case, even though we definitely have had as many problems as the KWin developers had, and still encounter driver issues. Although we already had an OpenGL canvas in Krita 1.6, for Krita 2.x the code was completely rewritten. Chiefly responsible here is Adrian Page, and later on Lukáš Tvrdý and Dmitry Kazakov.
The canvas is, of course, a QGlWidget implementation. Now a canvas has a couple of possible roles:
- Show the image - not as easy as it sounds!
- Show the grid and tool decorations - by that I mean the outlines of (for instance) the circle tool, the mesh points of the warp transform tool or the darkened area outside the crop area
- Show the cursor
- Show previews for some tools, like the gradient tool
To show the image, we hack up the rendered image - we call it the projection of all the layers in the layer graph - into small squares and create textures for every square. OpenGL is responsible for the scaling, zooming and rotating. Yes - Krita supports canvas rotation, which is especially nice for tablet users. Of course, there are complications: the HDR exposure display is written in GLSL.
For the grid and tool decorations we make good use of the extreme awesomeness of Qt. Prior to Qt 4, we had to write that stuff twice, once in OpenGL, once in QPainter, but since QPainter paints on an OpenGL widget just as easy as on any other QPaintDevice, we share the code here.
The cursor code is a bit more interesting, since we use 3D models of tools like pencils, brushes or a thumb to represent the brush engine you are using at the moment. Unfortunately, you'll only enjoy this feature if you're using a Wacom tablet that suports tilt, since by default you're looking at straight at the top of the tool, which tends to be boring.
The gradient tool preview is also implemented as a GLSL program. While it still doesn't support selections, the preview is on-canvas and in real-time - and very smooth of course.
The only place where Krita 1.6 used threading was in preparation for the preview images in the filter dialog. In Krita 2.x, threading is pervasive. This summer, Dmitry Kazakov reworked the core recomposition engine - that is, the code that takes all layers and masks and creates the final projection image - to be threaded. Threading is always complicated: many developers will maintain that a human brain cannot grasp the complexity, and I'm not sure they are wrong.
Here's Dmitry Kazakov's take on the most challenging bit of his Summer of Code project:
The most challenging part of my Summer project was the implemention of the swapper. It was really tricky, because it introduced three levels of locking in the tile manager: one for in-memory tiles store, one for swapped-out tiles store, and one more was placed inside every tile to guarantee it will not be swapped out during IO-operation accidentally. You know what it means! ;) I was fighting deadlocks and corrupted memory due to race conditions for about half a week, but I failed because I did it in an ad-hoc manner. Then I took a piece of paper, painted three wide horizontal stripes representing levels of locking and started drawing possible code paths of the data manager using vertical lines. The only rule I had to follow, the lines should have been continous. This trivial rule guaranteed to me that all the locks were taken in the same order. That was the solution!
Other places where we use threads are:
- Loading resources such as brushes, patterns and gradients
- Computing the brush masks
- Applying a filter destructively (filter masks are non-destructive, but those are handled by the recomposition engine)
- Creating the filter dialog preview images (still...)
In some cases we use ThreadWeaver here, a very impressive KDE-specific technology. Especially when applying filters, this works very well since it is a bunch of simultaneous jobs. For the image recomposition we had problems and moved to our own solution.
The OpenGTL project, created by Cyrille Berger, is not Krita-specific. There are GEGL bindings, for instance, as well as Qt bindings. Basically, OpenGTL is a set of languages for mucking about with pixels. The code written in those languages is compiled using LLVM and then executed natively. Currently, Krita offers filters and colorspaces implemented using OpenGTL languages. The challenge on the Krita side is to provide the pixels for OpenGTL in as efficient a way as possible. The overall goal is to make extending Krita even easier. We used to have Ruby and Python scripting (well, it still exists, but needs maintenance), but OpenGTL has three advantages. It is:
- Easier, because the language is specialized for pixel handling in a colorspace-independent way
- Faster, because it's compiled to native code
- Safer, because it cannot do anything except changing pixels
Cyrille has created the following languages:
- OpenCTL: a clone of AmpasCTL but compatible with the GPL. It's focused on transforming the value of a single pixel. Color models and color adjustment filters are the domain of this language.
- OpenShiva: inspired by Adobe's PixelBender, OpenShiva can work on more than one pixel at a time, applying kernel-like transformations. This is mainly useful for filters and generators.
In a very experimental phase is OpenRijn, which is a sketching language. This could be used for brush engines, for instance.
An interesting further step could be generating GPGPU instead of CPU code - and then we'd be using the graphics card again for our filters!
It's really nice to focus on the technical part for a while, but now it's time to remember that Krita is a product as well as a project - an application that apart from being a breeding ground for developers and an archive of algorithms and ideas is most importantly an application for artists to use. So: a nice little piece of Krita art to finish with, by NDee: