nanovgXC is a small library for rendering vector graphics, based on nanovg. The API is nearly identical to nanovg; the major user-facing changes are:
- rendering of arbitrary paths with "exact coverage" antialiasing
- it is not necessary to specify whether each subpath encloses a solid area or a hole
- including very thin (a few pixels or less) filled paths, with which nanovg's antialiasing technique has some difficulties
- support for both even-odd and non-zero fill rules
- support for rendering text as paths
- dashed strokes
If none of the above are needed for your use case, nanovg will likely have better performance and be a better choice.
Two rendering backends are available:
- OpenGL 3 / ES3 backend implementing "exact coverage" antialiased rendering using one of three approaches:
- GL_EXT_shader_framebuffer_fetch - iOS (also works on many desktop GPUs but with poor performance)
- GL_ARB_shader_image_load_store/GL_OES_shader_image_atomic - Android (ES 3.1+) and Windows/Linux (GL 4 level hardware)
- no extensions - switches between two framebuffers for each path (one for accumulating winding, one for final output). Not as slow as it sounds on desktop GPUs - faster then software renderer for large paths.
- software renderer backend based on nanosvg and stb_truetype (does not use "exact coverage" technique currently - see below)
Text is rendered by calculating a "summed" font atlas where each pixel stores the total coverage in the rectangle defined by that pixel and the origin (0,0). When rendering, the texture coordinates for the corners of the current pixel are calculated and the corresponding values (s00, s01, s10, s11) from the summed font atlas are read with bilinear interpolation. The pixel's coverage is then just s11 - s01 - s10 + s00. Text at font sizes above a threshold set by nvgAtlasTextThreshold()
is rendered directly as paths. Text at all sizes below the threshold is rendered from the single atlas.
The original intent of this approach was to support continuous scaling of text without rebuilding a font atlas every frame and to allow arbitrary subpixel positioning of glyphs, but it could well be slower than just rebuilding the font atlas (and it is for the current software renderer implementation). It works well enough for the author's purposes but probably should be reconsidered.
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". A 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 uses 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.
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_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:
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.
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:
- install SDL2 library and headers (libsdl2-dev package on Debian and Ubuntu)
- run
make
Windows:
- requires Visual C++ (free Visual Studio Community Edition is fine)
- download GNU make built for Windows: http://www.equation.com/servlet/equation.cmd?fa=make and ensure it is available in the path
- in
Makefile
, setDEPENDBASE
to the parent folder containing all dependencies thatmake
should track - open a Visual Studio command prompt (from the Start Menu) and run
make
iOS:
- requires XCode to be installed (for iOS SDK) but can be built from command line
- create Makefile.local:
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>
- run
make
- the resulting demo2_sdl.app can be installed on a device with ios-deploy:
ios-deploy --bundle Release/demo2_sdl.app
- Pathfinder - larger and much more sophisticated library from Mozilla that also does exact coverage antialiased path rendering on GPUs
- stb_truetype for font loading and rendering
- stb_image for image loading
- OpenGL loader created by glad