GL crashing on editor close


#1

My editor is consistently crashing when I close it since I added an instance of OpenGLAppComponent. The code for my OpenGLAppComponent (AudioView) is below. When the AudioProcessEditor is created I call initialise() and addAndMakeVisible(&my_audioview).

I have noticed that the editor is created and destroyed and then created again (Talked about here: http://www.juce.com/forum/topic/plugineditor-ctor). The first time it is destroyed it usually doesn't crash. The second time, it crashes without fail.

When my editor is destroyed, I call shutdown() as shown in the code below. However, when I close the second time, it crashes even before the destructor for my editor is executed. It doesn't even give me the chance to stop GL gracefully. Here is the error:

Closing VST UI: MyPlugin

***** GL_INVALID_FRAMEBUFFER_OPERATION  at /Applications/JUCE-OSX/modules/juce_opengl/opengl/juce_OpenGLContext.cpp : 955

JUCE Assertion failure in juce_opengl.cpp:141

// Audioviewer inherits from OpenGLAppComponent and Timer
AudioViewer::AudioViewer(){
  setSize(600,40);
}

AudioViewer::~AudioViewer(){
  jassert (! openGLContext.isAttached());
  shutdownOpenGL();
}

void AudioViewer::initialise(){
  openGLContext.attachTo(*this);
  startTimer (50);
}

void AudioViewer::shutdown(){
  stopTimer();
  openGLContext.detach();
}

void AudioViewer::renderOpenGL(){}

void AudioViewer::paint(Graphics &g){
// currently everything is commented out
}

void AudioViewer::newOpenGLContextCreated(){}

void AudioViewer::openGLContextClosing(){}

void AudioViewer::timerCallback(){
     //repaint();
}

I got the error often when my paint code was in moved to renderOpenGL(), but it stopped once I moved it to paint(). If it matters, the error is created in OpenGLContext::copyTexture during the last call to glDrawArrays. I know because I moved the JUCE_CHECK_OPENGL_ERROR line around until narrowing down to that. The error code I get from glCheckFrameBufferStatus before it occurs is GL_FRAMEBUFFER_UNDEFINED.

 

Any help is very much appreciated. I've been trying to fix this for hours.


openGL Component Crash on Shutdown
#2

I wasn't able to reproduce your problem on OS X and linux. The constructor of my editor looks like this:

GltestAudioProcessorEditor::GltestAudioProcessorEditor (GltestAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{
    addAndMakeVisible (glComponent = createMainContentComponent());
    glComponent->setName ("glComponent");
    setSize (400, 300);
}

createMainContentComponent() is defined in JUCE/examples/OpenGLAppExample/Source/MainComponent.cpp, which I copied (along with the resource teapot.obj) into the default audio plugin project created by the Introjucer. Can you try this and tell me how you get along?


#3

Thanks a lot for the reply. I expected to get an automatic email if someone responded to this but I didn't get one (hence my late reply to you).

I eventually found a working solution. I didn't use the scoped pointers, but as long as things are created and destroyed properly, that shouldn't matter, right?

 

AudioViewer::AudioViewer(){
  setSize(kLengthInPixels,kHeightInPixels);
  open_gl_context.setRenderer(this);
  open_gl_context.attachTo(*this);
  should_draw_ = true;
}

// Called from a 30fps timer callback
void AudioViewer::render_frame(){
  graphics_lock_.enter();
  if (should_draw_){
    repaint();
  }
  graphics_lock_.exit();
}

AudioViewer::~AudioViewer(){
  // Wait until it is finished drawing to delete it.
  should_draw_ = false;
  graphics_lock_.enter();
  open_gl_context.detach();
  graphics_lock_.exit();
}

 

 

 


#4

Yes as long as you create and destroy things properly everything should be find. However, I am not quite sure why you are using locks in your code. This shouldn't be necessary.


#5

Hi Chet, we were able to reproduce your bug and it seems to be a race condition. If you create an OpenGLAppComponent and embed it in your editor (as I suggested before), for some reason - at least on our machines - the race condition isn't triggered and it seems not to crash. However, your method (i.e. attaching an OpenGLContext directly), should also work and we are investigating why it seems not to be working. Stand by....


#6

The lock is to prevent the object from being deleted while it is in the middle of a paint as I'm sure you've guessed. It seems to work fine without it. I'm guessing the GLContext object is thread safe?

 

 


#7

Hi Chet

We've done some digging and what's happening is this:

- The plugin's openGL rendering is happily drawing stuff

- The host starts to remove the plugin's NSView from the screen in preparation for deleting it

- Deep in the Apple NSView code, it realises that an OpenGLView is being removed from the screen, so it frees the GL resources associated with it

- Our plugin's render thread is still busily doing GL stuff at this point, and has no idea the view is being hidden, so its GL calls start to fail, and the assertion is triggered

TBH I don't think the assertions are actually a problem in this case. The OSX GL calls seem to be thread-safe and although they fail to draw, that's actually ok because the window is off-screen, and everything survives if you ignore (or disable) the assertions.


openGL Component Crash on Shutdown
#8

Hi everyone,

the bug is still there, whether i use an OpenGLContext directly or choose to use the suggested OpenGLAppComponent. In both cases it seems to work without a real crash but debugging the assertions are created, which make it impossible to work.

@Jules: How do i disable the assertions the correct way (without touching JUCE library code)?

Thanks

Thomas


#9

Assertions are disabled in a release build, but you probably don't want to disable them in release, as you could miss other problems. Personally, I'd suggest just hacking that one out rather than turning them all off.


#10

Hi Jules,

thanks. I did not want to disable all assertions, just this one. What do you mean with hacking out? Currently i commented out the line in that juce file that invokes the assert. So i get the error messages in the console but i can continue debugging. In a release build would reverse that anyway to be on the safe side.

Cheers,

Thomas


#11

Commenting it is what was meant by hacking out (although remember that the assertions are there for a reason, so don't make a habit of it!). As Jules said, disabling all assertions in JUCE would be a bad idea, so you're really best off just changing that line.

Hope this helps! :)


#12

Hello guys !

For a reason I don't get, I have a similar problem. I have developed an OpenGL app with the Introjucer, I have the shutdownOpenGL() on the destructor of my main component. Sometimes, when I close the application, I get an assertion and then invalid operations (crashes), because the render function is called during the time the OpenGL context is supposed to be detached. It doesn't happen all the time so I don't have any log right now (I will write everything next time). I just remember something bad happens during the execution of the first instruction in the render function, which is :

ScopedPointer<LowLevelGraphicsContext> glRenderer(createOpenGLGraphicsContext(openGLContext,
        roundToInt(desktopScale * getWidth()),
        roundToInt(desktopScale * getHeight())));

I am on VS2015 + Windows 8.1.

I will add a lock in the destructor and in the render function to solve the issue momentarily if you can't see what is happening...

Thanks in advance !


#13

So, here is the call stack :

JUCEApp.exe!juce::OpenGLContext::getAssociatedObject(const char * name) Line 852    C++
JUCEApp.exe!juce::OpenGLRendering::CachedImageList::get(juce::OpenGLContext & c) Line 47    C++
JUCEApp.exe!juce::OpenGLRendering::GLState::GLState(const juce::OpenGLRendering::Target & t) Line 1343    C++
JUCEApp.exe!juce::OpenGLRendering::ShaderContext::ShaderContext(const juce::OpenGLRendering::Target & target) Line 1710    C++
JUCEApp.exe!juce::OpenGLRendering::createOpenGLContext(const juce::OpenGLRendering::Target & target) Line 1773    C++
JUCEApp.exe!juce::createOpenGLGraphicsContext(juce::OpenGLContext & context, unsigned int frameBufferID, int width, int height) Line 1795    C++
JUCEApp.exe!juce::createOpenGLGraphicsContext(juce::OpenGLContext & context, int width, int height) Line 1785    C++
JUCEApp.exe!MainContentComponent::render() Line 226    C++ (that's the instruction I have written before)
JUCEApp.exe!juce::OpenGLAppComponent::renderOpenGL() Line 58    C++
JUCEApp.exe!juce::OpenGLContext::CachedImage::renderFrame() Line 230    C++
JUCEApp.exe!juce::OpenGLContext::CachedImage::runJob() Line 427    C++
JUCEApp.exe!juce::ThreadPool::runNextJob(juce::ThreadPool::ThreadPoolThread & thread) Line 341    C++
JUCEApp.exe!juce::ThreadPool::ThreadPoolThread::run() Line 40    C++
JUCEApp.exe!juce::Thread::threadEntryPoint() Line 106    C++
JUCEApp.exe!juce::juce_threadEntryPoint(void * userData) Line 114    C++
JUCEApp.exe!juce::threadEntryProc(void * userData) Line 105    C++
JUCEApp.exe!invoke_thread_procedure(unsigned int (void *) * const procedure, void * const context) Line 92    C++
JUCEApp.exe!thread_start<unsigned int (__cdecl*)(void * __ptr64)>(void * const parameter) Line 115    C++

In getAssociatedObject, "CachedImage* const c = getCachedImage();" returns a nullptr, so the jassert is called, and then the application crashes... And the openGLContext is not null yet. Here is the call stack from the JUCE message thread :

[External Code]    
JUCEApp.exe!juce::WaitableEvent::wait(int timeOutMs) Line 91    C++
JUCEApp.exe!juce::ThreadPool::waitForJobToFinish(const juce::ThreadPoolJob * job, int timeOutMs) Line 181    C++
JUCEApp.exe!juce::ThreadPool::removeJob(juce::ThreadPoolJob * job, bool interruptIfRunning, int timeOutMs) Line 215    C++
JUCEApp.exe!juce::OpenGLContext::CachedImage::pause() Line 128    C++
JUCEApp.exe!juce::OpenGLContext::CachedImage::stop() Line 114    C++
JUCEApp.exe!juce::OpenGLContext::Attachment::detach() Line 651    C++
JUCEApp.exe!juce::OpenGLContext::Attachment::~Attachment() Line 545    C++
[External Code]    
JUCEApp.exe!juce::ContainerDeletePolicy<juce::OpenGLContext::Attachment>::destroy(juce::OpenGLContext::Attachment * object) Line 58    C++
JUCEApp.exe!juce::ScopedPointer<juce::OpenGLContext::Attachment>::operator=(juce::OpenGLContext::Attachment * const newObjectToTakePossessionOf) Line 144    C++
JUCEApp.exe!juce::OpenGLContext::detach() Line 746    C++
JUCEApp.exe!juce::OpenGLAppComponent::shutdownOpenGL() Line 47    C++
JUCEApp.exe!MainContentComponent::~MainContentComponent() Line 34    C++
[External Code]    
JUCEApp.exe!juce::Component::SafePointer<juce::Component>::deleteAndZero() Line 2139    C++
JUCEApp.exe!juce::ResizableWindow::clearContentComponent() Line 101    C++
JUCEApp.exe!juce::ResizableWindow::~ResizableWindow() Line 67    C++
JUCEApp.exe!juce::DocumentWindow::~DocumentWindow() Line 79    C++
[External Code]    
JUCEApp.exe!juce::ContainerDeletePolicy<JUCEAppApplication::MainWindow>::destroy(JUCEAppApplication::MainWindow * object) Line 58    C++
JUCEApp.exe!juce::ScopedPointer<JUCEAppApplication::MainWindow>::operator=(JUCEAppApplication::MainWindow * const newObjectToTakePossessionOf) Line 144    C++
JUCEApp.exe!JUCEAppApplication::shutdown() Line 39    C++
JUCEApp.exe!juce::JUCEApplicationBase::shutdownApp() Line 292    C++
JUCEApp.exe!juce::JUCEApplicationBase::main() Line 244    C++
JUCEApp.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 101    C++
[External Code]    

 


#14

Looks like i having the same issue


#15

I can't really seem to figure out why getCachedImage should fail. This is why you must call "shutdownOpenGL" before your Component is completely destructed: shutdownOpenGL will wait for all render calls to complete before the component is actually deleted, and hence getCachedImage should work. Could you add some debug logging to check why it fails and try to re-produce the problem?


#16

I have the initial issue from this thread on Mac.

Simply create a plugin with continuous redraw, and open it in the plugin host. 

When you close the window, you get thousands OpenGL Errors.

this is what happening i think: (jules already said)

- The plugin's openGL rendering is happily drawing stuff

- The host starts to remove the plugin's NSView from the screen in preparation for deleting it

- Deep in the Apple NSView code, it realises that an OpenGLView is being removed from the screen, so it frees the GL resources associated with it

- Our plugin's render thread is still busily doing GL stuff at this point, and has no idea the view is being hidden, so its GL calls start to fail, and the assertion is triggered

TBH I don't think the assertions are actually a problem in this case. The OSX GL calls seem to be thread-safe and although they fail to draw, that's actually ok because the window is off-screen, and everything survives if you ignore (or disable) the assertions.

Well, it looks really dangerous! 


#17

You also get this error also when the parent will be removed, which contains a openGLContext associated child. (Mac OS X)

(with continuous repaints which happen on a non-message-thread-locked thread)

(The error happens before the child is destructed)

There must be a check, if a component gets invisible, or the parent-component is removed, if there is currently a active openGL-context which is associated with this component (the gl-thread should be blocked until the component is showing (not visible) again) 


#18

Here is a simple demonstration:

Just wrap the original OpenGL Demo from the JUCE Demo into a parent-Component and remove it. You will instantly get the error. (Mac OSX)

 

   class OpenGLDemoTest : public Component, public Timer
   {
    public:

        OpenGLDemoTest()
        {
            addAndMakeVisible(demo1);
            startTimer(1000);
        };

        ~OpenGLDemoTest()
        {
        };

        void timerCallback()
        {
            if (demo1.getParentComponent()==this)
            {
                removeChildComponent(&demo1);
            } else
            {
                addAndMakeVisible(demo1);
            };
        };
        void resized()
        {
            demo1.setBoundsInset(BorderSize<int>(100));
        };

        OpenGLDemo demo1;
    };
};


// This static object will register this demo type in a global list of demos..
static JuceDemoType<OpenGLDemoClasses::OpenGLDemoTest> demo ("20 Graphics: OpenGL");

#19

Further investigation. When i delete the "OpenGLDemo"-comp directly, there are no issues, if i just remove it as a child, i get the errors. Seems like there is a missing lock somewhere.


#20

Another interesting fact:

when i remove the GL-child component from a mouseDown() callback, no issues occur, but if its done inside a timerCallback() the OpenGL errors appear.