OpenGL on message thread


#1

I’m currently using OpenGLRenderer which renders on a background thread.

How can I instead render on the message (main) thread?

Right now I have to lock a mutex in a lot of places to make sure the event handling code and rendering code don’t stomp on each other. I’d rather just render on the main thread.

thanks!


#2

This is pretty confusing actually. The documentation for OpenGLRenderer::renderOpenGL() says

Note that this callback will be made on a background thread, not the message thread, so make sure that your implementation is thread-safe

And yet the juce OpenGL demo doesn’t do any locking when reading values from the UI. Granted, I suppose they are just a couple floats.


#3

Ok as best I can tell from the docs, I can simply omit the OpenGLRenderer and then paint will be called on my component. Indeed paint is called and the OpenGLContext is active. However, I get a crash in the graphics driver after running my rendering code (which is very battle tested btw) on some separate thread within the driver dll. Sheesh.

Can I call normal OpenGL code from paint, or is that supposed to be reserved for 2d rendering?


#4

The 2D GL renderer uses a lot of state, so obviously if you change any settings while it’s in the middle of a paint call then it will go horribly wrong when it tries to carry on!

Technically you could use it as long as you reset every GL setting that you change before you return from the paint call, but you’d have to be very careful.


#5

The code in the GL-callback needs to run independently from the message thread (thats the trick to have smooth animations). I wouldn’t lock any resources together which also used by the message thread to long. Furthermore I wouldn’t lock the message thread from the callback at all.

The child components, of a component which is attached to a openGL context, will be rendered via normal paint()-calls, of cause on the message thread. This will be cached on a OpenGL image which will be overlaid with current openGL stuff.

If you want to draw fast 2d-graphics, using the 2d-graphics juce context, you need to use the createOpenGLGraphicsContext() directly on the renderCallback. All this operations on this Graphics Context need to be independently from the message thread.

BTW: the openGL Graphics Context isn’t very optimized at this point. (It breaks down anything in to one pixel height lines, which will drawn separately.)

The whole openGL stuff needs to be better documentated


#6

I tried to call MessageManager::callFunctionOnMessageThread but hit an assertion failure because the message thread was locked, as mentioned in the documentation for OpenGLRenderer::renderOpenGL().

If the context is attached to a component in order to do component rendering, then the MessageManager may be locked when this callback is made.

Why is it locked in the GL thread? Can I rely on it always to be locked?


#7

I tried using MessageManagerLock on the rendering thread (since it is re-entrant), but I end up with a deadlock when quitting my app. Is that expected? I can try to reproduce in a small project.

I think what’s going on is OpenGLContext::detach is called with the message thread locked, and then waits for rendering to finish. But rendering can’t complete without the message thread locked, so we have deadlock.


#8

Using juce’s 2d GL renderer isn’t an option right now. I’m using nanovg, which is really fast.


#9

Yes, thats always happens when you use the the MM-Lock, re-entrent makes no difference (re-entrance only means only if the same thread is locking the same resource twice it will not wait, but if its locked on another thread it will wait.) You have to find a design without locking the Message-Thread.

If both, the message thread & openGL thread need access to the same resource, use an additional CriticalSection to manage the access. Or use some kind of ring-buffer (FIFO) for communication.


#10

Right now I have on the order of 50 mutex locks (equivalent to juce’s CriticalSection I assume) around functions that mutate the app’s data model or render it. This is incredibly error prone, as I’ve added functions and forgot to lock the mutex, only to find intermittent crashes. Setting up some sort of FIFO for rendering of my scene would be even more of a mess. So you see why I want to lock the message thread, or just render on the message thread :wink:

Why does JUCE force the programmer to do GL on a separate thread? iOS doesn’t. Mac doesn’t. This is incredibly frustrating.


#11

On some platforms like Android it’s impossible to render on the message thread, so we implemented it in a way that works for all targets.

But of course you can use a MessageManagerLock in the GL render callback as long as you check for deadlocks. I think that on some platforms you could probably render on a Timer callback if you activate the GL context manually, but I don’t recommend that approach - using a thread is almost always the best approach. I suspect that if you’re struggling with too many locks you may just need to re-think your data model strategy.


