Drawing paths using OpenGL: Polyline2D

Since rendering paths with juce::Graphics hasn’t been performant enough for my use case, and modern OpenGL doesn’t support drawing thick lines, I wrote Polyline2D, a library to generate a 2D mesh from a set of points.

This way, you can render thick paths blazingly fast using OpenGL!

Here’s what it looks like:

For demonstration purposes, the generated mesh is once rendered in wireframe mode (light green), and once in fill mode (transparent green).
The red points show the input points.

It supports all join and end cap styles that JUCE supports as well.

For an example JUCE application, see Polyline2DExample.


Have you tried plugging this in to the JUCE OpenGL renderer?

This could be a game changer as the slowest thing in the GL rendering is the rath rasterisation. Great stuff!


A lot of your stuff in LineSegment.h could have been done with the juce::Line<> class, same for the stuff in Vec2.h replicating some of juce::Point<>. Was there a reason for reinventing the wheel, so to speak?

@matkatmusic the library itself doesn’t seem to depend on anything but the standard library. Not even OpenGL. I guess maximum portability was a design decision.

Nice work @CrushedPixel! I wonder how much work it would take to create a class that takes any juce::Path and generate vertices for OpenGL? Maybe even provide appropriate initialisation/render/shutdown methods and handle the shaders internally?


As @Clarke correctly guessed, my goal was to make a self-contained library without any external dependencies. We’re not in JS-land here :smiley:
Though I don’t know for certain, as I’ve never used it, I believe you could also send these vertices into a Direct3D rendering pipeline, so you’re neither restricted to JUCE nor to OpenGL.

Both of these are great ideas! After a quick look at the JUCE source code, it seems to me like I can use a juce::PathFlatteningIterator to break any juce::Path down into straight lines, which can then be sent into Polyline2D.
I’ll give @Clarke’s idea a try, and if that works out, we can figure out how to plug this directly into JUCE. I’ll probably need some help on the second part, though.


PathFlatteningIterator can be kinda slow if your path is large, fyi

Aw, that sucks. It’s surely not as bad as the rasterization’s performance though, is it?
Besides, I have to break the path down into points somehow, so I’ll just go for the easiest approach for now.

This is a juce::Path with multiple subpaths being rendered using OpenGL and Polyline2D:

I had to add support for closed paths to Polyline2D, but it all works smoothly now.

Here’s the source code: Polyline2DPathRenderer

Here’s the relevant bit, converting the juce::Path into a mesh:

@dave96 @jules If you want to use my source code to integrate this into JUCE to speed up Path rendering (at least when using OpenGL), you have my full permission to use and/or modify the code of both Polyline2D and Polyline2DPathRenderer however you need.


1 Like

How would one go about comparing the performance between drawing paths this way and using Juce paths ? (I’m a bit new to OpenGL (and xcode for that matter)).
I ask because I’m a bit hesitant (for now) just making a Juce path version of this example and then just comparing the CPU % from xcode’s debug session (is this cpu gauge accurate for this purpose ? )

Many thanks

Edit: as I suspected, the CPU reading on my end was strangely too high due to some bug with the current sdk ? (as read in this thread: GUI Drawing Efficiency). So I guess my question still stands about how one would recommend profiling this example against a normal Juce example. I’d be happy to work up a repo and upload it when I’m done. I’m kinda really keen to find out if it’s worth learning OpenGL to do my GUI (simple animated shapes but a high number of simultaneous shapes - MIDI sequencer app)

The way I profile my GUIs is by using XCode’s Instruments program.
I use the Time Profiler, record for a couple of seconds, and see how much time (relative to other functions) is spent inside the function responsible for rendering the paths. But if you have a sufficient amount of points in your path, the performance difference should be obvious by the amount of FPS you get - I got barely 10-15 FPS when using a JUCE path with ~200 points (rendering a waveform), but with Polyline2D I get 60FPS.

In general though, you should try to avoid switching over to OpenGL if possible at all - I’ve had to implement a fairly complex graphical component in OpenGL because of Path performance, and let me tell you, it’s nowhere near as simple as calling graphics.drawRect and friends. You have to prepare VAOs and Shaders, fill VBOs with vertices, generate and assign textures to the vertices, etc etc…

If you need any help though, feel free to DM me - I’ve created some neat OpenGL helper classes I can share with you, and will gladly help you with all of the tricky bits :slight_smile:
This tutorial helped me a lot: https://learnopengl.com/


just DM’ed. This is great stuff for the Juce community.

This is fantastic thanks so much for making this! Can this be easily adapted to draw filled paths as well?

Not yet, but I’ll have a look at that today.

It turns out Polygon triangulation is quite a complex issue, and not at all trivial to implement when it comes to non-simple polygons (i.e. those intersecting themselves). I think this is out of scope for this library.

Gotcha, thanks for looking into it though. Much appreciated.

I have use Javascript version of Clipper in my previous job. Worked great and could handle any geometry.


1 Like

That looks like a very good library, thanks for the tip!

@RustyPine I believe you could just use this library in your project directly, and feed the points of the juce::Path you want to fill into it (using juce::PathFlatteningIterator). There’s nothing my library has left to do for that use-case :slight_smile:

1 Like

Thanks for the tip! I ended up just rolling my own using a super duper simple geometry shader. But for future tasks will definitely keep these libraries in mind :slight_smile:

1 Like

What is preventing this to be taken into account by the juce team?