Skip to content

Instantly share code, notes, and snippets.

@h4k1m0u
Last active March 19, 2025 08:03
Show Gist options
  • Save h4k1m0u/2c3ddfd089447c209b8b9b671ec742de to your computer and use it in GitHub Desktop.
Save h4k1m0u/2c3ddfd089447c209b8b9b671ec742de to your computer and use it in GitHub Desktop.
Rendering basic shapes with OpenGL using NanoVG

Prerequisites

The following two submodules will be needed to build project:

$ git submodule add https://github.com/OpenGL-Graphics/opengl
$ git submodule add https://github.com/OpenGL-Graphics/glfw-window

Directory structure

+ src
  | main.cpp
  + nanovg
    | nanovg.c
+ include
  + nanovg
    | nanovg.h
+ glfw-window
+ opengl
| CMakeLists.txt

How to build project

$ mkdir build && cd build
$ cmake .. && make -j && ./main

Vector graphics libraries

Libraries candidates that could be used instead of Cairo to draw on image, as I couldn't find how to render directly to the OpenGL texture with Cairo.

ThorVG

ThorVG is built and installed with meson as suggested on Meson docs:

$ meson setup build-dir  # configure project
$ cd build-dir
$ meson compile  # build project
$ meson install  # install project

ThorVG will be installed in the default prefix location (i.e. /usr/local/{include, lib}/).

Weakness

  • Doesn't seem to support the OpenGL renderer yet.

NanoVG

This one will be used as it's intrinsically a vector rendering library for OpenGL. NanoVG will be built as follows:

$ git clone --depth=1 https://github.com/memononen/nanovg/
$ apt install premake4
$ cd nanovg
$ premake4 gmake
$ cd build
$ make -j
$ ./example_gl3
cmake_minimum_required(VERSION 3.10)
project(NanoVG-Example)
# use C++17 (in case std::fs needed)
set(CMAKE_CXX_STANDARD 17)
# autocomplete with YCM & debug with gdb
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_BUILD_TYPE Release)
# set(CMAKE_BUILD_TYPE Debug)
# glfw window & opengl loader libraries from submodule
add_subdirectory(glfw-window)
add_subdirectory(opengl)
# nanosvg shared library
add_library(nanovg SHARED
"src/nanosvg/nanovg.c"
)
target_include_directories(nanovg PUBLIC include/nanovg)
# main executable
file(GLOB SRC
"src/main.cpp"
)
add_executable(main ${SRC})
target_link_libraries(main
glfw_window
opengl
nanovg
)
// file: src/main.cpp
#include <iostream>
#include "glad/glad.h"
#include "window.hpp"
#include "nanovg.h"
#define NANOVG_GL3_IMPLEMENTATION
#include "nanovg_gl.h"
int main() {
// glfw window
Window window("Image visualizer");
if (window.is_null()) {
std::cout << "Failed to create window or OpenGL context" << "\n";
return 1;
}
// initialize glad before calling gl functions
window.make_context();
if (!gladLoadGL()) {
std::cout << "Failed to load Glad (OpenGL)" << "\n";
window.destroy();
return 1;
} else {
std::cout << "Opengl version: " << glGetString(GL_VERSION) << "\n";
std::cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << "\n";
}
// create nanovg context (similar to html5 canvas')
NVGcontext* vg = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_DEBUG);
// load image data with stb_image, create texture, copy image data to gpu texture, then free image data
int image = nvgCreateImage(vg, "../example/images/image1.jpg", 0);
std::cout << "Nanovg image: " << image << '\n';
// main loop
while (!window.is_closed()) {
// clear color & depth & stencil buffers before rendering every frame
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// draw rectangle & circle to nanovg frame
float pixel_ratio = 1.0f; // framebuffer & window have same size
nvgBeginFrame(vg, window.width, window.height, pixel_ratio);
// draw rectangle & circle
nvgBeginPath(vg);
nvgRect(vg, 100,100, 120,30);
nvgCircle(vg, 500, 500, 100);
nvgFillColor(vg, nvgRGBA(255,192,0,255));
nvgFill(vg);
// draw image
float alpha = 1.0f;
float angle = 0.0f;
float x = 100.0f;
float y = 100.0f;
int width, height;
nvgImageSize(vg, image, &width, &height);
NVGpaint imgPaint = nvgImagePattern(vg, x, y, width, height, angle, image, alpha);
nvgBeginPath(vg);
nvgRect(vg, x, y, width, height);
nvgFillPaint(vg, imgPaint);
nvgFill(vg);
nvgEndFrame(vg);
// process events & show rendered buffer
window.process_events();
window.render();
}
// delete opengl texture
nvgDeleteImage(vg, image);
// free nanovg context
nvgDeleteGL3(vg);
// destroy window & terminate glfw
window.destroy();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment