OpenGL Component initalization


#1

Hi,

I’ve it has been discussed already but I also have my 2 cts to give.

I think we should be able to initialize openGL context manually.

In my case, openGL rendering is needed all the time, and users can open and close openGL windows by themselves.

For efficiency, all the openGL commands are executed in a separated thread, that means I can’t call openGL stuff in GUI thread and I can’t call GUI stuff in openGL thread.

so I need the initialization to be done separatedly.

Here are the modifications for this :

    OpenGLComponent ::OpenGLComponent (OpenGLComponent* componentToShareContextWith,  bool initializeOnConstruction)
{
    setOpaque (true);
    internalData = new InternalGLContextHolder (this,
                                                (InternalGLContextHolder*) (share != 0 ? share->internalData : 0), 
                                                initializeOnConstruction);
// ...

void OpenGLComponent::initializeContext()
{
    InternalGLContextHolder* const context = (InternalGLContextHolder*) internalData;
    context->initialize();
}

bool OpenGLComponent::isContextInialized() const
{
    InternalGLContextHolder* const context = (InternalGLContextHolder*) internalData;
    return context->initialized();
}

/*
** in InternalGLContextHolder class
*/
void componentParentHierarchyChanged (Component& component)
{
// [...]
                ComponentPeer* peer = topComp->getPeer();

                if (owner->isShowing() && peer != 0 && initializeOnConstruction)
                    initialize();

                lastTopLevelComp = topComp;
// [...]
}

void initialize()
{
    ScopedLock _(cscontext);
    jassert (context == 0);

    if (context == 0)
    {
        context = juce_createOpenGLContext (owner,
                                            sharedContext != 0 ? sharedContext->context : 0,
                                            antialiasingSamples);
        if (!initializeOnConstruction)
          juce_updateOpenGLWindowPos (context, owner, owner->getTopLevelComponent());
    }
}

bool initialized() const
{
    ScopedLock _(cscontext);
    return context != NULL;
}

#2

ok, I see your problem.

Would it maybe be neater if didn’t initialise the context until the first time you call the makeCurrent() method? That’d always be on the thread that’s doing the drawing…?


#3

Yes thanks, that will solve the problem.

By the way, 2 other things :

  • It would be nice to have something like an OpenGLPixelFormat class to pass it in the OpenGLComponent constructor. It would specify the color bits, depth, stencil, double buffering, antialiasing samples …
    For now I’m working on the antialiasing in a quick and dirty way cause that’s the parameter I really need, and it’s probably the most complicated one to make fit in a nice design as it is linked to the platform and the openGL extensions …

  • I need a way to get the current rendering context, for easily switching rendering from one window to the other, so here is how I did it :

// in OpenGLComponent.h
    static OpenGLComponent* getCurrentContextComponent();

// in OpenGLComponent.cpp

// ...
extern bool juce_isCurrentOpenGLContext(void* context);
// ...
class InternalGLContextHolder  : public ComponentListener
{
    // ...
    static OpenGLComponent* getCurrent()
    {
        for (int i = activeGLWindows.size(); --i >= 0;)
        {
            OpenGLComponent* component = (OpenGLComponent*) activeGLWindows[i];
            InternalGLContextHolder* context = (InternalGLContextHolder*) component->internalData;
            if (context->initialized() && juce_isCurrentOpenGLContext(context->context))
                return component;
        }
        return NULL;
    }
    // ...
}

// ...

OpenGLComponent* OpenGLComponent::getCurrentContextComponent()
{
  return InternalGLContextHolder::getCurrent();
}

// in juce_win32_Windowing.cpp

bool juce_isCurrentOpenGLContext(void* context)
{
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;
    HGLRC current = wglGetCurrentContext();
    if (current == oc->renderContext)
        return true;
    return false;
}

// in juce_mac_Windowing.cpp

bool juce_isCurrentOpenGLContext(void* context)
{
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;

    AGLContext current = aglGetCurrentContext ();
    if (current == oc->renderContext)
        return true;
    return false;
}

// in juce_linux_Windowing.cpp

bool juce_isCurrentOpenGLContext(void* context)
{
    OpenGLContextInfo* const oc = (OpenGLContextInfo*) context;

    GLXContext current = glXGetCurrentContext();
    if (current == oc->renderContext)
        return true;
    return false;
}

I know the way it is done is far from optimal, but I couldn’t find another way.

It would be nice to have this in Juce, even if it may not be usefull for everyone.


#4

Ok, will have a think about that and try to find a nice solution. Thanks.


#5

Why do you want to share a single context between multiple windows, instead of just creating multiple contexts with shared data, which I believe is the typical approach?


#6

I do have one context per window, with shared data.

I need an easy way to get the current context because each of the window can be activated at anytime, so I need to do this :

[code]OpenGLComponent* current = OpenGLComponent::getCurrent();
current->makeContextInactive();

windowcontext->makeContextActive();
draw();
windowcontext->makeContextInactive();

current->makeContextActive();[/code]

There are probably other ways to do this, but this is much more clean because I don’t have to duplicate this information somewhere.

And it automatically does a context stack so that I can have another window inside a draw() call … and that’s very useful for my nodes based engine…


#7

Let me see if I understand correctly: you’re wanting to avoid using multiple threads to render different OpenGL windows, in order to reduce the overhead of context switching and get a more optimal pipeline for typical workstations. Is that right?

Perhaps one solution could be a custom scheduler which informs each OpenGL control to render itself in sequence. What I mean is that instead of controls deriving from juce::Timer and repainting in its callback, there could be some application wide timer for all the OpenGLComponents (and children), which repaints them one after the other. Hmm, I’m having some trouble explaining myself, but I hope the point gets across… In this way, the contexts switches and pipeline interruptions would be minimized.

I could be way off base on this, but it seems like that could do what you need? It might be a nice addition to JUCE’s OpenGLComponent at a more intrinsic level too. What could be even more killer with this method would be that the windows could repaint with VSYNC, instead of some arbitrary timer callback time.

Anyway, there are a lot of possibilities for achieving your aim, if I’ve understood your intent right, and I agree that it would be nice and more efficient than the current ‘usual’ way with JUCE.


#8

That’s right.

The current design of juce’s OpenGLComponent let you do this by using swapBuffer() manualy, you also have to implement an empty renderOpenGL() fuction.

That’s right, but in my case, I need to control the sequence order if my nodes : some may produce objects (texture, mesh…) that will be used by others.

I’m not sure it would be right to wait for vsync in the message thread.

I’m currently adding a setVSync() function to OpenGLComponent, I only did it on win32 for now, I’ll probably post the code when it’s done on mac and linux.

Anyway if I understood well, you can try your solution without changing Juce, using Timer, OpenGLComponent, ActionBroadcaster and ActionListener classes, like in the following pseudo-code :

class MyOpenGLTimer : public Timer, public ActionBroadcaster
{
  virtual void timerCallback()
  {
    sendActionMessage();
  }
};

class MyOpenGLComponent : public OpenGLComponent, public ActionListener
{
  MyOpenGLComponent::MyOpenGLComponent()
  {
    myOpenGLTimer.addActionListener(this);
  }

  virtual void actionListenerCallback()
  {
     renderAndSwapBuffers();
  }
};

edit:typo


#9