I always tried to avoid OpenGL. I read posts that it can fail on windows because of bad drivers and it is deprecated on macOS.
But I’m having main / UI thread performance problems in my latest project rendering a waveform and showing the playing position.
The problem is that the main thread also affects the processing thread when overloaded and this can lead to audio dropouts, especially on older systems when I move a slider or switch a tab.
I found that on macOS getClipBounds() always returns the whole area and it looks like there is no way to optimize this otherwise. It always triggers a repaint of the whole waveform when I move the play position component. Buffering to an image also does not make any difference.
I paint only paths with a few texts and it looks like rendering the waveform and play position into an OpenGLContext already reduces the CPU usage a lot without changing code. I just had to attach the components to the GL context.
What is your experience? Do i need to expect problems when using OpenGL or should i avoid it at all costs? We release for Linux, Windows, and macOS.
My 2 cents: OpenGL still works and will for a while. The only concern is apple decides to physically remove support from their new machines. However, even if that comes to pass, I don’t think learning OpenGL would have been a waste of time, as it will still give you some foundations of how graphics APIs work in general. What I mean is whatever you have to learn after OpenGL (Vulkan, metal, etc), it will likely come easier having an understanding of OpenGL.
However, without understanding the full context of your project, the idea that something in the graphics rendering is causing audio dropouts sounds off to me, and it raises the question of whether or not OpenGL would fix whatever issue you are experiencing.
You can always pass any path generation etc off to a worker thread and keep drawing on the cpu. See Daniels Frequalizer for a great example.
For me personally, I only turn to OpenGL when fluid animations are required.
Thanks for the answer. I hoped for an answer like this.
I’m also not sure why this happens. I may have to investigate a bit more here. AFAIK i don’t access the message thread and i also don’t have any locks or atomics in the processing thread.
My guess was that the DAW doesn’t work anymore when the message / UI thread is overloaded and this maybe lead to dropouts. But that’s just a wild guess…
You can always pass any path generation etc off to a worker thread and keep drawing on the CPU
That’s a good idea. My experience is that this also can lead to problems when people work in bigger DAW sessions with a lot of plugins. It may make it difficult for the DAW to plan how to spread the plugins on the different CPU cores.
It would be even better if i could do that work on the GPU. But i have to dive into OpenGL a bit more before i can do this
I will also rethink twice if OpenGL is really required for this or if I find other solutions to optimize this.
If you want to release a product but don’t want to have to put hours of work into maintaining it in the future - I’d say to avoid OpenGL and just stick with the limitations of JUCE’s software graphics. You can maybe look at using some of the other graphics contexts like Direct2D if you need a bit of a boost.
If you want to learn a new skill, that you could potentially apply in a future product, then 100% OpenGL would be a great thing to use! Most of what you’ll learn through using OpenGL will likely be transferable to other GPU libraries. Learning GLSL shaders on its own could be really valuable.
Personally, I went with the first approach - I love using OpenGL but it seems like it’s not going to be around in the near future, so I’m waiting patiently for the next cross-platform solution with JUCE before I start using that in any larger projects. For now, I just accept the limitations of the software renderer.
Thanks for the advice. I will try to live with the limitations. As you said, maybe i should go 100% GPU in a future project when juce is ready for this.
After some more measurements, I found that the CPU I save when using OpenGL with juce graphic features like paths and components does not speed up things a lot. Looks like still a lot of work is done on the UI thread.
I’ve found that just attaching a opengl context to a component very rarely has a significant improvement. As you say, the majority of the work to actually generate the path being drawn is still done on the CPU. The GPU really comes into effect when you give it the smallest amount of data you can and it runs shaders to render something much more complicated, but JUCE doesn’t have a way to utilise that through the graphics API alone (AFAIK).
I’m not sure why getClipBounds returns the whole area for you on Mac. We have the same scenario in our Juce-based host and when the playhead is over a component, it gets tiny clipped bounds as expected.
That is a different issue. This one says that if you have 2 repaint rectangles occuring in 2 areas, instead of invalidating the regions separately, it will invalidate a larger region encompassing both.
So it’s really an issue if you have things moving in opposite corners of your plugin, in which case indeed the whole UI will recompute.
And that’s indeed why we switched to OpenGL. But as others said I would advise you to avoid it as much as you can, especially in a plugin. Instead I would modify my UI to avoid the edge case described above.
Just wanted to add that to get maximum performance from OpenGL you want to essentially eliminate the use of the paint() routine. If you use render() instead, using vertex buffer data and glDrawLines, glDrawTriangleStrip etc, you can get really solid performance. You just lose one of the better parts of the juce api. A lot of the calls you’d likely make in paint() actually hit the message thread, and then to get that rendering to the OpenGL thread, locks are involved and things fall apart very quickly. Some commands are safe from memory, someone made a post here about it recently. I think some primitives are okay, like drawRect.