using OGL in multiple threads?

Started by supagu, April 18, 2010, 12:59:58

Previous topic - Next topic

supagu

so up until now, i have been using opengl through lwjgl in a single threaded fashion. the problem is, i want to be able to have an opengl drawing a loading screen in 1 thread, and in another thread, have it loading (eg. loading textures in opengl)
the problem is, you need to make the current thread able to use use opengl, normally done via
eglMakeCurrent in java or wglMakeCurrent( hDC, hRC ); in win32 programming.

in lwjgl, can i do this?
there appears to be a
makeCurrent

as part of the display not much documentation on this stuff though

spasi

Yes, you can use the makeCurrent method in Display/AWTGLCanvas/Pbuffer. It will call the native wglMakeCurrent (on Windows) internally.

supagu

but will this do what i want it to do as well? will it allow my to load an opengl texture in a thread which is not the rendering thread, assuming i call makecurrent in the other thread before loading the texture?

Kai

Quotebut will this do what i want it to do as well? will it allow my to load an opengl texture in a thread which is not the rendering thread, assuming i call makecurrent in the other thread before loading the texture?
Hi there.

Fact is that only one single thread can have a given OpenGL context current at the same time. So that would be a "no". It is not possible to use an OpenGL context with more than one single thread at a given time.

To resolve this, most people use a single thread for rendering. This thread will be given a queue of "Tasks" to complete (implemented via Runnable instances for example). This queue can be filled up with tasks by as many threads as you want (provided that the queue is thread-safe).

What you could do (theoretically, not practically tested yet), however, is to create multiple shared contexts and use these to render via multiple threads (each context with its own thread).

supagu

yeah only 1 thread at a time can issue OGL commands. so my idea is

thread 1 - acquires a mutex when rendering the loading screen, release the mutex lock when done

thread 2 - acquire mutex when loading a texture (or issusing some OGL commands) and release when done

this way the threads swap over to using the opengl context when they need it, the other thread gets blocked till the mutex is acquired.

I'm not having much success with this, I've tried  calling Display.makeCurrent() in the active thread to try to bind the OGL context to the current thread, but i am getting an error:

"Exception: From thread Thread[Thread-3,5,main]: Thread[main,5,main] already has the context current"


edit:
I  just got this working, i need to call releaseContext when finished issuing OGL commands in the thread :)

im just wondeirng whats happening in Display.releaseContext()?

spasi

Display.releaseContext does a wglMakeCurrent(NULL, NULL) internally, which is necessary if you want to make a context current in another thread. You can't have the same context current in 2 threads, you need to clear the current context in the first thread before making it current in the second.

With that said, if I understood correctly what you're doing, there's no gain from passing around a GL context between threads. You could achieve the same result if you do what Kai suggested (faster even, as there's no GL context switching involved). That is, load a texture/model/whatever in a background thread (the slow, IO related part), then synchronize with the main rendering thread and load the texture/VBO/etc from there. The Java-side synchronization cost remains the same, but you don't stall/flush the rendering pipeline by switching contexts, the driver is able to do other stuff while uploading your texture data to the GPU.

Anyway, the most efficient way to do multithreaded data loading requires 2 separate contexts with sharing enabled (see wglShareLists or the second parameter of the new wglCreateContextAttribsARB). Since we don't support multiple Displays in LWJGL, the only way to achieve this now is by using a Pbuffer. You create the Display in the main rendering thread, then you create a Pbuffer and pass Display.getDrawable() to the shared_drawable argument of the Pbuffer constructor. You call Pbuffer's makeCurrent in the background loader thread and start loading stuff. This way synchronization is done at the driver level basically, with most modern drivers supporting true multithreaded data uploads (the pipeline never stalls). You'll only have to do some basic synchronization for passing texture/VBO/etc IDs to the main rendering thread (that have been created in the Pbuffer thread).

I've also read that it is possible to create multiple contexts from the same device context. I'll investigate this and will try to add some new API to LWJGL that will allow the above scenario without the need of a dummy Pbuffer, so stay tuned.

spasi

OK, I tried it and it works fine. I've added a new Drawable implementation, called SharedDrawable, that you can use to do background thread loading of resources, like so:

Rendering Thread:

...
Display.create();
...


Background Thread:

Drawable sd = new SharedDrawable(Display.getDrawable());
sd.makeCurrent();
...
int texID = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texID);
GL11.glTexImage2D(...);


