OpenGL + Threads... again


#1

Is there any chance of a thread-based openGL example?

I just tried an OpenGL experiment using a timer, and it worked well enough but was interfering too much with the message thread. So, I tried using a thread for the rendering and faced plenty of varied problems.

  • deadlocks from the rendering thread calling makeCurrentContextActive() [which enters the contextLock]. when it tries to createNativeWindow() (creating the context, at the start), the message manager is used to call the createWindowCallback; meanwhile, the message manager is waiting for the context lock to be free [from updateContextPosition - called due to visibility change].

  • If i put some arbitrary delay in before the thread is started (e.g. wait til mouseMove), then it doesn’t deadlock, but the thread’s calls to makeCurrentContextActive() always fail, so it doesn’t do anything.

If i just leave it to the renderAndSwapBuffers() function from the main thread, then it works - though I’d prefer to ‘override it to decouple it from paint()’ [as from the docs] and do the rendering from another thread.

Searching the board just makes it seem like it should be easier than it is, without any actual answers!


#2

Hi,
This is how i got the opengl running in a different thread.

  1. Create context, create textures or other things you want to do before starting the render thread.

  2. call makeCurrentContextInactive() to make sure there is no active context before starting the thread otherwise you will have a problem.

  3. start the thread.

  4. in the thread code make sure you activate the context before staring to render and inactivate it again when finished.


void GlEngine::run() 
{

	while (! threadShouldExit())
	{	
		render();
		wait(15);
	}
 
}

void GlEngine::render() 
{   
	
  openglcanvas->makeCurrentContextActive();
  //render your things

  openglcanvas->makeCurrentContextInActive();



}

[/code]


#3

You make it sound so simple! And yet, still it just doesn’t do it. When you say ‘create context’, do you mean your own OpenGLContext subclass? If not, then … how? The context gets created itself, from - say - makeCurrentContextActive().

It seems no matter what I try, the OpenGL stuff just don’t-got-no-lovin’ for the thread


#4

Hi haydxn,
Yes sorry if it sounds too simple, it’s hard to post the code is use in my own application because it is cluttered with unrelevant code.
Give me an some time and i will post some ready to go code.


#5

Hi again,
I modified the Opengl Demo code from the juceDemo application.
just copy and past and it should work.
Sorry i don’t have time to post comments but let me know if you have any questions or suggestions.

Cheers
Edwin

[code]
class DemoOpenGLCanvas : public OpenGLComponent

{

public:
DemoOpenGLCanvas()
{
}

~DemoOpenGLCanvas()
{
}

// when the component creates a new internal context, this is called, and
// we'll use the opportunity to create the textures needed.
void newOpenGLContextCreated()
{
	//

	//	
}

void renderOpenGL()
{

}

void resized()
{

}

bool renderAndSwapBuffers()
{
	return true;
}

};

class CanvasSelector
{
private:
DemoOpenGLCanvas* selectedCanvas;
public:
CanvasSelector(DemoOpenGLCanvas* const canvasToSelect)
: selectedCanvas(canvasToSelect)
{
selectedCanvas->makeCurrentContextActive();
}

~CanvasSelector()
{
	selectedCanvas->makeCurrentContextInactive();
	selectedCanvas = 0;
}

};

//==============================================================================
class OpenGLDemo : public Component, public Thread
{
//==============================================================================
DemoOpenGLCanvas* canvas;
float rotation, delta;
CriticalSection renderCriticalSection;

public:
//==============================================================================
OpenGLDemo()
: Thread( T(“renderThread” ) )
{
setName (T(“OpenGL”));

    canvas = new DemoOpenGLCanvas();
    addAndMakeVisible (canvas);
			 
	const CanvasSelector cs ( canvas );	
    

	rotation = 0.0f;
    delta = 1.0f;			

	startThread( 7 );

}

void resized()
{
	const ScopedLock sl ( renderCriticalSection );	
	const CanvasSelector cs ( canvas );	
	
    canvas->setBounds (10, 10, getWidth() - 20, getHeight() - 50);
	
	glViewport(0, 0, (GLsizei) canvas->getWidth(), (GLsizei) canvas->getHeight() );		// Reset The Current Viewport
		
}


void render() 
{
	const ScopedLock sl ( renderCriticalSection );	

	const CanvasSelector cs ( canvas );	

	glViewport(0, 0, (GLsizei) canvas->getWidth(), (GLsizei) canvas->getHeight() );		// Reset The Current Viewport

	//clear the depth and color buffers
	glClearColor(1.0,0.0,0.0,1.0);		
	glClearDepth(1.0);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//make sure all the matrices are reset::ren
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
  
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	
	gluPerspective(60.0f, (float) canvas->getWidth() / (float) canvas->getHeight(), 0.1f, 100.0f);	// Calculate The Aspect Ratio Of The Window
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	rotation += delta;

	glPushMatrix();

	glTranslatef(0.0f, 0.0f, -4.0f);

    glColor4f (1.0f, 1.0f, 1.0f, 1.0f);
	
	glRotatef( rotation , 1.0, 1.0f, 0.0f);

	glBegin(GL_QUADS);
		//lower left
		glVertex2f(-1,-1);

		//upper left
		glVertex2f(-1,1);

		//upper right
		glVertex2f(1,1);

		//lower right
		glVertex2f(1,-1);
	glEnd();

	glPopMatrix();

	canvas->swapBuffers();

}
void run()
{
	
	while (! threadShouldExit())
	{	
		render();
		wait(15);
	}

}


~OpenGLDemo()
{
    deleteAllChildren();
}


juce_UseDebuggingNewOperator

};
[/code][/code]


#6

Well I tried turning that into a quick juce app (download) and it has the same problem; it deadlocks as soon as the program starts, in exactly the same place as my other test did.

Is there any chance anyone could have a look at the zip and see if they can figure out why it doesn’t work?


#7

The only right way to initialize opengl contexts is when the rest of the gui init is done (ie it cannot be done in the component’s constructor). This is because of the aweful win32 opengl implementation that juce nicely trying to hide.

Anyway, while playing around with your code, I found some of the problems you were talking about, mainly due to Win32ComponentPeer being not thread safe when interacting with the gui, some of these may be fixed so that initialization can be done anytime.
However this is not a major problem, because if you do a serious opengl application, you surely want to control exactly when threads get started and stopped, and when the opengl contexts get created and destroyed, and it’s ok to be forced to do it outside of the constructor / desctrutor.

If jules doesn’t decide to change this behavior, the context must created after any makeVisible / addChildComponent / setSize / … that might have some undesirable effects on the component peer,
the best solution is to do all the gui operations you want to set it up, and then start your rendering thread, which will let the context be created on the first makeCurrentContextActive() call.
stopThread() must also be called before any attempt to remove the component from its parent.

I also did some tests for multiple contexts in multiple threads because I need this for my application, and the surprise is that you can’t initialize multiple opengl contexts in the same time, mainly because some global variables that are not intended to be used from multiple threads.
below is a patch that made it work on my machine, but I don’t think it’s complete, and I don’t know if it is the real solution … is it jules ?
If you want to make some crash-tests and have a chance to make your graphic card burn, just increase the number of created contexts by changing the number here :

//MainAppWindow.cpp:52 // This creates a 8x6 grid of OpenGLComponentWithFps ComponentArray <OpenGLComponentWithFps> * content = new ComponentArray <OpenGLComponentWithFps> (8, 6);

you can get the modified version here
just extract it in juce’s ‘extra’ directory, I included a project file for visual studio 2005.

the patch for multiple context initialization :

[code]Index: src/juce_appframework/events/juce_MessageManager.h

— src/juce_appframework/events/juce_MessageManager.h (revision 338)
+++ src/juce_appframework/events/juce_MessageManager.h (working copy)
@@ -199,7 +199,7 @@
friend class ActionBroadcaster;
static MessageManager* instance;

  • SortedSet<const MessageListener*> messageListeners;
  • SortedSet<const MessageListener*, CriticalSection> messageListeners;
    ActionListenerList* broadcastListeners;

    friend class JUCEApplication;
    Index: src/juce_appframework/events/juce_Timer.cpp
    ===================================================================
    — src/juce_appframework/events/juce_Timer.cpp (revision 338)
    +++ src/juce_appframework/events/juce_Timer.cpp (working copy)
    @@ -323,7 +323,8 @@

//==============================================================================
#ifdef JUCE_DEBUG
-static SortedSet <Timer*> activeTimers;
+static SortedSet <Timer*, CriticalSection> activeTimers; // add Critical section cause this code is called by

  •                                                     // Win32ComponentPeer::TemporaryImage, called bu setPixelFormat()
    

#endif

Timer::Timer() throw()
Index: src/juce_appframework/gui/components/special/juce_OpenGLComponent.cpp

— src/juce_appframework/gui/components/special/juce_OpenGLComponent.cpp (revision 338)
+++ src/juce_appframework/gui/components/special/juce_OpenGLComponent.cpp (working copy)
@@ -69,7 +69,7 @@
}

//==============================================================================
-static VoidArray knownContexts;
+static Array <void*, CriticalSection> knownContexts;

OpenGLContext::OpenGLContext() throw()
{
Index: src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp

— src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp (revision 338)
+++ src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp (working copy)
@@ -59,7 +59,8 @@

static const int fakeMouseMoveMessage = 0x7fff00ff;

-static VoidArray heavyweightPeers (4);
+static Array <void*, CriticalSection> heavyweightPeers (4); // add a critical section cause it is used by getPeer() which

  •                                                        // is used in opengl context initialization
    

//==============================================================================
[/code]


#8

I had the same problem and came around to see if I can find the solution. first I tried to lock the context. but I failed . Just wanted to say that the simple code which is posted by gekkie100 worked for me. and it was great. although it makes no sense to make current context inactive just before runThread() . but it worked.

thanks


#9

You are probably using a single core processor, aren’t you ?

When using only 1 context per thread (ie if you don’t switch to another context in the same thread) it makes no sense doing makeCurrentContextActive() more than once.


#10

No, I am using a dual core processor. and if I take out that line of code before runThread() . The program will not work. What I meant was that It didm’t make any sense to me conceptually why I need such a thing . now that you explained, I know. thank you.


#11

thomas, am i right to assume that the altered project code you put up requires the change to the Juce code in order to work? [i tried to run it without and it fails pretty randomly inside some ComponentPeer functions]

perhaps it would be easier to explicitly create a context, but i guess that would involve creating a custom ‘OpenGLContext’ subclass - a little daunting seeing as it’s so abstract! tbh I’m quite surprised that it doesn’t just do it that way anyway - i.e. you create a context yourself (using a base with slightly less pure virtuals to define) and provide that for the Component to use. It seems a little strange to have the component demand that it be responsible for creating the context when it likes [using some ‘standard’ context] with no real straightforward way of simply instantiating one yourself; but then i must confess to having a fairly limited knowledge of OpenGL at this point.

All i can really see is the option of creating (laboriously) your own context subclass, and providing it to be a ‘shared’ context; i just want to be able to say ‘here is my context, i only want one - this one - i made it just for you, now you OpenGLComponent can use that! no sharing, nuh-uh, just this context. use it, use the hell out of it and have fun’ - but the OpenGLComponent just wants to make its own.

does this way of thinking prove that i don’t know enough about openGL? i imagine so!


#12

you should be able to run it without juce’s modification if you create only one context (you’ll have to change ‘new ComponentArray (8, 6)’ to ‘new ComponentArray (1, 1)’ for example).

The patch is required if you create multiple contexts, and particularly at the same time. I don’t claim the patch will work on any system, there are probably other issues involved, but I just tried to fix the bugs I met.
I think the problem is that juce’s gui classes were never intended to be multithread-safe, they have been designed to work in one thread.
So the real solution must be changing the way the context is created, but I don’t know if it is possible and how.

To create a windowed opengl context, you need to first create a window (depending on where your window located, you might not have the same capabilities). I don’t know if this window creation can be isolated from other juce’s classes so that they don’t have to be thread-safe, but windowed opengl context creation will still need position and size to be created.

Jules can you hear us ? : )

Our application now uses juce to create multiple contexts in a separate thread, and it works with the current svn tip, only we had to be very careful about the initialization and destruction order. There are probably still bugs cause the big opengl changes in juce are quite new.

This is more about how the win32 implementation and its conflicts with juce design, hopefuly once it is done you can have fun with the real part of opengl.


#13

Hi,
There is allways a lot of talk about using OpenGL with multiple threads. There is a lot of debate wether you get better performance with mutliple threads.

I use a working thread for gathering video data and a render thread to upload the video data and do all kind of fancy stuff (we are developing vj software).
Only one thread at a time can have a context active. So using multiple threads you will allways have to activate an decativate your context in your render loop.

So i guess when you create an OpenGLcomponent it is created in the applications background thread, so in order to use it in your own thread you will have to deactivate it first before starting the thread.
We are using multiple contexts in one thread, so not multiple threads for different contexts.


#14

[quote=“gekkie100”]Hi,
There is allways a lot of talk about using OpenGL with multiple threads. There is a lot of debate wether you get better performance with mutliple threads.[/quote]
Well that’s what I would like to profile by extending the test program as soon as possible.


#15