2D plug-in using OpenGL?

I use a lot of Paths in my GUIs to render frequency spectrums, audio waveforms, etc. and so I’d like to use OpenGL to render them since rendering them through the default Graphics engine eats up a lot of CPU.

However, the only examples I can find for using OpenGL in plug-ins seem to involve displaying 3D spinning teapots with nice shading, which isn’t really the asthetic I’m after. I simply want to render some basic 2D shapes.

So do I still need to write some GLSL to render my Paths or is there a simpler trick to have them rendered in OpenGL that I’m missing? Any advice or example projects would be much appreciated!

Here’s an example from my Spectrum Analyser plug-in which, as you can see, renders several complex paths with hundreds of points and fills with gradients - currently just rendering on the CPU:

2 Likes

So I’ve been playing around with the OpenGLContext and can see that the painting methods (g.fillPath(), etc.) are calling some OpenGL methods further down the line… but there doesn’t seem to be a noticable difference in performance!

Here’s some ugly code I write to test drawing overly-complex paths:

//==============================================================================
TestAudioProcessorEditor::TestAudioProcessorEditor (TestAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{
    openGL.attachTo(*this);

    Random r;

    path.startNewSubPath({ 0.f, 0.f });
    
    for (int i = 0; i < 1000; i++)
        path.cubicTo(
            { r.nextFloat() * 400.f, r.nextFloat() * 300.f },
            { r.nextFloat() * 400.f, r.nextFloat() * 300.f },
            { r.nextFloat() * 400.f, r.nextFloat() * 300.f }
        );

    path.closeSubPath();

    startTimerHz(60);
    setSize(400, 300);
}

//==============================================================================
void TestAudioProcessorEditor::paint(Graphics& g)
{
    g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId));

    g.setColour(Colours::red);
    g.strokePath(path, PathStrokeType(10.f));

    g.setGradientFill(ColourGradient::vertical(Colours::blue, Colours::orange, getLocalBounds()));
    g.fillPath(path);
}

void TestAudioProcessorEditor::timerCallback()
{
    repaint();
}

Adding and removing the openGL.attachTo(*this); call doesn’t seem to have a significant impact on CPU usage? Is there something I’m still missing here?

I’d recommend rendering those graphs yourself. You can use gl line drawing and use a shader for the fills. You can pull this off with opengl 2.0 features if you’re careful and have good compatibility. As far as I know, juce path drawing isn’t accelerated in any way by using opengl.

Here’s a quick summary of techniques you can use to take advantage of OpenGL in JUCE. I actually have a code-along video tutorial discussing these topics (link at bottom).

Technique 1 : Write 2D JUCE Graphics Code - Render with OpenGL

When writing JUCE 2D Graphics code, you can ask JUCE to render the graphics as OpenGL under the hood, which is what you have already shown. And you have done that correctly by attaching the OpenGLContext to the Component. I believe, behind the scenes, this uses code in juce_OpenGLGraphicsContext and juce_LowLevelGraphicsContext to render Component drawing as OpenGL image textures for potentially accelerated 2D rendering using the GPU.

Here’s another way to tell JUCE to render your juce::Graphics code as OpenGL. I’ve seen it done in the JUCE Demo Runner. The only difference being the getTopLevelComponent() call. From what I understand, this attempts to render the entire window contents in OpenGL.

class AudioProcessorEditor
{
public:
    AudioProcessorEditor::AudioProcessorEditor()
    {
        openGLContext.attachTo (*getTopLevelComponent());
    }

private:
    OpenGLContext openGlContext;
};

Technique 2 : Write OpenGL Code

Using the OpenGL API manually is more complicated for 2D graphics, but it could be worth it to implement specific parts of your UI in OpenGL leaving the rest for regular JUCE 2D rendering. I think your path-based spectrum would be a great example of a sub-component that could be custom rendered in OpenGL. Instead of storing Path points you would be storing 2D vertex coordinates or some similar structure that you pass off to OpenGL.

Check out my open source OpenGL visualizer project: https://github.com/TimArt/3DAudioVisualizers In the project I have a 2D oscilloscope and a 3D spectrum visualizer. By taking elements from both, you could probably reproduce the visual you are looking for in OpenGL.

Other 2D Graphics Rendering Options

There are other accelerated 2D graphics rendering options in JUCE including CoreGraphics for Mac and Direct2D for Windows. I believe CoreGraphics is enabled by default but check the USE_COREGRAPHICS_RENDERING macro. You might have to manually enable Direct2D via JUCE_DIRECT2D, but I have no experience using Direct2D. Both macros are in juce_graphics.h.

JUCE OpenGL Code-Along Tutorial

I recently made a JUCE OpenGL setup tutorial which discusses these various options. It also jumps into rendering of basic primitive 3D objects including a triangle, pyramid, and cube if you’re interested in that at all. The video comes with available source code and some utility classes.

You can access the tutorial for free or pay what you want via Gumroad: https://gum.co/juce-opengl-setup

I hope this was helpful!

19 Likes

Thankyou so much for your great response - this is just what I needed to know!

I took a look at your tutorial video… it was really well done and lots of great info!
Only issue however was that the demo project you provided doesn’t seem to work as provided (I’m on Windows 10, VS2019, JUCE 5.4.7) as the glGenVertexArrays and glBindVertexArray methods are locked behind a JUCE_OPENGL3 define. However, adding #define JUCE_OPENGL3 1 doesn’t seem to solve the problem - the code does infact run but I’m then given an Access violation executing location 0x... error instead.

Maybe this is a Windows problem, or just my specific system config isn’t correct? If it is a Windows thing, it may be worth adding a note to your video and/or sample projects about how to fix it?

Thanks again for your great reponse - I’ll look further into this error I’m getting and see what I can come up with.

1 Like

Okay, so after a quick search on the forum, it seems there’s a lot of discussion around JUCE_OPENGL3 not being defined for Windows.

Previously I’d just added #define JUCE_OPENGL3 1 to the OpenGLComponent.hpp of @TimArt’s sample project which allowed the project to compile, but threw an exception at runtime.

I found this thread from @jrlanglois where he’s added the same #define JUCE_OPENGL3 1 but to the juce_opengl.h file.

I copied this and moved the define to the openGL module header and the sample project now compiles and runs as expected!

Still not 100% why Windows has this behaviour though? Am I safe to use this in production? Later in that safe thread, @fabian suggests adding the define to the Preprocessor Definitions of the project in the Projucer - this also appears to work fine.

So why is the JUCE_OPENGL3 definition not inclded in the module options like, for example, JUCE_USE_DIRECTWRITE in the graphics module? Even if the default remained disabled.

1 Like

What makes this even more confusing is that my OpenGL version appears to be v4.60 :man_facepalming:

1 Like

This entire forum has always been in some form of peril wrt OpenGL… This topic is a consistent source of terrible confusion and misinformation.

Plainly put:

  • JUCE doesn’t follow OpenGL pipelining very well.
  • Your driver always reports the latest version of OpenGL. The version reported has nothing to do with the instantiation of the context on Windows.
  • To get all of the features you need, we shouldn’t be fucking around with macros in the framework - let alone cryptic ones that conflict logistically with the APIs provided. Instead, we should be wrangling the functions and loading any and all of that, even lazily - as is the way of GL. But alas, we aren’t… (See FR: Wrangle a comprehensive set of OpenGL functions & macros )
  • Various plugins do use JUCE’s GL. The performance gains are minimal at best, or cause cross platform issues at worst. You’re better off writing a simple renderer using the GL pipeline to be able to get everything out of it.
4 Likes

@Gramotech Thanks for the feedback! I was unaware of the whole Windows macro define shenanigans, that’s so odd. I am surprised it is not enabled by default.


JUCE functionality related to OpenGL versioning has been confusing in my experience. To be able to use newer OpenGL features >= OpenGL 3.2 in JUCE I must call:

openGLContext.setOpenGLVersionRequired (OpenGLContext::OpenGLVersion::openGL3_2);

In juce_OpenGLContext.h, the only options for versions are:

/** OpenGL versions, used by setOpenGLVersionRequired(). */
enum OpenGLVersion
{
    defaultGLVersion = 0,
    openGL3_2
};

With only these two options, I would’ve expected that defaultGLVersion would use the latest OpenGL version for my machine, but in fact it seems the opposite.

When I use:

openGLContext.setOpenGLVersionRequired (OpenGLContext::OpenGLVersion::defaultGLVersion);

I get the GLSL runtime compiler error for all GLSL shader versions #version 130 and above:

version 'XXX' is not supported

(where XXX is the GLSL shader version).

This error makes it look as though my machine does not support the GLSL version I specified in my shader file, although in reality my machine does.


In summary, in the current state of JUCE:

If you want to use GLSL shader versions 130 and later (you probably do), make sure to set JUCE’s OpenGL version to 3.2:

openGLContext.setOpenGLVersionRequired (OpenGLContext::OpenGLVersion::openGL3_2);

From what I can tell, features above 3.2 are accessible if the machine supports them.

1 Like

I wrote a short blog post about using OpenGL for 2D graphics in a JUCE plug-in:

I didn’t have any issues using GLSL 330 shaders with JUCE on Windows without specifying any particular version… Is the setOpenGLVersionRequired just to check if the user’s machine has at least the specified version? And if they don’t what happens? Will the shaders just not run and I get an empty window, or will my plug-in crash?

3 Likes

Just more info for people who is new for OpenGL (like me for example…)

I had an issues with running the example above. But seems now solved with the following shaders:

Just an example of part of the code:

I used this example:
https://medium.com/@Im_Jimmi/using-opengl-for-2d-graphics-in-a-juce-plug-in-24aa82f634ff?source=social.tw

Also this need to be removed (if someone has it):
o̶p̶e̶n̶G̶L̶C̶o̶n̶t̶e̶x̶t̶.̶s̶e̶t̶O̶p̶e̶n̶G̶L̶V̶e̶r̶s̶i̶o̶n̶R̶e̶q̶u̶i̶r̶e̶d̶(̶j̶u̶c̶e̶:̶:̶O̶p̶e̶n̶G̶L̶C̶o̶n̶t̶e̶x̶t̶:̶:̶O̶p̶e̶n̶G̶L̶V̶e̶r̶s̶i̶o̶n̶:̶:̶o̶p̶e̶n̶G̶L̶3̶_̶2̶)̶;̶

And now I have a basic rectangle running on iOS.

Thanks! Hope this will be helpful.

Ready made component:

1 Like