Jutting Bytes

Digressions of a research engineer

QTrace

| Comments

This post introduces a minimalistic proof of concept software on using Qt together with Qml to achieve interactive and concurrent rendering of fractal images. All sources available on GitHub.

Context

We were starting a coding sprint on distributed computation and distributed data structures, by reviewing existing technologies: message passing, multi-threading, GPU computing, etc. In the framework of multi threading, one can use either high level or low level programming interfaces, from concurrent mapping to thread subclassing with mutexes and semaphores.

Moreover, with Qt5, people move forward to separate logic and graphic interfaces even more, so this proof of concept projects illustrates how to do that, using only Qml for graphical user interface.

Use Case

Ray casting is well known to be a highly parallel job, in that sense that computing a pixel value does not depend on any other pixel value. So computation of pixel values can be run in parallel. Instead of computing a pixel value, which is the atomic operation in ray casting, let us consider computing values of pixels contained within a tile – a subdivision of the whole image to be rendered – that results from a tiling operation, that occurs in nearly all distributed computation pre processing operations.

Although not really state of the art in ray casting, fractal rendering follows the same pattern: instead of shooting a ray and computing intersections to accumulate diffuse color, transparency, reflection and/or refraction parameters, the pixel value is obtained by evaluating polynomials.

The type of fractal that is currently handled in this proof of concept software is newton fractals, an illustration of the solutions of a polynomial zn – 1 = 0 in the complex space, where solutions are points on the unit circle. In a rendered fractal image, such solutions can be found in the middle of so called “bassins of attraction”, whereas the fractal behavior is observed at boundaries of each bassins of attraction, illustrating the fact that the Newton-Raphson method being used to compute each pixel value may not converge for initial guesses that are not close enough to the solution.

Newton-Raphson method for solving polynomials

Assuming a function f, its derivative f’, and a first guess for a root x0, a better approximation of the root is given by x1 = x0 – f(x0) / f’(x0). This process is repeated either a predefined amount of times or until a sufficiently accurate value has been reached, e.g until xN – xM < 1e-6, where M = N-1.

Newton-Raphson method for rendering Newton fractals

To obtain a rendering of Newton fractals, we map each pixel value as defined in local screen coordinates to the complex space, obtaining an initial guess defined as x = a + ib, where a is the real part of x, and b the imaginary one of x, and apply the Newton-Raphson method to obtain the pixel value, i.e. the color in the HSV space (really handy in this case) of the root to which the solution provided by the Newton-Raphson method is the closest, with an alpha value chosen on the number of iterations required by the Newton-Raphson algorithm to reach the solution.

Implementation

As stated before, QLeap is implemented using Qt and Qml, architectured into modules that produces libraries, namely qtrMath, qtrCore and qtrQuick, and a set of applications for solving polynomials, non interactively generating fractal images either non threadedly or single threadedly or multi threadedly, and interactively generating fractal images through a responsive user interface, namely qtrNewtonSolver, qtrNewtonWriter-nt, qtrNewtonWriter-st, qtrNewtonWriter-mt and qtrNewtonViewer.

qtrMath

The math layer of qtrace, qtrMath, is composed of a few classes:

  • qtrPolynomial
  • qtrSolver

The first one is forked from the excellent Symbolic-C++ library, hence qtrace license. It provides evaluation and diferenciation of polynomials defined either over the reals or the complexes, which is more sufficient in the context of this proof of concept.

The second one however, implements the Newton-Raphson method for solving a polynomial equation given an initial guess, returning the solution and setting up inout parameters allowing to retrieve the count of iterations needed to reach the solution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <typename T> T qtrSolveNewton(qtrPolynomial<T> polynomial, T guess, int& count, double& error)
{
    int iteration;

    qtrPolynomial<T> ff0 = polynomial;
    qtrPolynomial<T> ff1 = Diff(ff0, "z");

    T p_guess = zero(T());
    T c_guess = guess;
    T n_guess;

    double c_error = 1e+6;
    double m_error = 1e-6;

    for(iteration = 0; ((c_error > m_error) && (iteration < 255)); iteration++) {
        p_guess =  c_guess;
        c_guess = (c_guess - (ff0(c_guess)/ff1(c_guess)));
        c_error = qtrSolveNewtonError(c_guess, p_guess);
    }

    count = iteration-1;
    error = c_error;

    return c_guess;
}

