Skip navigation.
KDE Developer's Journals

manyoso's blog

manyoso's picture

A QtWebKit KPart is no answer for a KDE browser

Disclaimer: I have no desire to re-ignite KHTML vs WebKit arguments. Rather, the purpose of this blog post is to hopefully enlighten a technical question.

Over the last few months I've heard many KDE developers in various forums bemoan the lack of a working and stable WebKit KPart. The motivation behind this complaint seems to be that KDE folk want a WebKit browser option for KDE. Thus the naive solution is to just get the WebKit KDE KPart in shape. Given this motivation... the solution is wrong IMO.

I can speak from some experience here. I've been working on QtWebKit for quite awhile and have also worked - in the past - on the WebKit KPart plus Konqueror integration that Simon started. For a time, it was building and running just fine. You could install it and change a configuration file and Konqueror would render using QtWebKit. However, the integration was *far* from complete. Plumbing the sources of Konqueror I learned a nasty secret: Konqueror is highly KHTML API specific. Konqueror has deep integration with KHTML that goes far above and beyond the KPart API. Creating a QtWebKit KPart is woefully insufficient for the purpose of providing anything more than a basic HTML viewer.

A simple HTML viewer is no where close to a fully modern desktop browser.

This all makes sense if you stop to think about the history of Konqueror. Konqueror is a generic desktop shell. It is designed to allow basic viewing of various documents in various formats. Of course, it has become much, much, more than that. And the key to this growth of features is Konqueror's steady adoption of API's above and beyond the generic KPart API.

Which brings me to my point: if parts of the KDE community truly want a modern browser based on QtWebKit they'd best be looking at solutions beyond Konqueror. Otherwise you are left with two hacky solutions: make a QtWebKit KPart that is API compatible with KHTMLPart OR migrate Konqueror source to make it less dependent on KHTMLPart. The former is not going to be fun as the KHTMLPart API is not refined or polished and highly KHTML specific. The latter can only be accomplished with a lot of work set aside for refactoring or through nasty '#ifdef KHTML callThisWay() #else callThatWay();'

Both of these solutions are sub-optimal in my opinion.

manyoso's picture

Better algorithm for QPainter::fillRect() with raster based painting

In my last blog I found out that Qt is being evil when using QPainter::eraseRect() with a QImage based textured brush. How evil? Well, calling QPainter::fillRect() with the same brush results in something like a 30-50% speedup while achieving the exact same results. Not only that, but the QPainter::eraseRect() codepath makes QImage not thread safe for painting outside the main thread because it is silently using QPixmap behind the scenes. However, this isn't the whole story. I was surprised that even with all this fixed the algorithm is still not optimal.

In fact, you can see a very substantial increase in performance using a fractal based filling and tiling algorithm. Here are the results for a 10000x10000 QImage for tiling operations:

Algorithm       Brush              msecs
----------------------------------------
naive           checkered brush    1186
eraseRect       checkered brush    4445
fillRect        checkered brush    704
fractalFill     checkered brush    284



As you can see, the QPainter::eraseRect() is evil. Just changing to QPainter::fillRect() provides a very big decrease in the amount of time to paint a pattern. However, the fractalFill algorithm is still much faster. It cuts that time by more than half. So what is this algorithm?

void ...
{
    QImage image(WIDTH, HEIGHT, FORMAT);

    /* create our pattern */
    QImage base(20, 20, FORMAT);
    QPainter p1(&base);
    p1.setCompositionMode(QPainter::CompositionMode_Source);
    p1.fillRect(0, 0, 10, 10, Qt::gray);
    p1.fillRect(10, 0, 10, 10, Qt::white);
    p1.fillRect(0, 10, 10, 10, Qt::white);
    p1.fillRect(10, 10, 10, 10, Qt::gray);

    QPainter p(&image);
    p.setCompositionMode(QPainter::CompositionMode_Source);

    /* draw the pattern once at 0,0 */
    p.drawImage(0, 0, base);

    const int imageW = image.width();
    const int imageH = image.height();
    int w = base.width();
    int h = base.height();
    while (w < imageW || h < imageH) {
        if (w < imageW) {
            /* Copy and draw the existing pattern to the right */
            p.drawImage(QRect(w, 0, w, h), image, QRect(0, 0, w, h));
            /* Update width of our pattern */
            w *= 2;
        }
        if (h < imageH) {
            /* Copy and draw the existing pattern to the bottom */
            p.drawImage(QRect(0, h, w, h), image, QRect(0, 0, w, h));
            /* Update height of our pattern */
            h *= 2;
        }
   }
}


This would be even faster if Qt were smarter about the overloads of QPainter::drawImage(). The real inline drawImage overload that does the heavy lifting takes a QRect. This forces the other overloads to create and destroy a QRect which is minimal overhead, but in a tight loop this cost starts starts to grow.

