openGL Component Crash on Shutdown

You see, we never used to call componentHierachyChanged when the daw would remove your editor from the screen. We did call it when it was added. So this was an inconsistency and fixing this also gets rid of the Opengl bug. But I think a lot of code (also at at least one point in JUCE) people were relying on the wrong behaviour. I’ll discuss this with Jules tomorrow what to do here. Sorry for the pain!!

1 Like

Actually with more testing I find that the window displays anywhere from a
very short time, up to a second or two before crashing, seemingly randomly
from one identical run to the next.

  • Chris

The only thing we’ve really changed is that we call componentHierachyChanged when someone calls removeFromDesktop to your component (or any of it’s parents). Is there any obvious place in your plug-in where you do something strange when componentHierachyChanged is invoked?

It seems like the same behavior occurs with the juce demo plugin as well

The ui of the plugin is trivial so far. Below is the entire editor.

Also, I rolled back my version of dev to just before the change to prevent
removing a component twice, the crash on startup goes away, and if
setContinuousRepainting
is set to false the crash on exit goes away and everything seems to work
fine.

#ifndef PLUGINEDITOR_H_INCLUDED
#define PLUGINEDITOR_H_INCLUDED

#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginProcessor.h"
//#include "Main Screen.h"
#include "ScrollingNoteViewer.h"

//==============================================================================
/**
*/
class ReExpressorAudioProcessorEditor  :    public AudioProcessorEditor,
                                            private Timer
{
public:
    ReExpressorAudioProcessorEditor (ReExpressorAudioProcessor*);
    ~ReExpressorAudioProcessorEditor();

    //==============================================================================
    void paint (Graphics&) override;
    void resized() override;
    void timerCallback() override;
    double timeInTicks;
    double prevTimeInTicks = -1.0;
private:
    ReExpressorAudioProcessor *processor;
    ScrollingNoteViewer viewer;

    ScopedPointer<ResizableCornerComponent> resizer;
    ComponentBoundsConstrainer resizeLimits;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReExpressorAudioProcessorEditor)
};


#endif  // PLUGINEDITOR_H_INCLUDED


#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
ReExpressorAudioProcessorEditor::ReExpressorAudioProcessorEditor (ReExpressorAudioProcessor *p)
    : AudioProcessorEditor (p), processor (p), viewer(p)
{
    addAndMakeVisible(viewer);
    addAndMakeVisible (resizer = new ResizableCornerComponent (this, &resizeLimits));
    resizeLimits.setSizeLimits (150, 100, 900, 700);
    setSize (700,500);
    startTimerHz (30); //VST Editor crashes on open if this is set too slow?
}

ReExpressorAudioProcessorEditor::~ReExpressorAudioProcessorEditor()
{
}

//==============================================================================
void ReExpressorAudioProcessorEditor::paint (Graphics& g)
{
    g.fillAll(Colours::aqua);
}

void ReExpressorAudioProcessorEditor::resized()
{
    viewer.setSize(getWidth()-50,getHeight()-50);
    resizer->setBounds (getWidth() - 16, getHeight() - 16, 16, 16);
}

void ReExpressorAudioProcessorEditor::timerCallback()
{
    //if (processor->isPlaying)
        viewer.repaint();
}

I should add that I’m using the Juce Plugin Host for this testing, and my
plugin is a VST2.

I think I need to rollback my “fixes”. Seems to be problematic that removeFromDesktop is called when the DAW is about remove it itself. We’ll probably need some kind of callback like componentPeerWillChange or so.

I’ve tried this same direction too and also couldn’t make it work.

I saw that Jules wrote that “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” (GL crashing on editor close),
so the fix that I ended up suggesting is “non-pervasive” change which just adds extra checks at the assertion to avoid these false-positives.

The crash when the editor is closed seems to only occur if setContinuousRepainting is true. Would it be a solution to have your plugin always setContinuousRepainting to false before closing? Is a plugin notified that it’s closing soon enough to do this?