qtrCore

The core layer of qtrace, qtrCore, is composed of to concepts.

  • qtrTile®
  • qtrRenderer

The first one fulfills a very common task in distributed computing: dividing the computation domain into smaller, possibly independant pieces, called “tiles”. The tiler is then responsible for generating tiles for the computation domain.

In the generic context of raytracing, it is convenient to give a tile attributes related to itself, e.g. position, size or rect (includes both position and size), as well as to its parent: the computation domain, referred to as a “whole”. It is therefore additionaly equipped with size and rect attributes for the whole (no need for a position when it comes to the whole, as it defines the origin of the computation domain).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#pragma once

#include <QtCore>
#include <QtGui>

class qtrTilePrivate;

class qtrTile
{
public:
     qtrTile(void);
     qtrTile(const QRect& tile, const QRect& whole);
     qtrTile(const qtrTile& other);
    ~qtrTile(void);

#pragma mark -
#pragma mark Tile operators

    qtrTile& operator=(const qtrTile& other);

#pragma mark -
#pragma mark Tile attributes

    QPoint tilePos(void) const;
    QSize tileSize(void) const;
    QRect tileRect(void) const;

#pragma mark -
#pragma mark Whole attributes

    QSize wholeSize(void) const;
    QRect wholeRect(void) const;

#pragma mark -
#pragma mark Rendered image

    QImage image(void) const;

#pragma mark -
#pragma mark Tile setup

    void setTilePos(const QPoint& pos);
    void setTileSize(const QSize& size);
    void setTileRect(const QRect& rect);

#pragma mark -
#pragma mark Whole setup

    void setWholeSize(const QSize& size);
    void setWholeRect(const QRect& rect);

#pragma mark -
#pragma mark Image setup

    void setImage(const QImage& image);

private:
    qtrTilePrivate *d;
};

typedef QVector<qtrTile> qtrTileList;

Tiling a computation domain, a whole, is then done with regards to a resolution in the x-direction, a resolution in the y-direction, that together define the count of resulting tiles, and of course, the computation domain attributes, size and rect respectively.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
qtrTileList qtrTiler::tile(void)
{
    qtrTileList tiles;

    int tile_w = d->whole.width() /d->resolution_x;
    int tile_h = d->whole.height()/d->resolution_y;
    int tile_u = d->whole.width() %d->resolution_x;
    int tile_v = d->whole.height()%d->resolution_y;

    for(int x = 0; x < (d->resolution_x-1)*tile_w; x += tile_w)
        for(int y = 0; y < (d->resolution_y-1)*tile_h; y += tile_h)
            tiles << qtrTile(QRect(x, y, tile_w, tile_h), d->whole);

    for(int x = 0; x < (d->resolution_x-1)*tile_w; x += tile_w)
        tiles << qtrTile(QRect(x, d->whole.height()-tile_h-tile_v, tile_w, tile_h+tile_v), d->whole);

    for(int y = 0; y < (d->resolution_y-1)*tile_h; y += tile_h)
        tiles << qtrTile(QRect(d->whole.width()-tile_w-tile_u, y, tile_w+tile_u, tile_h), d->whole);

    tiles << qtrTile(QRect(d->whole.width()-tile_w-tile_u, d->whole.height()-tile_h-tile_v, tile_w+tile_u, tile_h+tile_v), d->whole);

    qtrTileList shuffled; int n = tiles.count();

    for(int i = 0; i < n; i++) {
        int k = arc4random() % tiles.count();
        qtrTile t = tiles.value(k);
        tiles.remove(k);
        shuffled.append(t);
    }

    return shuffled;
}

