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.

10 comments:

  1. s/resizing/scrolling/, I should really learn to proofread more. ;)
    ReplyDelete
  2. Thanks. I just opened a bug report on "showtime" a tv app for the n900 which might have to do with this issue!

    https://bugs.maemo.org/show_bug.cgi?id=9856
    ReplyDelete
  3. @Goliath23:

    there's one really easy way to check this that I neglected to mention.

    start your app with the Qt '-style' option, plastique is a good style to test with

    e.g. if you run it with 'foobar', try 'foobar -style plastique' and see if it's still slow. If it is, it isn't the Maemo style doing it.

    HTH :)
    ReplyDelete
  4. It's going to be tricky to cache all kind of itemview sizes - the cache will just explode. We'll take a look however to see whether we can make it more bearable.
    ReplyDelete
  5. Im the developer of Showtime, and I had raised this issue a couple of times but got no concrete answers. Thanks to the above blog , its opened up something to check.

    Im wondering if this will get better if I reduce the font size in my lists and try to keep the rows under 70px. The slowness happened when I added an extra line to each row to make it 3 line row. Will post back.
    Will also try running it with Motif/other style.

    Btw, apart from this , i had tried already an example with a simple delegate for a Qtablewidget that displays a different colour for one of the 4 coloumns of the table.
    That makes the table drag as hell even with only 6 rows present !
    its all discussed here.
    http://discussion.forum.nokia.com/forum/showthread.php?t=196473

    sorry if the post wasnt clear enough, been typing all this from my mini-keyboard on the phone :)
    ReplyDelete
  6. Great catch Robin, thanks!
    ReplyDelete
  7. reducing the size of my fonts to keep the row limited to 70px improved scrolling considerably.
    Goliath23, thanks for link the bug report you opened has been updated.

    Robin, thanks a lot for this important revelation :-) !
    I never imagined this could be caused by the style implementation, I always thought it had something to do with kinetic scrolling implementation.
    ReplyDelete
  8. @krk969: I'm glad to hear this helped.

    It confused me a lot, too, because my drawing *could not* get any more efficient. I was drawing a pixmap directly and using QTextLayout to cache font rendering too - that was it. And when I *stopped* doing that, it was still slow.. bit never on my desktop.

    It was certainly frustrating, but at least we have an answer. :)
    ReplyDelete
  9. QT bug filed.
    http://bugreports.qt.nokia.com/browse/QTBUG-9700
    ReplyDelete