0

I want to call OpenGL functions from a render thread. I've got a simple queue of std::function<void()>

The idea is to queue commands from the main thread and when Flush() is called, notify the render thread and execute commands that were queued before Flush()

struct CommandQueue
{
    void Submit(std::function<void()> command) 
    {
        commandQueue.push_back(std::move(command));
    }

    bool Execute()
    {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock, [this](){ return !commandsToExecute.empty() || quit; });
        auto cmds = std::move(commandsToExecute);
        lock.unlock();

        if(cmds.empty()) {
            return false;
        }

        for(auto& cmd : cmds) {
            cmd();
        }

        return true;
    }

    void Flush() 
    {
        {
            std::lock_guard<std::mutex> guard(mutex);
            commandsToExecute = std::move(commandQueue);
        }
                
        cv.notify_one();
    }

    void Quit() 
    {
        {
            std::lock_guard<std::mutex> guard(mutex);
            quit = true;
        }
                
        cv.notify_one();     
    }

    bool quit = false;
    std::mutex mutex;
    std::condition_variable cv;
    std::vector<std::function<void()>> commandQueue;
    std::vector<std::function<void()>> commandsToExecute;
};

OpenGL context is created on the render thread

static void OpenGLLogMessage(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
    if(severity != GL_DEBUG_SEVERITY_NOTIFICATION) {
        std::cout << message << std::endl;
    }
}

int main(int argc, char* args[]) 
{
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);

    SDL_Window* window = SDL_CreateWindow("Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 900, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

    SDL_GLContext context = nullptr;

    CommandQueue queue;
    queue.Submit([&](){
        context = SDL_GL_CreateContext(window);
        gladLoadGLLoader(SDL_GL_GetProcAddress);

        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

        glDebugMessageCallback(OpenGLLogMessage, nullptr);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
    });

    std::thread renderThread([&](){
        while(true) {
            if(!queue.Execute()) {
                break;
            }
        }
    });

    bool quit = false;
    SDL_Event event;

    while(!quit) {
        while(SDL_PollEvent(&event)) {
            if(event.type == SDL_QUIT) {
                quit = true;
            }
        }
       
        //does not cause error
        queue.Submit([&](){
            SDL_GL_MakeCurrent(window, context);
        });

        queue.Submit([]() {
           //exception thrown
            glViewport(0, 0, 1600, 900);
        });

        queue.Submit([]() {
            //exception thrown
            glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        });

        queue.Submit([]() {
            //exception thrown
            glClear(GL_COLOR_BUFFER_BIT);
        });

        //does not cause error
        queue.Submit([&](){
            SDL_GL_SwapWindow(window);
        });

        queue.Flush();
    }

    queue.Flush();
    queue.Quit();
    renderThread.join();

    return 0;
}

I am stuck and I have no idea why it does not work. The error I get:enter image description here

@Update: I've found out that

queue.Submit([]() {
           //exception thrown
            glViewport(0, 0, 1600, 900);
        });

is executed before

queue.Submit([&](){
        context = SDL_GL_CreateContext(window);
        gladLoadGLLoader(SDL_GL_GetProcAddress);

        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

        glDebugMessageCallback(OpenGLLogMessage, nullptr);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
    });

and that may cause this error. How, though, can it be executed earlier when the queue goes linearly through the commands? And commands are queued also on one thread, so no shared states exist.

What do I do wrong? How can I fix it?

BrodaJarek3
  • 311
  • 1
  • 9
  • Do you have a backtrace at the point of the crash? Do you have a single threaded version of the code that works? – G.M. Feb 12 '21 at 13:52
  • It is very easy to turn it into a single-thread mode. Either remove `queue.Submit()` and execute code in these lambdas directly, or use `queue.Execute()` in the main loop right after `queue.Flush()`. Backtrace, you mean, things in the call stack? – BrodaJarek3 Feb 12 '21 at 14:13
  • You might want to read [this](https://stackoverflow.com/questions/20850196/what-lasts-after-using-stdmove-c11). Basically, your commandQueue vector is in an unspecified state. Pushing new values to it might give strange results. – BDL Feb 12 '21 at 16:44
  • Ok, it may be in the unspecified state, but after `.clear()` it should be fine. In the answer below, we've already discussed that `.clear()` does not solve the problem. I literally cleared every vector, just to be 500% sure. Also instead of move, I tried to copy. Same error. – BrodaJarek3 Feb 12 '21 at 22:39

1 Answers1

-1

OpenGL cannot be called from multiple threads (by default), so its context is always attached to exactly one thread. To change the attachment, use SDL_GL_MakeCurrent on the thread you want to make gl calls.

In your case, you should add this to the rendering thread:

SDL_GL_MakeCurrent(window,context);

Not relevant, you are doing this part correctly, I missed that in your code.

EDIT

I believe you have to reinitialize GLAD library for each thread, try:

queue.Submit([&](){
    gladLoadGLLoader(SDL_GL_GetProcAddress);
    SDL_GL_MakeCurrent(window, context);
});

You are not checking any return values, you really should.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • I'll try it, but context is created on the render thread, so should not it be already `Current` on that thread? I've just tried it, as I suspected, the result is not positive. Still the same error. – BrodaJarek3 Feb 12 '21 at 11:19
  • @BrodaJarek3 Not sure about SDL, but for GLFW, you have to call it at least once because new fresh context is not attached to anything. – Quimby Feb 12 '21 at 11:21
  • @BrodaJarek3 Oh, I missed that you already are calling it, sorry, in that case I do not know where the problem is, maybe check that all SDL functions returned 0? – Quimby Feb 12 '21 at 11:22
  • @BrodaJarek3 Edited the answer, with another idea :) Hoping that's it. – Quimby Feb 12 '21 at 11:37
  • I mean, something is messed up. Your idea to reinitialize GLAD seemed to work but after few runs, the same thing happened. It looks like commands that were queued in the `main` loop are executed earlier than commands queued once at a program startup and sometimes it is executed the way it should be. So it is more like the queue is written wrong way I think. – BrodaJarek3 Feb 12 '21 at 11:51
  • But when I put a breakpoint on `Execute()` everything is fine. Maybe I need to run my program with the breakpoint and then remove it when all be gucci haha – BrodaJarek3 Feb 12 '21 at 11:58
  • Not sure, if you can, try compiling with `-fsanitize=thread`, it can detect race conditions. – Quimby Feb 12 '21 at 11:59
  • So, I played a bit with visual studio and installed AddressSanitizer, not sure if it is the same, bet it is not and I don't see any threads sanitisers for VS on the internet – BrodaJarek3 Feb 12 '21 at 12:27
  • Nope, ThreadSanitizer is different, it requires clang or gcc. The only thing I see is that moved from vectors are not necessarily empty, try to explicitly clear them. – Quimby Feb 12 '21 at 12:38
  • Clear didnt help either – BrodaJarek3 Feb 12 '21 at 12:41
  • In that case I am sorry, I am out of ideas, hopefully someone else can help you. – Quimby Feb 12 '21 at 13:05