Note the tile list is returned shuffled, but this is only for the sake of nice demonstrations only.

qtrRenderer, is a functional class, that only aggregates rendering functions that fill tiles with corresponding rendered images, using the right solver for the right method, performing tile coordinate mapping.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void qtrRenderer::newton(qtrTile& tile)
{
          int c;
    const int w = tile.wholeSize().width();
    const int h = tile.wholeSize().height();

    const qreal ratio = (qreal)w/(qreal)h;
    const qreal y_min = -1.5;
    const qreal y_max = +1.5;
    const qreal x_min = y_min*ratio;
    const qreal x_max = y_max*ratio;

    const qreal d_x = -(qreal)w/2.0;
    const qreal d_y = -(qreal)h/2.0;
    const qreal s_x = (x_max-x_min)/w;
    const qreal s_y = (y_max-y_min)/h;

    QImage image(tile.tileSize().width(), tile.tileSize().height(), QImage::Format_RGB32);
    image.fill(Qt::black);

    typedef std::complex<double> complex;

    qtrPolynomial<complex> z("z");
    qtrPolynomial<complex> f = z;
    for(unsigned int k = 1; k < qtrRenderer::newtonOrder; k++)
        f = f*z;
    f = f - 1;

            complex  root;
    QVector<complex> roots;

    for(int k = 0; k < qtrRenderer::newtonOrder; k++)
        roots << complex(
            cos(k*((2*M_PI)/qtrRenderer::newtonOrder)),
            sin(k*((2*M_PI)/qtrRenderer::newtonOrder)));

    for(int x = tile.tilePos().x(); x < tile.tilePos().x()+tile.tileSize().width(); x++) {

        for(int y = tile.tilePos().y(); y < tile.tilePos().y()+tile.tileSize().height(); y++) {

            int i = x-tile.tilePos().x();
            int j = y-tile.tilePos().y();

            root = qtrSolveNewton<complex>(f, complex((x+d_x)*s_x, -(y+d_y)*s_y), c);

            for(int k = 0; k < qtrRenderer::newtonOrder; k++)
                if(abs(root-roots[k]) < 1e-6)
                    image.setPixel(i, j, QColor::fromHsv((k*((2*M_PI)/qtrRenderer::newtonOrder))/M_PI*180, 255, 255-((10*c)%255)).rgb());
        }
    }

    tile.setImage(image);
}

So far, composing rendered tiles, it is very easy to write output images, check out qtrNewtonWriter-* applications below.

qtrQuick

Ok, so far we have set up a fractal generation framework using QtCore. Here comes the interesting part: how to interface it with Qml. On the source side, this interface only consists in implementing a canvas that display tiles as they are rendered, exposing both itself and relevant parameters of the fractal rendering framework to the Qml runtime, so that a user interface written in Qml can instantiate the canvas, .e.g as a background for an application, and Qml controls to alter the fractal rendering framework at run time.

So the quick layer of qtrace, qtrQuick, implements one class:

  • qtrCanvas

and exposes it to Qml by implementing a Qml extension plugin:

  • qtrQuickPlugin

Let’s present the qtrCanvas interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#pragma once

#include <QtCore>
#include <QtQuick>

class qtrCanvasPrivate;

class qtrCanvas : public QQuickPaintedItem
{
    Q_OBJECT

#pragma mark -
#pragma Render method properties

    Q_PROPERTY(int newtonOrder READ newtonOrder WRITE setNewtonOrder)

#pragma mark -
#pragma Thread usage properties

    Q_PROPERTY(int curNumberOfThreads READ curNumberOfThreads WRITE setCurNumberOfThreads NOTIFY curNumberOfThreadsChanged)
    Q_PROPERTY(int maxNumberOfThreads READ maxNumberOfThreads WRITE setMaxNumberOfThreads NOTIFY maxNumberOfThreadsChanged)

#pragma mark -
#pragma Progress monitoring properties

    Q_PROPERTY(int minProgressValue READ minProgressValue NOTIFY minProgressValueChanged)
    Q_PROPERTY(int maxProgressValue READ maxProgressValue NOTIFY maxProgressValueChanged)
    Q_PROPERTY(int curProgressValue READ curProgressValue NOTIFY curProgressValueChanged)

public:
     qtrCanvas(QQuickItem *parent = 0);
    ~qtrCanvas(void);

public:
    void paint(QPainter *painter);

#pragma mark -
#pragma Render method management

    int     newtonOrder(void);
    void setNewtonOrder(int order);

    void setRenderMethod(qtrRenderer::qtrRenderMethod method);

#pragma mark -
#pragma Thread usage management

signals:
    void curNumberOfThreadsChanged(void);
    void maxNumberOfThreadsChanged(void);

public:
    int curNumberOfThreads(void);
    int maxNumberOfThreads(void);

public:
    void setCurNumberOfThreads(int);
    void setMaxNumberOfThreads(int);

#pragma mark -
#pragma Progress monitoring management

signals:
    void minProgressValueChanged(void);
    void maxProgressValueChanged(void);
    void curProgressValueChanged(void);

public:
    int minProgressValue(void);
    int maxProgressValue(void);
    int curProgressValue(void);

#pragma mark -
#pragma Internals

protected slots:
    void onResize(void);
    void onTileRendered(int);

protected:
    void geometryChanged(const QRectF& current, const QRectF& previous);

private:
    qtrCanvasPrivate *d;
};

Three different kinds of C++ classes can be exported to Qml: non-gui classes, painter based classes and scene graph based classes. Non-gui classes subclass either QObject or QQuickItem, the first one being prefered for non visual classes, the second one being the equivalent of the Item type in Qml. Painter classes are derived from QQuickPaintedItem and overload the QQuickPaintedItem::paint(...) method. Scene graph based classes derive from QQuickItem, implement QQuickItem::updatePaintNode(...) and create and initialize QSGNode subclasses, QSGGeometryNode, where QSGGeometry is used to specify meshes and QSGMaterial is used to specify textures. Any kind of exported C++ class is then registered to the Qml runtime.

The connections between the C++ and Qml world are then handled the following way:

  • Q_OBJECT: let moc collect following informations about the class.
  • Q_ENUMS: registers one or several enum types to the meta-object system.
  • Q_PROPERTY: behave like class data members, but have additional features accessible through the meta-object system.
  • Q_SIGNALS: connected to either signals or slots.
  • Q_SLOTS: invoked whenever a connected signals is emmited.
  • Q_INVOKABLE: allows member functions to be invoked via the meta-object system.

The qtrCanvas is, from an C++/Qt/Qml point of view, a very simple component as it only exposes propoerties, when then need to be handled on the Qml side. As a matter of fact, since we want to change the order of Newton polynomials by clicking on Qml items, such a property is created. Also the two gauges once can see in the top screenshot allow to monitor the number of threads being used to generate the fractal, and the progression of the rendering process in terms of rendered tiles.

1
2
3
4
5
6
7
8
9
10
11
12
void qtrCanvas::paint(QPainter *painter)
{
    painter->fillRect(boundingRect(), Qt::black);
    painter->setRenderHints(QPainter::HighQualityAntialiasing);
    painter->setPen(Qt::red);

    foreach(qtrTile tile, d->tiles)
        if (tile.image().isNull())
          painter->drawRect(tile.tileRect());
      else
          painter->drawImage(tile.tileRect(), tile.image());
}

The mandatory paint method only fill the item’s geometry by iterating over the tiles, rendering their image if it is available, a red rectangle indicating its contours otherwise. The concurrency is treated just like for non gui applications, so it will be covered in the next sections.

We could very well expose this class to the Qml runtime directly from within the main function of an app, but this would break a great feature of Qml design apps: it could be run using the qmlscene tool that is great for prototyping, also imagine serving a qml app through a web server, the entry point will definitly be the qml file itself. So let’s consider a main.qml file that actually is our app, a main.cpp being here only for convenience, basically instanciating a QQuickView, and handling various options of qmlscene like import paths for third party Qml modules, like in our case, the QtrQuick one.

