We’re encountering lots of performance issues with juce::OpenGLRenderer
on Windows 10 when running multiple instances of our plugin.
After a few instances things start becoming very unresponsive, and with more instances the rendering of the OpenGLRenderer
gets slower as well.
Our plugin itself uses component rendering and an OpenGLRenderer
, but we encounter the same issue if we disable the component rendering.
Below is a simple PIP that exhibited the problem for us (we ran on Release as a VST on Reaper and Mixbus). Since there is no component rendering going on (meaning we don’t need to acquire a MessageManager::Lock
in OpenGLContext::CachedImage::renderFrame()
).
I would have thought that each thread could run its rendering without experiencing these issues since they seem to be more specifically related to the use of the MessageManager::Lock
, but the PIP example slows down considerably when we start opening multiple instances
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: GLTestPlugin
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, juce_audio_plugin_client,
juce_audio_processors, juce_audio_utils, juce_core, juce_data_structures, juce_events,
juce_graphics, juce_gui_basics, juce_gui_extra, juce_opengl
exporters: vs2017
type: AudioProcessor
mainClass: GLTest
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class GLTestEditor : public AudioProcessorEditor,
public OpenGLRenderer
{
public:
GLTestEditor(AudioProcessor &p) : AudioProcessorEditor(p)
{
setSize(100, 100);
context.setContinuousRepainting(true);
context.setComponentPaintingEnabled(false);
context.setRenderer(this);
context.attachTo(*this);
}
~GLTestEditor()
{
}
void newOpenGLContextCreated() override
{
}
void renderOpenGL() override
{
OpenGLHelpers::clear(Colour((uint32)Random().nextInt()).withAlpha(1.0f));
}
void openGLContextClosing() override
{
}
void paint(Graphics &g)
{
g.setFont(32);
g.setColour(Colours::white);
g.drawText("JUCE", getLocalBounds(), Justification::centred);
}
private:
OpenGLContext context;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GLTestEditor);
};
//==============================================================================
class GLTest : public AudioProcessor
{
public:
//==============================================================================
GLTest()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo()))
{
}
~GLTest()
{
}
//==============================================================================
void prepareToPlay (double, int) override
{
// Use this method as the place to do any pre-playback
// initialisation that you need..
}
void releaseResources() override
{
// When playback stops, you can use this as an opportunity to free up any
// spare memory, etc.
}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// In case we have more outputs than inputs, this code clears any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
// This is here to avoid people getting screaming feedback
// when they first compile a plugin, but obviously you don't need to keep
// this code if your algorithm always overwrites all the output channels.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear (i, 0, buffer.getNumSamples());
// This is the place where you'd normally do the guts of your plugin's
// audio processing...
// Make sure to reset the state if your inner loop is processing
// the samples and the outer loop is handling the channels.
// Alternatively, you can process the samples with the channels
// interleaved by keeping the same state.
for (int channel = 0; channel < totalNumInputChannels; ++channel)
{
auto* channelData = buffer.getWritePointer (channel);
// ..do something to the data...
}
}
//==============================================================================
AudioProcessorEditor* createEditor() override { return new GLTestEditor(*this); }
bool hasEditor() const override { return true; }
//==============================================================================
const String getName() const override { return "GLTestPlugin"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return {}; }
void changeProgramName (int, const String&) override {}
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
// You should use this method to store your parameters in the memory block.
// You could do that either as raw data, or use the XML or ValueTree classes
// as intermediaries to make it easy to save and load complex data.
}
void setStateInformation (const void* data, int sizeInBytes) override
{
// You should use this method to restore your parameters from this memory block,
// whose contents will have been created by the getStateInformation() call.
}
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
// This is the place where you check if the layout is supported.
// In this template code we only support mono or stereo.
if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
return false;
// This checks if the input layout matches the output layout
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;
return true;
}
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GLTest)
};