Monday, 21 January 2013

Texture caching OpenGL fonts

As I mentioned in an earlier post, reliable GL font rendering has been a major source of cross-platform headaches for me in SleepyHead.

What works perfectly on Linux will look like crud on a Mac, an will break on windows depending on the off-screen rendering method used.

Big mess, until I got unlazy and implemented a pixmap caching system to try and work around this, and the results were rather surprising.

The boring technical stuff
Previously, I was using either the QGLWidget::renderText() method, or using QPainter to draw text directly to the screen device. As I said, neither method looked good on Macs, and neither allowed for glitch free offscreen text rendering, no matter what Frame/Pixel Buffering method I was using.

In an earlier attempt to improve performance, all graph area text in SleepyHead is queued up for drawing until all the other graphics primatives are finished drawing, then the DrawTextQue function in my graph area gGraphView (QGLWidget derived) dumps this queue to the paint engine.

I'm still using this Text queue, however now, instead of rendering to the GL context/screen device, instead I create a QPixmap/QImage of the dimensions the text would be (using QFontMetrics to get the sizes). I then render directly to the pixmap using QPainter, which is then bound to a 2D texture, and drawn to the screen.

Now if I just left it there, all I've achieved is slowing things down dramatically, as it's creating an extra step for every element of text drawn. (Which does alleviate mac font problems, but still not acceptable)

Enter pixmap caching.

Qt provides it's own image caching solution called QPixmapCache, which would work fine if I wasn't using so much native GL code. It currently lacks the ability to unbind OpenGL textures when purging the cache.

So I wrote my own, which basically does the same thing, but unbinding textures on cleanup.

For each text element that is drawn, I construct a hash key containing the text itself, a text key describing the QFont, a string describing the color, and the antialias boolean setting.  Size and rotation don't matter here.

I search a QHash using this key, to see if there is a prior record and image available for this text item.
The relevant structure looks like this

struct myPixmapCache
    quint64 last_used;
    QImage image; // was originally a pixmap.. i'm too lazy to rename the struct
    GLuint textureID;

The cache is defined as following hash.
QHash<QString,myPixmapCache *> pixmap_cache;
If the hash key is not found, the text pixmap image is created, then bound to the records textureID, and a new item is saved in the QHash.

Now that the text element definitely is in the cache, so the 2D texture can be blitted to the screen device. I use QGLWidget::drawTexture() to do this.

Then the last_used field is updated with the current timestamp. This is so the cleanup algorithm, which runs at the beginning of this DrawTextQue function, if the pixmap caches memory requirements go beyond a set threshold.

I track all images memory usage roughly using the images width()*height()*(depth()/8);

To cleanup I scan through the pixmap_cache and look for outdated last_used timestamps (about 4 or 5 seconds old) and drop these keys to a QList, then afterwards go through the QList and unbind and delete the Pixmap cache entries, removing them from the Hash.

All this can be seen in qGraphView.cpp/.h

Performance Results
I implemented a switch so this new method can be switched on or off in the preferences, and used the "fps" counter that displays down the right side of the graph view, turned on in Help->Debug Mode.
(It's not super accurate counter, but it will do. it's a guesstimate of the FPS, not the real one, as SleepyHead doesn't have to update every frame unless it's got something to do)

The best improvement came in the Overview tab.. Scrolling over the AHI/usage/etc bar graphs.. The tooltip pops up and cruises smoothly at around 200-300fps. Perviously I was only getting 21fps and it the tooltip lagged behind miserably.  that is at least a 10 times improvement.

Vertical scrolling took a huge boost too, as all the Horizontal and vertical graph text gets redrawn from the cache each update. Much smoother. Went from 60fps to 300-400fps on my mac.

Vertical graph headers only ever change when graphs or the screen are resized, so it helped here too.

All in all, a big performance gain, for not a huge amount of effort.  

Pixmap caching can be used for anything that contains repeated graphics primitives such as lines, points and quads, however text was the biggest performance bottleneck I found in my application

No comments:

Post a Comment