0
\$\begingroup\$

I am working on a very complicated graphics rendering, using OpenGL, and it is very time consuming, in the sense that it creates a delay in the event handling, of my application to an extend that, the OS will think my application is freezing ! Therefore, I was thinking of running two threads, one for the OpenGL rendering and the other for event handling. But, I dont think this is possible for my current framework that I'm using!

Basically, I'm using SDL 2.0 to set up an OpenGL context, and then using vertex and fragment shaders to do the rendering.

Code:

// Initialize OpenGL context
// Set up VAO, VBO, compile vertex/frag. shaders, link, use program  etc. 
// 
bool run = true;
SDL_Event e; 
while(run)
{
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
       render(); // Complicated rendering :(
       SDL_GL_SwapWindow(window); 
       while(SDL_PollEvent(&e))
       {
             switch(e.type){
                   case SDL_QUIT:
                        run = false;
                        break; 
             }
       }
}

As you might realize if render is slow, then event handling will lag. This might crash my application !

The SDL library wants everything in the same thread I guess. If I move the event handling part to another thread, then OpenGL won't render anything! Similarly, if I move the OpenGL based render function to another thread, still it won't render anything!!! In fact event handling is a must for SDL in particular and to the OS in general.

What is the practical remedy to this issue?

\$\endgroup\$
1
  • \$\begingroup\$ did you make the context current in the other thread? \$\endgroup\$ Commented Mar 24, 2021 at 11:53

2 Answers 2

2
\$\begingroup\$

OpenGL is not multithreaded. All your GL commands must run in the same thread in which the GL context was created: it is not shareable between threads.

Any attempt to share the context between threads will lead to undefined behaviour.

Your high ms/frame time is due to everything being done on a single thread.

I propose two practical solutions to your problem:

  1. Create a dedicated graphics thread.
  2. Offload all other work to other threads, and keep graphics on the main thread.

Generally this is 6 and half a dozen. Just choose one and go for it.

But as a general guide for the first solution:

When your program starts, spool up a thread for graphics. Refactor all your rendering code into a renderer class, that can be called from your rendering thread function.

The renderer handles window and context creation & destruction, and all rendering calls. It should also keep track of renderable "objects", which will be modifiable by the main thread. This means the renderer will need to be made "thread-safe" via mutexes.

Your renderer API should look something like this:

class Renderer
{
public:
    Renderer(void) { }
    virtual void addEntity(entity* e) = 0;
    virtual void removeEntity(entity* e) = 0;
    virtual void clearAllEntities(void) = 0;
    virtual void init(void) = 0; // creates window/context
    virtual void render(void) = 0; //performs actual drawing
    virtual bool shuttingDown(void) = 0; 
    virtual void shutDown(void) = 0;
    virtual ~Renderer(void) { }
}

This is known as the Interface for the renderer. You will implement that interface yourself. Your main thread will call the add/remove/clear functions.

Your thread function should look like this:

int threadFunc(void* arg)
{
    Renderer* renderer = std::dynamic_pointer_cast<Renderer>(arg);
    renderer.init();
    while (!renderer->shuttingDown())
    {
        renderer->render();
    }
    return 0;
}

Now the thread will just draw as fast as it can, whilst your main, and other, threads do other processing.

Once you do this, that thread, and only that thread will perform GL calls.

If you want to load resources (mesh data, textures etc) to the gpu, you either need to load it first into memory, then let the renderer handle the upload to gpu ram, or have a seperate gl context specifically for loading resources on another thread. However, if you choose the latter, then you need to make fool around with making contexts "current", which is a headache too, so some thought is required on that part.

I won't say much more on this, because, quite honestly you could write a book on this subject. The general idea is the same as Ghostbusters: Don't cross the streams!

Keep all your GL code to a single thread, but there are no restrictions on which thread that is.

\$\endgroup\$
1
\$\begingroup\$

From the Blue Book (OpenGL Superbible 7th ed., pp. 662f.):

You can have multiple contexts and share data between them, but that isn't very effective. The approach sketched in the other answer (one render thread - mutliple data generation threads) is surely the best practice.

You can have buffer objects to your application's needs and pass pointers around between threads and synchronize between threads and OpenGL calls. See OpenGL documentation for its synchronization functionalities.

You can also create commands for OpenGL by packing parameters into buffers and signal the main thread to turn them into OpenGL calls. This approach builds data structures in the generating threads and sends them to the main thread for analysis, to choose between varying functionalities and to hand them over to OpenGL. See glDrawElementsInstancedBaseVertexBaseInstance() to find out which data can be parametrized in that way.

The rest is programming :-)

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .