While investigating ways to render lots of vector graphics faster, I came across Piet-GPU and Pathfinder. Both of these projects are Rust-based vector graphics engines that target GPUs.
For one of my projects, I need a way to render millions of cubic Bezier curves with varying stroke thicknesses very quickly; ideally in just a few seconds. With that in mind, I compiled and ran some tests using Piet and Pathfinder. These are some notes from my experiments.
Piet-GPU
Piet initially captured my attention because of several recent articles written by its creator Raph Levien.
To compile Piet, run Cargo:
cargo build
And then run the command line example:
./target/debug/cli
This generates a file, image.png
, which looks like this:
The image above is ultimately rendered by a boilerplate render_scene() function, which renders some translucent ‘diamonds’, a clipped filled Bezier curve, a white line, and other primitives (aka programmer art).
For reference, the cli
command above also outputs the following:
INTEL-MESA: warning: Ivy Bridge Vulkan support is incomplete
scene: 152 elements, 42 paths, 107 path_segments, 0 transforms
doing non-indexed k4
elapsed = 53.632309ms
Element kernel time: 0.092ms
Tile allocation kernel time: 0.045ms
Coarse path kernel time: 0.245ms
Backdrop kernel time: 0.109ms
Binning kernel time: 0.027ms
Coarse raster kernel time: 0.982ms
Render kernel time: 10.872ms
You can also pass an SVG file to cli
:
./target/debug/cli piet-gpu/Ghostscript_Tiger.svg
That command renders the classic Tiger head SVG, via render_svg():
This time, the output of cli
is:
INTEL-MESA: warning: Ivy Bridge Vulkan support is incomplete
parsing time: 32.726325ms
flattening and encoding time: 1.732232ms
scene: 2745 elements, 182 paths, 2458 path_segments, 0 transforms
doing non-indexed k4
elapsed = 57.448498ms
Element kernel time: 0.405ms
Tile allocation kernel time: 0.111ms
Coarse path kernel time: 2.762ms
Backdrop kernel time: 0.292ms
Binning kernel time: 0.027ms
Coarse raster kernel time: 1.270ms
Render kernel time: 12.759ms
To compare timings, the images above were all rendered on Linux on an Intel i7-3740QM.
And here’s the relevant bit from glxinfo
:
Device: Mesa DRI Intel(R) Ivybridge Mobile (0x166)
Note that I’m using Piet-GPU at hash 07e07c7. Piet was evolving rapidly at the time of writing this article, so your mileage may vary as the code changes.
Piet-GPU Limitations
For my use case, I need to render large amounts of Bezier curves. However, Piet-GPU simply didn’t render anything when I used it to render an SVG file with more than approximately 1608 path objects. Whether this is an inherent Piet-GPU limitation or a bug, I don’t know.
Pathfinder
Pathfinder didn’t run at first.
I had to run a little workaround to make it compile & run: rustup override set 1.47.0
.
The real-time SVG example is impressive – only 1.5 milliseconds to render the 500 KB Tiger SVG file:
Performance for my example wasn’t as good - 100 ms to render 2,000 simple Bezier curves. Maybe it’s due to the coverage of the paths - perhaps there’s a way to split up the curves for a faster render…
…And extrapolating upward – and assuming that rendering time scales linearly – it would take 100 seconds to render 2,000,000 Bezier curves. So maybe Pathfinder isn’t much faster for my use case than Cairo or some other CPU-based vector graphics library.
Conclusion
After running this initial experiment, neither Piet-GPU nor Pathfinder passed the bar on the first try. Piet choked on just over 1600 paths, and Pathfinder wasn’t as fast as I’d expected for the specific scene structure I’m rendering.
Next I’ll benchmark Cairo and one or two other CPU-based libraries to see if they fare better when rendering millions of cubic beziers.
I might also benchmark HTML5 <Canvas>
for this scenario.