With continuous repaint off, OpenGL in plugins doesn't repaint properly

They don't get the repaint call when windows overlap the plugin,  or when the host is minimized and restored. 

This is confirmed in v3.02 with no modifications. 

To test I made a couple of adjustments to JuceDemoPlugin

PluginEditor.h


/*
  ==============================================================================
    This file was auto-generated by the Jucer!
    It contains the basic startup code for a Juce application.
  ==============================================================================
*/
#ifndef __PLUGINEDITOR_H_4ACCBAA__
#define __PLUGINEDITOR_H_4ACCBAA__
#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginProcessor.h"
class OpenGLTestComp;
//==============================================================================
/** This is the editor component that our filter will display.
*/
class JuceDemoPluginAudioProcessorEditor  : public AudioProcessorEditor,
                                            public SliderListener,
                                            public Timer
{
public:
    JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor* ownerFilter);
    ~JuceDemoPluginAudioProcessorEditor();
    //==============================================================================
    void timerCallback() override;
    void paint (Graphics&) override;
    void resized() override;
    void sliderValueChanged (Slider*) override;
private:
    MidiKeyboardComponent midiKeyboard;
    Label infoLabel, gainLabel, delayLabel;
    Slider gainSlider, delaySlider;
    ScopedPointer<ResizableCornerComponent> resizer;
    ComponentBoundsConstrainer resizeLimits;
    ScopedPointer<OpenGLTestComp> oglComp;
    AudioPlayHead::CurrentPositionInfo lastDisplayedPosition;
    JuceDemoPluginAudioProcessor* getProcessor() const
    {
        return static_cast <JuceDemoPluginAudioProcessor*> (getAudioProcessor());
    }
    void displayPositionInfo (const AudioPlayHead::CurrentPositionInfo& pos);
};

#endif  // __PLUGINEDITOR_H_4ACCBAA__

PluginEditor.cpp


/*
  ==============================================================================
    This file was auto-generated by the Jucer!
    It contains the basic startup code for a Juce application.
  ==============================================================================
*/
#include "PluginProcessor.h"
#include "PluginEditor.h"


class OpenGLTestComp : public OpenGLRenderer, public Component
{
public:
    OpenGLTestComp()
    {
        if(! context.isAttached())
        {
            context.setRenderer(this);
            context.setComponentPaintingEnabled(false);
            context.attachTo(*this);
        }
    }
    ~OpenGLTestComp()
    {
        context.detach();
    }
    void openGLContextClosing()
    {
    }
    void renderOpenGL()
    {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glColor3f(1.f, 0.f, 0.f);
        glBegin(GL_QUADS);
            glVertex2f(0, 0);
            glVertex2f(getWidth() / 2, 0);
            glVertex2f(getWidth() / 2, getHeight());
            glVertex2f(0, getHeight());
        glEnd();
    }
    void newOpenGLContextCreated()
    {
    }
private:
    OpenGLContext context;
};


