Sunday 25 April 2010

Every time you scale a pixmap, God kills a kitten.

I've had a fun weekend.

Carsten Munk, of Mer and other fame got me interested in insane schemes (once again): this time playing around with libdui (or libmeegotouch, as it is now apparently known), as we had repeatedly heard from many sources that it relied on GL, which meant that it couldn't be used with devices that require software rendering. Having found the -software flag to widgetsgallery, and poked around the source of DUI in the past, I thought this was a bit of a funny claim to make, until we actually used it and found out how horribly slow it was. Maybe they had a point?

Why was it slow? Well, it happened again. Not completely the same, of course, but this is a very similar issue to one I wrote about recently in Qt on Maemo. Something that a lot of developers, particularly ones working in higher level libraries or languages like C# or Qt forget from time to time is that image (or pixmap) scaling - in particular, smooth scaling - is generally not a fast operation.

DUI did a lot of scaling.

DUI has a class called MScalableImage, the purpose of which is to draw (and scale) a pixmap as needed, which is a good enough reason to have a class. MScalableImage is passed a pixmap, and stores it.

So far, so good.

Now, onto the details of scaling. When being asked to draw, QPainter pointer is passed, as well as an x, y, width and height. The width and height are checked against the stored pixmap's width and height, and if they match, it's drawn instantly, no questions asked.

So far, so good.

What happens, though, in the case where they don't match?

Oops.

DUI currently copies and rescales the stored pixmap to the target width/height, and then proceeds to draw as normal.

This behaviour is obviously wasteful. In the (frequent) case of GL acceleration, this doesn't really matter so much, because scaling on GL hardware - abstracted away by Qt - isn't all that slow, which is why this problem was probably not noticed previously. In the case of software rendering, though, that's a whole different story.

Patching this away to use the application-wide QPixmapCache drastically improves the performance of software rendering. My laptop isn't exactly slow, and I still ended up with a boost from an (unusable) 5-6 FPS to around 170-180 FPS.

Hopefully this post serves as an educational warning to help prevent future such mistakes.

(Oh, and the conclusion? No, DUI doesn't require GL. It helps, obviously, but it's not required.)

Labels: , , , , ,

Saturday 17 April 2010

"Is it out yet?" - open source and the mass market

One thing which has increasingly become apparent to me - from wandering around some of the threads on talk.maemo.org - is that there is a very big difference between typical commercial software development, and open development.

This might be one of those kind of obvious statements that elicits "face-palm" reactions, but more seriously, think about it.

When a company develop a commercial product, they typically will keep it under wraps, not letting anyone near it until it's done. While I hate to mention Apple, the iPad (or iPhone OS 4 update) are both great examples of this, as are a lot of their other products. They don't talk about them until they're done, which is great in a way for the typical consumer. You don't want to hear about something, and then not actually be able to get it for the next X months. That's frustrating.

And that is exactly the experience that has been seen on tmo a few times now - people getting frustrated about the unavailability of XYZ, be it a product (like an OS update) or even news about a product (like the ever-contentious subject of MeeGo on the n900).

This experience exists, of course, because information is out and floating around long before the product is actually due: the topic of the thread that sparked this post, PR1.2 for Maemo 5, was known (at least by me, in some form) when PR1.1 shipped in January, because a bug I was watching was marked "fixed", but "for the next update".

To get down to the point a bit, I wonder what the solution to this problem is. Obviously, not releasing information at all isn't really a plausible possibility for truly open development - but then, what?

I think that there are two solutions.

Firstly, the information that *is* released should be targeted. Obviously, since it's from a big company, it'll spread - but (for example) the announcement of MeeGo was assuredly premature, and even Day One was a total damp squib for a lot of people (even me: I was excited by it, but as a primarily applications developer, it wasn't all that useful). So obviously, the information that is going out needs to get to the right place. In that case, the OS developer community.

Secondly, that more information should be available, targeted at different subsections of the community. For the device enthusiasts, they should have rough ideas about what is coming, even if it's not details on features, a general idea would be nice. For the application developers, SDKs (and possibly even preview images or packages, if it's an OS release, Qt update, etc) would be lovely.

I don't have any firm conclusions on how this can be best managed, but I think with some thought applied to the above, it might be possible to get a little less confusion out in the wild.

Labels: , , ,

Monday 12 April 2010

Remote working and me

I've been a bit quiet lately, so I suppose it's about time to talk about what I've been up to.

There's been a lot of work (with fantastic people on fun stuff!), unfortunately that isn't something I can talk a lot about yet - hopefully more in the near future.

I've also been forcing myself to adjust to remote work, this isn't something I've done before, and considering my nature to flit from one thing to another fairly uncontrollably, I was initially worried at how I'd manage this, but I've now been doing it for over a month and so far so good.

I won't say it's not been an interesting ride, one of the strangest things seems to be my sleep pattern which seems to naturally not revolve around a single period of sleep at night: last night I finally went to bed at around 4am and woke up sometime around 11. But that's been varying hugely.

I guess I really am a night owl, or just really strange, but I've also been feeling *a lot* better of myself while operating like this, so hey, I guess I'm doing something right.

Labels: ,

FaceBrick - a Facebook client for Maemo

