Qt World Summit: Register Today!


QWindow on a non-GUI thread



  • Hi guys,

    I have a project where there is a subclass of QWindow rendering content on a thread. It runs well other than the assert error I get when rendering.:

    Program: ..._0_MSVC2017_64bit-Debug\source\GLRenderer\debug\Qt5Cored.dll
    Module: 5.12.0
    File: kernel\qcoreapplication.cpp
    Line: 578
    
    ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 0x0x28a6e997e10. Receiver '' (of type 'OpenGLRenderWindow') was created in thread 0x0x28a7bc0d190", file kernel\qcoreapplication.cpp, line 578
    

    Header file:

    #ifndef OPENGLRENDERWINDOW_H
    #define OPENGLRENDERWINDOW_H
    
    #include <QWindow>
    #include <QOpenGLContext>
    
    class OpenGLRenderWindow : public QWindow
    {
    public:
        OpenGLRenderWindow(QScreen* outputScreen,
    const QSurfaceFormat& surfaceFormat,
    QOpenGLContext* sharedContext);
    
        virtual ~OpenGLRenderWindow();
    
        bool event(QEvent *event) override;
        void exposeEvent(QExposeEvent* event) override;
    
        void resizeEvent(QResizeEvent *event) override;
    
        const QSurfaceFormat& getOpenGLFormat();
        QOpenGLContext* getOpenGLContext();
    
    protected:
        void swapSurfaceBuffers();
    
        bool makeContextCurrent();
        void doneContextCurrent();
    
        QSurfaceFormat openGLFormat;
        QOpenGLContext* openGLContext;
    
    public slots:
        void renderFrame();
    };
    
    #endif // OPENGLRENDERWINDOW_H
    

    CPP:

    #include "openglrenderwindow.h"
    
    OpenGLRenderWindow::OpenGLRenderWindow(
    QScreen *outputScreen,
    const QSurfaceFormat &surfaceFormat,
    QOpenGLContext *sharedContext) :
        QWindow(outputScreen),
        openGLFormat(surfaceFormat),
        openGLContext(nullptr)
    {
        //Create surface
        setSurfaceType(QWindow::OpenGLSurface);
        setFormat(openGLFormat);
    
        //Allocate memory for the context object and prepare to create it
        openGLContext = new QOpenGLContext(this);
        openGLContext->setFormat(openGLFormat);
        if(sharedContext)
            openGLContext->setShareContext(sharedContext);
    
        //Make sure the context is created & is sharing resources with the shared context
        bool contextCreated = openGLContext->create();
        assert(contextCreated);
    
        if(sharedContext)
        {
            bool sharing = QOpenGLContext::areSharing(openGLContext,sharedContext);
            assert(sharing);
        }
    }
    
    OpenGLRenderWindow::~OpenGLRenderWindow()
    {
    }
    
    bool OpenGLRenderWindow::event(QEvent *event)
    {
        switch (event->type())
        {
            case QEvent::UpdateRequest:
            renderFrame();
            return true;
            default:
            return QWindow::event(event);
        }
    }
    
    void OpenGLRenderWindow::exposeEvent(QExposeEvent *event)
    {
        if(isExposed())
            renderFrame();
    }
    
    void OpenGLRenderWindow::resizeEvent(QResizeEvent *event)
    {
        if(!makeContextCurrent())
            return;
    
        unsigned int w = event->size().width();
        unsigned int h = event->size().height();
    
        QWindow::resize(QSize(w,h));
    
        swapSurfaceBuffers();
        doneContextCurrent();
    }
    
    const QSurfaceFormat &OpenGLRenderWindow::getOpenGLFormat()
    {
        return openGLFormat;
    }
    
    QOpenGLContext *OpenGLRenderWindow::getOpenGLContext()
    {
        return openGLContext;
    }
    
    void OpenGLRenderWindow::swapSurfaceBuffers()
    {
        openGLContext->swapBuffers(this);
    }
    
    bool OpenGLRenderWindow::makeContextCurrent()
    {
        //Initialize OpenGL context & other resources
        return openGLContext->makeCurrent(this);
    }
    
    void OpenGLRenderWindow::doneContextCurrent()
    {
        openGLContext->doneCurrent();
    }
    
    void OpenGLRenderWindow::renderFrame()
    {
        if(!isExposed())
            return;
    
        if(!makeContextCurrent())
            return;
    
        //Rendering code here
    
        swapSurfaceBuffers();
        doneContextCurrent();
    }
    
    
    displayWindow = new OpenGLRenderWindow(mainOutputScreen,surfaceFormat,nullptr);
    displayWindow->show();
    displayWindow->moveToThread(thread);
    

    I have this exact same setup for an offscreen renderer subclassing QOffscreenSurface which works without issues (difference being the I call create() in the constructor of that class).

    It seems like there is no way around getting resize and expose events for a QWindow, but I don't quite know how to fix this.

    Is there something wrong with the order I create and move the resources?
    Is there a way to disable events to this window? It renders pretty well despite of the pop-up error.



  • An update on this mainly based on the results of this thread (links to example projects are in this thread):
    https://forum.qt.io/topic/125267/wgl-window-on-non-gui-thread

    To summarize things, the real issue here is using QT's OpenGL classes to render into a QWindow or similar class. Because QWindow can only live in the GUI thread, projects that have more complex UI and a lot of widgets, will have a hard time with high resolution and high framerate OpenGL rendering.

    There are two options at this time to have decent OpenGL performance while also using QT's UI:

    1 - Using QT's OpenGL classes and UI while optimizing UI widgets:

    This is the concept that QT is currently designed around and what everyone will recommend. Basically what I personally had to do was to use QT's concurrent programming options. The easiest example:

    QtConcurrent::run([=](){
    //Code that is currently causing your UI to block
    });
    

    While this option works and makes sense, it complicates things. You obviously cannot make UI calls in the threaded block of code and also there is the limitation of not having any control over existing QT implementations. For example, if a certain widget is by default performance-intensive, you are stuck with a performance hit.

    2 - Using QT's UI with non-QT OpenGL classes:

    The only option for rendering into an OpenGL window on a non-GUI thread is to get away completely from QT OpenGL classes and use a library like GLFW.

    I know people on this forum tend to immediately shut down the idea of a window on a non-GUI thread, but this seems to be the best way for complex projects using OpenGL.

    3 (Not possible currently) - Using QT's UI with QT OpenGL classes, along with a QWindow-type class that can actually live in a non-GUI thread:

    This would be heaven. The concept of a QOffscreenSurface being able to render on a non-GUI thread but not being able to do the same for QWindow really makes no sense. I understand that this is done to allow for events to be delivered to a QWindow but it would make sense to create some flexibility for programmers to take care of resizing and visibility themselves since this is perfectly doable with OpenGL.


  • Lifetime Qt Champion

    Hi,

    IIRC, you can move your QOpenGLContext to a different thread but not the QWindow.



  • @SGaist Thanks for replying.

    Are you aware of any alternatives to QWindow where using a thread is possible?

    I do most of my rendering on a QOffscreenSurface subclass and the QWindow is only displaying the result. But since it sits on the GUI thread, any events there cause it to drop frames and stall.


  • Lifetime Qt Champion

    Maybe the QQuickRenderControl example might help.



  • @SGaist Thanks! I will play around with that example



  • I'm still stuck on this.

    Anyone know if there is a way to use a QOpenGLContext and other QT OpenGL resources with a native window?

    QSurface seems to fit this purpose and it seems to be compatible with non-GUI thread use. I'm assuming I have to implement window resizing and visibility but I'm not sure where to start with that. If anyone's played around with that concept, do you have any tips or advice on getting started?



  • An update on this mainly based on the results of this thread (links to example projects are in this thread):
    https://forum.qt.io/topic/125267/wgl-window-on-non-gui-thread

    To summarize things, the real issue here is using QT's OpenGL classes to render into a QWindow or similar class. Because QWindow can only live in the GUI thread, projects that have more complex UI and a lot of widgets, will have a hard time with high resolution and high framerate OpenGL rendering.

    There are two options at this time to have decent OpenGL performance while also using QT's UI:

    1 - Using QT's OpenGL classes and UI while optimizing UI widgets:

    This is the concept that QT is currently designed around and what everyone will recommend. Basically what I personally had to do was to use QT's concurrent programming options. The easiest example:

    QtConcurrent::run([=](){
    //Code that is currently causing your UI to block
    });
    

    While this option works and makes sense, it complicates things. You obviously cannot make UI calls in the threaded block of code and also there is the limitation of not having any control over existing QT implementations. For example, if a certain widget is by default performance-intensive, you are stuck with a performance hit.

    2 - Using QT's UI with non-QT OpenGL classes:

    The only option for rendering into an OpenGL window on a non-GUI thread is to get away completely from QT OpenGL classes and use a library like GLFW.

    I know people on this forum tend to immediately shut down the idea of a window on a non-GUI thread, but this seems to be the best way for complex projects using OpenGL.

    3 (Not possible currently) - Using QT's UI with QT OpenGL classes, along with a QWindow-type class that can actually live in a non-GUI thread:

    This would be heaven. The concept of a QOffscreenSurface being able to render on a non-GUI thread but not being able to do the same for QWindow really makes no sense. I understand that this is done to allow for events to be delivered to a QWindow but it would make sense to create some flexibility for programmers to take care of resizing and visibility themselves since this is perfectly doable with OpenGL.


  • Lifetime Qt Champion

    Hi,

    For internal design and discussions about the OpenGL stack you should go to the interest mailing list. You'll find there Qt's developers/maintainers, this forum is more user oriented.



  • @SGaist Thanks, I will do that. I wanted to also share my experience to best use QT's existing OpenGL and UI features.



  • @rtavakko said in QWindow on a non-GUI thread:

    QT OpenGL resources with a native window?

    Most of the Qt OpenGL helper functions should actually "just work." They use whatever is the "current" OpenGL context. You just can't touch the GUI stuff. Something QOpenGLShaderProgram will have no idea if you are ultimately going to be rendering to a window or an offscreen surface.

    That said, I ultimately gave up trying to do multithreaded OpenGL with Qt. Between the Qt limitations, and the GL language-lawyering about what exactly is an "object" that will be shared between different contexts, etc. If you do try to do multithreaded OpenGL, you'll probably be much better off with an offscreen rendering thread and just drawing the most current completed FBO in the GUI window.

    You may want to pick up Vulkan if you really want to go down the rabbit hole of multithreaded drawing. It's a much more modern design, with much more explicit state that can be coordinated between threads and concurrent operations explicitly. (But it is a pain in the ass to do all of that. There's no "easy" GPU drawing in parallel.)



  • @wrosecrans

    Yes, I'm rendering using a QOffscreenSurface on a separate thread then displaying the frame on a QWindow on the GUI thread, but it makes sense to have all of the video rendering on the same thread so we're not interfering with the GUI thread.

    I've successfully tested this concept with GLFW-based OpenGL windows and QT UI. I doubt Vulkan will be any different since from what I understand the render window will still need to sit on the GUI thread.