You can then send texID to the rendering thread and use it for rendering. I've added an LWJGL test as an example of the above (org.lwjgl.test.opengl.multithread.BackgroundLoadTest). I've also added a few methods in the Drawable interface, they might be useful to some people: makeCurrent(), releaseContext(), destroy().

You should be able to test it with the next LWJGL build, let me know if you encounter any problems (only tested on my ATI so far).

princec

Hmm hold on a sec - can we not have a little think about how to properly create, destroy, and expose multiple contexts here? I'm not especially happy with AWTGLCanvas and not especially happy with Display.setParent()... would really rather have GLContext APIs that did everything we need them do to.

Cas :)

spasi

When you say multiple contexts, you mean multiple windows (Displays) right? Because in order to have a GL context, you need a device context first. We have support for 3 device contexts right now: Display (native OS window), Pbuffer (headless device) and the AWT drawing surface that's under AWTGLCanvas. In this sense, I think it's ok to expose Drawable to the user instead of Context (which is a package private class atm), since it's not really simple to hand any random Context to the user, especially if it can be destroyed. There's a bit of clean-up that needs to be done for each drawable implementation when its context is destroyed, so it's only natural to wrap/hide this with the Drawable interface. Also, there isn't anything particularly interesting about a Context, except makeCurrent(), which I just added to Drawable. The only other API that might be of interest to the user is GLContext.useContext(), which allows custom contexts to be used with LWJGL (e.g. SWT GLCanvas) and it's just fine as it is (a static utility method to support a static OpenGL API).

The issues we have now are:

1) No support for multiple Displays. As we've discussed before, it should be possible to fix this without breaking existing code, but it will take some serious refactoring and I'm not sure how "clean" the result is going to be. If it gets too messy, an option would be to break API compatibility and call it LWJGL 3.0.

2) No support for multiple contexts created from the same device context. This has been fixed in the current nightly with SharedDrawable. Assuming no issues arise (only tested on ATI/Win7), I think it's a clean solution.

3) The Drawable interface exposes Context getContext() and (as of the last commit) Context createSharedContext(). The Context class is package private and it's an implementation detail that got public by accident. We should refactor this (would be easier with 1) fixed).

4) We're not happy with AWTGLCanvas and Display.setParent() as you said. The solution I can think right now would be to get 1) fixed, remove AWTGLCanvas and add a new Drawable that implements the same thing that Display.setParent() does now (or simply replace the current AWTGLCanvas implementation).

MikOfClassX

Is there a way to know it the context is already current ? I didn't find anything "accessible" in the API.

I'm asking this because making current a contex which is already current normally leads to an LWJGL exception.

Mik

spasi

I could add boolean isCurrent() if there's a need for it.

MikOfClassX

Please! Do it!!

I think it's a logical addition..

Maybe it would be nice not to throw an exception but simply no-op in case the context is already current.

broumbroum

Quote from: spasi on April 21, 2010, 11:52:12
---

1) I'm not sure what multiple displays you're talking about : is that multiple screen devices (stereoscopic ?) or multplie scenes ? I'd be to add something like stereoscopic management, see how it is coming out in the theater these days (avatar, 3d hdtv, etc.)
2) Good to know that. ,)
3) How's that better ? ,D
4) Not again, removals ain't no good practices. looks like many people don't like the AwtGlcanvas in here... I'm of those who like to gain access to "extensible" libraries, like LWJGL is, open and friendly to impl. So AWtglcanvas let people (like me) customize render and other things directly "out of the sandbox". that is a good reason, isn't it ?

spasi

The next nightly build will have isCurrent(). I also removed getContext() and createSharedContext() from the Drawable interface.

Broumbroum, 1) means having multiple windows, either serially rendered by the same thread, or simultaneously by different threads. There's probably no real use for that, as most applications that require multiple viewports would use Canvas. The problem is multiple Canvases and Display.setParent(). If we had support for multiple Displays, we could do something like new Display(canvas) instead. So the idea is to make 4) better, not remove support for it.

MikOfClassX

Many thanks Spasi! It is already in the  Build #516 ? I didn't see it in the changes.

My experience about multiple displays: I did not find any problem in using multiple PBuffers/FBOs, do all the GL work offscreen and then show it somewhere (Window, canvas, JComponent). If you're looking for smooth animations, the multiple-buffering (BufferStrategy) API of Java is useful to display fast graphics without the tearing effect.

Cheers,
Mik