Is q5.js the Fastest 2D Graphics Library on the Web?
q5 v3.1 features major performance improvements
Great educators take stuff that at one time could’ve only been done by experts in the field, and give students the ability to try it out.
But it's even more exceptional when educators can provide students with a beginner friendly tool that's also right at the cutting edge!
The sky's the limit with q5.js and its new WebGPU renderer. Students can go nuts making interactive art like never before.
q5.js Progress Report
q5 v3.1 is another HUGE update, hot on the heels of the v3.0 release two weeks ago!
q5.js can now draw 30-50x more shapes per frame than p5.js. 👀
Making a comparison to Pixi.js, well known for its “unmatched performance”, is more interesting though! 😅
Performance Comparison with Pixi.js
Let’s do a test of rendering stroked circles that change fill and stroke color every frame.
Pixi’s Graphics API is not gonna cut it, maxing out at just 2,160 circles.
How about using Pixi’s new, insanely fast ParticleContainer? Well, it’s too limited for this purpose since it batch renders particles in one draw call, which does not allow for specific layering. Using a regular Container and Sprites in Pixi, we can go one by one, layering a tinted stroke graphic over each tinted fill graphic.
Note that this still isn’t equivalent to what q5 can do, since Pixi doesn't support HDR colors.
Nevertheless, let’s take a look at the results on my M1 chip MacBook Air 2020.
Pixi.js WebGPU maxes out at 19,440 circles. Note the CPU getting slammed but minimal GPU usage (shown in green at the bottom).
Here’s q5.js WebGPU drawing the same amount, but it only takes ~3ms each frame, 500% greater efficiency!
q5 maxes out at 87,840 circles, over 4x as many! Note the even split between the CPU and GPU.
Here’s the code I used in these tests: rainbow loops with q5 and rainbow loops with Pixi.
Comparison to p5.js and Processing
With the same test, p5.js can only draw 3,600 circles at 60fps.
Processing Java doesn’t fair much better, at 5,040 circles.
What Does it Mean?
There’s always been a massive divide between p5.js and Pixi.js. Isn’t it cool to see q5 closing the gap?
Ease of use is still my north star, but I want to show that in some ways q5 is right on the cutting edge!
So yeah, if you want to draw shapes in a dynamic way, q5 might just be the fastest 2D graphics library on the web. More extensive testing is needed though.
Why Instanced Rendering is GOATed
Before we get into the weeds here, take a look at the q5 v3.1 release notes.
Previously q5 used the same shapes shader for drawing all shapes. Shapes were sliced into triangles and their vertex positions were calculated in JS on the CPU before being sent to the GPU. Fills were drawn before strokes. This made the fragment shader as simple as possible: it just returns one color. For a single rectangle with no stroke, only two triangles are needed, but ellipses require many triangles in order to have nicely curved edges.
A benefit of the shapes shader being pretty simple is it makes it easier for users to customize. But while fast for rectangles, it’s inefficient for shapes with any roundness to them.
I knew about instancing when I first started using WebGPU but had only seen tutorials for it that used 3D models. For the past two weeks I struggled to figure out how to efficiently utilize this powerful feature within the q5 API. Luckily it was possible!
With instancing in q5 WebGPU, a shape’s center position, dimensions, stroke weight, transformation matrix, etc. are sent to the GPU. That small amount of data is used to calculate vertices dynamically in the vertex shader. The fragment shader then calculates the distance of each fragment from the shape’s center, to see if it should be painted with the fill color, stroke color, or a mix between them if the stroke is not opaque.
At first I tried drawing fills and strokes in separate shaders, but this requires pipeline switching and performing some calculations twice. No bueno. Rendering the fill and stroke in the same fragment shader was key. Although, note that because the shader is more complex, it takes longer to draw large shapes. That’s fine though, since it’s only useful to draw a ton of shapes if they’re small enough to all fit on screen.
Instancing gives more work to the GPU and frees up the CPU. That's an especially good tradeoff for q5 when used with CPU heavy addons, such as ml5.js and the upcoming q5play.
Never heard about q5, just saw this post on reddit. Sounds really intresting and worth looking into. Any idea if performance is better than vello? And how does it handle glyph rendering, any performance comparisons for font/glyphs with p5/pixi?