Implementing this algorithm and seeing the improvement led icefox and me to investigate whether we could use the same idea to speedup the general case of QPainter::fillRect() with a solid brush. We didn't have much hope it could be sped up as this is a fundamental painting routine in Qt, but surprise this algorithm does give a nice speedup Smiling

Algorithm       Brush              msecs
----------------------------------------
fillRect        solid brush took   508                                                                                          
fractalFill     solid brush took   293


This is a large performance increase in a fundamental painting routine. This same idea could be used inside of QPainter::eraseRect(), QPainter::fillRect(), and hopefully a future QPainter::drawTiledImage(). Hopefully the Trolls can investigate and see if something like this should go into Qt 4.5.

Here are the two methods side by side:

QImage fillRectSolid()
{
    QImage image(WIDTH, HEIGHT, FORMAT);
    QPainter p(&image);
    p.setCompositionMode(QPainter::CompositionMode_Source);
    p.fillRect(QRect(0, 0, WIDTH, HEIGHT), Qt::red);
    return image;
}

QImage fractalFillSolid()
{
    QImage image(WIDTH, HEIGHT, FORMAT);

    QPainter p(&image);
    p.setCompositionMode(QPainter::CompositionMode_Source);
    p.fillRect(0, 0, 1, 1, Qt::red);

    const int imageW = image.width();
    const int imageH = image.height();
    int w = 1;
    int h = 1;
    while (w < imageW || h < imageH) {
        if (w < imageW) {
            p.drawImage(QRect(w, 0, w, h), image, QRect(0, 0, w, h));
            w *= 2;
        }
        if (h < imageH) {
            p.drawImage(QRect(0, h, w, h), image, QRect(0, 0, w, h));
            h *= 2;
        }
   }
   return image;
}



A few notes: this speedup only occurs with non-alpha channel QImage::Format's or at least pre-multiplied. With the inline overloads rearranged again it'd be even faster. I hope this is useful. Read on for the source to all of these tests...

manyoso's picture

Beware QPainter::eraseRect() with a QBrush::texturedImage()!

Funny little story... I was profiling with valgrind recently and was shocked to discover a huge hiccup in the painting performance of my QImage raster based app. There was this repeated call to QPainter::drawTiledPixmap() that was soaking up CPU cycles. It was a true mystery as I had thought the canvas of my app was completely QImage based. Not so.

You see, I had created a QBrush with a QImage texture and called QPainter::eraseRect() with it in a fairly important path in my code. What I didn't know was that QPainter was silently turning my QImage based textured brush into a QPixmap with QPixmap::fromImage() and using that to operate on my QImage based source device.

I have no idea why QPainter would decide to do this other than the mistaken assumption that QPixmap is always faster to draw. And maybe that is true in some general case, but surely not when the device you are painting to is already itself a QImage. And not in many other cases too. In fact, I don't even know why the QBrush(const QImage &) constructor exists if QPainter is going to insist on turning it into a QPixmap anyway.

Interestingly, a comment in the QPainter sources for the eraseRect shows another problem with this: it isn't thread-safe. Qt 4.4 touts itself as thread-safe when painting to a QImage. But that isn't true here. If you happen to try painting to a QImage in another thread and you have this use case you're in for some crashy, crashy. I wonder that the comment doesn't recognize the performance implications of this...

So beware of QPainter::eraseRect() and a textured image. I really hope Qt 4.5 can solve some of these problems.

manyoso's picture

How to get faster Qt painting on N810 right now

My previous post touched on the horrid FPS you can expect from any graphics intensive Qt app on the N810 at the moment. Ariya has pointed out one reason for the bad numbers: Qt decides to convert all 16 bit pixmaps to 32 bit before blitting even if the source QPaintDevice and the destination QPaintDevice are both 16 bit.

Well, here is a workaround I found while we wait for some pretty big changes in the Qt painting engine for 4.5 that Ariya hinted at. Mind you it is a big hack, but it seems to work well for my use case and perhaps it would be useful to others.

Introducing QX11RasterWidget:

#include <QWidget>

class QX11RasterWidget : public QWidget {
public:
    QX11RasterWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
    virtual ~QX11RasterWidget();

    QImage *rasterDevice() const;

protected:
    virtual bool event(QEvent *event);
    virtual void paintEvent(QPaintEvent *event);
    virtual void resizeEvent(QResizeEvent *event);

private:
    void flushToX11();

private:
    QImage *m_rasterDevice;
};

#include "qx11rasterwidget.h"

#include <QEvent>
#include <QPainter>
#include <QResizeEvent>

#include <QX11Info>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

