Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Owning the game loop



  • Hi,

    I have been trying to find a solution to this problem of mine on the internet, but to no avail.
    I am currently writing an OpenGL Game for a computer graphics class project. Since we have been using Qt for previous homeworks to create supporting UIs easily, we are required to use Qt as a base for the project as well.
    However, in the game I am writing, I'd like to implement my own game loop, and thus will have to take control of the event loop (otherwise my loop would be blocking it, and conversely.).
    I have a QApplication and wrote classes deriving from QMainWindow and QOpenGLWidget to render to.
    I know that I can make everything work by connecting a QTimer to my rendering/update function, but this is very suboptimal.
    Optimally, I would like to not have to call QApplication::exec, while still being able to poll for events. I thought I could bypass it by calling QCoreApplication::processEvents in my update function of my game loop, and just not calling app.exec -- however, this doesn't really work, as my UI just (1) takes forever to start up and (2) freezes and (3) doesn't respond to UI stuff such as terminating when closing etc.

    How am I able to take control of the main loop while still using Qt to create a basic window that I can render to using my opengl functionality?

    Thanks!


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    What are you doing in your game loop ?



  • @SGaist
    Thanks for answering.
    Basically, I am separating rendering and updating to have fixed-timestep updating (for physics etc) as well as as-fast-as-possible rendering with time interpolation. (Heavily heavily inspired from Gaffer on Games article)
    My code looks as follows (only slightly simplified):

    ... within my OpenGLContext
    void GLWindow::gameLoop()
    {
        const double delta = 1.0 / fixedUpdatesPerSecond;
        auto time = QDateTime::currentMSecsSinceEpoch();
        double accumulated = 0.0;
        while (! this->terminate)
        {
            auto newTime = QDateTime::currentMSecsSinceEpoch();
            double frameExecution = tdiff(time, newTime);
            time = newTime;
            accumulated += frameExecution;
            while (accumulated >= delta)
            {
                // Handle user input / system polling
                    //However, since Qt has event driven polling, let's just call processEvents and then handle any input events by putting their results into a map.
                QCoreApplication::processEvents();
                // Update our states
                this->updateState(delta);
                accumulated -= delta;
            }
            this->render(accumulated / delta);
        }
    }
    

    In the same class, I have updateState and render:

    void MyGL::updateState(float delta)
    {
    //... Bunch of commented-out code to make sure base loop works first
        if(myKeys.at(Qt::Key_W)) {
            cam->translateAlongForward(delta * 3.0);
            cam->update();
        }
    }
    void MyGL::render(float aheadAlphaPercent)
    {
        this->update();
    }
    void MyGL::paintGL()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        lambertShader->setViewProj(cam->matrix());
        // a lot of other drawing stuff
    }
    

    I know that my rendering code itself works because I initially tested it by just using a QTimer.

    Instead, I now do the following:

    ...in main...
    QApplication a(argc, argv);
    QSurfaceFormat::setDefaultFormat(format);
    MainWindow w;
    w.show();
    w.start();
    //return a.exec();
    // ^Dont^want^to^uncomment^this^
    ... MainWindow::start (MainWindow is QMainWindow subclass)...
    uiElements->glWindow->gameLoop(); //glWindow is subclass of OpenGLContext
    

    As you can see, I tried to avoid calling Qt's blocking event loop in QApplication::exec, but am still polling events. However, this doesn't work properly, as described in my first post.