So how to register our Qml type in both cases ? Well, by writing and loading a Qml extension plugin. Here come qtrQuickPlugin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma once

#include <QQmlExtensionPlugin>

class qtrQuickPlugin : public QQmlExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")

public:
    void registerTypes(const char *);
};

#include "qtrCanvas.h"

void qtrQuickPlugin::registerTypes(const char *uri)
{
    Q_ASSERT(uri == QLatin1String("QtrQuick"));

    qmlRegisterType<qtrCanvas>(uri, 1, 0, "QtrCanvas");
}

And there you go. To get the QtrCanvas Qml item, either from within a C++ main function or directly launching the app using qmlscene, we simply have to specify an import directorty.

On the C++ side, this is done by setting up a QQuickView:

1
2
QQuickView view;
view.engine()->addImportPath(qtrQuickImportPath);

On the qml side, this is done by using the -I path option switch to the qmlscene tool:

$ qmlscene -I qml/ app/qtrNewtonViewer/main.qml

qtrNewtonSolver

A first app to test our math framework. It computes the root of z3 – 20 in R and roots of z3 – 20 in C.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <qtrMath/qtrPolynomial.h>
#include <qtrMath/qtrSolver.h>

int main(int argc, char **argv)
{
    typedef std::complex<double> complex;

    qtrPolynomial<double>  z1r("z"); qtrPolynomial<double>  p1r = z1r*z1r*z1r-1;
    qtrPolynomial<complex> z1c("z"); qtrPolynomial<complex> p1c = z1c*z1c*z1c-1;

    std::cout << "Polynomial NR computation of z^3-1 in R: " << "guess = " <<  double(+3.0)       << " \t\t- " << qtrSolveNewton<double> (p1r,  double(+3.0)     ) << std::endl;
    std::cout << "Polynomial NR computation in z^3-1 in C: " << "guess = " << complex(+3.0, +0.0) << " \t\t- " << qtrSolveNewton<complex>(p1c, complex(+3.0,+0.0)) << std::endl;
    std::cout << "Polynomial NR computation in z^3-1 in C: " << "guess = " << complex(-3.0, +1.0) << " \t- "   << qtrSolveNewton<complex>(p1c, complex(-3.0,+1.0)) << std::endl;
    std::cout << "Polynomial NR computation in z^3-1 in C: " << "guess = " << complex(-3.0, -1.0) << " \t- "   << qtrSolveNewton<complex>(p1c, complex(-3.0,-1.0)) << std::endl;

    qtrPolynomial<double>  z2r("z"); qtrPolynomial<double>  p2r = z2r*z2r*z2r*z2r-1;
    qtrPolynomial<complex> z2c("z"); qtrPolynomial<complex> p2c = z2c*z2c*z2c*z2c-1;

    std::cout << "Polynomial NR computation of z^4-1 in R: " << "guess = " <<  double(+3.0)       << " \t\t- " << qtrSolveNewton<double> (p2r,  double(+3.0)     ) << std::endl;
    std::cout << "Polynomial NR computation in z^4-1 in C: " << "guess = " << complex(+3.0, +0.0) << " \t\t- " << qtrSolveNewton<complex>(p2c, complex(+3.0,+0.0)) << std::endl;
    std::cout << "Polynomial NR computation in z^4-1 in C: " << "guess = " << complex(-3.0, +0.0) << " \t- "   << qtrSolveNewton<complex>(p2c, complex(-3.0,+0.0)) << std::endl;
    std::cout << "Polynomial NR computation in z^4-1 in C: " << "guess = " << complex(+0.0, +3.0) << " \t\t- " << qtrSolveNewton<complex>(p2c, complex(+0.0,+3.0)) << std::endl;
    std::cout << "Polynomial NR computation in z^4-1 in C: " << "guess = " << complex(+0.0, -3.0) << " \t- "   << qtrSolveNewton<complex>(p2c, complex(+0.0,-3.0)) << std::endl;

    return 0;
}

