styluslabs / nanovgXC

Lightweight vector graphics library implementing exact-coverage antialiasing in OpenGL
Other
139 stars 6 forks source link

nanovgXC

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:

  1. nanovg_vtex: Single pass OpenGL backend using vector texture approach (frag shader iterates over all edges for path, read from texture). Large paths are broken up into tiles.
  2. nanovg_gl: Two pass OpenGL backend 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 than software renderer for large paths.
  3. nanovg_sw: software renderer backend based on nanosvg and stb_truetype, supporting both "exact coverage" and sub-scanline rendering (see below). Supports multi-threaded rendering, with each thread rendering a separate region of the output. This significantly improves performance on desktop platforms, less so on mobile.

Text Rendering

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.

"Exact Coverage"

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.

sRGB

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.

How to use

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)

Example app

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:

Windows:

iOS:

Related Projects

Additional credits