KGameRenderer: Caching on multiple levels
June 13, 2010
If you read Planet KDE regularly over the course of the last weeks, you know that kdelibs 4.5 comes with the shiny new KSharedDataCache. Michael Pyne explained its purpose and design in a nice series of blog posts.
With KDE SC 4.5 coming onto my disk (at least the second Beta), it’s the right time to take advantage of this KSharedDataCache and its sibling KImageCache. Like many other applications in the kdegames module, KDiamond (your favorite clone of a trade-marked game about assembling rows of jewels) uses the predecessor of KImageCache, which is called KPixmapCache.
KPixmapCache is actually working quite well, but there may occur problems when multiple instances of KDiamond are running at the same time. I know that such bugreports are going to appear somewhen (regardless of the abstruseness of this configuration in the context of most of our games), so it’s better to fix these problems by moving to KImageCache. And while we’re at it, this is the right opportunity to rewrite the whole rendering code in KDiamond.
What’s the problem after all? Why does a game as simple as KDiamond need a complicated rendering infrastructure? The problem is that its fancy themes are vector graphics (in the SVG format). Simply speaking, vector graphics files are not an image of themselves (i.e. a bunch of pixels, like the bitmap graphics files which your digicam generates), but a bunch of painting instructions like “create a line from here to there”, “draw it with a thick black pen”, “fill this rect with that gradient” which can be used to create the image in question.
The advantage of vector graphics is that they can be used at different screen resolutions. But there is a clear disadvantage: Vector graphics are complicated to draw. In the simplest case , a bitmap can be drawn by simply putting the bitmap into the video memory at the right position (“blitting”), while drawing even simple vector graphics involve dozens to hundreds of rendering operations.
Therefore, most KDE games use some form of pixmap caching: The rendering operations are performed off-screen on a pixmap, which is then put on the screen. This pixmap can be reused, so some games save the pixmaps on the disk using the KPixmapCache (or its successor KImageCache).
In the above picture, we see the situation with a KImageCache. The single game elements (diamonds in this case) need to be rendered from the SVG graphics file only once. Any following requests will reuse the cached images. But there is another problem: Using the KImageCache introduces a non-negligible runtime overhead which slows down access to the cached pixmap (i.e. the right arrows, which, as you see, are plenty).
I have had a good experience lately with using a second cache inside the application, which is optimised towards serving pixmaps as fast as possible. With this change, KDiamond’s animations are suddenly fluid, especially the explosions when diamonds are removed. These animations consist of multiple images (“frames”) which are shown in fast succession, so there is a high traffic on the cache. While the application is running, these images become available in the high-availability cache and can be served quick enough.
With this experience, I am currently building new classes for our libkdegames: KGameRenderer implements theme management (a level higher than the current KGameTheme class), simple management for animated sprites (as in: series of frame images), and the caching strategy outlined above. It is accompanied by a helper class KGameRenderedItem. This is basically a QGraphicsPixmapItem which automatically reacts to theme changes and resizing by fetching suitable pixmaps from the cache.
A funny feature of KGameRenderer has been copied from KGoldRunner’s renderer (KGrTheme): If all pixmaps are cached already, the SVG file is not loaded at all. The impact of this optimization is not that high for KDiamond, but it certainly is for applications with many graphical elements, or for complicated themes.
For testing, I have ported KDiamond to use KGameRenderer and KGameRenderedItem. The stability is already suspiciously high. Compared to the previous KDiamond versions, graphical performance is much better during the game, but the startup is slower. When these problems are resolved, I hope to get these classes into libkdegames for KDE SC 4.6. If there is time left, I’ll probably port more of our games to KGameRenderer, and look into multi-threaded rendering. (You may also look forward to nicer animations in the next KDiamond, stay tuned!)