vg-renderer: a vector graphics renderer for bgfx
A vector graphics renderer for bgfx, based on ideas from NanoVG and ImDrawList (Dear ImGUI)
At a glance
- What is it?
- vg-renderer is a C library that draws SVG style paths, fills, strokes and text on top of bgfx, taking its cues from NanoVG and Dear ImGui's ImDrawList. It suits engine developers and tool authors who want batched 2D drawing without adopting a whole UI framework, and it drops features NanoVG users will notice, such as miter joins and polygon holes.
- Who is it for?
- Adopt vg-renderer when you already ship on bgfx and need crisp 2D geometry, on-screen text and clip regions inside the same renderer, and are willing to work within its limits, in particular no miter joins, no polygon holes and no skew transforms. Do not pick it for a port of an existing NanoVG code base that relies on miter joins or hole geometry, and do not expect a packaged library, because it is added as a CMake target from source.
- Can I use it commercially?
- Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 162 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A 2D vector layer that talks to bgfx directly
bgfx is a rendering abstraction, and it draws triangles. Everything else is up to the application, which is why a vector graphics renderer for it is a useful thing to have. vg-renderer fills that gap: it is described as a vector graphics renderer for bgfx, based on ideas from NanoVG and ImDrawList from Dear ImGUI.
That lineage explains the design. NanoVG contributes the idea of a retained path, fill and stroke pipeline with a compact C API. ImDrawList contributes the immediate-mode style of batching many small draw commands into few draw calls, which is what makes UI rendering fast on a GPU.
The implementation is C, released under BSD-2-Clause, and the source layout is small and conventional: include/, src/, img/ and a LICENSE at the root. There is no separate package registry entry, no release artefacts and no documentation site. Integration is by dropping the files into your own build.
The audience is engine developers and tool authors: anyone with a bgfx application that needs curves, glyphs, gradients or clipped regions and does not want to adopt a full immediate mode GUI framework to get them.
Fewer draw calls by batching paths that share state
The headline difference from NanoVG is listed first in the comparison, and it is the one that matters at runtime: vg-renderer generates fewer draw calls by batching multiple paths together when they share the same state.
In an immediate mode UI, the natural way to issue work is one call per rectangle, per border, per glyph run. Each call that changes state costs a GPU state transition, and a dense interface turns into hundreds of them. Grouping paths by shared state and flushing once per group is the standard fix, and it is why the project borrows from ImDrawList rather than only from NanoVG.
Two further choices reduce per-draw overhead. Separate shader programs for gradients and image patterns cut uniform usage, where the NanoVG bgfx backend uses a single program for all cases. And concave polygons are decomposed on the CPU, which moves that work away from the GPU and into the tessellator that produces the triangles.
Anti-aliasing control is per fill and per stroke, so you can decide separately whether anti-aliasing geometry is generated for a given shape. The porting notes make the practical case for this: a perfectly rectangular UI widget can skip the anti-aliasing geometry, and so can a gradient fill that is fully transparent at the edge of the shape, because the extra geometry buys nothing there.
The Path and Stroker structs work outside the renderer
The library is split so that the geometry work is separable from the drawing. Paths are tessellated using the Path struct in src/vg/path.cpp and src/vg/path.h, and the vg::pathXXX() functions convert SVG commands to a polyline for uses outside this renderer.
Strokes and fills are generated using the Stroker struct in src/vg/stroker.cpp and src/vg/stroker.h, with the vg::strokerXXX() functions producing strokes and fills for polylines for uses outside this renderer.
That is a deliberate API decision. If you only want outlines, for example to generate a collision mesh or to export geometry, you do not need a bgfx context, a shader or a texture atlas. You take the tessellator, feed it path commands and get polylines back.
Concave polygon handling is the third piece. The renderer optionally uses libtess2 for concave polygon decomposition, and the sources are vendored under src/libtess2 with bucketalloc.c, dict.c, geom.c, mesh.c, priorityq.c, sweep.c and tess.c. The word that matters is optionally: it is a compile-time choice, so a project that only draws convex shapes does not pay for it.
Adding the renderer to a project as a CMake target
Integration is done by adding the files as a new CMake target. The documentation assumes the project is a submodule in a folder ext/vg-renderer, and gives the target definition:
add_library(vg-renderer STATIC
"ext/vg-renderer/src/vg.cpp"
"ext/vg-renderer/src/stroker.cpp"
"ext/vg-renderer/src/path.cpp"
"ext/vg-renderer/src/vg_util.cpp"
"ext/vg-renderer/src/libs/fontstash.cpp"
"ext/vg-renderer/src/libs/stb_truetype.cpp"
)
target_include_directories(vg-renderer PUBLIC "ext/vg-renderer/include/")That is the core of the target, the renderer sources plus the bundled FontStash and stb_truetype, with the include directory exposed publicly. The libtess2 sources are listed in the same target when you want concave decomposition compiled in.
The target also has to link against bgfx and bx, and the documentation is candid that this part is left to you: the author used a macro in his own project and says you can roll your own.
link_bgfx_libs(vg-renderer Release)For a working example, the documentation points at an example in a bgfx fork, in examples/xx-vg-renderer/vg-renderer.cpp, which is the fastest way to see the library drawing something.
What NanoVG gives you that vg-renderer does not
The documentation keeps a section on what is not supported, and it is the most important part of the README for anyone porting an existing codebase.
Miter limit is gone, which in practice means miter joins never convert to bevel joins, so sharp corners in stroked geometry will look different from a NanoVG rendering. Polygon holes are not supported as such, though they can be emulated using clip in and out regions, which is a real cost in complexity and in draw calls. Variable text line height, font blur effect and letter-spacing control are all missing, so typographic control is fixed at what the font atlas provides.
Skew transformation matrix is not supported either. If your UI applies a skew, for example an isometric or pseudo-3D layout, you cannot express it here.
None of these are fatal, but they are the difference between a drop-in swap and a project. The miter join and hole cases in particular are the kind of feature that only shows up when a designer asks for it in week two of a project.
Command lists turn a widget tree into layered draw batches
Command lists with support for tessellation caching are another feature in the list, and the porting notes explain how to use them.
The recommendation is to render the UI in layers, where every layer is a command list. You still traverse the UI tree widget by widget, but you store draw commands onto the command lists per layer, which can greatly reduce state changes. The example given is a layer for widget shapes and outlines and a separate layer for text.
That idea comes straight from the ImDrawList heritage. Drawing text and shapes in separate layers means the renderer can flush all shape geometry with one state and then flush all glyph geometry with another, rather than interleaving them. For a dense interface it is the difference between a few dozen draw calls and a few hundred.
The caching part means the tessellation work for a path can be reused when the same path is drawn again, which matters for static chrome such as frames and separators that do not change between frames but are redrawn every time.
Two other capabilities widen the range beyond plain vector drawing: clip in and out using the stencil buffer, and user-specified indexed triangle list rendering, which the documentation gives as the way to handle complex gradients and sprite atlases. Fills with image patterns can also be colored, which is not possible in every comparable library.
Porting from NanoVG: renamed calls and a different colour type
The porting notes are short and concrete, which is what a migration needs.
nvgBezierTo is replaced by vg::cubicTo, and there is also vg::quadraticTo for a lower degree Bezier curve. nvgSave and nvgRestore become vg::pushState() and vg::popState(). Colours are the change most likely to surprise: vg-renderer uses uint32_t as colours instead of a struct of four floats, so during conversion you might want to use vg::color4ub rather than vg::color4f, and a few predefined colours are available in vg::Colors::.
There are also two FontStash changes carried over from the bundled copy. Glyph hashing uses BKDR, which the author says seems to give a better distribution of glyphs in the lookup table and fewer collisions when searching for cached glyphs. And a compile-time flag enables caching of glyph indices and kerning information for ASCII characters, avoiding repeated calls to the stbtt functions.
The pattern to follow is to port the drawing calls, then convert colours, then decide on the ASCII caching flag. The kerning cache only covers ASCII, so a project with a non-Latin typeface still pays the per-glyph cost there.
A small BSD library with no release history
The licence is BSD-2-Clause with the LICENSE file at the root. For a library you compile into your own binary, that is one of the simplest licences to accept: no copyleft obligations, no attribution beyond keeping the licence text, and no separate commercial condition. This is a clean answer to the licensing question that many small rendering libraries leave open.
On maintenance, the repository is not archived and the last push was on 2026-05-14. There are no GitHub releases, so there is no version to pin and no changelog to read in the usual place; the tracked branch is master.
That combination, a low version count of published history, no tagged releases and a last commit a few months back, means you should treat this as a library you vendor rather than a dependency you upgrade on a schedule. The upside is stability: nobody is going to change vg::cubicTo out from under you without a version tag. The downside is that a bug fix lands only when you pull the branch yourself, and the documentation lives in the README rather than in a versioned manual, so it changes with the code.
For a renderer embedded in a shipped product, that is a reasonable trade. For a library you expect to track over years, weigh it against the fact that no tagged release exists to fall back on.
Editorial conclusion
Adopt vg-renderer when you already ship on bgfx and need crisp 2D geometry, on-screen text and clip regions inside the same renderer, and are willing to work within its limits, in particular no miter joins, no polygon holes and no skew transforms. Do not pick it for a port of an existing NanoVG code base that relies on miter joins or hole geometry, and do not expect a packaged library, because it is added as a CMake target from source. Before you start, check that your shapes do not need the missing features, decide whether you want the libtess2 concave decomposition compiled in, and read the porting notes on colour types, since vg-renderer uses uint32_t colours where NanoVG uses four floats.
Frequently asked questions
What is vg-renderer and what does it add to bgfx?
vg-renderer is a C library that draws vector paths, fills, strokes and text on top of bgfx, based on ideas from NanoVG and Dear ImGui's ImDrawList. It batches paths that share the same state into fewer draw calls, uses separate shader programs for gradients and image patterns, and supports clip regions through the stencil buffer.
How do I add vg-renderer to my CMake build?
Add the sources under src/vg plus the bundled FontStash and stb_truetype as a static library target, expose the include directory, and link the bgfx and bx libraries. The documentation assumes the project sits in a submodule folder named ext/vg-renderer and shows the target definition, with libtess2 sources added if you want concave polygon decomposition.
Which NanoVG features are missing from vg-renderer?
Miter limit is not supported, so miter joins never convert to bevel joins. Polygon holes are not supported directly and can only be emulated with clip in and out regions. Variable text line height, font blur, letter-spacing control and skew transformation matrix are also absent.
How do I port code written against NanoVG?
Replace nvgBezierTo with vg::cubicTo, or vg::quadraticTo for a lower degree curve, and replace nvgSave and nvgRestore with vg::pushState() and vg::popState(). Colours also change: vg-renderer uses uint32_t, so convert with vg::color4ub instead of vg::color4f, and predefined colours live in vg::Colors::.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jdryg-vg-renderer)Was this useful?