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


#1

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);
}


#2

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?


#3

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. 


#4

..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!