Multi thread OpenGL and QtQuick

I quite like QtQuick even though there are elements of it that can be a little tedious at times. I’ll put that down to being a very young tool that needs some refinements, and some useful libraries to go with it. The biggest win for me though is how nicely it integrates with OpenGL. There are a few examples out there and I’ve had quite some success following the QmlOgre demo.

However, I ran into a problem where our rendering was far too slow. Basically it is running compute shaders against quite a large gridded data set and without a lot of trickery this can’t be done in real time. Least not on my silly little GPU. The result was that the QtQuick rendering thread was being delayed by our rendering. This badly affected the UI.

My solution was to create a third thread, after the main thread and QtQuick rendering thread, to house our rendering.

Thread Controller
This is a QThread that I use to run an event loop, and house the RenderWorker. Just instantiate one of these, and pass the worker through. The method launchWorker starts up the event loop and blocks until the thread and finished starting.

void ThreadController::launchWorker(QObject* worker)
    _worker = worker;

    // blocks until thread is ready

Derives from QSGGeometryNode and is created in RenderItem::updatePaintNode. All it does is link to a GL texture which looks like this:

void RenderNode::setTextureId(int id)
    if (id != _id)
        _id = id;

        QSGTexture* old = _texture;
        _texture = _item->window()->createTextureFromId(id, _size);

        delete old;

This class is a QObject derived class that ends up living within ThreadController. It has three slots initialize, resize, and render. The trick here is that we create the texture and render to it via a framebuffer. The texture id created here is passed through to RenderNode that then creates a QSGTexture that references it. Looks a like this:

void RenderWorker::initialize()
    // Create a shared context
    GLContext* shared = new GLContext(_item->window(), _item->window()->openglContext());
    _context.reset(new Context(shared));

    _frame = new FrameRender(_item->width(), _item->height(), 4);
    _frame->attachColorTexture(0, TextureFormat::Rgb8, _context->nearestClamp());
    _frame->attachDepthTexture(TextureFormat::DepthComponent, _context->nearestClamp());

    _renderer = new Renderer(_context.get());
    _renderer->resize(QSize(_item->width(), _item->height()));

    emit initDone();

Any time we resize our window we have to update the texture id also

void RenderWorker::resize(const QSize& size)
    _frame->resize(size.width(), size.height());

    _frame->render(_context.get(), [&] {
        glClearColor(0.0, 0.0, 0.0, 0.0);

    emit frameUpdate(_frame->colorTexture(0)->id());

Rendering is a normal framebuffer render

void RenderWorker::render()
    Context* context = _context.get();
    if (_engine->rendering())
        AutoFrameSwitch fs(context);
        _frame->render(context, [&] {

    emit renderDone();

RenderNode is the QQuickItem derived class used in QML. Its basically where the engine is instantiated and everything begins. This part is still a work in progress but what I have here works. The main bit is this:

    RenderNode *node = static_cast(oldNode);
    if (!node)
        _worker = new RenderWorker(_engine, this);
        connect(_worker, &RenderWorker::frameUpdate, this, &RenderItem::frameUpdate, Qt::QueuedConnection);
        connect(_worker, &RenderWorker::renderDone, this, &RenderItem::update);
        connect(_worker, &RenderWorker::initDone, this, &RenderItem::renderReady);
        QMetaObject::invokeMethod(_worker, "initialize", Qt::BlockingQueuedConnection);

//        connect(this, &RenderItem::viewResized, _worker, &RenderWorker::resize, Qt::BlockingQueuedConnection);

        node = new RenderNode(this);

    if (_resized)
        QMetaObject::invokeMethod(_worker, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, _size));
        _resized = false;

    QMetaObject::invokeMethod(_worker, "render", Qt::QueuedConnection);

So, I create the RenderWorker and bind up some events. Specifically I want QQuickItem::update to be called whenever we have finished rendering a frame.
Then initialize is invoked using a BlockingQueuedConnection so that the calling thread waits until it has completed. At this point we know our texture exists and we can use it in QtQuick.
Anytime a resize happens we again use a BlockingQueuedConnection to update the texture.
Finally render is called using a QueuedConnection.

There are a couple of problems and things I don’t like that I’ll be taking a look at.

  • I want to switch out the use of invokeMethod where possible. Now that Qt5 can handle templated signal slots I vastly prefer those versus using strings. For one you get compile errors when the slot doesn’t exist
  • The screen flickers during a resize. This is because of the delay between creating a new texture, and actually rendering to that texture. In the time being the QtQuick has gone off and used your brand new texture to paint to the screen. You’ll notice in the resize method of RenderWorker I’m clearing it to black as an interim solution.
  • I’m concerned about the way I’m using signals and slots to communicate with the RenderWorker. I suspect in some cases it could end up running almost a frame behind based on when the events get processed. I’ll need to inspect this more