QX11RasterWidget::QX11RasterWidget(QWidget *parent, Qt::WindowFlags f)
    : QWidget(parent, f)
{
    setAttribute(Qt::WA_PaintOnScreen);
    setAttribute(Qt::WA_NoSystemBackground);
    setAttribute(Qt::WA_OpaquePaintEvent, true);

    m_rasterDevice = 0;
}

QX11RasterWidget::~QX11RasterWidget()
{
    delete m_rasterDevice;
}

QImage *QX11RasterWidget::rasterDevice() const
{
    return m_rasterDevice;
}

bool QX11RasterWidget::event(QEvent *event)
{
    if (m_rasterDevice && event->type() == QEvent::Paint) {
        //Make sure all paint operations redirect here
        QPainter::setRedirected(this, m_rasterDevice);
        bool accept = QWidget::event(event);
        flushToX11();
        return accept;
    }
    return QWidget::event(event);
}

void QX11RasterWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
}

void QX11RasterWidget::resizeEvent(QResizeEvent *event)
{
    QWidget::resizeEvent(event);

    delete m_rasterDevice;
    m_rasterDevice = new QImage(event->size(), QImage::Format_RGB16);
}

void QX11RasterWidget::flushToX11()
{
    if (!m_rasterDevice) {
        return;
    }

    //Flush the m_rasterDevice to the X11 window
    Display *display = QX11Info::display();
    Visual *visual = (Visual*)x11Info().visual();
    Drawable hd = handle();
    int w = m_rasterDevice->width();
    int h = m_rasterDevice->height();
    int depth = x11Info().depth();

    XImage *xi = XCreateImage(display, visual, depth, ZPixmap, 0,
                               reinterpret_cast<char*>(m_rasterDevice->bits()), w, h, 32,
                               m_rasterDevice->bytesPerLine());

    GC gc = XCreateGC(display, hd, 0, 0);
    XPutImage(display, hd, gc, xi, 0, 0, 0, 0, w, h);
    XFreeGC(display, gc);
    XFlush(display);
}



The technique is straightforward. In QX11RasterWidget::event(...) we redirect all paint operations of the widget to the QImage m_rasterDevice. And then when the paint operations are done, we directly put the image to the X11 window by using XPutImage. This has obvious drawbacks, but will result in nice increase in FPS. You can forget about child widgets though. This does not do any composition management of child widgets. It will also not likely work if you want a remote connection to your X app. But it does make things faster.

manyoso's picture

Qt painting is SLOW on N810

With all the recent interest in Nokia's N810 device among KDE and Qt developers I think it is important to note that Qt/X11 painting performance is badly broken when running under Hildon. I've been porting a QtWebKit based browser to the N810 for awhile now and have noticed a steep drop in performance when compared to the same browser running under Qtopia Phone Edition on the very same device.

Specifically, Xrender performance on the N810 (at least the codepaths that Qt uses) is incredibly slow. What's more, using CPU based raster image is also very slow because of the overhead of blitting the QImage to the screen under X11. Inspired by Zack's recent post I thought it would be a good idea to quantify just how bad the problem is. With a little modification to disable the GL option, I ran Zack's qgears2 program on the N810.

Here are the results in FPS:

maemo-qgears
GEARSFANCY
Xrender: 1.411 FPS
Raster: 2.750 FPS

GEARS
Xrender: 3.028 FPS
Raster: 5.004 FPS

TEXT:
Xrender: 6.016 FPS
Raster: 4.629 FPS

COMPO:
Xrender: 3.545 FPS
Raster: 4.060 FPS

Just horrible numbers. However, this doesn't tell the whole story. The raster based method incurs more overhead than the Xrender path by design. For the raster based method all paint operations operate on a QImage and then are blitted at the end to the widget's backingstore. This blit takes anywhere from 20-40% of the time depending upon the test.

These numbers taken together do not bode well for the idea of getting any kind of performance out of graphics intensive Qt programs on the N810. At least until Xrender is fixed.

manyoso's picture

blah blah blah

blah blah blah who took away the delete function for kdedevelopers.org blog entries blah blah blah

manyoso's picture

"...violate the Separation of Powers ordained by the very Constitution of which this President is a creature."

The United States just took a gigantic step away from the cliff today. An unknown federal district court judge just told the President of the United States where he could shove his warrantless wiretapping program.

Federal District Judge Anna Diggs Taylor is my new personal hero. She has ordered President Bush to immediately end his warrantless wiretapping program as it is in violation of the very Constitution of which his office is a creature. Here is how she puts it in her injunction:

