OpenGL Thread MessageManager Lock


#1

=== Background ===

I have a question about MessageManager locks in juce_OpenGLContext.cpp. This whole post assumes that a component is attached to the OpenGL context and renderOpenGL() happens on a separate thread. I had previously been writing lock code with std::mutex to protect resources that are accessed from both my GL thread and my main JUCE message thread. I now suspect that this is not necessary. It seems that renderFrame() in juce_OpenGLContext.cpp acquires a MessageManager lock anyway, so renderOpenGL() is always mutually exclusive with the JUCE message thread.

Is my understanding here correct? I put some debug prints into renderFrame(), and it appears that the MM lock is always taken before renderOpenGL(). There’s an if()-else() path that allows for the MM lock to not be taken, but that code path never triggers (as far as I can tell).

Here’s the relevant file:

Look for the renderFrame() function, specifically.

=== Questions ===

  • Will the MM lock in renderFrame() ever not be acquired? It looks like this is possible if a renderFrame() happens and needsUpdating is false, but will this ever be the case?

  • If renderOpenGL() and the JUCE message loop both acquire the MM lock then why have them be on separate threads anyway? It seems to me that the main reason to do GL rendering on a separate thread is to prevent expensive main thread work from blocking GL rendering (and to prevent expensive GL rendering from blocking the main message thread). But if both threads acquire the MM lock then having them be separate threads in the first place feels moot. Is there something that I’m missing here?


#2

A related followup:

I just founds these docs here:
https://www.juce.com/doc/classOpenGLRenderer

The section for renderOpenGL() says “the MessageManager may be locked when this callback is made.” Why is the word “may” used there? What are the cases where the MM is not locked in renderOpenGL()? Documenting that feels pretty critical for thread safety.


#3

Well, if you read the whole thing, it actually says:

If the context is attached to a component in order to do component rendering, then the MessageManager may be locked

If it’s being used for component rendering then it’ll be locked (it has to be so that the Component methods can be safely called). If not, then it won’t be locked.

I agree the docs are a bit vague there, I’ll try to improve them.


#4

Yes, renderFrame will be called without the MM lock being acquired if needsUpdating is false. This will happen if you have continuousRepaint enabled and none of the components are dirty (i.e. need updating). You can easily see this if you run the JUCE demo and open the “OpenGL” teapot demo. After the components have settled, enable a breakpoint at juce_OpenGLContext.cpp:249: you will see that the message manager won’t be locked (mmLock=nullptr).

 [quote=“pmr, post:1, topic:22503”]
If renderOpenGL() and the JUCE message loop both acquire the MM lock then why have them be on separate threads anyway?
[/quote]
It wouldn’t make sense to have them on separate threads if both would always acquire the MM lock. But as stated, this is not the case. Basically, the use-case that JUCE is solving, is having pure OpenGL animations along-side some (mostly) static JUCE components - like in the teapot demo. In these sort of use-cases the renderOpenGL render callback will run independently from the message thread.