Linux X-Window Threads and OpenGL again again


#1

So, I’m re-writing my OpenGLComponent derived class to hopefully avoid threading problems with X.

That’s involved using my own private displayconnection, renderContext and glx window (to attach to the peer), but in order to access the sharecontext, please could we add to OpenGLComponent:

Also, what does updateContextPosition() do? It’s private, and it seems to refresh something. Do I need to re-implement that somehow?

I’ll report back if/when I can get everything stable.

Bruce[/code]


#2

Sure, I can add that. The updateposition thing is for putting the heavyweight window in the right place when the component moves around.


#3

Bruce, I’d be very interested in hearing from you if and when you figure out a nice, thread-safe way of doing the OpenGL thing under linux! Would you feel comfortable sharing your code?


#4

Sure, absolutely.

Off the top of my head, the tips are (and I haven’t solved everything yet, I still get X BadMatch errors):

[list]Use a separate Display Connection, GLX context etc. per thread
Don’t initialize or access the items created on the thread outside of the thread
[/list]

The main implication to that from Juce is that if you need to do any rendering from a thread you need to take control of all GLX calls on that component. In my case, I took my OpenGLComponent subclass, override the draw/render methods as Jules intended, and never call swap on it or anything else. Then, in the thread, detect whether we’ve initialized, and do something like this:

[code]void ScreenComponent::render(SyncSourceTimeRecord inTime)
{
ScopedLock l(getContextLock());

#if JUCE_LINUX
// on linux, we deal with the context ourselves, so threads are OK
if (threadDisplayConnection == 0)
{
    String displayName  = T(":0.0"); // force 0.0 for GUI!

    threadDisplayConnection   = XOpenDisplay (displayName);
}

if (renderContext == 0)
{
    needsReshape    = true;
    // get the top level X Window - we're going to tag onto it
    ComponentPeer* const peer = getTopLevelComponent()->getPeer();

    if (threadDisplayConnection && isShowing() && peer != 0)
    {
        XSync (threadDisplayConnection, False);

       GLint attribs[]		        = {	GLX_RGBA,
                                        GLX_DOUBLEBUFFER,
                                        GLX_RED_SIZE,			8,
                                        GLX_GREEN_SIZE,			8,
                                        GLX_BLUE_SIZE,			8,
                                        GLX_ALPHA_SIZE,			8,
                                        GLX_DEPTH_SIZE,         16,
                                        None };

        int numConfigs				= 0;
        int screen					= DefaultScreen (threadDisplayConnection);

        XVisualInfo* const bestVisual = glXChooseVisual (threadDisplayConnection, screen, attribs);

        if (bestVisual == 0)
            return;

        renderContext       = glXCreateContext (threadDisplayConnection, bestVisual,
                                                (getShareContext() != 0) ? (GLXContext)(getShareContext()->getRawContext()) : 0,
                                                GL_TRUE);

        Window windowH      = (Window) peer->getNativeHandle();

        Colormap colourMap  = XCreateColormap (threadDisplayConnection, windowH, bestVisual->visual, AllocNone);
        XSetWindowAttributes swa;
        swa.colormap        = colourMap;
        swa.border_pixel    = 0;
        swa.event_mask      = ExposureMask | StructureNotifyMask;

        embeddedWindow      = XCreateWindow (threadDisplayConnection, windowH,
                                        0, 0, 1, 1, 0,
                                        bestVisual->depth,
                                        InputOutput,
                                        bestVisual->visual,
                                        CWBorderPixel | CWColormap | CWEventMask,
                                        &swa);

        XSaveContext (threadDisplayConnection, (XID) embeddedWindow, improbableNumber, (XPointer) peer);

        XMapWindow (threadDisplayConnection, embeddedWindow);
        XFreeColormap (threadDisplayConnection, colourMap);

        XFree (bestVisual);
        XSync (threadDisplayConnection, False);

        if (renderContext != 0)
        {
            //updateContextPosition();
        }
    }
}

if (needsReshape && threadDisplayConnection && embeddedWindow)
{
    int x       = getX();
    int y       = getY();
    ComponentPeer* const peer = getTopLevelComponent()->getPeer();
    Component::relativePositionToOtherComponent (getTopLevelComponent(), x, y);
    XMoveResizeWindow (threadDisplayConnection, embeddedWindow, x, y, jmax (1, width), jmax (1, height));
}

if (renderContext != 0 && glXMakeCurrent (threadDisplayConnection, embeddedWindow, renderContext) && XSync (threadDisplayConnection, False))
#else
if (makeCurrentContextActive()) // will create one if needed
#endif
{
    if (needsReshape)
    {
        initOpenGL();
        needsReshape = false;
    }

    MeshScreen::render(inTime);

    #if JUCE_LINUX
        glXSwapBuffers (threadDisplayConnection, embeddedWindow);
    #else
        swapBuffers();
    #endif
}

}

[/code]
Note that this makes it self-sufficent, it even does the swap itself (in Linux). You don’t need to make active each time though, since this thread and context stay linked.
That seems to work a lot better. I have a feeling I had all X errors out, and most crashes, until I added a lot of shader and FBO code recently. I can keep you updated if you want.
Oh, and the same applies to other GL screens or views you make: manage their whole X and GLX lifespan yourself, and on the thread as much as possible.

Bruce

PS Just for completeness:

[code]ScreenComponent::~ScreenComponent()
{
removeKeyListener (commandManager->getKeyMappings());

stopThread(2500);

#if JUCE_LINUX
if (threadDisplayConnection)
{
    if (renderContext)
       glXDestroyContext (threadDisplayConnection, renderContext);

    if (embeddedWindow)
    {
        XUnmapWindow (threadDisplayConnection, embeddedWindow);
        XDestroyWindow (threadDisplayConnection, embeddedWindow);
    }

   XCloseDisplay(threadDisplayConnection);
}
#endif

}
[/code]