45
votes
  1. In my application, I paint a street map using QPainter on a widget
    • made by QPainterPaths that contain precalculated paths to be drawn
    • the widget is currently a QWidget, not a QGLWidget, but this might change.
  2. I'm trying to move the painting off-screen and split it into chunked jobs
    • I want to paint each chunk onto a QImage and finally draw all images onto the widget
    • QPainterPaths are already chunked, so this is not the problem
    • problem is, that drawing on QImages is about 5 times slower than drawing on QWidget
  3. Some benchmark testing I've done
    • time values are rounded averages over multiple runs
    • test chunk contains 100 QPainterPaths that have about 150 linear line segments each
    • the roughly 15k paths are drawn with QPainter::Antialiasing render hint, QPen uses round cap and round join
  4. Remember that my source are QPainterPaths (and line width + color; some drawn, some filled)
    • I don't need all the other types of drawing QPainter supports
    • Can QPainterPaths be converted to something else which can be drawn on a OpenGL buffer, this would be a good solution.
    • I'm not familiar with OpenGL off-screen rendering and I know that there are different types of OpenGL buffers, of which most of them aren't for 2D image rendering but for vertex data.
Paint Device for chunk | Rendering the chunk itself | Painting chunk on QWidget
-----------------------+----------------------------+--------------------------
QImage                 |                    2000 ms |                   < 10 ms
QPixmap (*)            |                     250 ms |                   < 10 ms
QGLFramebufferObj. (*) |                      50 ms |                   < 10 ms
QPicture               |                      50 ms |                    400 ms
-----------------------+----------------------------+--------------------------
none (directly on a QWidget in paintEvent)          |                    400 ms
----------------------------------------------------+--------------------------

(*) These 2 lines have been added afterwards and are solutions to the problem!

It would be nice if you can tell me a non-OpenGL-based solution, too, as I want to compile my application in two versions: OpenGL and non-OpenGL version.
Also, I want the solution to be able to render in a non-GUI thread.


Is there a good way to efficiently draw the chunks off-screen?
Is there an off-screen counter part of QGLWidget (an OpenGL off-screen buffer) which can be used as a paint device for QPainter?

1
Note: I created the QImage in ARGB32_Premultiplied format. I also tried RGB32 and ARGB32 (non-premultiplied), all with nearly the same poor performance, so I guess that the problem is not "a wrong format", which was my first guess.leemes
Is QPicture not a good choice?pearcoding
I'll test the performance of QPicture and extend my question with some benchmarks: QImage vs QPicture vs QWidget::paintEvent.leemes
QPicture is definitely not what you want, QPixmap is the preferred image class if you don't need pixel-level access. However, if you want to render in a non-GUI thread you will need to use QImage. For OpenGL, you could perhaps try QGLFrameBufferObject.Dan Milburn
Yeah! I got it working. I suggest that you, @DanMilburn, write an answer which suggests the use of QGLFramebufferObject for the OpenGL solution and QPixmap for the non-OpenGL solution. I will accept it, since your comment lead me in the right direction.leemes

1 Answers

5
votes

The document of Qt-interest Archive, August 2008 QGLContext::create()

says:

A QGLContext can only be created with a valid GL paint device, which means it needs to be bound to either a QGLWidget, QGLPixelBuffer or QPixmap when you create it. If you use a QPixmap it will give you software-only rendering, and you don't want that. A QGLFramebufferObject is not in itself a valid GL paint device, it can only be created within the context of a QGLWidget or a QGLPixelBuffer. What this means is that you need a QGLWidget or QGLPixelBuffer as the base for your QGLFramebufferObject.

As the document indicated, if you want to render in an off-screen buffer using opengl, you need QGLPixelBuffer. The code below is a very simple example which demonstrates how to use QGLPixelBuffer with OpenGL:

#include <QtGui/QApplication>
#include <Windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>
#include <QtOpenGL/QGLFormat>
#include <QtOpenGL/QGLPixelBuffer>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // Construct an OpenGL pixel buffer.
    QGLPixelBuffer glPixBuf(100, 100); 
    // Make the QGLContext object bound to pixel buffer the current context
    glPixBuf.makeCurrent();
    // The opengl commands 
    glClearColor(1.0, 1.0, 1.0, 0.0);
    glViewport(0, 0, 100, 100);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, 100, 0, 100);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 0.0, 0.0);
    glPointSize(4.0);
    glBegin(GL_TRIANGLES);
    glVertex2i(10, 10);
    glVertex2i(50, 50);
    glVertex2i(25, 75);
    glEnd();
    // At last, the pixel buffer was saved as an image
    QImage &pImage = glPixBuf.toImage();
    pImage.save(QString::fromLocal8Bit("gl.png"));

    return a.exec();
}

The result of the program is a png image file as:

enter image description here

For non-opengl version using QPixmap, the code maybe in forms of below:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPixmap pixmap(100, 100);
    QPainter painter;
    painter.begin(&pixmap);
    painter.drawText(10, 45, QString::fromLocal8Bit("I love American."));
    painter.end();
    pixmap.save(QString::fromLocal8Bit("pixmap.png"));

    return a.exec();
}

The result of the program above is a png file looks like:

enter image description here

Though the code is simple, but it works, maybe you can do some changes to make it suitable for you.