#12

That sounds good! Should I just use an atomic boolean to ensure the rendering thread doesn’t lock while OpenGLContext::detach is called?


#13

Well that didn’t work. Still deadlocks sometimes. My renderOpenGL call looks like:

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

if (!_detaching) {

	// Block event processing while rendering.
	const juce::MessageManagerLock mmLock(juce::Thread::getCurrentThread());

	if (!mmLock.lockWasGained())
		return;

	if (_vg) {

		_editor->Draw(_vg, openGLContext.getRenderingScale());

		_editor->Timer();

	}

}

I set _detaching to true before calling detach on the main thread. I suppose it is still deadlocking because _detaching can be set between the check for _detaching and the lock.

Message thread:

Audulus.exe!juce::WaitableEvent::wait(int timeOutMs) Line 64	C++
Audulus.exe!juce::ThreadPool::waitForJobToFinish(const juce::ThreadPoolJob * job, int timeOutMs) Line 181	C++
Audulus.exe!juce::ThreadPool::removeJob(juce::ThreadPoolJob * job, bool interruptIfRunning, int timeOutMs) Line 215	C++
Audulus.exe!juce::OpenGLContext::CachedImage::pause() Line 128	C++
Audulus.exe!juce::OpenGLContext::CachedImage::stop() Line 114	C++
Audulus.exe!juce::OpenGLContext::Attachment::detach() Line 571	C++
Audulus.exe!juce::OpenGLContext::detach() Line 761	C++

rendering thread:

Audulus.exe!juce::WaitableEvent::wait(int timeOutMs) Line 64 C++
Audulus.exe!juce::MessageManagerLock::attemptLock(juce::Thread * threadToCheck, juce::ThreadPoolJob * job) Line 327 C++
Audulus.exe!juce::MessageManagerLock::MessageManagerLock(juce::Thread * threadToCheck) Line 284 C++
Audulus.exe!AudulusEditorComponent::renderOpenGL() Line 73 C++

Jules, you must have already solved this for the 2D GL renderer, so this is doable, right?


#14

Ok this seems to work:

// Block event processing while rendering.
const juce::MessageManagerLock mmLock(juce::ThreadPoolJob::getCurrentThreadPoolJob());

if (!mmLock.lockWasGained()) {
	return;
}

The key being to get the current ThreadPoolJob, not the current Thread. Does that seem right?


#15

Yes, we changed it recently to use a threadpool internally, so that’s what you’d need to check.


#16

Thanks Jules!


#17

Did you actually get this to work?

I’m trying to do the same thing (locking MM from renderOpenGL), but still get the deadlocks when closing the plugin window.


#18

Howdy @myhrman! It sounds like you’re using/trying to use OpenGL in an audio plugin, and so far I haven’t found any JUCE user other than me that does it…
So I’m asking: could you please check if your plugin works on Steinberg’s Wavelab on OS X as VST2? I suspect that it may not work (OpenGL context will get closed), but luckily in that case I also have a solution.
Cheers! Yair


#19

Hi @yairadix, yes I can certainly try this, but will probably take another week before I can find the time. I currently don’t have wavelab installed on macOS.

Would be interested in hearing about your solution / workaround, in case I see similar results.

And yes, I’m indeed trying to use OpenGL in a plugin. It hasn’t been easy! I want to implement both an OpenGLRenderer (for some smooth 60FPS graphs) as well as traditional JUCE components (rest of the GUI) rendered in the same OpenGL-context. Had to try various different approaches, but still not completely happy with the solution.


#20

I maintain a branch where the problem is fixed, but I’d certainly like more feedback/testing etc. Of course I’d like to contribute any fixes or improvements to main JUCE but it’s not always possible.

This issue (Wavelab+VST+OpenGL+macOS problem) originated in a fix to another problem. So that fix is reverted in the SR branch where a different fix for that issue was applied. Note that the OpenGL module is a bit different in the SR branch in that it uses CVDisplayLink on macOS, which appears to be important for the performance, especially when multiple plugin windows are open.

Cheers, Yair