In my plugin I don’t need continuous repainting for now so disabling it will allow me to proceed. It would still be good to have a more general fix as I may eventually need it.

Yep that’s known. What fabian tried is exactly in the spirit of your suggestion - getting the component notified that it is closing to avoid the crash.

Is a plugin notified that it’s closing soon enough to do this?

I’d say that it’s still a mystery at this point…

I also found that I can set setContinuousRepainting to false and then update my plugin continually by calling repaint in a timer. In this case my component updates continually as if setContinuousRepainting were true, but doesn’t crash on exit.

I’m not sure if there are any disadvantages to this. Possibly that its not as efficient as setting continuousRepainting to true? Or that the refresh rate is not synchronized to the frame rate?

Well with the fix that I have on develop, it is notified soon enough. But so many parts of JUCE relied on this notification not being there, that it breaks quite a lot of code.

Well yes that works as the repaints will be done on the message thread. The point of continuous repaint is that all painting will be done on a separate thread, freeing up the message thread to do other valuable work. However, the OpenGL context becomes invalid once the host decides to remove the editor’s NSView from the screen. The NSView is still there however, it’s just been removed from the screen, so no de-allocations have had a chance to be invoked.

The fix that I put on develop is to use a native callback willBeRemovedFromWindow to let the OpenGL context know in advance that it will be removed from the screen. I’ve tried to make this workaround as general as possible by invoking JUCE’s parentHierachyChanged callback (which in turn would destroy the OpenGL context) - but so much code out there relies on this not being called just before destruction.

I’ve just talked to Jules about this and we will rework the fix to not call the parentHierachyChanged callback but just have JUCE internally detach the OpenGL context when an editor’s view is about to be removed from the screen. I’ll push this to develop soon and post an update. Sorry for the back and forth on this.

Here is what seems to be a related problem.

SetContinuousRepainting to false and call repaint in a timer. This works a lot of the time, but this causes occasional asserts when the component is opened. The faster the repaint rate the less likely the assert is to happen. If setContinuousRepainting is false and repaint is never called, it always asserts on open.

The assert happens in context.copyTexture() in juce_OpenGLContext.cpp here:

void drawComponentBuffer()
{
 #if ! JUCE_ANDROID
  glEnable (GL_TEXTURE_2D);
  clearGLError();
 #endif

 #if JUCE_WINDOWS
  // some stupidly old drivers are missing this function, so try to at least avoid a crash here,
  // but if you hit this assertion you may want to have your own version check before using the
  // component rendering stuff on such old drivers.
  jassert (context.extensions.glActiveTexture != nullptr);
  if (context.extensions.glActiveTexture != nullptr)
 #endif
      context.extensions.glActiveTexture (GL_TEXTURE0);

  glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID());
  bindVertexArray();

  const Rectangle<int> cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight());
  ////Asserts in call to copyTexture
  context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight(), false);
  glBindTexture (GL_TEXTURE_2D, 0);
  JUCE_CHECK_OPENGL_ERROR
  }

It’s trying to restore a cached texture representing the component. If repaint hasn’t been called, presumably the cache has not been created and it asserts.

The assert on open goes away if setContinuousRepainting is true, but now it assets on shutdown.

For now I’ll try selectively disabling the assert as suggested by yairadix.

Please indent code by 4 spaces to make it readable in the forum!

The original bug (not the android one), is now fixed on develop.

Sorry. I just added indents where needed to my previous posts.

I pulled the tip of the dev branch. Now nothing is being rendered.

Sometimes when the plugin is opened renderOpenGL() is called once, sometimes a couple of times, sometimes it’s not called at all. Often newOpenGLContextCreated() is not called on open either.

Setting a breakpoint at the start of runJob() in juce_OpenGLContext.cpp seems to always guarantee that newOpenGLContextCreated() and renderOpenGL() get called at least once. After that renderOpenGL() might be called once more, but never again.

