JoeyDeVries / LearnOpenGL

Code repository of all OpenGL chapters from the book and its accompanying website https://learnopengl.com
https://learnopengl.com
Other
10.81k stars 2.76k forks source link

Can I run the render loop from another thread? #194

Open alandefreitas opened 4 years ago

alandefreitas commented 4 years ago

I was browsing the examples in this repository looking for something that could help me. I'm trying to create a plotting backend that would work like a matplotlib backend for C++. There would be an independent window for plots whose contents would be updated depending on what happens in the main thread. Such a backend doesn't make sense if it blocks the main thread. Is that possible with glfw, or openGL, or any other related library? If so, can I still use your material as a reference?

That's what I tried to do (on Mac OSX). I have an idea of why it doesn't work but I have no idea if there is any way to solve it. I've tried all kinds of variations on that code already.

        if (!glfwInit()) {
            glfwTerminate();
            throw std::runtime_error("Failed to initialize GLFW");
        }
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
        window_ = glfwCreateWindow(default_screen_width, default_screen_height, "Figure 1", nullptr, nullptr);
        if (window_ == nullptr) {
            glfwTerminate();
            throw std::runtime_error("Failed to create GLFW window");
        }
        glfwMakeContextCurrent(window_);
        glfwSetFramebufferSizeCallback(window_, opengl_3::framebuffer_size_callback);
        if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
            throw std::runtime_error("Failed to initialize GLAD");
        }
        render_loop_ = std::thread([this](){
            while (!glfwWindowShouldClose(window_)) {
                process_input(window_);
                glClearColor(0.9400,0.9400,0.9400, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT);
                // ... render the plot data here
                // ... the main library would lock a mutex and update
                // this plot data one in a while
                glfwSwapBuffers(window_);
                glfwPollEvents();
            }
        });
        render_loop_.join();

The error:

Process finished with exit code 11

If I move the initialization functions inside the thread, then the error becomes:

2020-08-27 00:24:51.436 opengl_test[32373:4580657] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1677.104/Foundation/Misc.subproj/NSUndoManager.m:363
2020-08-27 00:24:51.437 opengl_test[32373:4580657] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.'

If I move the initialization functions to the main thread but create window in inside the thread (from glfwCreateWindow), the error is still:

2020-08-27 00:26:41.232 opengl_test[32645:4584311] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1677.104/Foundation/Misc.subproj/NSUndoManager.m:363
2020-08-27 00:26:41.234 opengl_test[32645:4584311] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.'
ButchDean commented 4 years ago

Maybe somebody could correct me if I am wrong but given that the OpenGL framework is a state machine you cannot split the rendering pipeline between threads, but you can transfer the entire process to another thread if need be.

I tried this a while ago when trying to do similar and it wouldn't work for this reason. HTH

bb1950328 commented 3 years ago

calling glfwMakeContextCurrent(window) when you use OpenGL in a different thread for the first time should help. And if you don't need the context anymore in a thread, call glfwMakeContextCurrent(nullptr); to "unbind" the context. In one of my projects, I use something like this to wrap all my OpenGL calls to make them thread-safe:

void executeOpenGL(std::function<void()> const &functor) {
        static std::recursive_mutex openGlMutex;
        std::lock_guard<std::recursive_mutex> lg(openGlMutex);
        glfwMakeContextCurrent(window);
        functor();
        glfwMakeContextCurrent(nullptr);
}