IT IS HEREBY ORDERED that Defendants, its agents, employees, representatives, and
any other persons or entities in active concert or participation with Defendants, are permanently
enjoined from directly or indirectly utilizing the Terrorist Surveillance Program (hereinafter
“TSP

manyoso's picture

Intro to KDE/Qt build systems for new developers

In the recent KDevelop dot interview I noticed a very astute comment by someone named borker:

"One of the things I've loved about picking up Qt/KDE was how quickly you can make non-trivial applications, but I found learning Qt/KDE was a weird reverse... rather than writing code first and then learning the complexities of the build process later, as required, I found myself needing to come up to speed with the build process first and then write code. Not being overly familiar with the GNU tool chain didn't help speed up this process either. I found it to be a frustrating blocker to actually writing code and it wasn't helped by most of the available tutorials being great on coding advice but very slim on build advice, so when I wanted to go from 'hello world' to something a bit more 'real world' I ran smack into the build system and all my momentum died completely. Anything that could help a beginner like me out in the way of teach as you go type stuff would be great." -- link

I can't tell you how many times I've seen someone new to KDE/Qt development come into the #kdevelop channel after being absolutely floored by Autohell. Well, as you might have heard, KDE is going to be moving away from Autotools, so hopefully that will help. Here is a short introduction to all the build systems you might encounter in KDE land.

  1. QMake Version 3 -- Used by Qt 3.x applications.
    Brain dead easy to learn.

  2. QMake Version 4 -- Used by Qt 4.x applications.
    Brain dead easy to learn.

  3. CMake -- Used by KDE 4.x and Qt 4.x applications.
    Fairly easy to learn, but more powerful.

  4. Autotools -- Used by KDE 3.x applications.
    This will make your eyes bleed and your brain beg for mercy.

  5. Scons -- Previously considered for KDE 4.x applications, but ultimately dropped for KDE/Qt development.

If you are a new developer and have no previous experience with KDE/Qt then learn #2 first. You need to learn how to use Qt 4 and this will be the most pain free way to do so. Once you've done that, then you can venture into KDE 4 land and learn how to use #3. It is not that hard and your experience with QMake 4 will probably help.

If you are familiar with Qt and how to use it, but you are thinking that you want to develop KDE 4 applications, then by all means start with #3.

Unless you are currently hacking on KDE 3 or have knowledge of Autotools, you have no reason to suffer through #4 especially if you are new to KDE/Qt development. Save your energy and learn QMake 4 first and then CMake.

If you like scons, great! But, please keep in mind that most KDE applications will be using CMake.

manyoso's picture

KDevelop 4 and Qt's new dockwidget tabs

With the release of Trolltech's java bindings it kinda feels like Christmas. I wanted to point out some more presents under the tree...

Look closely at the dockwidgets on the right of the image. See that? Recent versions of Qt 4.2 snapshot include a new Ideal like feature. When you drag a dockwidget completely on top of another an Ideal like tabbed bar is created. This is done entirely within Qt and any application which uses QMainWindow will have this functionality. Pretty cool, eh? Smiling

This is a screenshot of KDevelop 4 as it currently stands. As you can see, we now natively integrate Qt 4 designer. You can thank Matt Rogers and Roberto Raggi for this one. KDevelop 4 is going through some pretty incredible changes right now. Hopefully the dot is going to host an interview with some updates on what is going on. Stay tuned!

manyoso's picture

Go (STEELERS + REFS)!

Seele, I understand that you are a big Pittsburgh fan and are understandably happy they "won", but I can't imagine how you can call it a "great game to watch." See, unlike most of the country and unlike most of the people at the stadium in Detroit and unlike all of the referees on the field, I was hoping for a Seahawks win. That game was horrible. But, don't take my word for it... read just about every account of the game, anywhere.

The only people truly satisfied are folks like you: Steeler fans who couldn't care less that their team won due to a HUGE assist from the guys in white striped outfits. You earned your trophy with one of *the* *worst* *quarterback* performance in history. You earned your trophy despite a phantom touchdown from said quarterback that should never have been called, an offensive pass interference call that should never have been called, a holding call that would have put the Seahawks at the one yard line and ready for the go ahead score and a missed off sides call on the same play, a completely idiotic blocking call on a guy who was making a *tackle* for chris' sakes.

But, don't take my word for it. After all, I'm probably as big a Seahawk fan as you are a Steeler fan. I grew up with them and have watched them despite 30 years of misery. I still remember the 1984 championship, crystal clear, even though I was just a little kid at the time. Trust me, bad officiating against the Seahawks is nothing new. Go and google Testaverde's "Phantom Touchdown" to see what I mean. However, I know I'm not hallucinating. If you read ESPN, Foxsports, or many of the national rags it is apparent that any honest observer can see that this game was either willfully fixed OR subject to some of the worst officiating in Superbowl history.

The Seahawks were clearly the better team. Problem is, the game wasn't Seahawks VS Steelers... it was Seahawks VS ( Steeler's + Refs )

Syndicate content