qtrNewtonWriter-(nt-st-mt)

A sample app, C++ only that uses our qtrMath and qtrCore layers to render a fractal in a image that is written on the disk as an output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <QtConcurrent>
#include <QtCore>
#include <QtDebug>
#include <QtGui>

#include <qtrCore/qtrRenderer.h>
#include <qtrCore/qtrTile.h>

int main(int argc, char **argv)
{
    QCoreApplication application(argc, argv);

    if(application.arguments().count() < 4+1) {
        qDebug() << "Usage:" << argv[0] << "order" << "width" << "height" << "output";
        return 0;
    }

    qtrRenderer::newtonOrder = application.arguments().value(1).toInt();

    qtrTile tile;
    tile.setWholeRect(QRect(0, 0,
        application.arguments().value(2).toInt(),
        application.arguments().value(3).toInt()));
    tile.setTileRect(QRect(0, 0,
        application.arguments().value(2).toInt(),
        application.arguments().value(3).toInt()));

    qtrRenderer::newton(tile);

    tile.image().save(application.arguments().value(4));

    return 0;
}

In this non-threaded version, we start by defining the whole region that defines the dimensions of the output image, together with tiles that drive the rendering process. As this first version in not concurrent, we only define a single tile that matches the whole region.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// /////////////////////////////////////////////////////////////////
// Single concurrent run preprocessing
// /////////////////////////////////////////////////////////////////

    qtrRenderer::newtonOrder = application.arguments().value(1).toInt();

    qtrTile tile;
    tile.setWholeRect(QRect(0, 0,
        application.arguments().value(2).toInt(),
        application.arguments().value(3).toInt()));
    tile.setTileRect(QRect(0, 0,
        application.arguments().value(2).toInt(),
        application.arguments().value(3).toInt()));

// /////////////////////////////////////////////////////////////////
// Single concurrent run
// /////////////////////////////////////////////////////////////////

    QFuture<QImage> future = QtConcurrent::run(qtrRenderer::newtonImage, tile);

    QFutureWatcher<QImage> watcher;
    watcher.setFuture(future);
    watcher.waitForFinished();

// /////////////////////////////////////////////////////////////////
// Single concurrent run postprocessing
// /////////////////////////////////////////////////////////////////

    watcher.result().save(application.arguments().value(4));

In this single threaded version, we introduce the concurrent API of Qt. From this concurrent API, we use the run function, that runs a function as a runnable in a thread instanciated from the thread pool.

What is great with runnables is that it can be wrapped into futures and monitored by future watchers. A future allows threads to be synchronized against one or more results which will be ready at a later point in time. A future watcher provides information about a future like its status (whether it is running or finished), progression status (in case the concurrent API is called on a container).

From now on, we can’t expect any speedup as we are using the concurrent run entry point, but the multi-threaded version makes us of containers to take advantage of concurrency.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// /////////////////////////////////////////////////////////////////
// Multiple concurrent run preprocessing
// /////////////////////////////////////////////////////////////////

    qtrRenderer::newtonOrder = application.arguments().value(1).toInt();

    qtrTiler tiler;
    tiler.setWholeSize(QSize(
        application.arguments().value(2).toInt(),
        application.arguments().value(3).toInt()));
    tiler.setResolutionX(QThread::idealThreadCount());
    tiler.setResolutionY(QThread::idealThreadCount());

    QVector<qtrTile> tiles = tiler.tile();

// /////////////////////////////////////////////////////////////////
// Multiple concurrent run
// /////////////////////////////////////////////////////////////////

    QFuture<void> future = QtConcurrent::map(tiles, qtrRenderer::newton);

    QFutureWatcher<void> watcher;
    watcher.setFuture(future);
    watcher.waitForFinished();

