Skia is a great library, but as all things Google it's a pain to build. They don't use CMake and building it from source takes 20-30 minutes on a modern laptop. Furthermore, it's constantly changing its APIs and much of it is undocumented and unclear on how to use optimally. Most of the decisions taken by development team aren't discussed in the open and this makes it hard to understand the codebase.
I wish there was a nice and small vector graphics library with GPU acceleration. So far Skia is the only real option, despite its downsides.
I'm personally skeptical about GPU acceleration being the answer to 2D rendering for various reasons.
I'm looking forward to Blend2D < https://blend2d.com/ > which is JIT based maturing and being the preferred solution.
What happened to Cairo the 2d graphics tool?
Cairo is in a maintenance-only mode. Nobody develops this library anymore and it only has a maintainer or two. Since nobody really worked on Cairo in the past 15 years it's not optimized for modern hardware.
You can see some existing benchmarks here:
Both the benchmarking tool and Blend2D are open-source projects so anyone can verify the numbers presented are indeed correct, and anyone can review/improve the backend-specific code that is used by the benchmarking tool.That's too bad. Is their a successor planned or is Skia the recommended alternative?
I think that when it comes to 2D rendering libraries there is in general not too many options if you want to target CPU or both CPU+GPU. Targeting GPU-only is bad for users that run on a hardware where GPU doesn't perform well or is not available at all due to driver issues or just not present (like servers).
If you consider libraries that offer CPU rendering there are basically:
Nobody develops AGG and Cairo anymore and Qt's QPainter hasn't really improved in the past decade (Qt Company's focus is QtQuick, which doesn't use QPainter, so they don't really care about improving the performance of QPainter). So, only 2 libraries from this list have active development - Blend2D and Skia.As an author of Blend2D I hope that it will be a go-to replacement for both AGG and Cairo users. Architecturally, Blend2D should be fine after a 1.0 release as the plan is to offer a stable ABI with 1.0 - And since Blend2D only exports C-API it should be a great choice for users who want to use every cycle and who want their code to work instead of making changes every time the dependency is updated (hello Skia).
At the moment Blend2D focuses on AGG users though, because AGG is much more widespread in commercial applications due to its licensing model and extensibility. However, AGG is really slow especially when rendering to large images (like 4K) so switching from AGG to Blend2D can offer a great performance benefits while avoiding other architectural changes of the application itself.
BTW Blend2D is still under active development. It started as an experiment and historically it only offered great performance on X86 platforms, but that is changing with a new JIT backend, which provides both X86 and AArch64 support and is almost ready for merge. This is good news as it will enable great performance on Apple hardware and also other AArch64 devices, basically covering 99% of the market.
Do not forget: https://www.amanithvg.com (I'm one of the authors, 20+ years of active development). Full OpenVG 1.1 API, CPU only, cross-platform and analytical coverage antialiasing (rendering quality) as main feature. The rasterizer is really fast. I swear ;) At Mazatech we are working to a new GPU backend just these days.
AmanithVG is the library on which our SVG renderer: https://www.amanithsvg.com is based. All closed source as now, but things may change in future.
I will do some benchmarks of the current (and next, when the new GPU backend will be ready) version of our libraries against other libraries. Do you know if there are any standard tests (besides the classic post script Tiger)? Maybe we can all agree on a common test set for all vector graphics libs bechmarks?
That's right! I didn't consider closed source libraries when writing the list. There would be more options in that case like Direct2D and CoreGraphics. However, my opinion is that nobody should be using closed source libraries to render 2D graphics in 2024 :)
Regarding benchmarks - I think Tiger is not enough. Tiger is a great benchmark to exercise the rasterizer and stroker, but it doesn't provide enough metrics about anything else. Tt's very important how fast a 2D renderer renders small geometries, be it rectangles or paths. Because when you look at screen most stuff is actually small. That's the main reason why Blend2D benchmarking tool scales the size of geometries from 8x8 to 256x256 pixels to make sure small geometries are rendered fast and covered by benchmarks. When you explore the results you will notice how inefficient other libraries actually are when it comes to this.
I'm the author of another CPU-only 2D vector graphics library that might be of interest:
- Canvas Ity (https://github.com/a-e-k/canvas_ity)
It's a tiny single-header C++ library in the style of the STB libraries. My aim was to make it dirt simple to be able to drop into almost any project and get high-quality rendering while providing an API comfortable to those used to <canvas>.
I've been checking out Blend2D every now and then. It seems like a very nice option for the bigger, but faster and more fully-featured end of the spectrum.
(Though for what it's worth, while raw performance isn't my priority, my little library still can hit about 70fps rendering the Postscript Tiger to 733x757 res with a single thread on my 7950x. :-)
Nice project, thanks for sharing!
BTW for comparison - Blend2D can render SVG tiger in 1.68ms on the same machine (I also have 7950X) so it can provide almost an order of magnitude better performance in this case, which is great I think. But I understand the purpose of your library, sometimes it's nice to have something small :)
Qt Quick should support non-GPU rendering[1]. I don't know how good it is, tho.
[1] https://www.toradex.com/blog/running-qt-without-gpu
There is also thorvg by Samsung (or the authors work in there and they are also using it, not 100% sure but it's production ready)
https://github.com/thorvg/thorvg#dependencies
AFAIK they have experimental GPU backend but I'm not sure how far they are with it.
If I am not mistaken, NanoVG actually can render as by CPU (need external path rasterizer) as by GPU (OpenGL and other options).
NanoVG provided Canvas.Context kind of API in plain C.
That’s crazy. I once lurked in the IRC of the project. I knew the creator. He was a family friend. I was a silly teen kid toying with Linux he was a dev who worked at redhat and lived in the same town as me.
I wonder what he’s up to these days?
Update/edit:
Ahh he moved on to Ampere: https://www.linkedin.com/in/carl-worth
Also I was a bad fan: the library had a co-founder too I thought it was a bespoke creation of Carl’s own making.
I remember building a bunch of stuff from source back in the day and a lot of Linux applications had Cairo as a dependency.
Can it be GPU accelerated? (Is that even a dumb question to ask?)
Cairo has only limited support for GPU acceleration and hasn't seen much development this decade. So users who care about performance have either switched away from Cairo entirely or are at least reducing its usage and are taking it out of the fast path.
GPU support was removed from Cairo, because it was slower than CPU rendering and nobody wanted to maintain it.
Cairo's OpenGL support was removed, but I thought Cairo's X11 backend still has GPU acceleration for a few operations through XRender (depending on your video driver).
That's true, Cairo still provides XRender backend. Not sure it's that usable though as I think nobody really focuses on improving XRender, so it's probably in the same state as Cairo itself.
Nothing happened to it. It’s just slow.
It's not that slow, I was amazed by performance when I first used it, but maybe other libs are even faster.
It's perfectly decent for a CPU renderer, CPU rendering is just slow.
I had to make a map module for python desktop application , options were either embed full browser inside and use google maps, or do it myself. C module using cairo was MUCH faster and easier to achieve in 2013. So much faster I didn't have to implement some optimizations I planned because it was already running at about 50fps on average computers with ~1000 individually drawn markers on a map. Requiring any gpu for this was not possible anyway.
Some details in the article:
According to their benchmark, multi-threaded rendering on a Ryzen 7950X will take about 1.7ms with 4 cores for drawing 1000x polygons with 40 vertices each on a 32x32 px area, which seems like a reasonable approximation for a text character on a high-DPI display. The default font size in JetBrains fits about 2500 characters onto my screen, so I'd expect a 4.25ms frame time, meaning I am capped at 235 FPS with 4 CPU cores running at full speed.
I believe the best way is probably to use Blend2D for rendering glyph bitmaps and then compositing them into the full text on GPU.
Sadly, CPU memory is still plenty slow compared to GPU memory and when you need to copy around 100 MB images (4K RGB float), then that quickly becomes the limiting factor.
Text rendering is something that will get improved in the future.
At the moment when you render text Blend2D queries each character from the font and then rasterizes all the edges and runs a pipeline to composite them. All these steps are super optimized (there is even a SIMD accelerated TrueType decoder, which I have successfully ported to AArch64 recently), so when you compare this approach against other libraries you still get like 4-5x performance difference in favor of Blend2D, but if you compare this method against cached glyphs Blend2D loses as it has to do much more work per glyph.
So the plan is to use the existing pipeline for glyphs that are larger (let's say 30px+ vertically) and to use caching for glyphs that are smaller, but how it's gonna be cached is currently in research as I don't consider simple glyph caching in a mask a great solution (it cannot be sub-pixel positioned and it cannot be rotated - and if you want that subpixel positioned the cache would have to store each glyph several times).
There is a demo application in blend2d-apps repository that can be used to compare Blend2D text rendering vs Qt, and the caching Qt does is clearly visible in this demo - when the text is smaller Qt renders it differently and characters can "jump" from one pixel to another when the font size is slightly scaled up and down, so Qt glyph caching has its limits and it's not nice when you render animated text, for example. This is a property that I consider very important so that's why I want to design something better than glyph masks that would be simple to calculate on CPU. One additional interesting property of Qt glyph caching is that once you want to render text having a size that was not cached previously, something in Qt takes 5ms to setup, which is insane...
BTW one nice property of Blend2D text rendering is that when you use the multithreaded rendering context the whole text pipeline would run multithreaded as well (all the outline decoding, GSUB/GPOS processing, rasterization, etc...).
Have you heard about Vello (written in Rust) https://www.youtube.com/watch?v=mmW_RbTyj8c&feature=youtu.be ?
Thanks! I'm doing what I can to make Blend2D even faster. It's been really exciting project to work on and I have big plans with this library.
Out of interest what's difficult about it to build? In my experience CMake isn't exactly a great developer experience, and many projects of this size take similar times to build. Is the problem specific to Skia or Google open source projects, or is it more based on the (necessary) size of the project?
The difficult parts change all the time and usually boil down to some sort of undocumented or poorly-documented dependency, especially if you're trying to enable the GPU backends. Every time someone I know tries to get it building it takes them a week to figure out how to do it.
Does Skia have a continuous build somewhere? Is there any way to piggyback off the config for that?
I found this:
https://github.com/google/skia-buildbot
Assuming that the Skia maintainers keep that working, it might be easier to build the buildbot and use that to build Skia, than to build Skia directly!
Yes, it's built as part of Chrome IIRC.
I used to do that when I started out with C/C++...15 years ago. Now my patience is running out after 10 minutes. Tooling has become so much better, but it seems not that much with some C/C++ projects. At least the build times are better, I remember big libs would could take hours to build.
Meson and ninja improved a lot in this area for C/C++.
Took me less than an hour to figure out how to build it just reading the instructions they have on their website for building.
Skia really isn't that hard to use IMO even for GPU accelerated stuff.
o_O unlike GN?
Have you used it much?
https://gn.googlesource.com/gn/
Yes GN IMO is a much better experience than CMake. Its fully self-contained in a single binary that you can easily just distribute with your code. I have a small python script I use with it to regen files, and then to run Ninja to do the actual builds.
I've been using it for years and have no complaints.
I haven't used GN, I'm mostly thinking of Bazel, but was also just interested in the specifics.
I was skeptical of your claims about building it so I went ahead and downloaded skia and built it myself. It was simple and on my 4 year old desktop (8 cores) it took under a minute to compile skia after it had downloaded its dependencies. All I did was run 2 commands ./tools/git-sync-deps and bazel build //:skia_public. This was not painful at all.
How long did downloading dependencies take? That's part of the build process.
As a maintainer of a project that includes another popular library from Google, here's why it's difficult to build:
- building requires downloading Googles custom toolchain and build system
- dependencies are huge, so you have lenghty download times even on fast connections
- it usually works if you are using a recent versions of OS and Python, but if you try running the same command in a year or two it might fail because they changed the requirements
- if anything fails you have to dig through multiple levels of abstractions to figure out where it failed and why
- if you want to maintain software for a few years, you'll have to keep fixing the build process because the build will suddenly stop working for unknown reasons once a year or so
Around 5 minutes.
It's dependent on one's internet speed so I didn't think it made much sense to time it. If it took 20 to 30 minutes to download I would have mentioned it.
This doesn't reflect my experiences at all. I've never built it with bazel, are you sure it's not using some prebuilt binary?
The official instructions are https://skia.org/docs/user/build/
It looks like they are several years into a migration to bazel and support both a bazel and gn based build. Bazel is usually faster. It's possible the gn based build builds everything in the repo including all tests, but the bazel one is more targeted, building less things
https://skia.org/docs/dev/contrib/bazel/
Using the official instructions, it took 4 minutes to scratch download skia and all its deps and build it with ninja. It only had 1235 build actions ... that's like zero.
If this seems like a big lift, people are going to hate building Chromium.
It depends on what features you have enabled, how slow your internet connection is and obviously hardware/software config. On my 10th gen Intel i5 laptop 8gb ram (on Windows), it takes 15 minutes to build if I don't do anything else, but if I start using Firefox and IDEs, the build times are easily in the 20-25 minutes ballpark.
I have an open source project that uses Skia, and I just keep static libraries for all target platforms because the Skia build process is so painful.
Maybe once a year I bite the bullet, do a new Skia build on all the platforms, and then I have to figure out how the C++ API has changed. At least that’s just rote work of fixing compiler errors by looking at the new header files.
Even though it’s a pain in the ass, I still use Skia because it’s got the best combination of performance and features. Sadly Cairo doesn’t quite compete. Skia gives my project a pretty good guarantee that 2D graphics render like in Chrome, and that’s important for this use case.
Out of curiosity, why do you rebuild? What kinds of new features would get you to want to upgrade?
The Skia C++ API changes quite a lot. If I didn't sync up regularly, the tech debt could become a problem down the line, for example if I want to add a platform that does need a new build of the library.
CVE-2023-2136 and friends are good reasons to upgrade.
Have you considered Blend2D? It's much easier to build, and performance is on par with Skia.
I thought Blend2D was a CPU-only library. Skia offers hardware acceleration via the GPU.
Not an expert on graphics libs - but I did notice that the Google project Flutter is moving away from Skia to something new called Impeller.
https://medium.com/@gauravswarankar/flutter-will-use-an-impe...
I believe Impeller is even worse with regards to all the issues mentioned in the parent comment. In particular since it is so tight to Flutter.
I get the point about it being developed with one primary objective - but perhaps a naive question here - in the end isn't that primary objective a shared one - to render text, lines, curves and images as fast as possible, via some sort of higher level API?
And to do so onto multiple OS & hardware backends?
Skia relies heavily on runtime shader compilation, which is slow and causes frame jank but improves peak performance. Skia also supports much older devices than Flutter does.
There’s no free lunch, Impeller has a different set of trade offs that are a better fit for Flutter.
It's the old coupling versus cohesion problem, isn't it? The tighter the coupling the more likely that abstractions leak across API boundaries. The tighter the monorepo the less likely there's a concerted effort to avoid breaking changes and consider long term API stability.
https://github.com/linebender/vello is written in Rust and already used as the backend for Xilem, a reactive framework for native UI.
Vello is used in Skia
https://skia.googlesource.com/skia/+/2e551697dc56/third_part...
Disclaimer: I work on Vello[0], but not on the Skia integration directly
My understanding, having not dug into it too much, is that the Skia integration does exist, but isn't enabled by default/any clients at the moment. That is, I don't know that this integration is shipping anywhere.
Vello still has some definite rough edges at the moment, so I'm not sure I'd recommend using it in a production application at the moment. We also don't have a C API, which might rule it out for some cases where you'd be considering Skia.
[0]: https://linebender.org/blog/xilem-2024/
Yes. So in Sciter I've replaced its build system with relatively simple premake5 script. That replacement took couple of days but was worth it. Premake5 generates human-readable IDE solutions and make files. So you need just a compiler to build the whole thing.
It is not that bad actually. Just tried full rebuild of x64/Windows version:
Whole sciter.dll (HTML/CSS/JS/Graphics) with Skia backend:
Same sciter.dll but with Direct2D backend: So Skia takes ~4 minutes to build on pretty average development desktop machine.That's very true and is a pain indeed if to change its version frequently. Yet there is no such concept as "Skia version" - just revisions/milestones. It used to be an attempt to make stable plain C API but AFAIR it was removed recently.
Same thing about Google ANGLE that I started to use recently in Sciter.GLX: https://sciter.com/sciter-glx-beta2/
About premake5 in general.
premake5 is a monolithic/portable executable that contains Lua + specific runtime.
Thus it does not rely on installed Python as in GN case as other tools.
Having standard and well known and documented Lua on board benefits the maker a lot. In my opinion any modern build system must include generic and known PL.
And that above is the problem of modern CMake. It started as simple static declarative thing but life forced it to evolve into dynamic programming language with very strange notation and runtime model.
I'm curious if you can expand on how you're using python, and what pain points you have there? I think python 3.9 was one for us but not too bad.
Our joke was "the recommended way to build Skia is to become a Google employee, but there are workarounds available if for some reason that isn't practical".
There's also the question of "which parts of Skia". If there are five different conceivable ways to implement something in vector graphics, Skia will implement all five, and there will be some sort of hidden obscure configuration setting that Chrome and Android will use to determine which one actually gets used. It's a very unfriendly piece of software to use, honestly.
In 2024 it would probably be faster to hire a xoogler.
There are some Skia alternatives:
- NanoVG
- bgfx with vg-renderer
- Impeller
- Starling
I'd encourage you to look into bazel. It's really a great build system, albeit super opinionated. Should be relatively easy to build Skia as part of your project if you are using Bazel yourself.
Check out nanovg: https://github.com/memononen/nanovg
This.
We use it in LibreOffice, but its a right pain to update versions and debugging it is.... challenging.
The upside is that the Google Skia team is super friendly and willing to help.
I recommend blend2d: https://blend2d.com/. Very well done, very fast. No GPU acceleration but it has a JIT and you can probably get further than you think without it.
We wrote exactly that for our game-focused port of WebKit [1].
CPU renderer uses a tiny, custom fork of Skia (we only use the path rasterizer and their SSE2, AVX2, NEON backends) and our GPU renderer draws directly on GPU via tessellated paths / hardware MSAA (DX11, DX12, GL, Metal, Vulkan).
[1] https://ultralig.ht