PPP
PPP

Reputation: 1870

How to do gtkmm thread safe queue draw?

I'm building a video player in GTK using OpenGL to render things. I couldn't find the reason that it "crashed" after a few seconds playing the video. Actually, the program does not crash. The window simply gets non responsive, but the other thread continues to run. It's easier seeing the minimal reproducible example:

#include <iostream>
#include <gtkmm.h>
#include <epoxy/gl.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <GL/gl.h>
#include <chrono>
#include <thread>

class MyOpenGLArea : public Gtk::Window
{
public:
    MyOpenGLArea()
    {
        set_title("Test");
        set_default_size(640, 360);

        add(vBox);

        glArea.set_hexpand(true);
        glArea.set_vexpand(true);
        glArea.set_auto_render(true);
        vBox.add(glArea);

        glArea.signal_render().connect(sigc::mem_fun(*this, &MyOpenGLArea::render), false);

        glArea.show();
        vBox.show();
    };

    void run()
    {
        while (true)
        {
            printf("run()\n");
            queue_draw();
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }

    virtual bool render(const Glib::RefPtr<Gdk::GLContext> &context)
    {
        printf("render()\n");
    }

public:
    Gtk::GLArea glArea;
    Gtk::Box vBox{Gtk::ORIENTATION_VERTICAL, false};
};

int main(int argc, char **argv)
{
    auto app = Gtk::Application::create(argc, argv, "");
    MyOpenGLArea myOpenGLArea;
    std::thread t(&MyOpenGLArea::run, &myOpenGLArea);
    return app->run(myOpenGLArea);
}

This code outputs

render()
run()
//...

for some seconds, and then starts printing only

run()
//...

That is, the window thread stops. I cannot resize the window anymore, it becomes unresponsive. The code that outputs run() continues running forever.

What could be the reason?

UPDATE:

I found out that the reason is that I'm calling queue_draw() in a non thread safe way. However, there's no documentation in how to do it in gtkmm.

How to call things in a thread safe way in gtk?

THe only example I found was this very old one from 2008 which is deprecated https://www.velvetcache.org/2008/09/30/gtkmmglibmm-thread-example

Upvotes: 2

Views: 588

Answers (1)

PPP
PPP

Reputation: 1870

The secret is to use a GLib::Dispatcher :)

#include <iostream>
#include <gtkmm.h>
#include <epoxy/gl.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <GL/gl.h>
#include <chrono>
#include <thread>

class MyOpenGLArea : public Gtk::Window
{
public:
    MyOpenGLArea()
    {
        set_title("Test");
        set_default_size(640, 360);

        add(vBox);

        glArea.set_hexpand(true);
        glArea.set_vexpand(true);
        glArea.set_auto_render(true);
        vBox.add(glArea);

        glArea.signal_render().connect(sigc::mem_fun(*this, &MyOpenGLArea::render), false);
        drawerDispatcher.connect(sigc::mem_fun(*this, &MyOpenGLArea::onNotificationFromWorkerThread));
        glArea.show();
        vBox.show();
    };

    void run()
    {
        while (true)
        {
            drawerDispatcher.emit();
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }

    void onNotificationFromWorkerThread() {
        printf("onNotificationFromWorkerThread()\n");
        queue_draw();
    }

    virtual bool render(const Glib::RefPtr<Gdk::GLContext> &context)
    {
        printf("render()\n");
        return true;
    }

public:
    Gtk::GLArea glArea;
    Gtk::Box vBox{Gtk::ORIENTATION_VERTICAL, false};
private:
    Glib::Dispatcher drawerDispatcher;
};

int main(int argc, char **argv)
{
    auto app = Gtk::Application::create(argc, argv, "");
    MyOpenGLArea myOpenGLArea;
    std::thread t(&MyOpenGLArea::run, &myOpenGLArea);
    return app->run(myOpenGLArea);
}

Upvotes: 2

Related Questions