I’m creating component that uses openGL for display rather than paint because I need to efficiently draw a large number of rectangles which will be the note indicators on a sort of piano roll, a class called “ScrollingNoteViewer”.
As a learning exercise I created a simplified version of openGLAppExample that uses vertex buffers and shaders to draw a few rectangle of different colours, and this works fine. I’m now trying to port this to be not a standalone app, but a component I can use in the plugin I’m developing.
I changed my non openGL component class as follows:
class ScrollingNoteViewer: public Component, private OpenGLContext, private OpenGLRenderer, private Timer
…and ported my openGL code to it. It displays the rectangles fine in my VST but crashes when I close the plugin’s window. I’m probably missing some step in shutting down the openGL context.
The assert is triggered by drawComponentBuffer() below, which is part of juce_OpenGLContext.cpp :
if (context.renderComponents)
{
if (isUpdating)
{
paintComponent();
if (! hasInitialised)
return false;
mmLock = nullptr;
lastMMLockReleaseTime = Time::getMillisecondCounter();
}
glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
drawComponentBuffer(); //This triggers an assert on shutdown
}
context.swapBuffers();
OpenGLContext::deactivateCurrentContext();
You can see that on shutdown if (context.renderComponents) is still true which is probably not correct.
What do I need to do in my component to tell the renderer it’s closing?
My complete code for the header and implementation of ScrollingNoteViewer is below.
#ifndef __JUCE_HEADER_B11C4CC4490D982E__
#define __JUCE_HEADER_B11C4CC4490D982E__
//[Headers] -- You can add your own extra header files here --
#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginProcessor.h"
//[/Headers]
struct Vertex // class storing the information about a single vertex
{
float position[2];
float colour[3];
};
class ScrollingNoteViewer : public Component, private OpenGLContext, private OpenGLRenderer, private Timer
{
public:
ScrollingNoteViewer (ReExpressorAudioProcessor*);
~ScrollingNoteViewer();
OpenGLContext openGLContext;
virtual void newOpenGLContextCreated() override;
virtual void renderOpenGL() override;
virtual void openGLContextClosing() override;
Matrix3D<float> getProjectionMatrix() const;
Matrix3D<float> getViewMatrix() const;
void setRectangleColour (int rect, float r, float g, float b);
void addRectangle(Array<Vertex> &verts, float x, float y, float w, float h, float red);
void createShaders();
void paint (Graphics& g) override;
void resized() override;
String getNoteText (const int midiNoteNumber);
enum ColourIds
{
whiteNoteColourId = 0x1005000,
blackNoteColourId = 0x1005001,
keySeparatorLineColourId = 0x1005002,
mouseOverKeyOverlayColourId = 0x1005003, /**< Will be overlaid on the normal note colour.*/
keyDownOverlayColourId = 0x1005004, /**< Will be overlaid on the normal note colour.*/
textLabelColourId = 0x1005005,
upDownButtonBackgroundColourId = 0x1005006,
upDownButtonArrowColourId = 0x1005007,
shadowColourId = 0x1005008
};
// This class manages the uniform values that the demo shaders use.
struct Uniforms
{
Uniforms (OpenGLContext& openGLContext, OpenGLShaderProgram& shaderProgram)
{
projectionMatrix = createUniform (openGLContext, shaderProgram, "projectionMatrix");
viewMatrix = createUniform (openGLContext, shaderProgram, "viewMatrix");
}
ScopedPointer<OpenGLShaderProgram::Uniform> projectionMatrix, viewMatrix;
private:
static OpenGLShaderProgram::Uniform* createUniform (OpenGLContext& openGLContext,
OpenGLShaderProgram& shaderProgram,
const char* uniformName)
{
if (openGLContext.extensions.glGetUniformLocation (shaderProgram.getProgramID(), uniformName) < 0)
return nullptr;
return new OpenGLShaderProgram::Uniform (shaderProgram, uniformName);
}
};
private:
ReExpressorAudioProcessor *processor;
double timeInTicks;
double startTime;
double timeRange;
double prevTimeInTicks = -1.0;
float wKbd;
int maxNote;
int minNote;
bool compressNotes;
float lineWidthRatio; //As fraction of key width
int octaveNumForMiddleC;
int beatsPerBar = 4;
int beatsHistory = 4;
int beatsInDisplay = 20;
int upperBorderThickness;
int leftMargin;
int frameCounter = 0;
GLuint vertexBuffer, indexBuffer;
int numIndices;
float delta = 0.002;
float x;
float y;
float pos = 0;
float desktopScale;
Array<Vertex> vertices;
const char* vertexShader;
const char* fragmentShader;
ScopedPointer<OpenGLShaderProgram> shader;
ScopedPointer<OpenGLShaderProgram::Attribute> position, sourceColour;
ScopedPointer<Uniforms> uniforms;
String newVertexShader, newFragmentShader;
void timerCallback() override {
x += delta;
if (x >= 1.0)
delta = - 0.002;
else if (x<=0.0)
delta = 0.001;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollingNoteViewer)
};
//[EndFile] You can add extra defines here...
//[/EndFile]
#endif // __JUCE_HEADER_B11C4CC4490D982E__
#include "ScrollingNoteViewer.h"
#include <array>
//[MiscUserDefs] You can add your own user definitions and misc code here...
//[/MiscUserDefs]
//==============================================================================
ScrollingNoteViewer::ScrollingNoteViewer (ReExpressorAudioProcessor* p) :
processor(p),
wKbd(24.f),
maxNote(84),
minNote(59),
compressNotes(false),
lineWidthRatio(0.8f), //As fraction of key width
octaveNumForMiddleC (3),
upperBorderThickness(14),
leftMargin(24)
{
setPaintingIsUnclipped(true);
//setOpaque (true);
startTimerHz(1000);
x = 0;
y = 0;
position = nullptr;
sourceColour = nullptr;
openGLContext.setRenderer (this);
openGLContext.attachTo (*this);
openGLContext.setContinuousRepainting (true);
}
ScrollingNoteViewer::~ScrollingNoteViewer()
{
}
void ScrollingNoteViewer::addRectangle(Array<Vertex> &verts, float x, float y, float w, float h, float red)
{
Vertex v0 =
{
{ x, y-h},
{ red, 0.0f, 0.0f}
};
verts.add(v0);
Vertex v1 =
{
{ x, y},
{ red, 0.0f, 0.0f}
};
verts.add(v1);
Vertex v2 =
{
{ x+w, y-h},
{ red, 0.0f, 0.0f}
};
verts.add (v2);
Vertex v3 =
{
{ x+w, y},
{ red, 0.0f, 0.0f}
};
verts.add (v3);
}
Matrix3D<float> ScrollingNoteViewer::getProjectionMatrix() const
{
float w = 1.0f / (0.5f + 0.1f);
float h = w * getLocalBounds().toFloat().getAspectRatio (false);
return Matrix3D<float>::fromFrustum (-w, w, -h, h, 4.0f, 30.0f);
}
Matrix3D<float> ScrollingNoteViewer::getViewMatrix() const
{
Matrix3D<float> viewMatrix (Vector3D<float> (-.3f, 0.0f, -4.0f));
Matrix3D<float> rotationMatrix = viewMatrix.rotated (Vector3D<float> (0.f, 0.0f, 0.0f));
return rotationMatrix * viewMatrix;
}
void ScrollingNoteViewer::setRectangleColour (int rect, float r, float g, float b)
{
float colour[3] = {r,g,b};
openGLContext.extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
glBufferSubData(GL_ARRAY_BUFFER,
sizeof(Vertex)*(4*rect) + 8,
sizeof(colour),
colour);
glBufferSubData(GL_ARRAY_BUFFER,
sizeof(Vertex)*(4*rect+1) + 8,
sizeof(colour),
colour);
glBufferSubData(GL_ARRAY_BUFFER,
sizeof(Vertex)*(4*rect+2) + 8,
sizeof(colour),
colour);
glBufferSubData(GL_ARRAY_BUFFER,
sizeof(Vertex)*(4*rect+3) + 8,
sizeof(colour),
colour);
openGLContext.extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
}
//initialise
void ScrollingNoteViewer::newOpenGLContextCreated()
{
createShaders();
Array<Vertex> vertices;
ScrollingNoteViewer::addRectangle(vertices, -1.0f, 0.5f, 0.3f, 0.5f,0.5f);
ScrollingNoteViewer::addRectangle(vertices, 0.5f, 0.5f, 1.0f, 0.5f,0.2f);
ScrollingNoteViewer::addRectangle(vertices, 0.0f, -0.8f, 1.0f, 0.2f,0.7f);
openGLContext.extensions.glGenBuffers (1, &vertexBuffer);
openGLContext.extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
openGLContext.extensions.glBufferData (GL_ARRAY_BUFFER,
static_cast<GLsizeiptr> (static_cast<size_t> (vertices.size()) * sizeof (Vertex)),
vertices.getRawDataPointer(), GL_DYNAMIC_DRAW);
Array<int> indices;
for (int i=0;i<vertices.size()/4;i++)
{
indices.add(0+4*i);
indices.add(1+4*i);
indices.add(2+4*i);
indices.add(1+4*i);
indices.add(2+4*i);
indices.add(3+4*i);
}
numIndices = 6*(vertices.size()/4);
//generate buffer object name(s) (names are ints) (indexBuffer is an GLuint)
openGLContext.extensions.glGenBuffers (1, &indexBuffer); //Gets id of indexBuffer
//bind a named buffer object (to a buffer type such as GL_ELEMENT_ARRAY_BUFFER)
openGLContext.extensions.glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
openGLContext.extensions.glBufferData (GL_ELEMENT_ARRAY_BUFFER,
static_cast<GLsizeiptr> (static_cast<size_t> (numIndices) * sizeof (juce::uint32)),
indices.getRawDataPointer(), GL_STATIC_DRAW);
////Projection & View Matrices
if (uniforms->projectionMatrix != nullptr)
uniforms->projectionMatrix->setMatrix4 (getProjectionMatrix().mat, 1, false);
if (uniforms->viewMatrix != nullptr)
uniforms->viewMatrix->setMatrix4 (getViewMatrix().mat, 1, false);
}
//render
void ScrollingNoteViewer::renderOpenGL()
{
++frameCounter;
jassert (OpenGLHelpers::isContextActive());
desktopScale = (float) openGLContext.getRenderingScale();
OpenGLHelpers::clear (Colour::greyLevel (0.1f));
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glViewport (0, 0, roundToInt (desktopScale * getWidth()), roundToInt (desktopScale * getHeight()));
shader->use();
setRectangleColour(0, 1.0f, 1.0f, (x+1.0f)/2.0f);
setRectangleColour(1, 1.0f, (x+1.0f)/2.0f, 1.0f);
setRectangleColour(2, 1.0f, (x+1.0f)/2.0f, 0.5f);
openGLContext.extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
openGLContext.extensions.glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
if (position != nullptr)
{
openGLContext.extensions.glVertexAttribPointer (position->attributeID, 2, GL_FLOAT, GL_FALSE, sizeof (Vertex), 0);
openGLContext.extensions.glEnableVertexAttribArray (position->attributeID);
}
if (sourceColour != nullptr)
{
openGLContext.extensions.glVertexAttribPointer (sourceColour->attributeID, 3, GL_FLOAT, GL_FALSE, sizeof (Vertex), (GLvoid*) (sizeof (float) * 2));
openGLContext.extensions.glEnableVertexAttribArray (sourceColour->attributeID);
}
glDrawElements (GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);
if (position != nullptr)
openGLContext.extensions.glDisableVertexAttribArray (position->attributeID);
if (sourceColour != nullptr)
openGLContext.extensions.glDisableVertexAttribArray (sourceColour->attributeID);
openGLContext.extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
openGLContext.extensions.glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
}
//shutdown openGL
void ScrollingNoteViewer::openGLContextClosing()
{
shader = nullptr;
uniforms = nullptr;
openGLContext.extensions.glDeleteBuffers (1, &vertexBuffer);
openGLContext.extensions.glDeleteBuffers (1, &indexBuffer);
}
void ScrollingNoteViewer::createShaders()
{
vertexShader =
"attribute vec4 position;\n"
"attribute vec3 sourceColour;\n"
"\n"
"uniform mat4 projectionMatrix;\n"
"uniform mat4 viewMatrix;\n"
"\n"
"varying vec3 destinationColour;\n"
"\n"
"void main()\n"
"{\n"
" destinationColour = sourceColour;\n"
" gl_Position = projectionMatrix * viewMatrix * position;\n"
"}\n";
fragmentShader =
#if JUCE_OPENGL_ES
"varying lowp vec3 destinationColour;\n"
#else
"varying vec3 destinationColour;\n"
#endif
"\n"
"void main()\n"
"{\n"
" gl_FragColor = vec4(destinationColour,1.0); \n"
"}\n";
ScopedPointer<OpenGLShaderProgram> newShader (new OpenGLShaderProgram (openGLContext));
String statusText;
if (newShader->addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (vertexShader))
&& newShader->addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (fragmentShader))
&& newShader->link())
{
shader = newShader;
uniforms = nullptr;
shader->use();
if (openGLContext.extensions.glGetAttribLocation (shader->getProgramID(), "position") < 0)
position = nullptr;
else
position = new OpenGLShaderProgram::Attribute (*shader, "position");
if (openGLContext.extensions.glGetAttribLocation (shader->getProgramID(), "sourceColour") < 0)
sourceColour = nullptr;
else
sourceColour = new OpenGLShaderProgram::Attribute (*shader, "sourceColour");
uniforms = new Uniforms (openGLContext, *shader);
std::cout << "GLSL: v" + String (OpenGLShaderProgram::getLanguageVersion(), 2) + " Shaders created" << "\n";
}
else
{
std::cout << newShader->getLastError()<<"\n";
}
}
//==============================================================================
void ScrollingNoteViewer::paint (Graphics& g)
{
}
void ScrollingNoteViewer::resized()
{
}
String ScrollingNoteViewer::getNoteText (const int midiNoteNumber)
{
return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
}
//==============================================================================
#if 0
/* -- Projucer information section --
This is where the Projucer stores the metadata that describe this GUI layout, so
make changes in here at your peril!
BEGIN_JUCER_METADATA
<JUCER_COMPONENT documentType="Component" className="ScrollingNoteViewer" componentName=""
parentClasses="public Component" constructorParams="" variableInitialisers=""
snapPixels="8" snapActive="1" snapShown="1" overlayOpacity="0.330"
fixedSize="0" initialWidth="600" initialHeight="400">
<BACKGROUND backgroundColour="ffffffff"/>
</JUCER_COMPONENT>
END_JUCER_METADATA
*/
#endif
//[EndFile] You can add extra defines here...
//[/EndFile]