// /////////////////////////////////////////////////////////////////
// Multiple concurrent run postprocessing
// /////////////////////////////////////////////////////////////////

    QImage image(
        application.arguments().value(2).toInt(),
        application.arguments().value(3).toInt(),
        QImage::Format_RGB32);
    image.fill(Qt::black);

    QPainter painter(&image);
    foreach(qtrTile tile, tiles)
        painter.drawImage(tile.tileRect(), tile.image());

    image.save(application.arguments().value(4));

In this multi-threaded version, we first use the tiler in order to generate a container of tiles, and replace the run call to the concurrent API by a map call on the container that applies a function to every item in a container, modifying the items in-place, in this case, the newton render method for a given tile.

qtrNewtonViewer

The final application in this proof of concept, achieves the link between Qt and Qml. As a matter of fact, it consists of a C++ entry point, main.cpp, that is optional and can be used in place of the qmlscene tool.

1
2
3
4
5
6
7
QQuickView view;
view.engine()->addImportPath(qtrQuickImportPath);
view.setColor(Qt::black);
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:main.qml"));
view.setTitle("Newton fractal tracer");
view.show();

The main.qml actually contains the graphical interface of our interactive fractal rendering application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import  QtQuick 2.0
import QtrQuick 1.0

Item {

    width: 800;
    height: 360;

    QtrCanvas {
        id: canvas;

        anchors.centerIn: parent;
        anchors.fill: parent;

        onCurNumberOfThreadsChanged: {
            thread_gauge.value = canvas.curNumberOfThreads;
        }

        onMinProgressValueChanged: {
            progress_gauge.value_min = canvas.minProgressValue;
        }

        onMaxProgressValueChanged: {
            progress_gauge.value_max = canvas.maxProgressValue;
        }

        onCurProgressValueChanged: {
            progress_gauge.value = canvas.curProgressValue;
        }
    }

    QtrGauge {
        id: thread_gauge;

        anchors.right: parent.right;
        anchors.bottom: parent.bottom;

        value: 0;
        value_min: 0;
        value_max: canvas.maxNumberOfThreads;

        caption: "Thead usage";

        MouseArea {
            anchors.fill: parent;

            onClicked: {
                canvas.newtonOrder = canvas.newtonOrder + 1;
            }

            onDoubleClicked: {
                canvas.newtonOrder = 2;
            }
        }
    }

    QtrGauge {
        id: progress_gauge;

        value: 0;
        value_min: 0;
        value_max: 100;

        caption: "Progress";

        anchors.right: thread_gauge.left;
        anchors.bottom: parent.bottom;
    }
}

Note the integration of Qt and Qml: the QtrCanvas features slots that react to its C++ counterpart qtrCanvas class, e.g. onCurNumberOfThreadsChanged. Similarily, its properties are set, e.g. line 48: canvas.newtonOrder = canvas.newtonOrder + 1;.

To finalize this post on Qt/Qml integration proof of concept, by presenting the qml source folder of the project.

jwintz@utopis:~/Development/qtrace/qml$ tree .
.
├── CMakeLists.txt
├── qtrQuick
│   ├── CMakeLists.txt
│   ├── qmldir
│   ├── qmldir.in
│   ├── qtrGauge.qml
│   ├── qtrGauge_background.png
│   ├── qtrGauge_background.pxm
│   ├── qtrGauge_center.png
│   ├── qtrGauge_center.pxm
│   └── qtrGauge_needle.png
└── qtrQuickConfig.h.in

1 directory, 11 files

This source folder, that contains .qml files instead of .(h|cpp), provides Qml items, such as the gauge that is used in the qtrNewtonViewer application. Note the presence of a qmldir file that lists provided items, and also, allows on to load Qt/Qml extension plugins.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module QtrQuick

## ###################################################################
## QML defined QML types
## ###################################################################

QtrGauge 1.0 qtrGauge.qml

## ###################################################################
## C++ defined QML types
## ###################################################################

plugin qtrQuick @CMAKE_BINARY_DIR@/lib

# - QtrCanvas 1.0
# - QtrGauge  1.0

Comments