Another great example of how open source is all about 'scratch your own itch'. I started writing code for a Facebook client on Maemo (although once MeeGo is in a more usable state with regards to UI and application development, I'll be porting it to work there as well) a week or two ago now. I finally got tired of using the Nokia Facebook widget which is totally and utterly useless (if I wanted a link to facebook on my desktop, I'd make one. Ok, it's nice being able to see status updates - but come *on*. Let me click them at least.)

So, I started writing code after having found libqfacebookconnect, using Qt. Within a day or two I had something that could read newsfeeds, and post status updates. I found a Qt on Maemo bug along the way, and spent some time digging into that, but it's progressing nicely, and seems to have been quite popular with the folks in the Maemo community.
(facebrick, pre-v0.1)



A few weeks later, and it's growing really well. I'm happy with the progress (although I have a really long list of stuff I still want to do, including replacing that useless desktop widget).

It's going to keep me busy for a fair while to get it up to scratch, but I'm having fun, and the feedback and mini-community I'm forming around this are awesome.
(facebrick, first release)



I'd also like to take a moment to thank m165 (from talk.maemo.org) amongst others for their many ideas and feedback, lcuk (for his interesting discussion), and MohammadAG (from #maemo and TMO) for his help with packaging (though I still have *a lot* to learn in that department)
(facebrick, latest release)










As (I hope) is visible, it's evolving fairly rapidly, mostly through suggestions from the die-hard early adopters I've found, but it's got a long way to go yet.

Thankfully, most of the fun of software development comes from the ride. ;)

Labels: , , ,

Tuesday 6 April 2010

Qt on Maemo: a warning

Just a quick post to share a fairly important piece of information I found out today with the rest of the internets - hopefully this will help if someone is searching to find out why the list in their Qt application is so slow.

Basically, the setup I have is as follows: A model -> providing images/text to a delegate, text laid out in sizeHint using QTextLayout and friends -> rendered in paint onto the view. This is a pretty standard setup, though the info in this post probably applies for other API like QListWidget::setItemWidget() too - I haven't checked.

Back to the code, in my delegate paint event, I had the following, also fairly standard code:

    QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
This is nothing special. It ensures that Qt draws my selection rect around items when they are selected and all the other standard style prettiness.

However - and here's the kicker - my size hint wasn't always 70 pixels tall. "Why the hell is this an issue" I hear you ask? Good question.

Let's run our application and scroll around a bit on Maemo. Wow, that's slow. Let's profile.

What's that? Why is such a huge amount of time being spent in QMaemo5Style? That's insane - all it is doing is drawing a border and selection rectangle!

Time to go source diving...

















          case PE_PanelItemViewItem: {
          {snip}
                int rowHeight = 70;
                if (GtkWidget *gtkTreeView = d->gtkWidget("HildonPannableArea.GtkTreeView"))
                    d->gtk_widget_style_get(gtkTreeView, "row-height", &rowHeight, NULL);

                if (option->rect.height() != rowHeight) {
                    QPixmap scalePix(option->rect.width(), rowHeight);
                    scalePix.fill(Qt::transparent);
                    QPainter scalePainter(&scalePix);
                    QGtkPainter gtkScalePainter(&scalePainter);
                    gtkScalePainter.setUsePixmapCache(false); // cached externally

                    // the sapwood engine won't scale the image, but instead tile it, which looks ridiculous
                    gtkScalePainter.paintFlatBox(gtkTreeView, detail, QRect(0, 0, option->rect.width(), rowHeight),
                                                 option->state & State_Selected ? GTK_STATE_SELECTED :
                                                 option->state & State_Enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE,
                                                 GTK_SHADOW_NONE, gtkTreeView->style);

                    // don't just scale the whole pixmap - the bottom border line would look extremly ugly for big items
                    int dh = 8; // just an arbitrary value which looks good with the default Maemo styles
                    p->drawPixmap(cacheRect.topLeft(), scalePix, QRect(0, 0, scalePix.width(), dh));
                    p->drawPixmap(cacheRect.adjusted(0, dh, 0, -dh), scalePix, QRect(0, dh, scalePix.width(), scalePix.height() - 2 * dh));
                    p->drawPixmap(cacheRect.bottomLeft() - QPoint(0, dh), scalePix, QRect(0, scalePix.height() - dh, scalePix.width(), dh));
                } else {
                    gtkCachedPainter.paintFlatBox(gtkTreeView, detail, cacheRect,
                                            option->state & State_Selected ? GTK_STATE_SELECTED :
                                            option->state & State_Enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE,
                                            GTK_SHADOW_NONE, gtkTreeView->style);
                }
            }
            END_STYLE_PIXMAPCACHE
        break;

Oh dear. It appears that whenever a cell's row height is not 70px, QMaemo5Style renders onto a pixmap and does all sorts of voodoo to scale it and paint it where required. That is *not* going to be fast.

However, if it is 70px (the UI standard), it's rendered direct from cache. Much better.

(Can it be made better? I don't know. I was in a rush when looking into this - and regardless, with PR1.2 probably round the corner, it's a bit late - at least for now)

The workaround, at least in my case, was this in my delegate sizeHint:


QSize s(imageSize.width() + qMax(nameRect.width(), textRect.width()) + 20,
                  qMax(height, 60));
 
+    // Maemo is very, very bad with nonstandard row sizes.
+    // It involves a lot of pixmap resizing and other horrors which is *really* noticably slow.
+    // To prevent this, we min-bound rows at 70px (style default)
+    if (QApplication::style()->inherits("QMaemo5Style")) {
+        if (s.height() < 70) {
+            s.setHeight(70); // MAEMO hack
+       }
+    }
+

With this done, it's back to silky smooth resizing. Hopefully this helps someone else in a similar predicament. :)

Thanks to harryF from Qt for his help diagnosing this issue.

Labels: , , , ,