nanovgXC is a small library for rendering vector graphics, based on nanovg and under the same permissive zlib license. The API is nearly identical to nanovg; the major user-facing changes are:
Cursory testing suggests that nanovgXC is several times faster than skia for GPU and multithreaded CPU rendering - perhaps Cunningham's Law will inspire more careful testing. See example/skia-test/Makefile.
Three rendering backends are available:
Text can be rendered using the signed distance field (SDF) method (with 4 samples per pixel) or a summed area table method. Pass the NVG_SDF_TEXT
flag to nvglCreate()
or nvgswCreate()
to use SDF text rendering. Both approaches support continuous scaling of text and arbitrary subpixel positioning of glyphs with a single atlas with similar quality and performance (which is not great for the software renderer). With SDF rendering, nvgFontBlur()
can be used to adjust the weight of text. Text at font sizes above a threshold set by nvgAtlasTextThreshold()
is rendered directly as paths. The font size used for the atlas is twice this threshold. Text at all sizes below the threshold is rendered from the single atlas.
The atlas is managed by fontstash.h
(modified from the original nanovg fontstash). To avoid unnecessary duplication, a single fontstash context can be shared between multiple nanovg contexts by passing the NVG_NO_FONTSTASH
flag to nvglCreate()
or nvgswCreate()
, then calling nvgSetFontStash()
.
The nanovg_sw backend can be used to generate SDF textures when created with the NVGSW_SDFGEN
flag. In this mode, the output framebuffer is treated as an array of floats. See createFontstash()
in example_sdl.c for an example. Compared with stb_truetype, SDF generation is about 10x faster and OpenType (cubic Bezier) outlines are supported.
The exact coverage antialiasing technique calculates the intersection area of the path and each pixel (represented as a square) to greater than 1/256 accuracy, hence the word "exact". An early reference to this terminology can be found in libart.
This technique overestimates the coverage of partially-covered pixels for self-intersecting (i.e. self-overlapping) paths, resulting in incorrect antialiasing for these pixels. This problem is not unique to GPU implementations of the technique, although with a CPU implementation it is easier to select a different approach for such paths. The software renderer backend supports another common approach - splitting each scanline into a small number of horizontal "sub-scanlines" and fully including or excluding each path segment from each sub-scanline (while retaining high precision in the horizontal direction). This can have reduced accuracy, especially for horizontal edges, but does not suffer from the coverage overestimation issue. Pass the NVGSW_PATHS_XC
flag to nvgswCreate
to select the exact coverage algorithm for the software renderer.
nanovgXC supports sRGB-aware rendering - if the NVG_SRGB
flag is passed to nvglCreate
or nvgswCreate
, colors will be blended in the linear RGB color space. Colors are still passed to nvgRGBA
, etc. in the sRGB color space, as usual. For the GL backend, the NVG_IMAGE_SRGB
flag should be passed to nvgluCreateFramebuffer
(and to the nvgCreateImage
calls for any images loaded), and nvgluSetFramebufferSRGB(1)
should be called on desktop platforms. The SW backend always assumes images are in the sRGB color space (the usual case).
Blending in the linear RGB color space is necessary to obtain the highest antialiasing quality but since most applications do not do this, content rendered with NVG_SRGB
will look slightly different than when rendered by other applications (e.g., thin lines will look a bit thinner). Transparency is also affected - this is why the demo looks different with NVG_SRGB
.
Add nanovg.c to your sources and then in one source file add:
#define NANOVG_GLES3_IMPLEMENTATION // or NANOVG_GL3_IMPLEMENTATION
#include "nanovg_vtex.h" // or "nanovg_gl.h"
#include "nanovg_gl_utils.h" // to use framebuffer creation and blitting functions
and/or
#define NANOVG_SW_IMPLEMENTATION
#include "nanovg_sw.h"
The drawing functions are documented in nanovg.h
. For example, to draw a triangle:
#include "nanovg.h"
// ...
NVGcontext* vg = nvglCreate(NVG_SRGB | NVG_AUTOW_DEFAULT); // or nvgswCreate
// for SW renderer, call nvgswSetFramebuffer to configure output before nvgBeginFrame
nvgBeginFrame(vg);
nvgBeginPath(vg);
nvgMoveTo(vg, 100, 100);
nvgLineTo(vg, 200, 100)
nvgLineTo(vg, 150, 50);
nvgClosePath(vg);
nvgFillColor(vg, nvgRGBA(255,192,0,255));
nvgFill(vg);
nvgEndFrame(vg);
// ... then swap buffers (GL backend) or copy output buffer to screen (SW backend)
The example app uses SDL2: build SDL2 for your platform then edit the path to SDL library in Makefile
as needed (the default is ../SDL/Release/
). For Linux, the appropriate package can be installed instead of building SDL. The exact versions of SDL used for development are here.
A GLFW version of the sample app is also provided for Windows and Linux. Replace make
with make -f Makefile.glfw
to use.
Building the example app:
The makefile creates the demo executable demo2_sdl(.exe) in Debug/ (make DEBUG=1) or Release/ (make DEBUG=0).
Linux:
make
Windows:
Makefile
, set DEPENDBASE
to the parent folder containing all dependencies that make
should trackmake
iOS:
PROVISIONING_PROFILE = <path to your .mobileprovision file>
SIGNING_ID = <your code signing ID>
TEAM_ID = <your Apple Developer "team id">
BUNDLE_ID = <bundle identifier for app, e.g., com.styluslabs.demo2_sdl>
make
ios-deploy --bundle Release/demo2_sdl.app