The code for my ScrollingNoteViewer class is still exactly as I posted at the start of this thread.

Did all these OpenGL issues get resolved? I have a VST plugin with two OpenGL components. I can open and close the editor without issues, however, as soon as I have two instances of the plugin open in Reaper, I get a crash closing the second plugin window. Just wondering if I’m seeing the same issue or something different.

The call stack looks like this:

#0	0x00007fffd4eca921 in gleRunVertexSubmitImmediate ()
#1	0x00007fffd4e43db7 in glDrawElements_ACC_Exec ()
#2	0x00000001149b7b79 in homados::Shape::draw(juce::OpenGLContext&, homados::Attributes&) at /Volumes/Lolrus/dev.github/spotlight-gss/tools/maux-plugin/Common/UI/glHelpers.h:134
#3	0x00000001149b7ab9 in homados::SphereComponent::renderSmallSphere(float, homados::SphereComponent::Color const&) at /Volumes/Lolrus/dev.github/spotlight-gss/tools/maux-plugin/Common/UI/SphereComponent.cpp:340
#4	0x00000001149b766b in homados::SphereComponent::renderOpenGL() at /Volumes/Lolrus/dev.github/spotlight-gss/tools/maux-plugin/Common/UI/SphereComponent.cpp:206
#5	0x00000001149b7c0c in non-virtual thunk to homados::SphereComponent::renderOpenGL() ()
#6	0x0000000114f28de0 in juce::OpenGLContext::CachedImage::renderFrame() at /Volumes/Lolrus/dev.github/spotlight-gss/ext/JUCE/modules/juce_opengl/opengl/juce_OpenGLContext.cpp:247
#7	0x0000000114f261f9 in juce::OpenGLContext::CachedImage::runJob() at /Volumes/Lolrus/dev.github/spotlight-gss/ext/JUCE/modules/juce_opengl/opengl/juce_OpenGLContext.cpp:451
#8	0x0000000114f26309 in non-virtual thunk to juce::OpenGLContext::CachedImage::runJob() ()
#9	0x0000000114ba4cf8 in juce::ThreadPool::runNextJob(juce::ThreadPool::ThreadPoolThread&) at /Volumes/Lolrus/dev.github/spotlight-gss/ext/JUCE/modules/juce_core/threads/juce_ThreadPool.cpp:341
#10	0x0000000114beeee2 in juce::ThreadPool::ThreadPoolThread::run() at /Volumes/Lolrus/dev.github/spotlight-gss/ext/JUCE/modules/juce_core/threads/juce_ThreadPool.cpp:40
#11	0x0000000114ba2727 in juce::Thread::threadEntryPoint() at /Volumes/Lolrus/dev.github/spotlight-gss/ext/JUCE/modules/juce_core/threads/juce_Thread.cpp:101
#12	0x0000000114ba2a95 in juce::juce_threadEntryPoint(void*) at /Volumes/Lolrus/dev.github/spotlight-gss/ext/JUCE/modules/juce_core/threads/juce_Thread.cpp:113
#13	0x0000000114bc2fbe in ::threadEntryProc(void *) at /Volumes/Lolrus/dev.github/spotlight-gss/ext/JUCE/modules/juce_core/native/juce_posix_SharedCode.h:855
#14	0x0000000100e8bb90 in _pthread_body ()
#15	0x0000000100e8badc in _pthread_start ()
#16	0x0000000100e8b2c5 in thread_start ()

I should point out, it’s not the plugin that’s closing that is crashing. It’s the plugin that should still be running, could the closing plugin be killing it’s OpenGL somehow? Sorry, not an OpenGL expert.

Unfortunately I never did get it to work reliably. I finally gave up and made my application into a standalone audio/midi app (sort of a sequencer) where OpenGL is working fine, although even that was quite difficult.

If I recall correctly, a challenge with plugins seems to be that the host can close the plugin window without notifying the plugin, so the plugin doesn’t get a chance to close its OpenGL context before its window is closed.