OpenGL drawing stops with plug-in instances of different format

Drawing can sometimes halt or not start at all when opening the GUIs of instances of an OpenGL plug-in in different formats. This only occurs with an intensive drawing work-load and has only been seen on OSX.

For instance, in Reaper if you instantiate a VST2, VST3 and an AU instance of the same draw-intensive OpenGL plug-in, then open all of their GUIs:

  1. Each successive GUI that is opened will take longer to draw initially (expected).
  2. Occasionally the last GUI opened will not draw at all.

Debugging shows that the 3 pool threads and the main thread all need to lock the message manager before doing any work, which leads me to believe that in some cases one of the OpenGL pool threads isn’t able to obtain the lock at all.

The problem can be reproduced in the JUCE Demo Plugin built from the latest develop tip with modifications similar to these below:

Add an OpenGLContext and an Image to draw to the JuceDemoPluginAudioProcessorEditor class.

OpenGLContext glContext;
Image image;
float radians;

Setup the new objects in the constructor.

    image (Image::ARGB, 40, 40, true),
    radians (0.f)
{
    // change these so they're not visible
    addChildComponent (gainSlider = new ParameterSlider (*owner.gainParam));
    ...
    addChildComponent (delaySlider = new ParameterSlider (*owner.delayParam));
    ...
    addChildComponent (midiKeyboard);
    ...
    addChildComponent (timecodeDisplayLabel);
    ...

    // set to a size that requires enough drawing
    setSize (600, 600);

    glContext.attachTo (*this);
    glContext.setComponentPaintingEnabled (true);

    Graphics g (image);

    g.setColour (Colours::red);
    g.drawRect (0, 0, 40, 40, 4);
    ...
}

Trigger a repaint in the timer callback.

void JuceDemoPluginAudioProcessorEditor::timerCallback()
{
    updateTimecodeDisplay (getProcessor().lastPosInfo);
    repaint();
}

Then do something like the following in the paint method:

void JuceDemoPluginAudioProcessorEditor::paint (Graphics& g)
{
    g.setColour (backgroundColour);
    g.fillAll();
    
    const float angleIncrement = MathConstants<float>::pi / 20.f;
    const float positionIncrement = 12.f;
    const float pivotX = image.getWidth() / 2.f;
    const float pivotY = image.getHeight() / 2.f;
    
    AffineTransform t (AffineTransform::rotation (radians, pivotX, pivotY));
    
    for (int i, j = 0; j < 40; ++j)
    {
        AffineTransform ti = t;
        
        for (i = 0; i < 40; ++i)
        {
            g.drawImageTransformed (image, ti);
            ti = ti.translated (positionIncrement, 0.f)
                   .rotated (angleIncrement,
                             positionIncrement * i + pivotX,
                             positionIncrement * j + pivotY);
            
            radians += angleIncrement;
        }
        t = t.translated (0.f, positionIncrement)
             .rotated (radians, pivotX, positionIncrement * j + pivotY);
    }
}

Once built, open the plug-in in Reaper and add an instance of each of VST2, VST3 and AU.
Just one instance is fairly slow (which is expected, they’re doing a lot!), but by leaving them all open then closing/re-opening each window it doesn’t take long before one gets completely stuck waiting for a chance to lock the message manager.

I suppose the real solution here is to profile and optimise drawing routines so that they don’t cause this problem in the first place. Plus, there’s the point that it’s probably unlikely that users will encounter this situation very often (who uses a VST and VST3 instance of the same plug-in simultaneously?), although it could be accidental.

Even so, is there anything being overlooked here? Is there anything besides drawing optimisation that could be done to prevent a complete blockage for the OpenGL pool threads?

2 Likes