//==============================================================================
JuceDemoPluginAudioProcessorEditor::JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor* ownerFilter)
    : AudioProcessorEditor (ownerFilter),
      midiKeyboard (ownerFilter->keyboardState, MidiKeyboardComponent::horizontalKeyboard),
      infoLabel (String::empty),
      gainLabel ("", "Throughput level:"),
      delayLabel ("", "Delay:"),
      gainSlider ("gain"),
      delaySlider ("delay")
{
    // add some sliders..
    addAndMakeVisible (&gainSlider);
    gainSlider.setSliderStyle (Slider::Rotary);
    gainSlider.addListener (this);
    gainSlider.setRange (0.0, 1.0, 0.01);
    addAndMakeVisible (&delaySlider);
    delaySlider.setSliderStyle (Slider::Rotary);
    delaySlider.addListener (this);
    delaySlider.setRange (0.0, 1.0, 0.01);
    // add some labels for the sliders..
    gainLabel.attachToComponent (&gainSlider, false);
    gainLabel.setFont (Font (11.0f));
    delayLabel.attachToComponent (&delaySlider, false);
    delayLabel.setFont (Font (11.0f));
    // add the midi keyboard component..
    addAndMakeVisible (&midiKeyboard);
    addAndMakeVisible(oglComp = new OpenGLTestComp());
    // add a label that will display the current timecode and status..
    addAndMakeVisible (&infoLabel);
    infoLabel.setColour (Label::textColourId, Colours::blue);
    // add the triangular resizer component for the bottom-right of the UI
    addAndMakeVisible (resizer = new ResizableCornerComponent (this, &resizeLimits));
    resizeLimits.setSizeLimits (150, 150, 800, 300);
    // set our component's initial size to be the last one that was stored in the filter's settings
    setSize (ownerFilter->lastUIWidth,
             ownerFilter->lastUIHeight);
    startTimer (50);
}
JuceDemoPluginAudioProcessorEditor::~JuceDemoPluginAudioProcessorEditor()
{
}
//==============================================================================
void JuceDemoPluginAudioProcessorEditor::paint (Graphics& g)
{
    g.setGradientFill (ColourGradient (Colours::white, 0, 0,
                                       Colours::grey, 0, (float) getHeight(), false));
    g.fillAll();
}
void JuceDemoPluginAudioProcessorEditor::resized()
{/*
    infoLabel.setBounds (10, 4, 400, 25);
    gainSlider.setBounds (20, 60, 150, 40);
    delaySlider.setBounds (200, 60, 150, 40);
    const int keyboardHeight = 70;
    midiKeyboard.setBounds (4, getHeight() - keyboardHeight - 4, getWidth() - 8, keyboardHeight);
    resizer->setBounds (getWidth() - 16, getHeight() - 16, 16, 16);*/
    oglComp->setBounds(getLocalBounds().reduced(20, 20));
    
    getProcessor()->lastUIWidth = getWidth();
    getProcessor()->lastUIHeight = getHeight();
}
//==============================================================================
// This timer periodically checks whether any of the filter's parameters have changed...
void JuceDemoPluginAudioProcessorEditor::timerCallback()
{
    JuceDemoPluginAudioProcessor* ourProcessor = getProcessor();
    AudioPlayHead::CurrentPositionInfo newPos (ourProcessor->lastPosInfo);
    if (lastDisplayedPosition != newPos)
        displayPositionInfo (newPos);
    gainSlider.setValue (ourProcessor->gain, dontSendNotification);
    delaySlider.setValue (ourProcessor->delay, dontSendNotification);
}
// This is our Slider::Listener callback, when the user drags a slider.
void JuceDemoPluginAudioProcessorEditor::sliderValueChanged (Slider* slider)
{
    if (slider == &gainSlider)
    {
        // It's vital to use setParameterNotifyingHost to change any parameters that are automatable
        // by the host, rather than just modifying them directly, otherwise the host won't know
        // that they've changed.
        getProcessor()->setParameterNotifyingHost (JuceDemoPluginAudioProcessor::gainParam,
                                                   (float) gainSlider.getValue());
    }
    else if (slider == &delaySlider)
    {
        getProcessor()->setParameterNotifyingHost (JuceDemoPluginAudioProcessor::delayParam,
                                                   (float) delaySlider.getValue());
    }
}
//==============================================================================
// quick-and-dirty function to format a timecode string
static const String timeToTimecodeString (const double seconds)
{
    const double absSecs = fabs (seconds);
    const int hours =  (int) (absSecs / (60.0 * 60.0));
    const int mins  = ((int) (absSecs / 60.0)) % 60;
    const int secs  = ((int) absSecs) % 60;
    String s (seconds < 0 ? "-" : "");
    s << String (hours).paddedLeft ('0', 2) << ":"
      << String (mins) .paddedLeft ('0', 2) << ":"
      << String (secs) .paddedLeft ('0', 2) << ":"
      << String (roundToInt (absSecs * 1000) % 1000).paddedLeft ('0', 3);
    return s;
}
// quick-and-dirty function to format a bars/beats string
static const String ppqToBarsBeatsString (double ppq, double /*lastBarPPQ*/, int numerator, int denominator)
{
    if (numerator == 0 || denominator == 0)
        return "1|1|0";
    const int ppqPerBar = (numerator * 4 / denominator);
    const double beats  = (fmod (ppq, ppqPerBar) / ppqPerBar) * numerator;
    const int bar    = ((int) ppq) / ppqPerBar + 1;
    const int beat   = ((int) beats) + 1;
    const int ticks  = ((int) (fmod (beats, 1.0) * 960.0 + 0.5));
    String s;
    s << bar << '|' << beat << '|' << ticks;
    return s;
}
// Updates the text in our position label.
void JuceDemoPluginAudioProcessorEditor::displayPositionInfo (const AudioPlayHead::CurrentPositionInfo& pos)
{
    lastDisplayedPosition = pos;
    String displayText;
    displayText.preallocateBytes (128);
    displayText << String (pos.bpm, 2) << " bpm, "
                << pos.timeSigNumerator << '/' << pos.timeSigDenominator
                << "  -  " << timeToTimecodeString (pos.timeInSeconds)
                << "  -  " << ppqToBarsBeatsString (pos.ppqPosition, pos.ppqPositionOfLastBarStart,
                                                    pos.timeSigNumerator, pos.timeSigDenominator);
    if (pos.isRecording)
        displayText << "  (recording)";
    else if (pos.isPlaying)
        displayText << "  (playing)";
    infoLabel.setText (displayText, dontSendNotification);
}

Presumably something wierd caused by the behaviour of the host's parent window, but TBH I haven't a clue why it'd happen or what could be done about it. Have you tried other hosts?

It is host-independent - see attached - in reaper nothing will overlap the plugin window because it's top level, but if you drag it's window below the screen and bring it back up the dirty area isn't repainted - this happens on OSX too.

It's most apparent in hosts where windows overlap a lot (so FL is especially troublesome). 

 

I made an attempt at tracing the repaint calls Win32 paint messages but it's hopeless without some other debugging / tracing tool, in that, when the debugger takes focus more repaint events are generated and it's impossible to be a passive observer of the events. 

Maybe you had to use a tool like this in developing your message dispatcher? 

 

My only idea so far is to have a timer trying Desktop::findComponentAt hit-tests for each corner of all the opengl areas, which obviously isn't pretty. 

..just came back to look at this issue. It's just your own code that's broken:


    void renderOpenGL()
    {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glColor3f(1.f, 0.f, 0.f);
        glBegin(GL_QUADS);
            glVertex2f(0, 0);
            glVertex2f(getWidth() / 2, 0);
            glVertex2f(getWidth() / 2, getHeight());
            glVertex2f(0, getHeight());
        glEnd();
    }

You're not actually clearing anything.. You just set the clear colour, and then don't actually glClear. So of course you'll get garbage left behind!