Browse Source

Merge pull request #2268 from Skytrias/skytrias-vendor-additions

fontstash / nanovg vendor additions
gingerBill 2 years ago
parent
commit
1bf4c8c9ba

+ 9 - 1
examples/all/all_vendor.odin

@@ -47,6 +47,10 @@ import CA         "vendor:darwin/QuartzCore"
 // NOTE(bill): only one can be checked at a time
 // NOTE(bill): only one can be checked at a time
 import lua_5_4    "vendor:lua/5.4"
 import lua_5_4    "vendor:lua/5.4"
 
 
+import nvg       "vendor:nanovg"
+import nvg_gl    "vendor:nanovg/gl"
+import fontstash "vendor:fontstash"
+
 _ :: botan_bindings
 _ :: botan_bindings
 _ :: botan_blake2b
 _ :: botan_blake2b
 _ :: gost
 _ :: gost
@@ -92,4 +96,8 @@ _ :: MTL
 _ :: MTK
 _ :: MTK
 _ :: CA
 _ :: CA
 
 
-_ :: lua_5_4
+_ :: lua_5_4
+
+_ :: nvg
+_ :: nvg_gl
+_ :: fontstash

+ 11 - 3
vendor/README.md

@@ -6,9 +6,9 @@ Its use is similar to that of `core:` packages, which would be available in any
 
 
 Presently, the `vendor:` collection comprises the following packages:
 Presently, the `vendor:` collection comprises the following packages:
 
 
-## microui
+## microui (Port)
 
 
-A tiny, portable, immediate-mode UI library written in Odin. (Ported from [rxi/microui](https://github.com/rxi/microui).)
+A tiny, portable, immediate-mode UI library written in Odin. [rxi/microui](https://github.com/rxi/microui)
 
 
 This package is available under the MIT license. See `LICENSE` for more details.
 This package is available under the MIT license. See `LICENSE` for more details.
 
 
@@ -158,4 +158,12 @@ Includes full bindings.
 
 
 Used in: [bgfx](https://github.com/bkaradzic/bgfx), [Filament](https://github.com/google/filament), [gltfpack](https://github.com/zeux/meshoptimizer/tree/master/gltf), [raylib](https://github.com/raysan5/raylib), [Unigine](https://developer.unigine.com/en/docs/2.14.1/third_party?rlang=cpp#cgltf), and more!
 Used in: [bgfx](https://github.com/bkaradzic/bgfx), [Filament](https://github.com/google/filament), [gltfpack](https://github.com/zeux/meshoptimizer/tree/master/gltf), [raylib](https://github.com/raysan5/raylib), [Unigine](https://developer.unigine.com/en/docs/2.14.1/third_party?rlang=cpp#cgltf), and more!
 
 
-See also LICENCE in `cgltf` directory itself.
+Se also LICENCE in `cgltf` directory itself.
+
+## nanovg (Port)
+
+[NanoVG](https://github.com/memononen/nanovg) is a small antialiased vector graphics rendering library for OpenGL. It has lean API modeled after HTML5 canvas API. It is aimed to be a practical and fun toolset for building scalable user interfaces and visualizations.
+
+## fontstash (Port)
+
+[Font stash](https://github.com/memononen/fontstash) is a light-weight online font texture atlas builder. It uses stb_truetype to render fonts on demand to a texture atlas.

+ 1229 - 0
vendor/fontstash/fontstash.odin

@@ -0,0 +1,1229 @@
+//+build windows, linux, darwin
+package fontstash
+
+import "core:runtime"
+import "core:log"
+import "core:os"
+import "core:mem"
+import "core:math"
+import "core:strings"
+import stbtt "vendor:stb/truetype"
+
+// This is a port from Fontstash into odin - specialized for nanovg
+
+// Notable features of Fontstash:
+// Contains a *single* channel texture atlas for multiple fonts
+// Manages a lookup table for frequent glyphs
+// Allows blurred font glyphs
+// Atlas can resize
+
+// Changes from the original:
+// stb truetype only 
+// no scratch allocation -> parts use odins dynamic arrays
+// leaves GPU vertex creation & texture management up to the user
+// texture atlas expands by default
+
+INVALID :: -1
+MAX_STATES :: 20
+HASH_LUT_SIZE :: 256
+INIT_GLYPHS :: 256
+INIT_ATLAS_NODES :: 256
+MAX_FALLBACKS :: 20
+Glyph_Index :: i32 // in case you want to change the handle for glyph indices
+
+AlignHorizontal :: enum {
+	LEFT,
+	CENTER,
+	RIGHT,
+}
+
+AlignVertical :: enum {
+	TOP,
+	MIDDLE,
+	BOTTOM,
+	BASELINE,
+}
+
+Font :: struct {
+	name: string, // allocated
+
+	info: stbtt.fontinfo,
+	loadedData: []byte,
+	freeLoadedData: bool, // in case you dont want loadedData to be removed
+
+	ascender: f32,
+	descender: f32,
+	lineHeight: f32,
+
+	glyphs: [dynamic]Glyph,
+	lut: [HASH_LUT_SIZE]int,
+
+	fallbacks: [MAX_FALLBACKS]int,
+	nfallbacks: int,
+}
+
+Glyph :: struct {
+	codepoint: rune,
+	index: Glyph_Index,
+	next: int,
+	isize: i16,
+	blurSize: i16,
+	x0, y0, x1, y1: i16,
+	xoff, yoff: i16,
+	xadvance: i16,
+}
+
+AtlasNode :: struct {
+	x, y, width: i16,
+}
+
+Vertex :: struct #packed {
+	x, y: f32,
+	u, v: f32,
+	color: [4]u8,
+}
+
+QuadLocation :: enum {
+	TOPLEFT,
+	BOTTOMLEFT,
+}
+
+FontContext :: struct {
+	fonts: [dynamic]Font, // allocated using context.allocator
+
+	// always assuming user wants to resize
+	nodes: [dynamic]AtlasNode,
+
+	// actual pixels
+	textureData: []byte, // allocated using context.allocator
+	width, height: int,
+	// 1 / texture_atlas_width, 1 / texture_atlas_height
+	itw, ith: f32,
+
+	// state 
+	states: []State,
+	state_count: int, // used states
+
+	location: QuadLocation,
+
+	// dirty rectangle of the texture region that was updated
+	dirtyRect: [4]f32,
+
+	// callbacks with userData passed
+	userData: rawptr, // by default set to the context
+
+	// called when a texture is expanded and needs handling
+	callbackResize: proc(data: rawptr, w, h: int), 
+	// called in state_end to update the texture region that changed
+	callbackUpdate: proc(data: rawptr, dirtyRect: [4]f32, textureData: rawptr), 
+}
+
+Init :: proc(using ctx: ^FontContext, w, h: int, loc: QuadLocation) {
+	userData = ctx
+	location = loc
+	fonts = make([dynamic]Font, 0, 8)
+
+	itw = f32(1) / f32(w)
+	ith = f32(1) / f32(h)
+	textureData = make([]byte, w * h)
+	
+	width = w
+	height = h
+	nodes = make([dynamic]AtlasNode, 0, INIT_ATLAS_NODES)
+	__dirtyRectReset(ctx)
+
+	states = make([]State, MAX_STATES)
+
+	// NOTE NECESSARY
+	append(&nodes, AtlasNode {
+		width = i16(w),
+	})
+
+	__AtlasAddWhiteRect(ctx, 2, 2)
+
+	PushState(ctx)
+	ClearState(ctx)
+}
+
+Destroy :: proc(using ctx: ^FontContext) {
+	for font in &fonts {
+		if font.freeLoadedData {
+			delete(font.loadedData)
+		}
+
+		delete(font.name)
+		delete(font.glyphs)
+	}
+
+	delete(states)
+	delete(textureData)
+	delete(fonts)
+	delete(nodes)
+}
+
+Reset :: proc(using ctx: ^FontContext) {
+	__atlasReset(ctx, width, height)
+	__dirtyRectReset(ctx)
+	mem.zero_slice(textureData)
+
+	for font in &fonts {
+		__lutReset(&font)
+	}
+
+	__AtlasAddWhiteRect(ctx, 2, 2)
+	PushState(ctx)
+	ClearState(ctx)
+}
+
+__atlasInsertNode :: proc(using ctx: ^FontContext, idx, x, y, w: int) {
+	// resize is alright here
+	resize(&nodes, len(nodes) + 1)
+
+	// shift nodes up once to leave space at idx
+	for i := len(nodes) - 1; i > idx; i -= 1 {
+		nodes[i] = nodes[i - 1]
+	}
+
+	// set new inserted one to properties
+	nodes[idx].x = i16(x)
+	nodes[idx].y = i16(y)
+	nodes[idx].width = i16(w)
+}
+
+__atlasRemoveNode :: proc(using ctx: ^FontContext, idx: int) {
+	if len(nodes) == 0 {
+		return
+	}
+
+	// remove node at index, shift elements down
+	for i in idx..<len(nodes) - 1 {
+		nodes[i] = nodes[i + 1]
+	}
+
+	// reduce size of array
+	raw := transmute(^mem.Raw_Dynamic_Array) &nodes
+	raw.len -= 1
+}
+
+__atlasExpand :: proc(using ctx: ^FontContext, w, h: int) {
+	if w > width {
+		__atlasInsertNode(ctx, len(nodes), width, 0, w - width)
+	}
+
+	width = w
+	height = h
+}
+
+__atlasReset :: proc(using ctx: ^FontContext, w, h: int) {
+	width = w
+	height = h
+	clear(&nodes)
+
+	// init root node
+	append(&nodes, AtlasNode {
+		width = i16(w),
+	})
+}
+
+__AtlasAddSkylineLevel :: proc(using ctx: ^FontContext, idx, x, y, w, h: int) {
+	// insert new node
+	__atlasInsertNode(ctx, idx, x, y + h, w)
+
+	// Delete skyline segments that fall under the shadow of the new segment.
+	for i := idx + 1; i < len(nodes); i += 1 {
+		if nodes[i].x < nodes[i - 1].x + nodes[i - 1].width {
+			shrink := nodes[i-1].x + nodes[i-1].width - nodes[i].x
+			nodes[i].x += i16(shrink)
+			nodes[i].width -= i16(shrink)
+			
+			if nodes[i].width <= 0 {
+				__atlasRemoveNode(ctx, i)
+				i -= 1
+			} else {
+				break
+			}
+		} else {
+			break
+		}
+	}
+
+	// Merge same height skyline segments that are next to each other.
+	for i := 0; i < len(nodes) - 1; i += 1 {
+		if nodes[i].y == nodes[i + 1].y {
+			nodes[i].width += nodes[i + 1].width
+			__atlasRemoveNode(ctx, i + 1)
+			i -= 1
+		}
+	}
+}
+
+__AtlasRectFits :: proc(using ctx: ^FontContext, i, w, h: int) -> int {
+	// Checks if there is enough space at the location of skyline span 'i',
+	// and return the max height of all skyline spans under that at that location,
+	// (think tetris block being dropped at that position). Or -1 if no space found.
+	x := int(nodes[i].x)
+	y := int(nodes[i].y)
+	
+	if x + w > width {
+		return -1
+	}
+
+	i := i
+	space_left := w
+	for space_left > 0 {
+		if i == len(nodes) {
+			return -1
+		}
+
+		y = max(y, int(nodes[i].y))
+		if y + h > height {
+			return -1
+		}
+
+		space_left -= int(nodes[i].width)
+		i += 1
+	}
+
+	return y
+}
+
+__AtlasAddRect :: proc(using ctx: ^FontContext, rw, rh: int) -> (rx, ry: int, ok: bool) {
+	besth := height
+	bestw := width
+	besti, bestx, besty := -1, -1, -1
+
+	// Bottom left fit heuristic.
+	for i in 0..<len(nodes) {
+		y := __AtlasRectFits(ctx, i, rw, rh)
+		
+		if y != -1 {
+			if y + rh < besth || (y + rh == besth && int(nodes[i].width) < bestw) {
+				besti = i
+				bestw = int(nodes[i].width)
+				besth = y + rh
+				bestx = int(nodes[i].x)
+				besty = y
+			}
+		}
+	}
+
+	if besti == -1 {
+		return
+	}
+
+	// Perform the actual packing.
+	__AtlasAddSkylineLevel(ctx, besti, bestx, besty, rw, rh) 
+	ok = true
+	rx = bestx
+	ry = besty
+	return
+}
+
+__AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) {
+	gx, gy, ok := __AtlasAddRect(ctx, w, h)
+
+	if !ok {
+		return
+	}
+
+	// Rasterize
+	dst := ctx.textureData[gx + gy * ctx.width:]
+	for _ in 0..<h {
+		for x in 0..<w {
+			dst[x] = 0xff
+		}
+
+		dst = dst[ctx.width:]
+	}
+
+	ctx.dirtyRect[0] = cast(f32) min(int(ctx.dirtyRect[0]), gx)
+	ctx.dirtyRect[1] = cast(f32) min(int(ctx.dirtyRect[1]), gy)
+	ctx.dirtyRect[2] = cast(f32) max(int(ctx.dirtyRect[2]), gx + w)
+	ctx.dirtyRect[3] = cast(f32) max(int(ctx.dirtyRect[3]), gy + h)
+}
+
+AddFontPath :: proc(
+	ctx: ^FontContext,
+	name: string,
+	path: string,
+) -> int {
+	data, ok := os.read_entire_file(path)
+
+	if !ok {
+		log.panicf("FONT: failed to read font at %s", path)
+	}
+
+	return AddFontMem(ctx, name, data, true)
+}
+
+// push a font to the font stack
+// optionally init with ascii characters at a wanted size
+AddFontMem :: proc(
+	ctx: ^FontContext,
+	name: string,
+	data: []u8, 
+	freeLoadedData: bool,
+) -> int {
+	append(&ctx.fonts, Font {})
+	res := &ctx.fonts[len(ctx.fonts) - 1]
+	res.loadedData = data
+	res.freeLoadedData = freeLoadedData
+	res.name = strings.clone(name)
+
+	stbtt.InitFont(&res.info, &res.loadedData[0], 0)
+	ascent, descent, line_gap: i32
+	stbtt.GetFontVMetrics(&res.info, &ascent, &descent, &line_gap)
+	fh := f32(ascent - descent)
+	res.ascender = f32(ascent) / fh
+	res.descender = f32(descent) / fh
+	res.lineHeight = (fh + f32(line_gap)) / fh
+	res.glyphs = make([dynamic]Glyph, 0, INIT_GLYPHS)
+
+	__lutReset(res)
+	return len(ctx.fonts) - 1
+}
+
+AddFont :: proc { AddFontPath, AddFontMem }
+
+AddFallbackFont :: proc(ctx: ^FontContext, base, fallback: int) -> bool {
+	base_font := __getFont(ctx, base)
+	
+	if base_font.nfallbacks < MAX_FALLBACKS {
+		base_font.fallbacks[base_font.nfallbacks] = fallback
+		base_font.nfallbacks += 1
+		return true
+	}
+
+	return false
+}
+
+ResetFallbackFont :: proc(ctx: ^FontContext, base: int) {
+	base_font := __getFont(ctx, base)
+	base_font.nfallbacks = 0
+	clear(&base_font.glyphs)
+	__lutReset(base_font)
+}
+
+// find font by name
+GetFontByName :: proc(ctx: ^FontContext, name: string) -> int {
+	for font, i in ctx.fonts {
+		if font.name == name {
+			return i
+		}
+	}
+
+	return INVALID
+}
+
+__lutReset :: proc(font: ^Font) {
+	// set lookup table
+	for i in 0..<HASH_LUT_SIZE {
+		font.lut[i] = -1
+	}
+}
+
+__hashint :: proc(a: u32) -> u32 {
+	a := a
+	a += ~(a << 15)
+	a ~=  (a >> 10)
+	a +=  (a << 3)
+	a ~=  (a >> 6)
+	a +=  (a << 11)
+	a ~=  (a >> 16)
+	return a
+}
+
+__renderGlyphBitmap :: proc(
+	font: ^Font,
+	output: []u8,
+	outWidth: i32,
+	outHeight: i32,
+	outStride: i32,
+	scaleX: f32,
+	scaleY: f32,
+	glyphIndex: Glyph_Index,
+) {
+	stbtt.MakeGlyphBitmap(&font.info, raw_data(output), outWidth, outHeight, outStride, scaleX, scaleY, glyphIndex)
+}
+
+__buildGlyphBitmap :: proc(
+	font: ^Font, 
+	glyphIndex: Glyph_Index,
+	pixelSize: f32,
+	scale: f32,
+) -> (advance, lsb, x0, y0, x1, y1: i32) {
+	stbtt.GetGlyphHMetrics(&font.info, glyphIndex, &advance, &lsb)
+	stbtt.GetGlyphBitmapBox(&font.info, glyphIndex, scale, scale, &x0, &y0, &x1, &y1)
+	return
+}
+
+// get glyph and push to atlas if not exists
+__getGlyph :: proc(
+	ctx: ^FontContext,
+	font: ^Font,
+	codepoint: rune,
+	isize: i16,
+	blur: i16 = 0,
+) -> (res: ^Glyph) #no_bounds_check {
+	if isize < 2 {
+		return
+	}
+
+	// find code point and size
+	h := __hashint(u32(codepoint)) & (HASH_LUT_SIZE - 1)
+	i := font.lut[h]
+	for i != -1 {
+		glyph := &font.glyphs[i]
+		
+		if 
+			glyph.codepoint == codepoint && 
+			glyph.isize == isize &&
+			glyph.blurSize == blur
+		{
+			res = glyph
+			return
+		}
+
+		i = glyph.next
+	}
+
+	// could not find glyph, create it.
+	render_font := font // font used to render
+	glyph_index := __getGlyph_index(font, codepoint)
+	if glyph_index == 0 {
+		// lookout for possible fallbacks
+		for i in 0..<font.nfallbacks {
+			fallback_font := __getFont(ctx, font.fallbacks[i])
+			fallback_index := __getGlyph_index(fallback_font, codepoint)
+
+			if fallback_index != 0 {
+				glyph_index = fallback_index
+				render_font = fallback_font
+				break
+			}
+		}
+	}
+
+	pixel_size := f32(isize) / 10
+	blurSize := min(blur, 20)
+	padding := i16(blurSize + 2) // 2 minimum padding
+	scale := __getPixelHeightScale(render_font, pixel_size)
+	advance, _, x0, y0, x1, y1 := __buildGlyphBitmap(render_font, glyph_index, pixel_size, scale)
+	gw := (x1 - x0) + i32(padding) * 2
+	gh := (y1 - y0) + i32(padding) * 2 
+
+	// Find free spot for the rect in the atlas
+	gx, gy, ok := __AtlasAddRect(ctx, int(gw), int(gh))
+	if !ok {
+		// try again with expanded
+		ExpandAtlas(ctx, ctx.width * 2, ctx.height * 2)
+		gx, gy, ok = __AtlasAddRect(ctx, int(gw), int(gh))
+	}
+
+	// still not ok?
+	if !ok {
+		return
+	}
+	
+	// Init glyph.
+	append(&font.glyphs, Glyph {
+		codepoint = codepoint,
+		isize = isize,
+		blurSize = blurSize,
+		index = glyph_index,
+		x0 = i16(gx),
+		y0 = i16(gy),
+		x1 = i16(i32(gx) + gw),
+		y1 = i16(i32(gy) + gh),
+		xadvance = i16(scale * f32(advance) * 10),
+		xoff = i16(x0 - i32(padding)),
+		yoff = i16(y0 - i32(padding)),
+
+		// insert char to hash lookup.
+		next = font.lut[h],
+	})
+	font.lut[h] = len(font.glyphs) - 1
+	res = &font.glyphs[len(font.glyphs) - 1]
+
+	// rasterize
+	dst := ctx.textureData[int(res.x0 + padding) + int(res.y0 + padding) * ctx.width:]
+	__renderGlyphBitmap(
+		render_font,
+		dst,
+		gw - i32(padding) * 2, 
+		gh - i32(padding) * 2, 
+		i32(ctx.width), 
+		scale,
+		scale,
+		glyph_index,
+	)
+
+	// make sure there is one pixel empty border.
+	dst = ctx.textureData[int(res.x0) + int(res.y0) * ctx.width:]
+	// y direction
+	for y in 0..<int(gh) {
+		dst[y * ctx.width] = 0
+		dst[int(gw - 1) + y * ctx.width] = 0
+	}
+	// x direction
+	for x in 0..<int(gw) {
+		dst[x] = 0
+		dst[x + int(gh - 1) * ctx.width] = 0
+	}
+
+	if blurSize > 0 {
+		__blur(dst, int(gw), int(gh), ctx.width, blurSize)
+	}
+
+	ctx.dirtyRect[0] = cast(f32) min(int(ctx.dirtyRect[0]), int(res.x0))
+	ctx.dirtyRect[1] = cast(f32) min(int(ctx.dirtyRect[1]), int(res.y0))
+	ctx.dirtyRect[2] = cast(f32) max(int(ctx.dirtyRect[2]), int(res.x1))
+	ctx.dirtyRect[3] = cast(f32) max(int(ctx.dirtyRect[3]), int(res.y1))
+
+	return
+}
+
+/////////////////////////////////
+// blur
+/////////////////////////////////
+
+// Based on Exponential blur, Jani Huhtanen, 2006
+
+BLUR_APREC :: 16
+BLUR_ZPREC :: 7
+
+__blurCols :: proc(dst: []u8, w, h, dstStride, alpha: int) {
+	dst := dst
+
+	for _ in 0..<h {
+		z := 0 // force zero border
+
+		for x in 1..<w {
+			z += (alpha * ((int(dst[x]) << BLUR_ZPREC) - z)) >> BLUR_APREC
+			dst[x] = u8(z >> BLUR_ZPREC)
+		}
+
+		dst[w - 1] = 0 // force zero border
+		z = 0
+
+		for x := w - 2; x >= 0; x -= 1 {
+			z += (alpha * ((int(dst[x]) << BLUR_ZPREC) - z)) >> BLUR_APREC
+			dst[x] = u8(z >> BLUR_ZPREC)
+		}
+
+		dst[0] = 0 // force zero border
+		dst = dst[dstStride:] // advance slice
+	}
+}
+
+__blurRows :: proc(dst: []u8, w, h, dstStride, alpha: int) {
+	dst := dst
+
+	for _ in 0..<w {
+		z := 0 // force zero border
+		for y := dstStride; y < h * dstStride; y += dstStride {
+			z += (alpha * ((int(dst[y]) << BLUR_ZPREC) - z)) >> BLUR_APREC
+			dst[y] = u8(z >> BLUR_ZPREC)
+		}
+
+		dst[(h - 1) * dstStride] = 0 // force zero border
+		z = 0
+
+		for y := (h - 2) * dstStride; y >= 0; y -= dstStride {
+			z += (alpha * ((int(dst[y]) << BLUR_ZPREC) - z)) >> BLUR_APREC
+			dst[y] = u8(z >> BLUR_ZPREC)
+		}
+
+		dst[0] = 0 // force zero border
+		dst = dst[1:] // advance
+	}
+}
+
+__blur :: proc(dst: []u8, w, h, dstStride: int, blurSize: i16) {
+	assert(blurSize != 0)
+
+	// Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
+	sigma := f32(blurSize) * 0.57735 // 1 / sqrt(3)
+	alpha := int((1 << BLUR_APREC) * (1 - math.exp(-2.3 / (sigma + 1))))
+	__blurRows(dst, w, h, dstStride, alpha)
+	__blurCols(dst, w, h, dstStride, alpha)
+	__blurRows(dst, w, h, dstStride, alpha)
+	__blurCols(dst, w, h, dstStride, alpha)
+}
+
+/////////////////////////////////
+// Texture expansion
+/////////////////////////////////
+
+ExpandAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.allocator) -> bool {
+	w := max(ctx.width, width)
+	h := max(ctx.height, height)
+
+	if w == ctx.width && h == ctx.height {
+		return true
+	}
+
+	if ctx.callbackResize != nil {
+		ctx.callbackResize(ctx.userData, w, h)
+	}
+
+	data := make([]byte, w * h, allocator)
+
+	for i in 0..<ctx.height {
+		dst := &data[i * w]
+		src := &ctx.textureData[i * ctx.width]
+		mem.copy(dst, src, ctx.width)
+
+		if w > ctx.width {
+			mem.set(&data[i * w + ctx.width], 0, w - ctx.width)
+		}
+	}
+
+	if h > ctx.height {
+		mem.set(&data[ctx.height * w], 0, (h - ctx.height) * w)
+	}
+
+	delete(ctx.textureData)
+	ctx.textureData = data
+
+	// increase atlas size
+	__atlasExpand(ctx, w, h)
+
+	// add existing data as dirty
+	maxy := i16(0)
+	for node in ctx.nodes {
+		maxy = max(maxy, node.y)
+	}
+	ctx.dirtyRect[0] = 0
+	ctx.dirtyRect[1] = 0
+	ctx.dirtyRect[2] = f32(ctx.width)
+	ctx.dirtyRect[3] = f32(maxy)
+
+	ctx.width = w
+	ctx.height = h
+	ctx.itw = 1.0 / f32(w)
+	ctx.ith = 1.0 / f32(h)
+
+	return true
+}
+
+ResetAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.allocator) -> bool {
+	if width == ctx.width && height == ctx.height {
+		// just clear
+		mem.zero_slice(ctx.textureData)
+	} else {
+		// realloc
+		ctx.textureData = make([]byte, width * height, allocator)
+	}
+
+	ctx.dirtyRect[0] = f32(width)
+	ctx.dirtyRect[1] = f32(height)
+	ctx.dirtyRect[2] = 0
+	ctx.dirtyRect[3] = 0
+
+	// reset fonts
+	for font in &ctx.fonts {
+		clear(&font.glyphs)
+		__lutReset(&font)
+	}
+
+	ctx.width = width
+	ctx.height = height
+	ctx.itw = 1.0 / f32(width)
+	ctx.ith = 1.0 / f32(height)
+
+	__AtlasAddWhiteRect(ctx, 2, 2)
+	return true
+}
+
+__getGlyph_index :: proc(font: ^Font, codepoint: rune) -> Glyph_Index {
+	return stbtt.FindGlyphIndex(&font.info, codepoint)
+}
+
+__getPixelHeightScale :: proc(font: ^Font, pixel_height: f32) -> f32 {
+	return stbtt.ScaleForPixelHeight(&font.info, pixel_height)
+}
+
+__getGlyphKernAdvance :: proc(font: ^Font, glyph1, glyph2: Glyph_Index) -> i32 {
+	return stbtt.GetGlyphKernAdvance(&font.info, glyph1, glyph2)
+}
+
+// get a font with bounds checking
+__getFont :: proc(ctx: ^FontContext, index: int, loc := #caller_location) -> ^Font #no_bounds_check {
+	runtime.bounds_check_error_loc(loc, index, len(ctx.fonts))
+	return &ctx.fonts[index]
+}
+
+// only useful for single glyphs where you quickly want the width
+CodepointWidth :: proc(
+	font: ^Font,
+	codepoint: rune,
+	scale: f32,
+) -> f32 {
+	glyph_index := __getGlyph_index(font, codepoint)
+	xadvance, lsb: i32
+	stbtt.GetGlyphHMetrics(&font.info, glyph_index, &xadvance, &lsb)
+	return f32(xadvance) * scale
+}
+
+// get top and bottom line boundary
+LineBounds :: proc(ctx: ^FontContext, y: f32) -> (miny, maxy: f32) {
+	state := __getState(ctx)
+	font := __getFont(ctx, state.font)
+	isize := i16(state.size * 10.0)
+	y := y
+	y += __getVerticalAlign(ctx, font, state.av, isize)
+
+	if ctx.location == .TOPLEFT {
+		miny = y - font.ascender * f32(isize) / 10
+		maxy = miny + font.lineHeight * f32(isize / 10)
+	} else if ctx.location == .BOTTOMLEFT {
+		miny = y + font.ascender * f32(isize) / 10
+		maxy = miny - font.lineHeight * f32(isize / 10)
+	}
+
+	return
+}
+
+// reset dirty rect
+__dirtyRectReset :: proc(using ctx: ^FontContext) {
+	dirtyRect[0] = f32(width)
+	dirtyRect[1] = f32(height)
+	dirtyRect[2] = 0
+	dirtyRect[3] = 0
+}
+
+// true when the dirty rectangle is valid and needs a texture update on the gpu
+ValidateTexture :: proc(using ctx: ^FontContext, dirty: ^[4]f32) -> bool {
+	if dirtyRect[0] < dirtyRect[2] && dirtyRect[1] < dirtyRect[3] {
+		dirty[0] = dirtyRect[0]
+		dirty[1] = dirtyRect[1]
+		dirty[2] = dirtyRect[2]
+		dirty[3] = dirtyRect[3]
+		__dirtyRectReset(ctx)
+		return true
+	}
+
+	return false
+}
+
+// get alignment based on font
+__getVerticalAlign :: proc(
+	ctx: ^FontContext,
+	font: ^Font,
+	av: AlignVertical,
+	pixelSize: i16,
+) -> (res: f32) {
+	switch ctx.location {
+	case .TOPLEFT:
+		switch av {
+		case .TOP: res = font.ascender * f32(pixelSize) / 10
+		case .MIDDLE: res = (font.ascender + font.descender) / 2 * f32(pixelSize) / 10
+		case .BASELINE: res = 0
+		case .BOTTOM: res = font.descender * f32(pixelSize) / 10
+		}
+
+	case .BOTTOMLEFT: 
+		switch av {
+			case .TOP: res = -font.ascender * f32(pixelSize) / 10
+			case .MIDDLE: res = -(font.ascender + font.descender) / 2 * f32(pixelSize) / 10
+			case .BASELINE: res = 0
+			case .BOTTOM: res = -font.descender * f32(pixelSize) / 10
+		}
+	}
+
+	return
+}
+
+@(private)
+UTF8_ACCEPT :: 0
+
+@(private)
+UTF8_REJECT :: 1
+
+@(private)
+utf8d := [400]u8 {
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
+	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
+	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
+	8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
+	0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
+	0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
+	0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
+	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
+	1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
+	1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
+	1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
+}
+
+// decode codepoints from a state
+@(private)
+__decutf8 :: #force_inline proc(state: ^rune, codep: ^rune, b: byte) -> bool {
+	b := rune(b)
+	type := utf8d[b]
+	codep^ = (state^ != UTF8_ACCEPT) ? ((b & 0x3f) | (codep^ << 6)) : ((0xff >> type) & (b))
+	state^ = rune(utf8d[256 + state^ * 16 + rune(type)])
+	return state^ == UTF8_ACCEPT
+}
+
+// state used to share font options
+State :: struct {
+	font: int,
+	size: f32,
+	color: [4]u8,
+	spacing: f32,
+	blur: f32,
+
+	ah: AlignHorizontal,
+	av: AlignVertical,
+}
+
+// quad that should be used to draw from the texture atlas
+Quad :: struct {
+	x0, y0, s0, t0: f32,
+	x1, y1, s1, t1: f32,
+}
+
+// text iteration with custom settings
+TextIter :: struct {
+	x, y, nextx, nexty, scale, spacing: f32,
+	isize, iblur: i16,
+
+	font: ^Font,
+	previousGlyphIndex: Glyph_Index,
+
+	// unicode iteration
+	utf8state: rune, // utf8
+	codepoint: rune,
+	text: string,
+	codepointCount: int,
+
+	// byte indices
+	str: int,
+	next: int,
+	end: int,
+}
+
+// push a state, copies the current one over to the next one
+PushState :: proc(using ctx: ^FontContext, loc := #caller_location) #no_bounds_check {
+	runtime.bounds_check_error_loc(loc, state_count, MAX_STATES)
+
+	if state_count > 0 {
+		states[state_count] = states[state_count - 1]
+	}
+
+	state_count += 1
+}
+
+// pop a state 
+PopState :: proc(using ctx: ^FontContext) {
+	if state_count <= 1 {
+		log.error("FONTSTASH: state underflow! to many pops were called")
+	} else {
+		state_count -= 1
+	}
+}
+
+// clear current state
+ClearState :: proc(ctx: ^FontContext) {
+	state := __getState(ctx)
+	state.size = 12
+	state.color = 255
+	state.blur = 0
+	state.spacing = 0
+	state.font = 0
+	state.ah = .LEFT
+	state.av = .BASELINE
+}
+
+__getState :: #force_inline proc(ctx: ^FontContext) -> ^State #no_bounds_check {
+	return &ctx.states[ctx.state_count - 1]
+}
+
+SetSize :: proc(ctx: ^FontContext, size: f32) {
+	__getState(ctx).size = size
+}
+
+SetColor :: proc(ctx: ^FontContext, color: [4]u8) {
+	__getState(ctx).color = color
+}
+
+SetSpacing :: proc(ctx: ^FontContext, spacing: f32) {
+	__getState(ctx).spacing = spacing
+}
+
+SetBlur :: proc(ctx: ^FontContext, blur: f32) {
+	__getState(ctx).blur = blur
+}
+
+SetFont :: proc(ctx: ^FontContext, font: int) {
+	__getState(ctx).font = font
+}
+
+SetAH :: SetAlignHorizontal
+SetAV :: SetAlignVertical
+
+SetAlignHorizontal :: proc(ctx: ^FontContext, ah: AlignHorizontal) {
+	__getState(ctx).ah = ah
+}
+
+SetAlignVertical :: proc(ctx: ^FontContext, av: AlignVertical) {
+	__getState(ctx).av = av
+}
+
+__getQuad :: proc(
+	ctx: ^FontContext,
+	font: ^Font,
+	
+	previousGlyphIndex: i32,
+	glyph: ^Glyph,
+
+	scale: f32,
+	spacing: f32,
+	
+	x, y: ^f32,
+	quad: ^Quad,
+) {
+	if previousGlyphIndex != -1 {
+		adv := f32(__getGlyphKernAdvance(font, previousGlyphIndex, glyph.index)) * scale
+		x^ += f32(int(adv + spacing + 0.5))
+	}
+
+	// fill props right
+	rx, ry, x0, y0, x1, y1, xoff, yoff: f32
+	xoff = f32(glyph.xoff + 1)
+	yoff = f32(glyph.yoff + 1)
+	x0 = f32(glyph.x0 + 1)
+	y0 = f32(glyph.y0 + 1)
+	x1 = f32(glyph.x1 - 1)
+	y1 = f32(glyph.y1 - 1)
+
+	switch ctx.location {
+	case .TOPLEFT:
+		rx = math.floor(x^ + xoff)
+		ry = math.floor(y^ + yoff)
+		
+		quad.x0 = rx
+		quad.y0 = ry
+		quad.x1 = rx + x1 - x0
+		quad.y1 = ry + y1 - y0
+
+		quad.s0 = x0 * ctx.itw
+		quad.t0 = y0 * ctx.ith
+		quad.s1 = x1 * ctx.itw
+		quad.t1 = y1 * ctx.ith
+
+	case .BOTTOMLEFT:
+		rx = math.floor(x^ + xoff)
+		ry = math.floor(y^ - yoff)
+
+		quad.x0 = rx
+		quad.y0 = ry
+		quad.x1 = rx + x1 - x0
+		quad.y1 = ry - y1 + y0
+
+		quad.s0 = x0 * ctx.itw
+		quad.t0 = y0 * ctx.ith
+		quad.s1 = x1 * ctx.itw
+		quad.t1 = y1 * ctx.ith
+	}
+
+	x^ += f32(int(f32(glyph.xadvance) / 10 + 0.5))
+}
+
+// init text iter struct with settings
+TextIterInit :: proc(
+	ctx: ^FontContext,
+	x: f32,
+	y: f32,
+	text: string,
+) -> (res: TextIter) {
+	state := __getState(ctx)
+	res.font = __getFont(ctx, state.font)
+	res.isize = i16(f32(state.size) * 10)
+	res.iblur = i16(state.blur)
+	res.scale = __getPixelHeightScale(res.font, f32(res.isize) / 10)
+
+	// align horizontally
+	x := x
+	y := y
+	switch state.ah {
+	case .LEFT: {}
+
+	case .CENTER:
+		width := TextBounds(ctx, text, x, y, nil)
+		x = math.round(x - width * 0.5)
+
+	case .RIGHT: 
+		width := TextBounds(ctx, text, x, y, nil)
+		x -= width
+	}
+
+	// align vertically
+	y = math.round(y + __getVerticalAlign(ctx, res.font, state.av, res.isize))
+
+	// set positions
+	res.x = x
+	res.nextx = x
+	res.y = y
+	res.nexty = y
+	res.previousGlyphIndex = -1
+	res.spacing = state.spacing
+	res.text = text
+
+	res.str = 0
+	res.next = 0
+	res.end = len(text)
+
+	return
+}
+
+// step through each codepoint
+TextIterNext :: proc(
+	ctx: ^FontContext, 
+	iter: ^TextIter, 
+	quad: ^Quad,
+) -> (ok: bool) {
+	str := iter.next
+	iter.str = iter.next
+
+	for str < iter.end {
+		defer str += 1
+
+		if __decutf8(&iter.utf8state, &iter.codepoint, iter.text[str]) {
+			iter.x = iter.nextx
+			iter.y = iter.nexty
+			iter.codepointCount += 1
+			glyph := __getGlyph(ctx, iter.font, iter.codepoint, iter.isize, iter.iblur)
+			
+			if glyph != nil {
+				__getQuad(ctx, iter.font, iter.previousGlyphIndex, glyph, iter.scale, iter.spacing, &iter.nextx, &iter.nexty, quad)
+			}
+
+			iter.previousGlyphIndex = glyph == nil ? -1 : glyph.index
+			ok = true
+			break
+		}
+	}
+
+	iter.next = str
+	return
+}
+
+// width of a text line, optionally the full rect
+TextBounds :: proc(
+	ctx: ^FontContext,
+	text: string,
+	x: f32 = 0,
+	y: f32 = 0,
+	bounds: ^[4]f32 = nil,
+) -> f32 {
+	state := __getState(ctx)
+	isize := i16(state.size * 10)
+	iblur := i16(state.blur)
+	font := __getFont(ctx, state.font)
+
+	// bunch of state
+	x := x
+	y := y
+	minx := x
+	maxx := x
+	miny := y 
+	maxy := y
+	start_x := x
+
+	// iterate	
+	scale := __getPixelHeightScale(font, f32(isize) / 10)
+	previousGlyphIndex: Glyph_Index = -1
+	quad: Quad
+	utf8state: rune
+	codepoint: rune
+	for byte_offset in 0..<len(text) {
+		if __decutf8(&utf8state, &codepoint, text[byte_offset]) {
+			glyph := __getGlyph(ctx, font, codepoint, isize, iblur)
+
+			if glyph != nil {
+				__getQuad(ctx, font, previousGlyphIndex, glyph, scale, state.spacing, &x, &y, &quad)
+
+				if quad.x0 < minx {
+					minx = quad.x0
+				}
+				if quad.x1 > maxx {
+					maxx = quad.x1
+				}
+
+				if ctx.location == .TOPLEFT {
+					if quad.y0 < miny {
+						miny = quad.y0
+					}
+					if quad.y1 > maxy {
+						maxy = quad.y1
+					}
+				} else if ctx.location == .BOTTOMLEFT {
+					if quad.y1 < miny {
+						miny = quad.y1
+					}
+					if quad.y0 > maxy {
+						maxy = quad.y0
+					}
+				}
+			}
+
+			previousGlyphIndex = glyph == nil ? -1 : glyph.index
+		}
+	}
+
+	// horizontal alignment
+	advance := x - start_x
+	switch state.ah {
+	case .LEFT: {}
+
+	case .CENTER: 
+		minx -= advance * 0.5
+		maxx -= advance * 0.5
+
+	case .RIGHT:
+		minx -= advance
+		maxx -= advance
+	}
+
+	if bounds != nil {
+		bounds^ = { minx, miny, maxx, maxy }
+	}
+
+	return advance
+}
+
+VerticalMetrics :: proc(
+	ctx: ^FontContext,
+) -> (ascender, descender, lineHeight: f32) {
+	state := __getState(ctx)
+	isize := i16(state.size * 10.0)
+	font := __getFont(ctx, state.font)
+	ascender = font.ascender * f32(isize / 10)
+	descender = font.descender * f32(isize / 10)
+	lineHeight = font.lineHeight * f32(isize / 10)
+	return
+}
+
+// reset to single state
+BeginState :: proc(using ctx: ^FontContext) {
+	state_count = 0
+	PushState(ctx)
+	ClearState(ctx)
+}
+
+// checks for texture updates after potential __getGlyph calls
+EndState :: proc(using ctx: ^FontContext) {
+	// check for texture update
+	if dirtyRect[0] < dirtyRect[2] && dirtyRect[1] < dirtyRect[3] {
+		if callbackUpdate != nil {
+			callbackUpdate(userData, dirtyRect, raw_data(textureData))
+		}
+
+		__dirtyRectReset(ctx)
+	}
+}

+ 123 - 0
vendor/nanovg/gl/frag.glsl

@@ -0,0 +1,123 @@
+#ifdef GL_ES
+#if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3)
+ precision highp float;
+#else
+ precision mediump float;
+#endif
+#endif
+#ifdef NANOVG_GL3
+#ifdef USE_UNIFORMBUFFER
+	layout(std140) uniform frag {
+		mat3 scissorMat;
+		mat3 paintMat;
+		vec4 innerCol;
+		vec4 outerCol;
+		vec2 scissorExt;
+		vec2 scissorScale;
+		vec2 extent;
+		float radius;
+		float feather;
+		float strokeMult;
+		float strokeThr;
+		int texType;
+		int type;
+	};
+#else // NANOVG_GL3 && !USE_UNIFORMBUFFER
+	uniform vec4 frag[UNIFORMARRAY_SIZE];
+#endif
+	uniform sampler2D tex;
+	in vec2 ftcoord;
+	in vec2 fpos;
+	out vec4 outColor;
+#else // !NANOVG_GL3
+	uniform vec4 frag[UNIFORMARRAY_SIZE];
+	uniform sampler2D tex;
+	varying vec2 ftcoord;
+	varying vec2 fpos;
+#endif
+#ifndef USE_UNIFORMBUFFER
+	#define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
+	#define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
+	#define innerCol frag[6]
+	#define outerCol frag[7]
+	#define scissorExt frag[8].xy
+	#define scissorScale frag[8].zw
+	#define extent frag[9].xy
+	#define radius frag[9].z
+	#define feather frag[9].w
+	#define strokeMult frag[10].x
+	#define strokeThr frag[10].y
+	#define texType int(frag[10].z)
+	#define type int(frag[10].w)
+#endif
+
+float sdroundrect(vec2 pt, vec2 ext, float rad) {
+	vec2 ext2 = ext - vec2(rad,rad);
+	vec2 d = abs(pt) - ext2;
+	return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;
+}
+
+// Scissoring
+float scissorMask(vec2 p) {
+	vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);
+	sc = vec2(0.5,0.5) - sc * scissorScale;
+	return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);
+}
+#ifdef EDGE_AA
+// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
+float strokeMask() {
+	return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);
+}
+#endif
+
+void main(void) {
+   vec4 result;
+	float scissor = scissorMask(fpos);
+#ifdef EDGE_AA
+	float strokeAlpha = strokeMask();
+	if (strokeAlpha < strokeThr) discard;
+#else
+	float strokeAlpha = 1.0;
+#endif
+	if (type == 0) {			// Gradient
+		// Calculate gradient color using box gradient
+		vec2 pt = (paintMat * vec3(fpos,1.0)).xy;
+		float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);
+		vec4 color = mix(innerCol,outerCol,d);
+		// Combine alpha
+		color *= strokeAlpha * scissor;
+		result = color;
+	} else if (type == 1) {		// Image
+		// Calculate color fron texture
+		vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;
+#ifdef NANOVG_GL3
+		vec4 color = texture(tex, pt);
+#else
+		vec4 color = texture2D(tex, pt);
+#endif
+		if (texType == 1) color = vec4(color.xyz*color.w,color.w);
+		if (texType == 2) color = vec4(color.x);
+		// Apply color tint and alpha.
+		color *= innerCol;
+		// Combine alpha
+		color *= strokeAlpha * scissor;
+		result = color;
+	} else if (type == 2) {		// Stencil fill
+		result = vec4(1,1,1,1);
+	} else if (type == 3) {		// Textured tris
+#ifdef NANOVG_GL3
+		vec4 color = texture(tex, ftcoord);
+#else
+		vec4 color = texture2D(tex, ftcoord);
+#endif
+		if (texType == 1) color = vec4(color.xyz*color.w,color.w);
+		if (texType == 2) color = vec4(color.x);
+		color *= scissor;
+		result = color * innerCol;
+	}
+#ifdef NANOVG_GL3
+	outColor = result;
+#else
+	gl_FragColor = result;
+#endif
+}

+ 1453 - 0
vendor/nanovg/gl/gl.odin

@@ -0,0 +1,1453 @@
+//+build windows, linux, darwin
+package nanovg_gl
+
+import "core:log"
+import "core:strings"
+import "core:mem"
+import "core:math"
+import "core:fmt"
+import gl "vendor:OpenGL"
+import nvg "../../nanovg"
+
+Color :: nvg.Color
+Vertex :: nvg.Vertex
+ImageFlags :: nvg.ImageFlags
+TextureType :: nvg.Texture
+Paint :: nvg.Paint
+ScissorT :: nvg.ScissorT
+
+CreateFlag :: enum {
+	// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
+	ANTI_ALIAS,
+	// Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
+	// slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once.
+	STENCIL_STROKES,
+	// additional debug checks
+	DEBUG,
+}
+CreateFlags :: bit_set[CreateFlag]
+
+USE_STATE_FILTER :: #config(USE_STATE_FILTER, true)
+
+UniformLoc :: enum {
+	VIEW_SIZE,
+	TEX,
+	FRAG,
+}
+
+ShaderType :: enum i32 {
+	FILL_GRAD,
+	FILL_IMG,
+	SIMPLE,
+	IMG,
+}
+
+Shader :: struct {
+	prog: u32,
+	frag: u32,
+	vert: u32,
+	loc: [UniformLoc]i32,
+}
+
+Texture :: struct {
+	id: int,
+	tex: u32,
+	width, height: int,
+	type: TextureType,
+	flags: ImageFlags,
+}
+
+Blend :: struct {
+	src_RGB: u32,
+	dst_RGB: u32,
+	src_alpha: u32,
+	dst_alpha: u32,
+}
+
+CallType :: enum {
+	NONE,
+	FILL,
+	CONVEX_FILL,
+	STROKE,
+	TRIANGLES,
+}
+
+Call :: struct {
+	type: CallType,
+	image: int,
+	pathOffset: int,
+	pathCount: int,
+	triangleOffset: int,
+	triangleCount: int,
+	uniformOffset: int,
+	blendFunc: Blend,
+}
+
+Path :: struct {
+	fillOffset: int,
+	fillCount: int,
+	strokeOffset: int,
+	strokeCount: int,
+}
+
+GL_UNIFORMARRAY_SIZE :: 11
+
+when GL2_IMPLEMENTATION {
+	FragUniforms :: struct #raw_union {
+		using _: struct {
+			scissorMat: [12]f32, // matrices are actually 3 vec4s
+			paintMat: [12]f32,
+			innerColor: Color,
+			outerColor: Color,
+			scissorExt: [2]f32,
+			scissorScale: [2]f32,
+			extent: [2]f32,
+			radius: f32,
+			feather: f32,
+			strokeMult: f32,
+			strokeThr: f32,
+			texType: i32,
+			type: ShaderType,
+		},
+		uniform_array: [GL_UNIFORMARRAY_SIZE][4]f32,
+	}
+} else {
+	FragUniforms :: struct #packed {
+		scissorMat: [12]f32, // matrices are actually 3 vec4s
+		paintMat: [12]f32,
+		innerColor: Color,
+		outerColor: Color,
+		scissorExt: [2]f32,
+		scissorScale: [2]f32,
+		extent: [2]f32,
+		radius: f32,
+		feather: f32,
+		strokeMult: f32,
+		strokeThr: f32,
+		texType: i32,
+		type: ShaderType,
+	}
+}
+
+DEFAULT_IMPLEMENTATION_STRING :: #config(NANOVG_GL_IMPL, "GL3")
+GL2_IMPLEMENTATION   :: DEFAULT_IMPLEMENTATION_STRING  == "GL2"
+GL3_IMPLEMENTATION   :: DEFAULT_IMPLEMENTATION_STRING  == "GL3"
+GLES2_IMPLEMENTATION :: DEFAULT_IMPLEMENTATION_STRING  == "GLES2"
+GLES3_IMPLEMENTATION :: DEFAULT_IMPLEMENTATION_STRING  == "GLES3"
+
+when GL2_IMPLEMENTATION {
+	GL2 :: true
+	GL3 :: false
+	GLES2 :: false
+	GLES3 :: false
+	GL_IMPLEMENTATION :: true
+	GL_USE_UNIFORMBUFFER :: false
+} else when GL3_IMPLEMENTATION {
+	GL2 :: false
+	GL3 :: true
+	GLES2 :: false
+	GLES3 :: false
+	GL_IMPLEMENTATION :: true
+	GL_USE_UNIFORMBUFFER :: true
+} else when GLES2_IMPLEMENTATION {
+	GL2 :: false
+	GL3 :: false
+	GLES2 :: true
+	GLES3 :: false
+	GL_IMPLEMENTATION :: true
+	GL_USE_UNIFORMBUFFER :: false
+} else when GLES3_IMPLEMENTATION {
+	GL2 :: false
+	GL3 :: false
+	GLES2 :: false
+	GLES3 :: true
+	GL_IMPLEMENTATION :: true
+	GL_USE_UNIFORMBUFFER :: false
+}
+
+Context :: struct {
+	shader: Shader,
+	textures: [dynamic]Texture,
+	view: [2]f32,
+	textureId: int,
+
+	vertBuf: u32,
+	vertArr: u32, // GL3
+	fragBuf: u32, // USE_UNIFORMBUFFER
+	fragSize: int,
+	flags: CreateFlags,
+	frag_binding: u32,
+
+	// Per frame buffers
+	calls: [dynamic]Call,
+	paths: [dynamic]Path,
+	verts: [dynamic]Vertex,
+	uniforms: [dynamic]byte,
+
+	// cached state used for state filter
+	boundTexture: u32,
+	stencilMask: u32,
+	stencilFunc: u32,
+	stencilFuncRef: i32,
+	stencilFuncMask: u32,
+	blendFunc: Blend,
+
+	dummyTex: int,
+}
+
+__nearestPow2 :: proc(num: uint) -> uint {
+	n := num > 0 ? num - 1 : 0
+	n |= n >> 1
+	n |= n >> 2
+	n |= n >> 4
+	n |= n >> 8
+	n |= n >> 16
+	n += 1
+	return n
+}
+
+__bindTexture :: proc(ctx: ^Context, tex: u32) {
+	when USE_STATE_FILTER {
+		if ctx.boundTexture != tex {
+			ctx.boundTexture = tex
+			gl.BindTexture(gl.TEXTURE_2D, tex)
+		}
+	} else {
+		gl.BindTexture(gl.TEXTURE_2D, tex)
+	}
+}
+
+__stencilMask :: proc(ctx: ^Context, mask: u32) {
+	when USE_STATE_FILTER {
+		if ctx.stencilMask != mask {
+			ctx.stencilMask = mask
+			gl.StencilMask(mask)
+		}
+	} else {
+		gl.StencilMask(mask)
+	}
+}
+
+__stencilFunc :: proc(ctx: ^Context, func: u32, ref: i32, mask: u32) {
+	when USE_STATE_FILTER {
+		if ctx.stencilFunc != func ||
+			ctx.stencilFuncRef != ref ||
+			ctx.stencilFuncMask != mask {
+			ctx.stencilFunc = func
+			ctx.stencilFuncRef = ref
+			ctx.stencilFuncMask = mask
+			gl.StencilFunc(func, ref, mask)
+		}
+	} else {
+		gl.StencilFunc(func, ref, mask)
+	}
+}
+
+__blendFuncSeparate :: proc(ctx: ^Context, blend: ^Blend) {
+	when USE_STATE_FILTER {
+		if ctx.blendFunc != blend^ {
+			ctx.blendFunc = blend^
+			gl.BlendFuncSeparate(blend.src_RGB, blend.dst_RGB, blend.src_alpha, blend.dst_alpha)
+		}
+	} else {
+		gl.BlendFuncSeparate(blend.src_RGB, blend.dst_RGB, blend.src_alpha, blend.dst_alpha)
+	}
+}
+
+__allocTexture :: proc(ctx: ^Context) -> (tex: ^Texture) {
+	for texture in &ctx.textures {
+		if texture.id == 0 {
+			tex = &texture
+			break
+		}
+	}
+
+	if tex == nil {
+		append(&ctx.textures, Texture {})
+		tex = &ctx.textures[len(ctx.textures) - 1]
+	}
+
+	tex^ = {}
+	ctx.textureId += 1
+	tex.id = ctx.textureId
+
+	return
+}
+
+__findTexture :: proc(ctx: ^Context, id: int) -> ^Texture {
+	for texture in &ctx.textures {
+		if texture.id == id {
+			return &texture
+		}
+	}
+
+	return nil
+}
+
+__deleteTexture :: proc(ctx: ^Context, id: int) -> bool {
+	for texture, i in &ctx.textures {
+		if texture.id == id {
+			if texture.tex != 0 && (.NO_DELETE not_in texture.flags) {
+				gl.DeleteTextures(1, &texture.tex)
+			}
+
+			ctx.textures[i] = {}
+			return true
+		}
+	}
+
+	return false
+}
+
+__deleteShader :: proc(shader: ^Shader) {
+	if shader.prog != 0 {
+		gl.DeleteProgram(shader.prog)
+	}
+
+	if shader.vert != 0 {
+		gl.DeleteShader(shader.vert)
+	}
+
+	if shader.frag != 0 {
+		gl.DeleteShader(shader.frag)
+	}
+}
+
+__getUniforms :: proc(shader: ^Shader) {
+	shader.loc[.VIEW_SIZE] = gl.GetUniformLocation(shader.prog, "viewSize")
+	shader.loc[.TEX] = gl.GetUniformLocation(shader.prog, "tex")
+	
+	when GL_USE_UNIFORMBUFFER {
+		shader.loc[.FRAG] = i32(gl.GetUniformBlockIndex(shader.prog, "frag"))
+	} else {
+		shader.loc[.FRAG] = gl.GetUniformLocation(shader.prog, "frag")
+	}
+}
+
+vert_shader := #load("vert.glsl")
+frag_shader := #load("frag.glsl")
+
+__renderCreate :: proc(uptr: rawptr) -> bool {
+	ctx := cast(^Context) uptr
+
+	// just build the string at runtime
+	builder := strings.builder_make(0, 512, context.temp_allocator)
+
+	when GL2 {
+		strings.write_string(&builder, "#define NANOVG_GL2 1\n")
+	} else when GL3 {
+		strings.write_string(&builder, "#version 150 core\n#define NANOVG_GL3 1\n")
+	} else when GLES2 {
+		strings.write_string(&builder, "#version 100\n#define NANOVG_GL2 1\n")
+	} else when GLES3 {
+		strings.write_string(&builder, "#version 300 es\n#define NANOVG_GL3 1\n")
+	}
+
+	when GL_USE_UNIFORMBUFFER {
+		strings.write_string(&builder, "#define USE_UNIFORMBUFFER 1\n")
+	} else {
+		strings.write_string(&builder, "#define UNIFORMARRAY_SIZE 11\n")
+	} 
+
+	__checkError(ctx, "init")
+
+	shader_header := strings.to_string(builder)
+	anti: string = .ANTI_ALIAS in ctx.flags ? "#define EDGE_AA 1\n" : " "
+	if !__createShader(
+		&ctx.shader, 
+		shader_header,
+		anti, 
+		string(vert_shader),
+		string(frag_shader),
+	) {
+		return false
+	}
+
+	__checkError(ctx, "uniform locations")
+	__getUniforms(&ctx.shader)
+
+	when GL3 {
+		gl.GenVertexArrays(1, &ctx.vertArr)
+	} 
+
+	gl.GenBuffers(1, &ctx.vertBuf)
+	align := i32(4)
+
+	when GL_USE_UNIFORMBUFFER {
+		// Create UBOs
+		gl.UniformBlockBinding(ctx.shader.prog, u32(ctx.shader.loc[.FRAG]), ctx.frag_binding)
+		gl.GenBuffers(1, &ctx.fragBuf)
+		gl.GetIntegerv(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align)
+	} 
+
+	ctx.fragSize = int(size_of(FragUniforms) + align - size_of(FragUniforms) % align)
+	// ctx.fragSize = size_of(FragUniforms)
+	ctx.dummyTex = __renderCreateTexture(ctx, .Alpha, 1, 1, {}, nil)
+
+	__checkError(ctx, "create done")
+	
+	gl.Finish()
+
+	return true
+}
+
+__renderCreateTexture :: proc(
+	uptr: rawptr, 
+	type: TextureType, 
+	w, h: int, 
+	imageFlags: ImageFlags,
+	data: []byte,
+) -> int {
+	ctx := cast(^Context) uptr
+	tex := __allocTexture(ctx)
+	imageFlags := imageFlags
+
+	if tex == nil {
+		return 0
+	}
+
+	when GLES2 {
+		if __nearestPow2(uint(w)) != uint(w) || __nearestPow2(uint(h)) != uint(h) {
+			// No repeat
+			if (.REPEAT_X in imageFlags) || (.REPEAT_Y in imageFlags) {
+				log.errorf("Repeat X/Y is not supported for non power-of-two textures (%d x %d)\n", w, h)
+				excl(&imageFlags, ImageFlags { .REPEAT_X, .REPEAT_Y })
+			}
+
+			// No mips.
+			if .GENERATE_MIPMAPS in imageFlags {
+				log.errorf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h)
+				excl(&imageFlags, ImageFlags { .GENERATE_MIPMAPS })
+			}
+		}
+	}
+
+	gl.GenTextures(1, &tex.tex)
+	tex.width = w
+	tex.height = h
+	tex.type = type
+	tex.flags = imageFlags
+	__bindTexture(ctx, tex.tex)
+
+	gl.PixelStorei(gl.UNPACK_ALIGNMENT,1)
+	
+	when GLES2 {
+		gl.PixelStorei(gl.UNPACK_ROW_LENGTH, i32(tex.width))
+		gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, 0)
+		gl.PixelStorei(gl.UNPACK_SKIP_ROWS, 0)
+	}
+
+	when GL2 {
+		if .GENERATE_MIPMAPS in imageFlags {
+			gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, 1)
+		}
+	}
+
+	if type == .RGBA {
+		gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, i32(w), i32(h), 0, gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data))
+	} else {
+		when GLES2 || GL2 {
+			gl.TexImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, i32(w), i32(h), 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, raw_data(data))
+		} else when GLES3 {
+			gl.TexImage2D(gl.TEXTURE_2D, 0, gl.R8, i32(w), i32(h), 0, gl.RED, gl.UNSIGNED_BYTE, raw_data(data))
+		} else {
+			gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, i32(w), i32(h), 0, gl.RED, gl.UNSIGNED_BYTE, raw_data(data))
+		}
+	}
+
+	if .GENERATE_MIPMAPS in imageFlags {
+		if .NEAREST in imageFlags {
+			gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST)
+		} else {
+			gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
+		}
+	} else {
+		if .NEAREST in imageFlags {
+			gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
+		} else {
+			gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
+		}
+	}
+
+	if .NEAREST in imageFlags {
+		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
+	} else {
+		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
+	}
+
+	if .REPEAT_X in imageFlags {
+		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
+	}	else {
+		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+	}
+
+	if .REPEAT_Y in imageFlags {
+		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
+	}	else {
+		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+	}
+
+	gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
+
+	when GLES2 {
+		gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
+		gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, 0)
+		gl.PixelStorei(gl.UNPACK_SKIP_ROWS, 0)
+	}
+
+	// The new way to build mipmaps on GLES and GL3
+	when !GL2 {
+		if .GENERATE_MIPMAPS in imageFlags {
+			gl.GenerateMipmap(gl.TEXTURE_2D)
+		}
+	}
+
+	__checkError(ctx, "create tex")
+	__bindTexture(ctx, 0)
+
+	return tex.id
+}
+
+__checkError :: proc(ctx: ^Context, str: string) {
+	if .DEBUG in ctx.flags {
+		err := gl.GetError()
+
+		if err != gl.NO_ERROR {
+			log.errorf("FOUND ERROR %08x:\n\t%s\n", err, str)
+		}
+	}
+}
+
+__checkProgramError :: proc(prog: u32) {
+	status: i32
+	gl.GetProgramiv(prog, gl.LINK_STATUS, &status)
+	length: i32
+	gl.GetProgramiv(prog, gl.INFO_LOG_LENGTH, &length)
+
+	if status == 0 {
+		temp := make([]byte, length)
+		defer delete(temp)
+
+		gl.GetProgramInfoLog(prog, length, nil, raw_data(temp))
+		log.errorf("Program Error:\n%s\n", string(temp[:length]))
+	}
+}
+
+__checkShaderError :: proc(shader: u32, type: string) {
+	status: i32
+	gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
+	length: i32
+	gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &length)
+
+	if status == 0 {
+		temp := make([]byte, length)
+		defer delete(temp)
+
+		gl.GetShaderInfoLog(shader, length, nil, raw_data(temp))
+		log.errorf("Shader error:\n%s\n", string(temp[:length]))	
+	}
+}
+
+// TODO good case for or_return
+__createShader :: proc(
+	shader: ^Shader,
+	header: string,
+	opts: string,
+	vshader: string,
+	fshader: string,
+) -> bool {
+	shader^ = {}
+	str: [3]cstring
+	lengths: [3]i32
+	str[0] = cstring(raw_data(header))
+	str[1] = cstring(raw_data(opts))
+
+	lengths[0] = i32(len(header))
+	lengths[1] = i32(len(opts))
+
+	prog := gl.CreateProgram()
+	vert := gl.CreateShader(gl.VERTEX_SHADER)
+	frag := gl.CreateShader(gl.FRAGMENT_SHADER)
+	
+	// vert shader
+	str[2] = cstring(raw_data(vshader))
+	lengths[2] = i32(len(vshader))
+	gl.ShaderSource(vert, 3, &str[0], &lengths[0])
+	gl.CompileShader(vert)
+	__checkShaderError(vert, "vert")
+	
+	// fragment shader
+	str[2] = cstring(raw_data(fshader))
+	lengths[2] = i32(len(fshader))
+	gl.ShaderSource(frag, 3, &str[0], &lengths[0])
+	gl.CompileShader(frag)
+	__checkShaderError(frag, "frag")
+
+	gl.AttachShader(prog, vert)
+	gl.AttachShader(prog, frag)
+
+	gl.BindAttribLocation(prog, 0, "vertex")
+	gl.BindAttribLocation(prog, 1, "tcoord")
+
+	gl.LinkProgram(prog)
+	__checkProgramError(prog)
+
+	shader.prog = prog
+	shader.vert = vert
+	shader.frag = frag
+	return true
+}
+
+__renderDeleteTexture :: proc(uptr: rawptr, image: int) -> bool {
+	ctx := cast(^Context) uptr
+	return __deleteTexture(ctx, image)
+}
+
+__renderUpdateTexture :: proc(
+	uptr: rawptr, 
+	image: int,
+	x, y: int,
+	w, h: int,
+	data: []byte,
+) -> bool {
+	ctx := cast(^Context) uptr
+	tex := __findTexture(ctx, image)
+
+	if tex == nil {
+		return false
+	}
+
+	__bindTexture(ctx, tex.tex)
+
+	gl.PixelStorei(gl.UNPACK_ALIGNMENT,1)
+
+	x := x
+	w := w
+	data := data
+
+	when GLES2 {
+		gl.PixelStorei(gl.UNPACK_ROW_LENGTH, i32(tex.width))
+		gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, i32(x))
+		gl.PixelStorei(gl.UNPACK_SKIP_ROWS, i32(y))
+	} else {
+		// No support for all of skip, need to update a whole row at a time.
+		if tex.type == .RGBA {
+			data = data[y * tex.width * 4:]
+		}	else {
+			data = data[y * tex.width:]
+		}
+
+		x = 0
+		w = tex.width
+	}
+
+	if tex.type == .RGBA {
+		gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data))
+	} else {
+		when GLES2 || GL2 {
+			gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.LUMINANCE, gl.UNSIGNED_BYTE, raw_data(data))
+		} else {
+			gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.RED, gl.UNSIGNED_BYTE, raw_data(data))
+		}
+	}
+
+	gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
+
+	when GLES2 {
+		gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
+		gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, 0)
+		gl.PixelStorei(gl.UNPACK_SKIP_ROWS, 0)
+	}
+
+	__bindTexture(ctx, 0)
+
+	return true
+}
+
+__renderGetTextureSize :: proc(uptr: rawptr, image: int, w, h: ^int) -> bool {
+	ctx := cast(^Context) uptr
+	tex := __findTexture(ctx, image)
+
+	if tex == nil {
+		return false
+	}
+
+	w^ = tex.width
+	h^ = tex.height
+	return true
+}
+
+__xformToMat3x4 :: proc(m3: ^[12]f32, t: [6]f32) {
+	m3[0] = t[0]
+	m3[1] = t[1]
+	m3[2] = 0
+	m3[3] = 0
+	m3[4] = t[2]
+	m3[5] = t[3]
+	m3[6] = 0
+	m3[7] = 0
+	m3[8] = t[4]
+	m3[9] = t[5]
+	m3[10] = 1
+	m3[11] = 0
+}
+
+__premulColor :: proc(c: Color) -> (res: Color) {
+	res = c
+	res.r *= c.a
+	res.g *= c.a
+	res.b *= c.a
+	return
+}
+
+__convertPaint :: proc(
+	ctx: ^Context,
+	frag: ^FragUniforms,
+	paint: ^Paint,
+	scissor: ^ScissorT,
+	width: f32,
+	fringe: f32,
+	strokeThr: f32,
+) -> bool {
+	invxform: [6]f32
+	frag^ = {}
+	frag.innerColor = __premulColor(paint.innerColor)
+	frag.outerColor = __premulColor(paint.outerColor)
+
+	if scissor.extent[0] < -0.5 || scissor.extent[1] < -0.5 {
+		frag.scissorMat = {}
+		frag.scissorExt[0] = 1.0
+		frag.scissorExt[1] = 1.0
+		frag.scissorScale[0] = 1.0
+		frag.scissorScale[1] = 1.0
+	} else {
+		nvg.TransformInverse(&invxform, scissor.xform)
+		__xformToMat3x4(&frag.scissorMat, invxform)
+		frag.scissorExt[0] = scissor.extent[0]
+		frag.scissorExt[1] = scissor.extent[1]
+		frag.scissorScale[0] = math.sqrt(scissor.xform[0]*scissor.xform[0] + scissor.xform[2]*scissor.xform[2]) / fringe
+		frag.scissorScale[1] = math.sqrt(scissor.xform[1]*scissor.xform[1] + scissor.xform[3]*scissor.xform[3]) / fringe
+	}
+
+	frag.extent = paint.extent
+	frag.strokeMult = (width * 0.5 + fringe * 0.5) / fringe
+	frag.strokeThr = strokeThr
+
+	if paint.image != 0 {
+		tex := __findTexture(ctx, paint.image)
+		
+		if tex == nil {
+			return false
+		}
+		
+		// TODO maybe inversed?
+		if .FLIP_Y in tex.flags {
+			m1: [6]f32
+			m2: [6]f32
+			nvg.TransformTranslate(&m1, 0.0, frag.extent[1] * 0.5)
+			nvg.TransformMultiply(&m1, paint.xform)
+			nvg.TransformScale(&m2, 1.0, -1.0)
+			nvg.TransformMultiply(&m2, m1)
+			nvg.TransformTranslate(&m1, 0.0, -frag.extent[1] * 0.5)
+			nvg.TransformMultiply(&m1, m2)
+			nvg.TransformInverse(&invxform, m1)
+		} else {
+			nvg.TransformInverse(&invxform, paint.xform)
+		}
+
+		frag.type = .FILL_IMG
+
+		when GL_USE_UNIFORMBUFFER {
+			if tex.type == .RGBA {
+				frag.texType = (.PREMULTIPLIED in tex.flags) ? 0 : 1
+			}	else {
+				frag.texType = 2
+			}
+		} else {
+			if tex.type == .RGBA {
+				frag.texType = (.PREMULTIPLIED in tex.flags) ? 0.0 : 1.0
+			}	else {
+				frag.texType = 2.0
+			}
+		}
+	} else {
+		frag.type = .FILL_GRAD
+		frag.radius = paint.radius
+		frag.feather = paint.feather
+		nvg.TransformInverse(&invxform, paint.xform)
+	}
+
+	__xformToMat3x4(&frag.paintMat, invxform)
+
+	return true
+}
+
+__setUniforms :: proc(ctx: ^Context, uniformOffset: int, image: int) {
+	when GL_USE_UNIFORMBUFFER {
+		gl.BindBufferRange(gl.UNIFORM_BUFFER, ctx.frag_binding, ctx.fragBuf, uniformOffset, size_of(FragUniforms))
+	} else {
+		frag := __fragUniformPtr(ctx, uniformOffset)
+		gl.Uniform4fv(ctx.shader.loc[.FRAG], GL_UNIFORMARRAY_SIZE, cast(^f32) frag)
+	}
+
+	__checkError(ctx, "uniform4")
+
+	tex: ^Texture
+	if image != 0 {
+		tex = __findTexture(ctx, image)
+	}
+	
+	// If no image is set, use empty texture
+	if tex == nil {
+		tex = __findTexture(ctx, ctx.dummyTex)
+	}
+
+	__bindTexture(ctx, tex != nil ? tex.tex : 0)
+	__checkError(ctx, "tex paint tex")
+}
+
+__renderViewport :: proc(uptr: rawptr, width, height, devicePixelRatio: f32) {
+	ctx := cast(^Context) uptr
+	ctx.view[0] = width
+	ctx.view[1] = height
+}
+
+__fill :: proc(ctx: ^Context, call: ^Call) {
+	paths := ctx.paths[call.pathOffset:]
+
+	// Draw shapes
+	gl.Enable(gl.STENCIL_TEST)
+	__stencilMask(ctx, 0xff)
+	__stencilFunc(ctx, gl.ALWAYS, 0, 0xff)
+	gl.ColorMask(gl.FALSE, gl.FALSE, gl.FALSE, gl.FALSE)
+
+	// set bindpoint for solid loc
+	__setUniforms(ctx, call.uniformOffset, 0)
+	__checkError(ctx, "fill simple")
+
+	gl.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP)
+	gl.StencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP)
+	gl.Disable(gl.CULL_FACE)
+	for i in 0..<call.pathCount {
+		gl.DrawArrays(gl.TRIANGLE_FAN, i32(paths[i].fillOffset), i32(paths[i].fillCount))
+	}
+	gl.Enable(gl.CULL_FACE)
+
+	// Draw anti-aliased pixels
+	gl.ColorMask(gl.TRUE, gl.TRUE, gl.TRUE, gl.TRUE)
+
+	__setUniforms(ctx, call.uniformOffset + ctx.fragSize, call.image)
+	__checkError(ctx, "fill fill")
+
+	if .ANTI_ALIAS in ctx.flags {
+		__stencilFunc(ctx, gl.EQUAL, 0x00, 0xff)
+		gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
+		// Draw fringes
+		for i in 0..<call.pathCount {
+			gl.DrawArrays(gl.TRIANGLE_STRIP, i32(paths[i].strokeOffset), i32(paths[i].strokeCount))
+		}
+	}
+
+	// Draw fill
+	__stencilFunc(ctx, gl.NOTEQUAL, 0x0, 0xff)
+	gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
+	gl.DrawArrays(gl.TRIANGLE_STRIP, i32(call.triangleOffset), i32(call.triangleCount))
+
+	gl.Disable(gl.STENCIL_TEST)
+}
+
+__convexFill :: proc(ctx: ^Context, call: ^Call) {
+	paths := ctx.paths[call.pathOffset:]
+
+	__setUniforms(ctx, call.uniformOffset, call.image)
+	__checkError(ctx, "convex fill")
+
+	for i in 0..<call.pathCount {
+		gl.DrawArrays(gl.TRIANGLE_FAN, i32(paths[i].fillOffset), i32(paths[i].fillCount))
+	
+		// draw fringes
+		if paths[i].strokeCount > 0 {
+			gl.DrawArrays(gl.TRIANGLE_STRIP, i32(paths[i].strokeOffset), i32(paths[i].strokeCount))
+		}
+	}
+}
+
+__stroke :: proc(ctx: ^Context, call: ^Call) {
+	paths := ctx.paths[call.pathOffset:]
+
+	if .STENCIL_STROKES in ctx.flags {
+		gl.Enable(gl.STENCIL_TEST)
+		__stencilMask(ctx, 0xff)
+
+		// Fill the stroke base without overlap
+		__stencilFunc(ctx, gl.EQUAL, 0x0, 0xff)
+		gl.StencilOp(gl.KEEP, gl.KEEP, gl.INCR)
+		__setUniforms(ctx, call.uniformOffset + ctx.fragSize, call.image)
+		__checkError(ctx, "stroke fill 0")
+		
+		for i in 0..<call.pathCount {
+			gl.DrawArrays(gl.TRIANGLE_STRIP, i32(paths[i].strokeOffset), i32(paths[i].strokeCount))
+		}
+
+		// Draw anti-aliased pixels.
+		__setUniforms(ctx, call.uniformOffset, call.image)
+		__stencilFunc(ctx, gl.EQUAL, 0x00, 0xff)
+		gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
+		for i in 0..<call.pathCount {
+			gl.DrawArrays(gl.TRIANGLE_STRIP, i32(paths[i].strokeOffset), i32(paths[i].strokeCount))
+		}
+
+		// Clear stencil buffer.
+		gl.ColorMask(gl.FALSE, gl.FALSE, gl.FALSE, gl.FALSE)
+		__stencilFunc(ctx, gl.ALWAYS, 0x0, 0xff)
+		gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
+		__checkError(ctx, "stroke fill 1")
+		for i in 0..<call.pathCount {
+			gl.DrawArrays(gl.TRIANGLE_STRIP, i32(paths[i].strokeOffset), i32(paths[i].strokeCount))
+		}
+		gl.ColorMask(gl.TRUE, gl.TRUE, gl.TRUE, gl.TRUE)
+
+		gl.Disable(gl.STENCIL_TEST)
+	} else {
+		__setUniforms(ctx, call.uniformOffset, call.image)
+		__checkError(ctx, "stroke fill")
+		
+		// Draw Strokes
+		for i in 0..<call.pathCount {
+			gl.DrawArrays(gl.TRIANGLE_STRIP, i32(paths[i].strokeOffset), i32(paths[i].strokeCount))
+		}
+	}
+}
+
+__triangles :: proc(ctx: ^Context, call: ^Call) {
+	__setUniforms(ctx, call.uniformOffset, call.image)
+	__checkError(ctx, "triangles fill")
+	gl.DrawArrays(gl.TRIANGLES, i32(call.triangleOffset), i32(call.triangleCount))
+}
+
+__renderCancel :: proc(uptr: rawptr) {
+	ctx := cast(^Context) uptr
+	clear(&ctx.verts)
+	clear(&ctx.paths)
+	clear(&ctx.calls)
+	clear(&ctx.uniforms)
+}
+
+BLEND_FACTOR_TABLE :: [nvg.BlendFactor]u32 {
+	.ZERO = gl.ZERO,
+	.ONE = gl.ONE,
+	.SRC_COLOR = gl.SRC_COLOR,
+	.ONE_MINUS_SRC_COLOR = gl.ONE_MINUS_SRC_COLOR,
+	.DST_COLOR = gl.DST_COLOR,
+	.ONE_MINUS_DST_COLOR = gl.ONE_MINUS_DST_COLOR,
+	.SRC_ALPHA = gl.SRC_ALPHA,
+	.ONE_MINUS_SRC_ALPHA = gl.ONE_MINUS_SRC_ALPHA,
+	.DST_ALPHA = gl.DST_ALPHA,
+	.ONE_MINUS_DST_ALPHA = gl.ONE_MINUS_DST_ALPHA,
+	.SRC_ALPHA_SATURATE = gl.SRC_ALPHA_SATURATE,
+}
+
+__blendCompositeOperation :: proc(op: nvg.CompositeOperationState) -> Blend {
+	table := BLEND_FACTOR_TABLE
+	blend := Blend {
+		table[op.srcRGB],
+		table[op.dstRGB],
+		table[op.srcAlpha],
+		table[op.dstAlpha],
+	}
+	return blend
+}
+
+__renderFlush :: proc(uptr: rawptr) {
+	ctx := cast(^Context) uptr
+
+	if len(ctx.calls) > 0 {
+		// Setup require GL state.
+		gl.UseProgram(ctx.shader.prog)
+
+		gl.Enable(gl.CULL_FACE)
+		gl.CullFace(gl.BACK)
+		gl.FrontFace(gl.CCW)
+		gl.Enable(gl.BLEND)
+		gl.Disable(gl.DEPTH_TEST)
+		gl.Disable(gl.SCISSOR_TEST)
+		gl.ColorMask(gl.TRUE, gl.TRUE, gl.TRUE, gl.TRUE)
+		gl.StencilMask(0xffffffff)
+		gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
+		gl.StencilFunc(gl.ALWAYS, 0, 0xffffffff)
+		gl.ActiveTexture(gl.TEXTURE0)
+		gl.BindTexture(gl.TEXTURE_2D, 0)
+		
+		when USE_STATE_FILTER {
+			ctx.boundTexture = 0
+			ctx.stencilMask = 0xffffffff
+			ctx.stencilFunc = gl.ALWAYS
+			ctx.stencilFuncRef = 0
+			ctx.stencilFuncMask = 0xffffffff
+			ctx.blendFunc.src_RGB = gl.INVALID_ENUM
+			ctx.blendFunc.src_alpha = gl.INVALID_ENUM
+			ctx.blendFunc.dst_RGB = gl.INVALID_ENUM
+			ctx.blendFunc.dst_alpha = gl.INVALID_ENUM
+		}
+
+		when GL_USE_UNIFORMBUFFER {
+			// Upload ubo for frag shaders
+			gl.BindBuffer(gl.UNIFORM_BUFFER, ctx.fragBuf)
+			gl.BufferData(gl.UNIFORM_BUFFER, len(ctx.uniforms), raw_data(ctx.uniforms), gl.STREAM_DRAW)
+		}
+
+		// Upload vertex data
+		when GL3 {
+			gl.BindVertexArray(ctx.vertArr)
+		}
+
+		gl.BindBuffer(gl.ARRAY_BUFFER, ctx.vertBuf)
+		gl.BufferData(gl.ARRAY_BUFFER, len(ctx.verts) * size_of(Vertex), raw_data(ctx.verts), gl.STREAM_DRAW)
+		gl.EnableVertexAttribArray(0)
+		gl.EnableVertexAttribArray(1)
+		gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, size_of(Vertex), 0)
+		gl.VertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, size_of(Vertex), 2 * size_of(f32))
+
+		// Set view and texture just once per frame.
+		gl.Uniform1i(ctx.shader.loc[.TEX], 0)
+		gl.Uniform2fv(ctx.shader.loc[.VIEW_SIZE], 1, &ctx.view[0])
+
+		when GL_USE_UNIFORMBUFFER {
+			gl.BindBuffer(gl.UNIFORM_BUFFER, ctx.fragBuf)
+		}
+
+		for i in 0..<len(ctx.calls) {
+			call := &ctx.calls[i]
+			__blendFuncSeparate(ctx, &call.blendFunc)
+
+			switch call.type {
+			case .NONE: {}
+			case .FILL: __fill(ctx, call)
+			case .CONVEX_FILL: __convexFill(ctx, call)
+			case .STROKE: __stroke(ctx, call)
+			case .TRIANGLES: __triangles(ctx, call)
+			}
+		}
+
+		gl.DisableVertexAttribArray(0)
+		gl.DisableVertexAttribArray(1)
+
+		when GL3 {
+			gl.BindVertexArray(0)
+		}
+
+		gl.Disable(gl.CULL_FACE)
+		gl.BindBuffer(gl.ARRAY_BUFFER, 0)
+		gl.UseProgram(0)
+		__bindTexture(ctx, 0)
+	}
+
+	// Reset calls
+	clear(&ctx.verts)
+	clear(&ctx.paths)
+	clear(&ctx.calls)
+	clear(&ctx.uniforms)
+}
+
+__maxVertCount :: proc(paths: []nvg.Path) -> (count: int) {
+	for i in 0..<len(paths) {
+		count += len(paths[i].fill)
+		count += len(paths[i].stroke)
+	}
+	return
+}
+
+__allocCall :: #force_inline proc(ctx: ^Context) -> ^Call {
+	append(&ctx.calls, Call {})
+	return &ctx.calls[len(ctx.calls) - 1]
+}
+
+// alloc paths and return the original start position
+__allocPaths :: proc(ctx: ^Context, count: int) -> int {
+	old := len(ctx.paths)
+	resize(&ctx.paths, len(ctx.paths) + count)
+	return old
+}
+
+// alloc verts and return the original start position
+__allocVerts :: proc(ctx: ^Context, count: int) -> int {
+	old := len(ctx.verts)
+	resize(&ctx.verts, len(ctx.verts) + count)
+	return old
+}
+
+// alloc uniforms and return the original start position
+__allocFragUniforms :: proc(ctx: ^Context, count: int) -> int {
+	ret := len(ctx.uniforms)
+	resize(&ctx.uniforms, len(ctx.uniforms) + count * ctx.fragSize)
+	return ret
+}
+
+// get frag uniforms from byte slice offset
+__fragUniformPtr :: proc(ctx: ^Context, offset: int) -> ^FragUniforms {
+	return cast(^FragUniforms) &ctx.uniforms[offset]
+}
+
+///////////////////////////////////////////////////////////
+// CALLBACKS
+///////////////////////////////////////////////////////////
+
+__renderFill :: proc(
+	uptr: rawptr, 
+	paint: ^nvg.Paint, 
+	compositeOperation: nvg.CompositeOperationState, 
+	scissor: ^ScissorT,
+	fringe: f32,
+	bounds: [4]f32,
+	paths: []nvg.Path,
+) {
+	ctx := cast(^Context) uptr
+	call := __allocCall(ctx)
+
+	call.type = .FILL
+	call.triangleCount = 4
+	call.pathOffset = __allocPaths(ctx, len(paths))
+	call.pathCount = len(paths)
+	call.image = paint.image
+	call.blendFunc = __blendCompositeOperation(compositeOperation)
+
+	if len(paths) == 1 && paths[0].convex {
+		call.type = .CONVEX_FILL
+		call.triangleCount = 0
+	}
+
+	// allocate vertices for all the paths
+	maxverts := __maxVertCount(paths) + call.triangleCount
+	offset := __allocVerts(ctx, maxverts)
+
+	for i in 0..<len(paths) {
+		copy := &ctx.paths[call.pathOffset + i]
+		copy^ = {}
+		path := &paths[i]
+
+		if len(path.fill) > 0 {
+			copy.fillOffset = offset
+			copy.fillCount = len(path.fill)
+			mem.copy(&ctx.verts[offset], &path.fill[0], size_of(Vertex) * len(path.fill))
+			offset += len(path.fill)
+		}
+
+		if len(path.stroke) > 0 {
+			copy.strokeOffset = offset
+			copy.strokeCount = len(path.stroke)
+			mem.copy(&ctx.verts[offset], &path.stroke[0], size_of(Vertex) * len(path.stroke))
+			offset += len(path.stroke)
+		}
+	}
+
+	// setup uniforms for draw calls
+	if call.type == .FILL {
+		// quad
+		call.triangleOffset = offset
+		quad := ctx.verts[call.triangleOffset:call.triangleOffset+4]
+		quad[0] = { bounds[2], bounds[3], 0.5, 1 }
+		quad[1] = { bounds[2], bounds[1], 0.5, 1 }
+		quad[2] = { bounds[0], bounds[3], 0.5, 1 }
+		quad[3] = { bounds[0], bounds[1], 0.5, 1 }
+
+		// simple shader for stencil
+		call.uniformOffset = __allocFragUniforms(ctx, 2)
+		frag := __fragUniformPtr(ctx, call.uniformOffset)
+		frag^ = {}
+		frag.strokeThr = -1
+		frag.type = .SIMPLE
+
+		// fill shader
+		__convertPaint(
+			ctx, 
+			__fragUniformPtr(ctx, call.uniformOffset + ctx.fragSize),
+			paint, 
+			scissor,
+			fringe,
+			fringe,
+			-1,
+		)
+	} else {
+		call.uniformOffset = __allocFragUniforms(ctx, 1)
+		// fill shader
+		__convertPaint(
+			ctx,
+			__fragUniformPtr(ctx, call.uniformOffset),
+			paint, 
+			scissor,
+			fringe,
+			fringe,
+			-1,
+		)
+	}
+} 
+
+__renderStroke :: proc(
+	uptr: rawptr, 
+	paint: ^Paint, 
+	compositeOperation: nvg.CompositeOperationState, 
+	scissor: ^ScissorT,
+	fringe: f32,
+	strokeWidth: f32,
+	paths: []nvg.Path,
+) {
+	ctx := cast(^Context) uptr
+	call := __allocCall(ctx)
+
+	call.type = .STROKE
+	call.pathOffset = __allocPaths(ctx, len(paths))
+	call.pathCount = len(paths)
+	call.image = paint.image
+	call.blendFunc = __blendCompositeOperation(compositeOperation)
+
+	// allocate vertices for all the paths
+	maxverts := __maxVertCount(paths)
+	offset := __allocVerts(ctx, maxverts)
+
+	for i in 0..<len(paths) {
+		copy := &ctx.paths[call.pathOffset + i]
+		copy^ = {}
+		path := &paths[i]
+
+		if len(path.stroke) != 0 {
+			copy.strokeOffset = offset
+			copy.strokeCount = len(path.stroke)
+			mem.copy(&ctx.verts[offset], &path.stroke[0], size_of(Vertex) * len(path.stroke))
+			offset += len(path.stroke)
+		}
+	}
+
+	if .STENCIL_STROKES in ctx.flags {
+		// fill shader 
+		call.uniformOffset = __allocFragUniforms(ctx, 2)
+
+		__convertPaint(
+			ctx,
+			__fragUniformPtr(ctx, call.uniformOffset),
+			paint,
+			scissor,
+			strokeWidth,
+			fringe,
+			-1,
+		)
+
+		__convertPaint(
+			ctx,
+			__fragUniformPtr(ctx, call.uniformOffset + ctx.fragSize),
+			paint,
+			scissor,
+			strokeWidth,
+			fringe,
+			1 - 0.5 / 255,
+		)
+	} else {
+		// fill shader
+		call.uniformOffset = __allocFragUniforms(ctx, 1)
+		__convertPaint(
+			ctx,
+			__fragUniformPtr(ctx, call.uniformOffset),
+			paint,
+			scissor,
+			strokeWidth,
+			fringe,
+			-1,
+		)
+	}
+}
+
+__renderTriangles :: proc(
+	uptr: rawptr, 
+	paint: ^Paint, 
+	compositeOperation: nvg.CompositeOperationState, 
+	scissor: ^ScissorT,
+	verts: []Vertex,
+	fringe: f32,
+) {
+	ctx := cast(^Context) uptr
+	call := __allocCall(ctx)
+
+	call.type = .TRIANGLES
+	call.image = paint.image
+	call.blendFunc = __blendCompositeOperation(compositeOperation)
+
+	// allocate the vertices for all the paths
+	call.triangleOffset = __allocVerts(ctx, len(verts))
+	call.triangleCount = len(verts)
+	mem.copy(&ctx.verts[call.triangleOffset], raw_data(verts), size_of(Vertex) * len(verts))
+
+	// fill shader
+	call.uniformOffset = __allocFragUniforms(ctx, 1)
+	frag := __fragUniformPtr(ctx, call.uniformOffset)
+	__convertPaint(ctx, frag, paint, scissor, 1, fringe, -1)
+	frag.type = .IMG	
+}
+
+__renderDelete :: proc(uptr: rawptr) {
+	ctx := cast(^Context) uptr
+	__deleteShader(&ctx.shader)
+
+	when GL3 {
+		when GL_USE_UNIFORMBUFFER {
+			if ctx.fragBuf != 0 {
+				gl.DeleteBuffers(1, &ctx.fragBuf)
+			}
+		}
+
+		if ctx.vertArr != 0 {
+			gl.DeleteVertexArrays(1, &ctx.vertArr)
+		}
+	}
+
+	if ctx.vertBuf != 0 {
+		gl.DeleteBuffers(1, &ctx.vertBuf)
+	}
+
+	for texture in &ctx.textures {
+		if texture.tex != 0 && (.NO_DELETE not_in texture.flags) {
+			gl.DeleteTextures(1, &texture.tex)
+		}
+	}
+
+	delete(ctx.textures)
+	delete(ctx.paths)
+	delete(ctx.verts)
+	delete(ctx.uniforms)
+	delete(ctx.calls)
+	free(ctx)
+}
+
+///////////////////////////////////////////////////////////
+// CREATION?
+///////////////////////////////////////////////////////////
+
+Create :: proc(flags: CreateFlags) -> ^nvg.Context {
+	ctx := new(Context)
+	params: nvg.Params
+	params.renderCreate = __renderCreate
+	params.renderCreateTexture = __renderCreateTexture
+	params.renderDeleteTexture = __renderDeleteTexture
+	params.renderUpdateTexture = __renderUpdateTexture
+	params.renderGetTextureSize = __renderGetTextureSize
+	params.renderViewport = __renderViewport
+	params.renderCancel = __renderCancel
+	params.renderFlush = __renderFlush
+	params.renderFill = __renderFill
+	params.renderStroke = __renderStroke
+	params.renderTriangles = __renderTriangles
+	params.renderDelete = __renderDelete
+	params.userPtr = ctx
+	params.edgeAntiAlias = (.ANTI_ALIAS in flags)
+	ctx.flags = flags
+	return nvg.CreateInternal(params)
+}
+
+Destroy :: proc(ctx: ^nvg.Context) {
+	nvg.DeleteInternal(ctx)
+}
+
+CreateImageFromHandle :: proc(ctx: ^nvg.Context, textureId: u32, w, h: int, imageFlags: ImageFlags) -> int {
+	gctx := cast(^Context) ctx.params.userPtr
+	tex := __allocTexture(gctx)
+	tex.type = .RGBA
+	tex.tex = textureId
+	tex.flags = imageFlags
+	tex.width = w
+	tex.height = h
+	return tex.id
+}
+
+ImageHandle :: proc(ctx: ^nvg.Context, textureId: int) -> u32 {
+	gctx := cast(^Context) ctx.params.userPtr
+	tex := __findTexture(gctx, textureId)
+	return tex.tex
+}
+
+// framebuffer additional
+
+framebuffer :: struct {
+	ctx: ^nvg.Context,
+	fbo: u32,
+	rbo: u32,
+	texture: u32,
+	image: int,
+}
+
+DEFAULT_FBO :: 100_000
+defaultFBO := i32(DEFAULT_FBO)
+
+// helper function to create GL frame buffer to render to
+BindFramebuffer :: proc(fb: ^framebuffer) {
+	if defaultFBO == DEFAULT_FBO {
+		gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &defaultFBO)
+	}
+	gl.BindFramebuffer(gl.FRAMEBUFFER, fb != nil ? fb.fbo : u32(defaultFBO))
+}
+
+CreateFramebuffer :: proc(ctx: ^nvg.Context, w, h: int, imageFlags: ImageFlags) -> (fb: framebuffer) {
+	tempFBO: i32
+	tempRBO: i32
+	gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &tempFBO)
+	gl.GetIntegerv(gl.RENDERBUFFER_BINDING, &tempRBO)
+
+	imageFlags := imageFlags
+	incl(&imageFlags, ImageFlags { .FLIP_Y, .PREMULTIPLIED })
+	fb.image = nvg.CreateImageRGBA(ctx, w, h, imageFlags, nil)
+	fb.texture = ImageHandle(ctx, fb.image)
+	fb.ctx = ctx
+
+	// frame buffer object
+	gl.GenFramebuffers(1, &fb.fbo)
+	gl.BindFramebuffer(gl.FRAMEBUFFER, fb.fbo)
+
+	// render buffer object
+	gl.GenRenderbuffers(1, &fb.rbo)
+	gl.BindRenderbuffer(gl.RENDERBUFFER, fb.rbo)
+	gl.RenderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, i32(w), i32(h))
+
+	// combine all
+	gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.texture, 0)
+	gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, fb.rbo)
+
+	if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
+// #ifdef gl.DEPTH24_STENCIL8
+		// If gl.STENCIL_INDEX8 is not supported, try gl.DEPTH24_STENCIL8 as a fallback.
+		// Some graphics cards require a depth buffer along with a stencil.
+		gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, i32(w), i32(h))
+		gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.texture, 0)
+		gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, fb.rbo)
+
+		if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
+			fmt.eprintln("ERROR")
+		}
+// #endif // gl.DEPTH24_STENCIL8
+// 			goto error
+	}
+
+	gl.BindFramebuffer(gl.FRAMEBUFFER, u32(tempFBO))
+	gl.BindRenderbuffer(gl.RENDERBUFFER, u32(tempRBO))
+	return 
+}
+
+DeleteFramebuffer :: proc(fb: ^framebuffer) {
+	if fb == nil {
+		return
+	}
+
+	if fb.fbo != 0 {
+		gl.DeleteFramebuffers(1, &fb.fbo)
+	}
+	
+	if fb.rbo != 0 {
+		gl.DeleteRenderbuffers(1, &fb.rbo)
+	}
+	
+	if fb.image >= 0 {
+		nvg.DeleteImage(fb.ctx, fb.image)
+	}
+
+	fb.ctx = nil
+	fb.fbo = 0
+	fb.rbo = 0
+	fb.texture = 0
+	fb.image = -1
+}

+ 19 - 0
vendor/nanovg/gl/vert.glsl

@@ -0,0 +1,19 @@
+#ifdef NANOVG_GL3
+	uniform vec2 viewSize;
+	in vec2 vertex;
+	in vec2 tcoord;
+	out vec2 ftcoord;
+	out vec2 fpos;
+#else
+	uniform vec2 viewSize;
+	attribute vec2 vertex;
+	attribute vec2 tcoord;
+	varying vec2 ftcoord;
+	varying vec2 fpos;
+#endif
+
+void main(void) {
+	ftcoord = tcoord;
+	fpos = vertex;
+	gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);
+}

+ 3481 - 0
vendor/nanovg/nanovg.odin

@@ -0,0 +1,3481 @@
+//+build windows, linux, darwin
+package nanovg
+
+// TODO rename structs to old nanovg style!
+// TODO rename enums to old nanovg style!
+
+import "core:mem"
+import "core:math"
+import "core:fmt"
+import "../fontstash"
+import stbi "vendor:stb/image"
+
+AlignVertical :: fontstash.AlignVertical
+AlignHorizontal :: fontstash.AlignHorizontal
+
+INIT_FONTIMAGE_SIZE :: 512
+MAX_FONTIMAGE_SIZE :: 2048
+MAX_FONTIMAGES :: 4
+
+MAX_STATES :: 32
+INIT_COMMANDS_SIZE :: 256
+INIT_POINTS_SIZE :: 128
+INIT_PATH_SIZE :: 16
+INIT_VERTS_SIZE :: 26
+KAPPA :: 0.5522847493
+
+Color :: [4]f32
+Matrix :: [6]f32
+Vertex :: [4]f32 // x,y,u,v
+
+ImageFlag :: enum {
+	GENERATE_MIPMAPS,
+	REPEAT_X,
+	REPEAT_Y,
+	FLIP_Y,
+	PREMULTIPLIED,
+	NEAREST,
+	NO_DELETE,
+}
+ImageFlags :: bit_set[ImageFlag]
+
+Paint :: struct {
+	xform: Matrix,
+	extent: [2]f32,
+	radius: f32,
+	feather: f32,
+	innerColor: Color,
+	outerColor: Color,
+	image: int,
+}
+
+Winding :: enum {
+	CCW = 1,
+	CW,
+}
+
+Solidity :: enum {
+	SOLID = 1, // CCW
+	HOLE, // CW
+}
+
+LineCapType :: enum {
+	BUTT,
+	ROUND,
+	SQUARE,
+	BEVEL,
+	MITER,
+}
+
+BlendFactor :: enum {
+	ZERO,
+	ONE,
+	SRC_COLOR,
+	ONE_MINUS_SRC_COLOR,
+	DST_COLOR,
+	ONE_MINUS_DST_COLOR,
+	SRC_ALPHA,
+	ONE_MINUS_SRC_ALPHA,
+	DST_ALPHA,
+	ONE_MINUS_DST_ALPHA,
+	SRC_ALPHA_SATURATE,
+}
+
+CompositeOperation :: enum {
+	SOURCE_OVER,
+	SOURCE_IN,
+	SOURCE_OUT,
+	ATOP,
+	DESTINATION_OVER,
+	DESTINATION_IN,
+	DESTINATION_OUT,
+	DESTINATION_ATOP,
+	LIGHTER,
+	COPY,
+	XOR,
+}
+
+CompositeOperationState :: struct {
+	srcRGB: BlendFactor,
+	dstRGB: BlendFactor,
+	srcAlpha: BlendFactor,
+	dstAlpha: BlendFactor,
+}
+
+// render data structures
+
+Texture :: enum {
+	Alpha,
+	RGBA,
+}
+
+ScissorT :: struct {
+	xform: Matrix,
+	extent: [2]f32,
+}
+
+Commands :: enum {
+	MOVE_TO,
+	LINE_TO,
+	BEZIER_TO,
+	CLOSE,
+	WINDING,
+}
+
+PointFlag :: enum {
+	CORNER,
+	LEFT,
+	BEVEL,
+	INNER_BEVEL,
+}
+PointFlags :: bit_set[PointFlag]
+
+Point :: struct {
+	x, y: f32,
+	dx, dy: f32,
+	len: f32,
+	dmx, dmy: f32,
+	flags: PointFlags,
+}
+
+PathCache :: struct {
+	points: [dynamic]Point,
+	paths: [dynamic]Path,
+	verts: [dynamic]Vertex,
+	bounds: [4]f32,
+}
+
+Path :: struct {
+	first: int,
+	count: int,
+	closed: bool,
+	nbevel: int,
+	fill: []Vertex,
+	stroke: []Vertex,
+	winding: Winding,
+	convex: bool,
+}
+
+State :: struct {
+	compositeOperation: CompositeOperationState,
+	shapeAntiAlias: bool,
+	fill: Paint,
+	stroke: Paint,
+	strokeWidth: f32,
+	miterLimit: f32,
+	lineJoin: LineCapType,
+	lineCap: LineCapType,
+	alpha: f32,
+	xform: Matrix,
+	scissor: ScissorT,
+
+	// font state
+	fontSize: f32,
+	letterSpacing: f32,
+	lineHeight: f32,
+	fontBlur: f32,
+	alignHorizontal: AlignHorizontal,
+	alignVertical: AlignVertical,
+	fontId: int,
+}
+
+Context :: struct {
+	params: Params,
+	commands: [dynamic]f32,
+	commandx, commandy: f32,
+	states: [MAX_STATES]State,
+	nstates: int,
+	cache: PathCache,
+	tessTol: f32,
+	distTol: f32,
+	fringeWidth: f32,
+	devicePxRatio: f32,
+
+	// font
+	fs: fontstash.FontContext,
+	fontImages: [MAX_FONTIMAGES]int,
+	fontImageIdx: int,
+
+	// stats
+	drawCallCount: int,
+	fillTriCount: int,
+	strokeTriCount: int,
+	textTriCount: int,
+
+	// flush texture
+	textureDirty: bool,
+}
+
+Params :: struct {
+	userPtr: rawptr,
+	edgeAntiAlias: bool,
+	
+	// callbacks to fill out
+	renderCreate: proc(uptr: rawptr) -> bool,
+	renderDelete: proc(uptr: rawptr),
+
+	// textures calls
+	renderCreateTexture: proc(
+		uptr: rawptr, 
+		type: Texture,
+		w, h: int, 
+		imageFlags: ImageFlags, 
+		data: []byte,
+	) -> int,
+	renderDeleteTexture: proc(uptr: rawptr, image: int) -> bool,
+	renderUpdateTexture: proc(
+		uptr: rawptr, 
+		image: int,
+		x, y: int,
+		w, h: int,
+		data: []byte,
+	) -> bool,
+	renderGetTextureSize: proc(uptr: rawptr, image: int, w, h: ^int) -> bool,
+
+	// rendering calls
+	renderViewport: proc(uptr: rawptr, width, height, devicePixelRatio: f32),
+	renderCancel: proc(uptr: rawptr),
+	renderFlush: proc(uptr: rawptr),
+	renderFill: proc(
+		uptr: rawptr, 
+		paint: ^Paint, 
+		compositeOperation: CompositeOperationState, 
+		scissor: ^ScissorT,
+		fringe: f32,
+		bounds: [4]f32,
+		paths: []Path,
+	),
+	renderStroke: proc(
+		uptr: rawptr, 
+		paint: ^Paint, 
+		compositeOperation: CompositeOperationState, 
+		scissor: ^ScissorT,
+		fringe: f32,
+		strokeWidth: f32,
+		paths: []Path,
+	),	
+	renderTriangles: proc(
+		uptr: rawptr, 
+		paint: ^Paint, 
+		compositeOperation: CompositeOperationState, 
+		scissor: ^ScissorT,
+		verts: []Vertex,
+		fringe: f32,
+	),
+}
+
+__allocPathCache :: proc(c: ^PathCache) {
+	c.points = make([dynamic]Point, 0, INIT_POINTS_SIZE)
+	c.paths = make([dynamic]Path, 0, INIT_PATH_SIZE)
+	c.verts = make([dynamic]Vertex, 0, INIT_VERTS_SIZE)
+}
+
+__deletePathCache :: proc(c: PathCache) {
+	delete(c.points)
+	delete(c.paths)
+	delete(c.verts)
+}
+
+__setDevicePxRatio :: proc(ctx: ^Context, ratio: f32) {
+	ctx.tessTol = 0.25 / ratio
+	ctx.distTol = 0.01 / ratio
+	ctx.fringeWidth = 1.0 / ratio
+	ctx.devicePxRatio = ratio
+}
+
+__getState :: #force_inline proc(ctx: ^Context) -> ^State #no_bounds_check {
+	return &ctx.states[ctx.nstates - 1]
+}
+
+CreateInternal :: proc(params: Params) -> (ctx: ^Context) {
+	ctx = new(Context)
+	ctx.params = params
+	ctx.commands = make([dynamic]f32, 0, INIT_COMMANDS_SIZE)
+	__allocPathCache(&ctx.cache)
+
+	Save(ctx)
+	Reset(ctx)
+	__setDevicePxRatio(ctx, 1)
+
+	assert(ctx.params.renderCreate != nil)
+	if !ctx.params.renderCreate(ctx.params.userPtr) {
+		DeleteInternal(ctx)
+		panic("Nanovg - CreateInternal failed")
+	}
+
+	w := INIT_FONTIMAGE_SIZE
+	h := INIT_FONTIMAGE_SIZE
+	fontstash.Init(&ctx.fs, w, h, .TOPLEFT)
+	assert(ctx.params.renderCreateTexture != nil)
+	ctx.fs.userData = ctx
+	
+	// handle to the image needs to be set to the new generated texture
+	ctx.fs.callbackResize = proc(data: rawptr, w, h: int) {
+		ctx := cast(^Context) data
+		ctx.fontImages[0] = ctx.params.renderCreateTexture(ctx.params.userPtr, .Alpha, w, h, {}, ctx.fs.textureData)
+	}
+	
+	// texture atlas
+	ctx.fontImages[0] = ctx.params.renderCreateTexture(ctx.params.userPtr, .Alpha, w, h, {}, nil)
+	ctx.fontImageIdx = 0
+
+	return
+}
+
+DeleteInternal :: proc(ctx: ^Context) {
+	__deletePathCache(ctx.cache)
+	fontstash.Destroy(&ctx.fs)
+
+	for image in &ctx.fontImages {
+		if image != 0 {
+			DeleteImage(ctx, image)
+		}
+	}
+
+	if ctx.params.renderDelete != nil {
+		ctx.params.renderDelete(ctx.params.userPtr)
+	}
+
+	free(ctx)
+}
+
+/*
+	Begin drawing a new frame
+	Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame()
+	nvgBeginFrame() defines the size of the window to render to in relation currently
+	set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
+	control the rendering on Hi-DPI devices.
+	For example, GLFW returns two dimension for an opened window: window size and
+	frame buffer size. In that case you would set windowWidth/Height to the window size
+	devicePixelRatio to: frameBufferWidth / windowWidth.
+*/
+BeginFrame :: proc(
+	ctx: ^Context,
+	windowWidth: f32,
+	windowHeight: f32,
+	devicePixelRatio: f32,
+) {
+	ctx.nstates = 0
+	Save(ctx)
+	Reset(ctx)
+	__setDevicePxRatio(ctx, devicePixelRatio)
+
+	assert(ctx.params.renderViewport != nil)
+	ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight, devicePixelRatio)
+
+	ctx.drawCallCount = 0
+	ctx.fillTriCount = 0
+	ctx.strokeTriCount = 0
+	ctx.textTriCount = 0
+}
+
+@(deferred_out=EndFrame)
+FrameScoped :: proc(
+	ctx: ^Context,
+	windowWidth: f32,
+	windowHeight: f32,
+	devicePixelRatio: f32,
+) -> ^Context {
+	BeginFrame(ctx, windowWidth, windowHeight, devicePixelRatio)
+	return ctx
+}
+
+// Cancels drawing the current frame.
+CancelFrame :: proc(ctx: ^Context) {
+	assert(ctx.params.renderCancel != nil)
+	ctx.params.renderCancel(ctx.params.userPtr)	
+}
+
+// Ends drawing flushing remaining render state.
+EndFrame :: proc(ctx: ^Context) {
+	// flush texture only once
+	if ctx.textureDirty {
+		__flushTextTexture(ctx)
+		ctx.textureDirty = false
+	}
+
+	assert(ctx.params.renderFlush != nil)
+	ctx.params.renderFlush(ctx.params.userPtr)
+
+	// delete textures with invalid size
+	if ctx.fontImageIdx != 0 {
+		font_image := ctx.fontImages[ctx.fontImageIdx]
+		ctx.fontImages[ctx.fontImageIdx] = 0
+
+		if font_image == 0 {
+			return
+		}
+
+		iw, ih := ImageSize(ctx, font_image)
+		j: int
+		for i in 0..<ctx.fontImageIdx {
+			if ctx.fontImages[i] != 0 {
+				image := ctx.fontImages[i]
+				ctx.fontImages[i] = 0
+				nw, nh := ImageSize(ctx, image)
+
+				if nw < iw || nh < ih {
+					DeleteImage(ctx, image)
+				} else {
+					ctx.fontImages[j] = image
+					j += 1
+				}
+			}
+		}
+
+		// make current font image to first
+		ctx.fontImages[j] = ctx.fontImages[0]
+		ctx.fontImages[0] = font_image
+		ctx.fontImageIdx = 0
+	}
+}
+
+///////////////////////////////////////////////////////////
+// COLORS
+//
+// Colors in NanoVG are stored as unsigned ints in ABGR format.
+///////////////////////////////////////////////////////////
+
+// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
+RGB :: proc(r, g, b: u8) -> Color {
+	return RGBA(r, g, b, 255)
+}
+
+// Returns a color value from red, green, blue and alpha values.
+RGBA :: proc(r, g, b, a: u8) -> (res: Color) {
+	res.r = f32(r) / f32(255)
+	res.g = f32(g) / f32(255)
+	res.b = f32(b) / f32(255)
+	res.a = f32(a) / f32(255)
+	return
+}
+
+// Linearly interpolates from color c0 to c1, and returns resulting color value.
+LerpRGBA :: proc(c0, c1: Color, u: f32) -> (cint: Color) {
+	clamped := clamp(u, 0.0, 1.0)
+	oneminu := 1.0 - clamped
+	for i in 0..<4 {
+		cint[i] = c0[i] * oneminu + c1[i] * clamped
+	}
+
+	return
+}
+
+// Returns color value specified by hue, saturation and lightness.
+// HSL values are all in range [0..1], alpha will be set to 255.
+HSL :: proc(h, s, l: f32) -> Color {
+	return HSLA(h,s,l,255)
+}
+
+// Returns color value specified by hue, saturation and lightness and alpha.
+// HSL values are all in range [0..1], alpha in range [0..255]
+HSLA :: proc(hue, saturation, lightness: f32, a: u8) -> (col: Color) {
+	hue_get :: proc(h, m1, m2: f32) -> f32 {
+		h := h
+
+		if h < 0 {
+			h += 1
+		}
+		
+		if h > 1 {
+			h -= 1
+		} 
+		
+		if h < 1.0 / 6.0 {
+			return m1 + (m2 - m1) * h * 6.0
+		} else if h < 3.0 / 6.0 {
+			return m2
+		} else if h < 4.0 / 6.0 {
+			return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0
+		}
+
+		return m1
+	}
+
+	h := math.mod(hue, 1.0)
+	if h < 0.0 {
+		h += 1.0
+	} 
+	s := clamp(saturation, 0.0, 1.0)
+	l := clamp(lightness, 0.0, 1.0)
+	m2 := l <= 0.5 ? (l * (1 + s)) : (l + s - l * s)
+	m1 := 2 * l - m2
+	col.r = clamp(hue_get(h + 1.0/3.0, m1, m2), 0.0, 1.0)
+	col.g = clamp(hue_get(h, m1, m2), 0.0, 1.0)
+	col.b = clamp(hue_get(h - 1.0/3.0, m1, m2), 0.0, 1.0)
+	col.a = f32(a) / 255.0
+	return
+}
+
+// hex to 0xAARRGGBB color
+ColorHex :: proc(color: u32) -> (res: Color) {
+	color := color
+	res.b = f32(0x000000FF & color) / 255
+	color = color >> 8
+	res.g = f32(0x000000FF & color) / 255
+	color = color >> 8
+	res.r = f32(0x000000FF & color) / 255
+	color = color >> 8
+	res.a = f32(0x000000FF & color) / 255
+	return
+}
+
+///////////////////////////////////////////////////////////
+// TRANSFORMS
+//
+// The following functions can be used to make calculations on 2x3 transformation matrices.
+// A 2x3 matrix is represented as float[6].
+///////////////////////////////////////////////////////////
+
+// Sets the transform to identity matrix.
+TransformIdentity :: proc(t: ^Matrix) {
+	t[0] = 1
+	t[1] = 0
+	t[2] = 0
+	t[3] = 1
+	t[4] = 0
+	t[5] = 0
+}
+
+// Sets the transform to translation matrix matrix.
+TransformTranslate :: proc(t: ^Matrix, tx, ty: f32) {
+	t[0] = 1
+	t[1] = 0
+	t[2] = 0
+	t[3] = 1
+	t[4] = tx
+	t[5] = ty
+}
+
+// Sets the transform to scale matrix.
+TransformScale :: proc(t: ^Matrix, sx, sy: f32) {
+	t[0] = sx
+	t[1] = 0
+	t[2] = 0
+	t[3] = sy
+	t[4] = 0
+	t[5] = 0
+}
+
+// Sets the transform to rotate matrix. Angle is specified in radians.
+TransformRotate :: proc(t: ^Matrix, a: f32) {
+	cs := math.cos(a)
+	sn := math.sin(a)
+	t[0] = cs
+	t[1] = sn
+	t[2] = -sn
+	t[3] = cs
+	t[4] = 0
+	t[5] = 0
+}
+
+// Sets the transform to skew-x matrix. Angle is specified in radians.
+TransformSkewX :: proc(t: ^Matrix, a: f32) {
+	t[0] = 1
+	t[1] = 0
+	t[2] = math.tan(a)
+	t[3] = 1
+	t[4] = 0
+	t[5] = 0
+}
+
+// Sets the transform to skew-y matrix. Angle is specified in radians.
+TransformSkewY :: proc(t: ^Matrix, a: f32) {
+	t[0] = 1
+	t[1] = math.tan(a)
+	t[2] = 0
+	t[3] = 1
+	t[4] = 0
+	t[5] = 0
+}
+
+// Sets the transform to the result of multiplication of two transforms, of A = A*B.
+TransformMultiply :: proc(t: ^Matrix, s: Matrix) {
+	t0 := t[0] * s[0] + t[1] * s[2]
+	t2 := t[2] * s[0] + t[3] * s[2]
+	t4 := t[4] * s[0] + t[5] * s[2] + s[4]
+	t[1] = t[0] * s[1] + t[1] * s[3]
+	t[3] = t[2] * s[1] + t[3] * s[3]
+	t[5] = t[4] * s[1] + t[5] * s[3] + s[5]
+	t[0] = t0
+	t[2] = t2
+	t[4] = t4
+}
+
+// Sets the transform to the result of multiplication of two transforms, of A = B*A.
+TransformPremultiply :: proc(t: ^Matrix, s: Matrix) {
+	temp := s
+	TransformMultiply(&temp, t^)
+	t^ = temp
+}
+
+// Sets the destination to inverse of specified transform.
+// Returns true if the inverse could be calculated, else false.
+TransformInverse :: proc(inv: ^Matrix, t: Matrix) -> bool {
+	// TODO could be bad math? due to types
+	det := f64(t[0]) * f64(t[3]) - f64(t[2]) * f64(t[1])
+	
+	if det > -1e-6 && det < 1e-6 {
+		TransformIdentity(inv)
+		return false
+	}
+	
+	invdet := 1.0 / det
+	inv[0] = f32(f64(t[3]) * invdet)
+	inv[2] = f32(f64(-t[2]) * invdet)
+	inv[4] = f32((f64(t[2]) * f64(t[5]) - f64(t[3]) * f64(t[4])) * invdet)
+	inv[1] = f32(f64(-t[1]) * invdet)
+	inv[3] = f32(f64(t[0]) * invdet)
+	inv[5] = f32((f64(t[1]) * f64(t[4]) - f64(t[0]) * f64(t[5])) * invdet)
+	return true
+}
+
+// Transform a point by given transform.
+TransformPoint :: proc(
+	dx: ^f32, 
+	dy: ^f32, 
+	t: Matrix, 
+	sx: f32, 
+	sy: f32,
+) {
+	dx^ = sx * t[0] + sy * t[2] + t[4]
+	dy^ = sx * t[1] + sy * t[3] + t[5]
+}
+
+DegToRad :: proc(deg: f32) -> f32 {
+	return deg / 180.0 * math.PI
+}
+
+RadToDeg :: proc(rad: f32) -> f32 {
+	return rad / math.PI * 180.0
+}
+
+///////////////////////////////////////////////////////////
+// STATE MANAGEMENT
+//
+// NanoVG contains state which represents how paths will be rendered.
+// The state contains transform, fill and stroke styles, text and font styles,
+// and scissor clipping.
+///////////////////////////////////////////////////////////
+
+// Pushes and saves the current render state into a state stack.
+// A matching nvgRestore() must be used to restore the state.
+Save :: proc(ctx: ^Context) {
+	if ctx.nstates >= MAX_STATES {
+		return
+	}
+
+	// copy prior
+	if ctx.nstates > 0 {
+		ctx.states[ctx.nstates] = ctx.states[ctx.nstates - 1]
+	}
+
+	ctx.nstates += 1
+}
+
+// Pops and restores current render state.
+Restore :: proc(ctx: ^Context) {
+	if ctx.nstates <= 1 {
+		return
+	}
+
+	ctx.nstates -= 1
+}
+
+// NOTE useful helper
+@(deferred_in=Restore)
+SaveScoped :: #force_inline proc(ctx: ^Context) {
+	Save(ctx)
+}
+
+__setPaintColor :: proc(p: ^Paint, color: Color) {
+	p^ = {}
+	TransformIdentity(&p.xform)
+	p.radius = 0
+	p.feather = 1
+	p.innerColor = color
+	p.outerColor = color
+}
+
+// Resets current render state to default values. Does not affect the render state stack.
+Reset :: proc(ctx: ^Context) {
+	state := __getState(ctx)
+	state^ = {}
+
+	__setPaintColor(&state.fill, RGBA(255, 255, 255, 255))
+	__setPaintColor(&state.stroke, RGBA(0, 0, 0, 255))
+
+	state.compositeOperation = __compositeOperationState(.SOURCE_OVER)
+	state.shapeAntiAlias = true
+	state.strokeWidth = 1
+	state.miterLimit = 10
+	state.lineCap = .BUTT
+	state.lineJoin = .MITER
+	state.alpha = 1
+	TransformIdentity(&state.xform)
+
+	state.scissor.extent[0] = -1
+	state.scissor.extent[1] = -1
+
+	// font settings
+	state.fontSize = 16
+	state.letterSpacing = 0
+	state.lineHeight = 1
+	state.fontBlur = 0
+	state.alignHorizontal = .LEFT
+	state.alignVertical = .BASELINE
+	state.fontId = 0
+}
+
+///////////////////////////////////////////////////////////
+// STATE SETTING
+///////////////////////////////////////////////////////////
+
+// Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default.
+ShapeAntiAlias :: proc(ctx: ^Context, enabled: bool) {
+	state := __getState(ctx)
+	state.shapeAntiAlias = enabled
+}
+
+// Sets the stroke width of the stroke style.
+StrokeWidth :: proc(ctx: ^Context, width: f32) {
+	state := __getState(ctx)
+	state.strokeWidth = width		
+}
+
+// Sets the miter limit of the stroke style.
+// Miter limit controls when a sharp corner is beveled.
+MiterLimit :: proc(ctx: ^Context, limit: f32) {
+	state := __getState(ctx)
+	state.miterLimit = limit
+}
+
+// Sets how the end of the line (cap) is drawn,
+// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE.
+LineCap :: proc(ctx: ^Context, cap: LineCapType) {
+	state := __getState(ctx)
+	state.lineCap = cap
+}
+
+// Sets how sharp path corners are drawn.
+// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL.
+LineJoin :: proc(ctx: ^Context, join: LineCapType) {
+	state := __getState(ctx)
+	state.lineJoin = join
+}
+
+// Sets the transparency applied to all rendered shapes.
+// Already transparent paths will get proportionally more transparent as well.
+GlobalAlpha :: proc(ctx: ^Context, alpha: f32) {
+	state := __getState(ctx)
+	state.alpha = alpha
+}
+
+// Sets current stroke style to a solid color.
+StrokeColor :: proc(ctx: ^Context, color: Color) {
+	state := __getState(ctx)
+	__setPaintColor(&state.stroke, color)	
+}
+
+// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
+StrokePaint :: proc(ctx: ^Context, paint: Paint) {
+	state := __getState(ctx)
+	state.stroke = paint
+	TransformMultiply(&state.stroke.xform, state.xform)
+}
+
+// Sets current fill style to a solid color.
+FillColor :: proc(ctx: ^Context, color: Color) {
+	state := __getState(ctx)
+	__setPaintColor(&state.fill, color)	
+}
+
+// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
+FillPaint :: proc(ctx: ^Context, paint: Paint) {
+	state := __getState(ctx)
+	state.fill = paint
+	TransformMultiply(&state.fill.xform, state.xform)
+}
+
+///////////////////////////////////////////////////////////
+// STATE TRANSFORMS
+//
+// The paths, gradients, patterns and scissor region are transformed by an transformation
+// matrix at the time when they are passed to the API.
+// The current transformation matrix is a affine matrix:
+//   [sx kx tx]
+//   [ky sy ty]
+//   [ 0  0  1]
+// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation.
+// The last row is assumed to be 0,0,1 and is not stored.
+//
+// Apart from nvgResetTransform(), each transformation function first creates
+// specific transformation matrix and pre-multiplies the current transformation by it.
+//
+// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore().
+///////////////////////////////////////////////////////////
+
+Transform :: proc(ctx: ^Context, a, b, c, d, e, f: f32) {
+	state := __getState(ctx)
+	TransformPremultiply(&state.xform, { a, b, c, d, e, f })	
+}
+
+// Resets current transform to a identity matrix.
+ResetTransform :: proc(ctx: ^Context) {
+	state := __getState(ctx)
+	TransformIdentity(&state.xform)
+}
+
+// Translates current coordinate system.
+Translate :: proc(ctx: ^Context, x, y: f32) {
+	state := __getState(ctx)
+	temp: Matrix
+	TransformTranslate(&temp, x, y)
+	TransformPremultiply(&state.xform, temp)
+}
+
+// Rotates current coordinate system. Angle is specified in radians.
+Rotate :: proc(ctx: ^Context, angle: f32) {
+	state := __getState(ctx)
+	temp: Matrix
+	TransformRotate(&temp, angle)
+	TransformPremultiply(&state.xform, temp)
+}
+
+// Skews the current coordinate system along X axis. Angle is specified in radians.
+SkewX :: proc(ctx: ^Context, angle: f32) {
+	state := __getState(ctx)
+	temp: Matrix
+	TransformSkewX(&temp, angle)
+	TransformPremultiply(&state.xform, temp)
+}
+
+// Skews the current coordinate system along Y axis. Angle is specified in radians.
+SkewY :: proc(ctx: ^Context, angle: f32) {
+	state := __getState(ctx)
+	temp: Matrix
+	TransformSkewY(&temp, angle)
+	TransformPremultiply(&state.xform, temp)
+}
+
+// Scales the current coordinate system.
+Scale :: proc(ctx: ^Context, x, y: f32) {
+	state := __getState(ctx)
+	temp: Matrix
+	TransformScale(&temp, x, y)
+	TransformPremultiply(&state.xform, temp)
+}
+
+/*
+	Stores the top part (a-f) of the current transformation matrix in to the specified buffer.
+	  [a c e]
+	  [b d f]
+	  [0 0 1]
+	There should be space for 6 floats in the return buffer for the values a-f.
+*/
+CurrentTransform :: proc(ctx: ^Context, xform: ^Matrix) {
+	state := __getState(ctx)
+	if xform == nil {
+		return
+	}
+	xform^ = state.xform
+}
+
+///////////////////////////////////////////////////////////
+// IMAGE HANDLING
+//
+// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering.
+// In addition you can upload your own image. The image loading is provided by stb_image.
+// The parameter imageFlags is a combination of flags defined in NVGimageFlags.
+///////////////////////////////////////////////////////////
+
+// Creates image by loading it from the disk from specified file name.
+// Returns handle to the image.
+CreateImagePath :: proc(ctx: ^Context, filename: cstring, imageFlags: ImageFlags) -> int {
+	stbi.set_unpremultiply_on_load(1)
+	stbi.convert_iphone_png_to_rgb(1)
+	w, h, n: i32
+	img := stbi.load(filename, &w, &h, &n, 4)
+	
+	if img == nil {
+		return 0
+	}
+
+	data := mem.slice_ptr(img, int(w) * int(h) * int(n))
+	image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, data)
+	stbi.image_free(img)
+	return image
+}
+
+// Creates image by loading it from the specified chunk of memory.
+// Returns handle to the image.
+CreateImageMem :: proc(ctx: ^Context, data: []byte, imageFlags: ImageFlags) -> int {
+	stbi.set_unpremultiply_on_load(1)
+	stbi.convert_iphone_png_to_rgb(1)
+	w, h, n: i32
+	img := stbi.load_from_memory(raw_data(data), i32(len(data)), &w, &h, &n, 4)
+	
+	if img == nil {
+		return 0
+	}
+
+	pixel_data := mem.slice_ptr(img, int(w) * int(h) * int(n))
+	image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, pixel_data)
+	stbi.image_free(img)
+	return image
+}
+
+CreateImage :: proc { CreateImagePath, CreateImageMem }
+
+// Creates image from specified image data.
+// Returns handle to the image.
+CreateImageRGBA :: proc(ctx: ^Context, w, h: int, imageFlags: ImageFlags, data: []byte) -> int {
+	assert(ctx.params.renderCreateTexture != nil)
+	return ctx.params.renderCreateTexture(
+		ctx.params.userPtr,
+		.RGBA,
+		w, h,
+		imageFlags,
+		data,
+	)
+}
+
+// Updates image data specified by image handle.
+UpdateImage :: proc(ctx: ^Context, image: int, data: []byte) {
+	assert(ctx.params.renderGetTextureSize != nil)
+	assert(ctx.params.renderUpdateTexture != nil)
+	
+	w, h: int
+	found := ctx.params.renderGetTextureSize(ctx.params.userPtr, image, &w, &h)
+	if found {
+		ctx.params.renderUpdateTexture(ctx.params.userPtr, image, 0, 0, w, h, data)
+	}
+}
+
+// Returns the dimensions of a created image.
+ImageSize :: proc(ctx: ^Context, image: int) -> (w, h: int) {
+	assert(ctx.params.renderGetTextureSize != nil)
+	ctx.params.renderGetTextureSize(ctx.params.userPtr, image, &w, &h)
+	return
+}
+
+// Deletes created image.
+DeleteImage :: proc(ctx: ^Context, image: int) {
+	assert(ctx.params.renderDeleteTexture != nil)
+	ctx.params.renderDeleteTexture(ctx.params.userPtr, image)
+}
+
+///////////////////////////////////////////////////////////
+// PAINT gradients / image
+//
+// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
+// These can be used as paints for strokes and fills.
+///////////////////////////////////////////////////////////
+
+/*
+	Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates
+	of the linear gradient, icol specifies the start color and ocol the end color.
+	The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+*/
+LinearGradient :: proc(
+	sx, sy: f32,
+	ex, ey: f32,
+	icol: Color,
+	ocol: Color,
+) -> (p: Paint) {
+	LARGE :: f32(1e5)
+
+	// Calculate transform aligned to the line
+	dx := ex - sx
+	dy := ey - sy
+	d := math.sqrt(dx*dx + dy*dy)
+	if d > 0.0001 {
+		dx /= d
+		dy /= d
+	} else {
+		dx = 0
+		dy = 1
+	}
+
+	p.xform[0] = dy
+	p.xform[1] = -dx
+	p.xform[2] = dx
+	p.xform[3] = dy
+	p.xform[4] = sx - dx*LARGE
+	p.xform[5] = sy - dy*LARGE
+
+	p.extent[0] = LARGE
+	p.extent[1] = LARGE + d*0.5
+
+	p.feather = max(1.0, d)
+
+	p.innerColor = icol
+	p.outerColor = ocol
+
+	return
+}
+
+/*
+	Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
+	drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,
+	(w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
+	the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
+	The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+*/
+RadialGradient :: proc(
+	cx, cy: f32,
+	inr: f32,
+	outr: f32,
+	icol: Color,
+	ocol: Color,
+) -> (p: Paint) {
+	r := (inr+outr)*0.5
+	f := (outr-inr)
+
+	TransformIdentity(&p.xform)
+	p.xform[4] = cx
+	p.xform[5] = cy
+
+	p.extent[0] = r
+	p.extent[1] = r
+
+	p.radius = r
+	p.feather = max(1.0, f)
+
+	p.innerColor = icol
+	p.outerColor = ocol
+
+	return 
+}
+
+/*
+	Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify
+	the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
+	The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+*/
+BoxGradient :: proc(
+	x, y: f32,
+	w, h: f32,
+	r: f32,
+	f: f32,
+	icol: Color,
+	ocol: Color,
+) -> (p: Paint) {
+	TransformIdentity(&p.xform)
+	p.xform[4] = x+w*0.5
+	p.xform[5] = y+h*0.5
+
+	p.extent[0] = w*0.5
+	p.extent[1] = h*0.5
+
+	p.radius = r
+	p.feather = max(1.0, f)
+
+	p.innerColor = icol
+	p.outerColor = ocol
+
+	return 
+}
+
+/*
+	Creates and returns an image pattern. Parameters (ox,oy) specify the left-top location of the image pattern,
+	(ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render.
+	The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+*/
+ImagePattern :: proc(
+	cx, cy: f32,
+	w, h: f32,
+	angle: f32,
+	image: int,
+	alpha: f32,
+) -> (p: Paint) {
+	TransformRotate(&p.xform, angle)
+	p.xform[4] = cx
+	p.xform[5] = cy
+
+	p.extent[0] = w
+	p.extent[1] = h
+
+	p.image = image
+	p.innerColor = { 1,1,1,alpha }
+	p.outerColor = p.innerColor
+
+	return
+}
+
+///////////////////////////////////////////////////////////
+// SCISSOR
+//
+// Scissoring allows you to clip the rendering into a rectangle. This is useful for various
+// user interface cases like rendering a text edit or a timeline.
+///////////////////////////////////////////////////////////
+
+// Sets the current scissor rectangle.
+// The scissor rectangle is transformed by the current transform.
+Scissor :: proc(
+	ctx: ^Context,
+	x, y: f32,
+	width, height: f32,
+) {
+	state := __getState(ctx)
+	w := max(width, 0)
+	h := max(height, 0)
+	
+	TransformIdentity(&state.scissor.xform)
+	state.scissor.xform[4] = x + w * 0.5
+	state.scissor.xform[5] = y + h * 0.5
+	TransformMultiply(&state.scissor.xform, state.xform)
+
+	state.scissor.extent[0] = w * 0.5
+	state.scissor.extent[1] = h * 0.5
+}
+
+/*
+	Intersects current scissor rectangle with the specified rectangle.
+	The scissor rectangle is transformed by the current transform.
+	Note: in case the rotation of previous scissor rect differs from
+	the current one, the intersection will be done between the specified
+	rectangle and the previous scissor rectangle transformed in the current
+	transform space. The resulting shape is always rectangle.
+*/
+IntersectScissor :: proc(
+	ctx: ^Context,
+	x, y, w, h: f32,
+) {
+	isect_rects :: proc(
+		dst: ^[4]f32,
+		ax, ay, aw, ah: f32,
+		bx, by, bw, bh: f32,
+	) {
+		minx := max(ax, bx)
+		miny := max(ay, by)
+		maxx := min(ax + aw, bx + bw)
+		maxy := min(ay + ah, by + bh)
+		dst[0] = minx
+		dst[1] = miny
+		dst[2] = max(0.0, maxx - minx)
+		dst[3] = max(0.0, maxy - miny)
+	}
+
+	state := __getState(ctx)
+	pxform: Matrix
+	invxorm: Matrix
+
+	// If no previous scissor has been set, set the scissor as current scissor.
+	if state.scissor.extent[0] < 0 {
+		Scissor(ctx, x, y, w, h)
+		return
+	}
+
+	pxform = state.scissor.xform
+	ex := state.scissor.extent[0]
+	ey := state.scissor.extent[1]
+	TransformInverse(&invxorm, state.xform)
+	TransformMultiply(&pxform, invxorm)
+	tex := ex * abs(pxform[0]) + ey * abs(pxform[2])
+	tey := ex * abs(pxform[1]) + ey * abs(pxform[3])
+	
+	rect: [4]f32
+	isect_rects(&rect, pxform[4] - tex, pxform[5] - tey, tex * 2, tey * 2, x,y,w,h)
+	Scissor(ctx, rect.x, rect.y, rect.z, rect.w)
+}
+
+// Reset and disables scissoring.
+ResetScissor :: proc(ctx: ^Context) {
+	state := __getState(ctx)
+	state.scissor.xform = 0
+	state.scissor.extent[0] = -1
+	state.scissor.extent[1] = -1
+}
+
+///////////////////////////////////////////////////////////
+// Global composite operation
+//
+// The composite operations in NanoVG are modeled after HTML Canvas API, and
+// the blend func is based on OpenGL (see corresponding manuals for more info).
+// The colors in the blending state have premultiplied alpha.
+///////////////////////////////////////////////////////////
+
+// state table instead of if else chains
+OP_STATE_TABLE :: [CompositeOperation][2]BlendFactor {
+	.SOURCE_OVER = { .ONE, .ONE_MINUS_SRC_ALPHA	},
+	.SOURCE_IN = { .DST_ALPHA, .ZERO },
+	.SOURCE_OUT = { .ONE_MINUS_DST_ALPHA, .ZERO },
+	.ATOP = { .DST_ALPHA, .ONE_MINUS_SRC_ALPHA },
+
+	.DESTINATION_OVER = { .ONE_MINUS_DST_ALPHA, .ONE },
+	.DESTINATION_IN = { .ZERO, .SRC_ALPHA },
+	.DESTINATION_OUT = { .ZERO, .ONE_MINUS_SRC_ALPHA },
+	.DESTINATION_ATOP = { .ONE_MINUS_DST_ALPHA, .SRC_ALPHA },
+
+	.LIGHTER = { .ONE, .ONE },
+	.COPY = { .ONE, .ZERO },
+	.XOR = { .ONE_MINUS_DST_ALPHA, .ONE_MINUS_SRC_ALPHA },
+}
+
+__compositeOperationState :: proc(op: CompositeOperation) -> (res: CompositeOperationState) {
+	table := OP_STATE_TABLE
+	factors := table[op]
+	res.srcRGB = factors.x
+	res.dstRGB = factors.y
+	res.srcAlpha = factors.x
+	res.dstAlpha = factors.y
+	return
+}
+
+// Sets the composite operation. The op parameter should be one of NVGcompositeOperation.
+GlobalCompositeOperation :: proc(ctx: ^Context, op: CompositeOperation) {
+	state := __getState(ctx)
+	state.compositeOperation = __compositeOperationState(op)
+}
+
+// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor.
+GlobalCompositeBlendFunc :: proc(ctx: ^Context, sfactor, dfactor: BlendFactor) {
+	GlobalCompositeBlendFuncSeparate(ctx, sfactor, dfactor, sfactor, dfactor)
+}
+
+// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor.
+GlobalCompositeBlendFuncSeparate :: proc(
+	ctx: ^Context,
+	srcRGB: BlendFactor,
+	dstRGB: BlendFactor,
+	srcAlpha: BlendFactor,
+	dstAlpha: BlendFactor,
+) {
+	op := CompositeOperationState {
+		srcRGB,
+		dstRGB,
+		srcAlpha,
+		dstAlpha,
+	}
+	state := __getState(ctx)
+	state.compositeOperation = op
+}
+
+///////////////////////////////////////////////////////////
+// Points / Path handling
+///////////////////////////////////////////////////////////
+
+__cross :: proc(dx0, dy0, dx1, dy1: f32) -> f32 {
+	return dx1*dy0 - dx0*dy1
+}
+
+__ptEquals :: proc(x1, y1, x2, y2, tol: f32) -> bool {
+	dx := x2 - x1
+	dy := y2 - y1
+	return dx * dx + dy * dy < tol * tol
+}
+
+__distPtSeg :: proc(x, y, px, py, qx, qy: f32) -> f32 {
+	pqx := qx - px
+	pqy := qy - py
+	dx := x - px
+	dy := y - py
+	d := pqx * pqx + pqy * pqy
+	t := pqx * dx + pqy * dy
+	
+	if d > 0 {
+		t /= d
+	}
+	
+	if t < 0 {
+		t = 0
+	} else if t > 1 {
+		t = 1
+	} 
+
+	dx = px + t * pqx - x
+	dy = py + t * pqy - y
+	return dx * dx + dy * dy
+}
+
+__appendCommands :: proc(ctx: ^Context, values: []f32) {
+	state := __getState(ctx)
+
+	if Commands(values[0]) != .CLOSE && Commands(values[0]) != .WINDING {
+		ctx.commandx = values[len(values) - 2]
+		ctx.commandy = values[len(values) - 1]
+	}
+
+	i := 0
+	for i < len(values) {
+		cmd := Commands(values[i])
+
+		switch cmd {
+		case .MOVE_TO, .LINE_TO:
+			TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2])
+			i += 3
+
+		case .BEZIER_TO:
+			TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2])
+			TransformPoint(&values[i + 3], &values[i + 4], state.xform, values[i + 3], values[i + 4])
+			TransformPoint(&values[i + 5], &values[i + 6], state.xform, values[i + 5], values[i + 6])
+			i += 7
+
+		case .CLOSE: i += 1
+		case .WINDING: i += 2
+
+		// default
+		case: i += 1
+		}
+	}
+
+	// append values
+	append(&ctx.commands, ..values)
+}
+
+__clearPathCache :: proc(ctx: ^Context) {
+	clear(&ctx.cache.points)
+	clear(&ctx.cache.paths)
+}
+
+__lastPath :: proc(ctx: ^Context) -> ^Path {
+	if len(ctx.cache.paths) > 0 {
+		return &ctx.cache.paths[len(ctx.cache.paths) - 1]
+	}
+
+	return nil
+}
+
+__addPath :: proc(ctx: ^Context) {
+	append(&ctx.cache.paths, Path {
+		first = len(ctx.cache.points),
+		winding = .CCW,
+	})
+}
+
+__lastPoint :: proc(ctx: ^Context) -> ^Point {
+	if len(ctx.cache.paths) > 0 {
+		return &ctx.cache.points[len(ctx.cache.points) - 1]
+	}
+
+	return nil
+}
+
+__addPoint :: proc(ctx: ^Context, x, y: f32, flags: PointFlags) {
+	path := __lastPath(ctx)
+
+	if path == nil {
+		return
+	}
+
+	if path.count > 0 && len(ctx.cache.points) > 0 {
+		pt := __lastPoint(ctx)
+
+		if __ptEquals(pt.x, pt.y, x, y, ctx.distTol) {
+			pt.flags |= flags
+			return
+		}
+	}
+
+	append(&ctx.cache.points, Point {
+		x = x,
+		y = y,
+		flags = flags,
+	})
+	path.count += 1
+}
+
+__closePath :: proc(ctx: ^Context) {
+	path := __lastPath(ctx)
+	if path == nil {
+		return
+	}
+	path.closed = true
+}
+
+__pathWinding :: proc(ctx: ^Context, winding: Winding) {
+	path := __lastPath(ctx)
+	if path == nil {
+		return
+	}
+	path.winding = winding
+}
+
+__getAverageScale :: proc(t: []f32) -> f32 {
+	assert(len(t) > 4)
+	sx := math.sqrt(f64(t[0]) * f64(t[0]) + f64(t[2]) * f64(t[2]))
+	sy := math.sqrt(f64(t[1]) * f64(t[1]) + f64(t[3]) * f64(t[3]))
+	return f32((sx + sy) * 0.5)
+	// sx := math.sqrt(t[0] * t[0] + t[2] * t[2])
+	// sy := math.sqrt(t[1] * t[1] + t[3] * t[3])
+	// return (sx + sy) * 0.5
+}
+
+__triarea2 :: proc(ax, ay, bx, by, cx, cy: f32) -> f32 {
+	abx := bx - ax
+	aby := by - ay
+	acx := cx - ax
+	acy := cy - ay
+	return acx * aby - abx * acy
+}
+
+__polyArea :: proc(points: []Point) -> f32 {
+	area := f32(0)
+	
+	for i := 2; i < len(points); i += 1 {
+		a := &points[0]
+		b := &points[i - 1]
+		c := &points[i]
+		area += __triarea2(a.x, a.y, b.x, b.y, c.x, c.y)
+	}
+	
+	return area * 0.5
+}
+
+__polyReverse :: proc(points: []Point) {
+	tmp: Point
+	i := 0 
+	j := len(points) - 1
+	
+	for i < j {
+		tmp = points[i]
+		points[i] = points[j]
+		points[j] = tmp
+		i += 1
+		j -= 1
+	}
+}
+
+__normalize :: proc(x, y: ^f32) -> f32 {
+	d := math.sqrt(x^ * x^ + y^ * y^)
+	if d > 1e-6 {
+		id := 1.0 / d
+		x^ *= id
+		y^ *= id
+	}
+	return d
+}
+
+__tesselateBezier :: proc(
+	ctx: ^Context,
+	x1, y1: f32,
+	x2, y2: f32,
+	x3, y3: f32,
+	x4, y4: f32,
+	level: int,
+	flags: PointFlags,
+) {
+	if level > 10 {
+		return
+	}
+
+	x12 := (x1 + x2) * 0.5
+	y12 := (y1 + y2) * 0.5
+	x23 := (x2 + x3) * 0.5
+	y23 := (y2 + y3) * 0.5
+	x34 := (x3 + x4) * 0.5
+	y34 := (y3 + y4) * 0.5
+	x123 := (x12 + x23) * 0.5
+	y123 := (y12 + y23) * 0.5
+
+	dx := x4 - x1
+	dy := y4 - y1
+	d2 := abs(((x2 - x4) * dy - (y2 - y4) * dx))
+	d3 := abs(((x3 - x4) * dy - (y3 - y4) * dx))
+
+	if (d2 + d3)*(d2 + d3) < ctx.tessTol * (dx*dx + dy*dy) {
+		__addPoint(ctx, x4, y4, flags)
+		return
+	}
+
+	x234 := (x23 + x34) * 0.5
+	y234 := (y23 + y34) * 0.5
+	x1234 := (x123 + x234) * 0.5
+	y1234 := (y123 + y234) * 0.5
+
+	__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, {})
+	__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, flags)
+}
+
+__flattenPaths :: proc(ctx: ^Context) {
+	cache := &ctx.cache
+
+	if len(cache.paths) > 0 {
+		return
+	}
+
+	// flatten
+	i := 0
+	for i < len(ctx.commands) {
+		cmd := Commands(ctx.commands[i])
+		
+		switch cmd {
+		case .MOVE_TO:
+			__addPath(ctx)
+			p := ctx.commands[i + 1:]
+			__addPoint(ctx, p[0], p[1], { .CORNER })
+			i += 3
+
+		case .LINE_TO:
+			p := ctx.commands[i + 1:]
+			__addPoint(ctx, p[0], p[1], { .CORNER })
+			i += 3
+
+		case .BEZIER_TO:
+			last := __lastPoint(ctx)
+		
+			if last != nil {
+				cp1 := ctx.commands[i + 1:]
+				cp2 := ctx.commands[i + 3:]
+				p := ctx.commands[i + 5:]
+				__tesselateBezier(ctx, last.x,last.y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, { .CORNER })
+			}
+
+			i += 7
+
+		case .CLOSE:
+			__closePath(ctx)
+			i += 1
+
+		case .WINDING:
+			__pathWinding(ctx, Winding(ctx.commands[i + 1]))
+			i += 2
+
+		case: i += 1
+		}
+	}
+
+	cache.bounds[0] = 1e6
+	cache.bounds[1] = 1e6
+	cache.bounds[2] = -1e6
+	cache.bounds[3] = -1e6
+
+	// Calculate the direction and length of line segments.
+	for j in 0..<len(cache.paths) {
+		path := &cache.paths[j]
+		pts := cache.points[path.first:]
+
+		// If the first and last points are the same, remove the last, mark as closed path.
+		p0 := &pts[path.count-1]
+		p1 := &pts[0]
+		if __ptEquals(p0.x,p0.y, p1.x,p1.y, ctx.distTol) {
+			path.count -= 1
+			p0 = &pts[path.count - 1]
+			path.closed = true
+		}
+
+		// enforce winding
+		if path.count > 2 {
+			area := __polyArea(pts[:path.count])
+			
+			if path.winding == .CCW && area < 0 {
+				__polyReverse(pts[:path.count])
+			}
+			
+			if path.winding == .CW && area > 0 {
+				__polyReverse(pts[:path.count])
+			}
+		}
+
+		for _ in 0..<path.count {
+			// Calculate segment direction and length
+			p0.dx = p1.x - p0.x
+			p0.dy = p1.y - p0.y
+			p0.len = __normalize(&p0.dx, &p0.dy)
+			
+			// Update bounds
+			cache.bounds[0] = min(cache.bounds[0], p0.x)
+			cache.bounds[1] = min(cache.bounds[1], p0.y)
+			cache.bounds[2] = max(cache.bounds[2], p0.x)
+			cache.bounds[3] = max(cache.bounds[3], p0.y)
+			
+			// Advance
+			p0 = p1
+			p1 = mem.ptr_offset(p1, 1)
+		}
+	}
+}
+
+__curveDivs :: proc(r, arc, tol: f32) -> f32 {
+	da := math.acos(r / (r + tol)) * 2
+	return max(2, math.ceil(arc / da))
+}
+
+__chooseBevel :: proc(
+	bevel: bool,
+	p0: ^Point,
+	p1: ^Point,
+	w: f32,
+	x0, y0, x1, y1: ^f32,
+) {
+	if bevel {
+		x0^ = p1.x + p0.dy * w
+		y0^ = p1.y - p0.dx * w
+		x1^ = p1.x + p1.dy * w
+		y1^ = p1.y - p1.dx * w
+	} else {
+		x0^ = p1.x + p1.dmx * w
+		y0^ = p1.y + p1.dmy * w
+		x1^ = p1.x + p1.dmx * w
+		y1^ = p1.y + p1.dmy * w
+	}
+}
+
+///////////////////////////////////////////////////////////
+// Vertice Setting
+///////////////////////////////////////////////////////////
+
+// set vertex & increase slice position (decreases length)
+__vset :: proc(dst: ^[]Vertex, x, y, u, v: f32, loc := #caller_location) {
+	dst[0] = { x, y, u, v }
+	dst^ = dst[1:]
+}
+
+__roundJoin :: proc(
+	dst: ^[]Vertex,
+	p0: ^Point,
+	p1: ^Point,
+	lw: f32,
+	rw: f32,
+	lu: f32,
+	ru: f32,
+	ncap: int,
+) {
+	dlx0 := p0.dy
+	dly0 := -p0.dx
+	dlx1 := p1.dy
+	dly1 := -p1.dx
+
+	if .LEFT in p1.flags {
+		lx0,ly0,lx1,ly1: f32
+		__chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, lw, &lx0,&ly0, &lx1,&ly1)
+		a0 := math.atan2(-dly0, -dlx0)
+		a1 := math.atan2(-dly1, -dlx1)
+		
+		if a1 > a0 {
+			a1 -= math.PI * 2
+		} 
+
+		__vset(dst, lx0, ly0, lu, 1)
+		__vset(dst, p1.x - dlx0 * rw, p1.y - dly0 * rw, ru, 1)
+
+		temp := int(math.ceil((a0 - a1) / math.PI * f32(ncap)))
+		n := clamp(temp, 2, ncap)
+
+		for i := 0; i < n; i += 1 {
+			u := f32(i) / f32(n - 1)
+			a := a0 + u * (a1 - a0)
+			rx := p1.x + math.cos(a) * rw
+			ry := p1.y + math.sin(a) * rw
+			__vset(dst, p1.x, p1.y, 0.5, 1)
+			__vset(dst, rx, ry, ru,1)
+		}
+
+		__vset(dst, lx1, ly1, lu,1)
+		__vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1)
+	} else {
+		rx0,ry0,rx1,ry1: f32
+		__chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1)
+		a0 := math.atan2(dly0, dlx0)
+		a1 := math.atan2(dly1, dlx1)
+		if a1 < a0 {
+			a1 += math.PI * 2
+		}
+
+		__vset(dst, p1.x + dlx0*rw, p1.y + dly0*rw, lu,1)
+		__vset(dst, rx0, ry0, ru,1)
+
+		temp := int(math.ceil((a1 - a0) / math.PI * f32(ncap)))
+		n := clamp(temp, 2, ncap)
+
+		for i := 0; i < n; i += 1 {
+			u := f32(i) / f32(n - 1)
+			a := a0 + u*(a1-a0)
+			lx := p1.x + math.cos(a) * lw
+			ly := p1.y + math.sin(a) * lw
+			__vset(dst, lx, ly, lu, 1)
+			__vset(dst, p1.x, p1.y, 0.5, 1)
+		}
+
+		__vset(dst, p1.x + dlx1*rw, p1.y + dly1*rw, lu,1)
+		__vset(dst, rx1, ry1, ru,1)
+	}
+}
+
+__bevelJoin :: proc(
+	dst: ^[]Vertex,
+	p0: ^Point,
+	p1: ^Point,
+	lw: f32,
+	rw: f32,
+	lu: f32,
+	ru: f32,
+) {
+	dlx0 := p0.dy
+	dly0 := -p0.dx
+	dlx1 := p1.dy
+	dly1 := -p1.dx
+
+	rx0, ry0, rx1, ry1: f32
+	lx0, ly0, lx1, ly1: f32
+
+	if .LEFT in p1.flags {
+		__chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, lw, &lx0,&ly0, &lx1,&ly1)
+
+		__vset(dst, lx0, ly0, lu,1)
+		__vset(dst, p1.x - dlx0*rw, p1.y - dly0*rw, ru,1)
+
+		if .BEVEL in p1.flags {
+			__vset(dst, lx0, ly0, lu,1)
+			__vset(dst, p1.x - dlx0*rw, p1.y - dly0*rw, ru,1)
+
+			__vset(dst, lx1, ly1, lu,1)
+			__vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1)
+		} else {
+			rx0 = p1.x - p1.dmx * rw
+			ry0 = p1.y - p1.dmy * rw
+
+			__vset(dst, p1.x, p1.y, 0.5,1)
+			__vset(dst, p1.x - dlx0*rw, p1.y - dly0*rw, ru,1)
+
+			__vset(dst, rx0, ry0, ru,1)
+			__vset(dst, rx0, ry0, ru,1)
+
+			__vset(dst, p1.x, p1.y, 0.5,1)
+			__vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1)
+		}
+
+		__vset(dst, lx1, ly1, lu,1)
+		__vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1)
+	} else {
+		__chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1)
+
+		__vset(dst, p1.x + dlx0*lw, p1.y + dly0*lw, lu,1)
+		__vset(dst, rx0, ry0, ru,1)
+
+		if .BEVEL in p1.flags {
+			__vset(dst, p1.x + dlx0*lw, p1.y + dly0*lw, lu,1)
+			__vset(dst, rx0, ry0, ru,1)
+
+			__vset(dst, p1.x + dlx1*lw, p1.y + dly1*lw, lu,1)
+			__vset(dst, rx1, ry1, ru,1)
+		} else {
+			lx0 = p1.x + p1.dmx * lw
+			ly0 = p1.y + p1.dmy * lw
+
+			__vset(dst, p1.x + dlx0*lw, p1.y + dly0*lw, lu,1)
+			__vset(dst, p1.x, p1.y, 0.5,1)
+
+			__vset(dst, lx0, ly0, lu,1)
+			__vset(dst, lx0, ly0, lu,1)
+
+			__vset(dst, p1.x + dlx1*lw, p1.y + dly1*lw, lu,1)
+			__vset(dst, p1.x, p1.y, 0.5,1)
+		}
+
+		__vset(dst, p1.x + dlx1*lw, p1.y + dly1*lw, lu,1)
+		__vset(dst, rx1, ry1, ru,1)
+	}
+}
+
+__buttCapStart :: proc(
+	dst: ^[]Vertex,
+	p: ^Point,
+	dx, dy: f32,
+	w: f32,
+	d: f32,
+	aa: f32,
+	u0: f32,
+	u1: f32,
+) {
+	px := p.x - dx * d
+	py := p.y - dy * d
+	dlx := dy
+	dly := -dx
+	__vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, u0,0)
+	__vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, u1,0)
+	__vset(dst, px + dlx*w, py + dly*w, u0,1)
+	__vset(dst, px - dlx*w, py - dly*w, u1,1)
+}
+
+__buttCapEnd :: proc(
+	dst: ^[]Vertex,
+	p: ^Point,
+	dx, dy: f32,
+	w: f32,
+	d: f32,
+	aa: f32,
+	u0: f32,
+	u1: f32,
+) {
+	px := p.x + dx * d
+	py := p.y + dy * d
+	dlx := dy
+	dly := -dx
+	__vset(dst, px + dlx*w, py + dly*w, u0,1)
+	__vset(dst, px - dlx*w, py - dly*w, u1,1)
+	__vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, u0,0)
+	__vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, u1,0)
+}
+
+__roundCapStart :: proc(
+	dst: ^[]Vertex,
+	p: ^Point,
+	dx, dy: f32,
+	w: f32,
+	ncap: int,
+	u0: f32,
+	u1: f32,
+) {
+	px := p.x
+	py := p.y
+	dlx := dy
+	dly := -dx
+
+	for i in 0..<ncap {
+		a := f32(i) / f32(ncap-1) * math.PI
+		ax := math.cos(a) * w
+		ay := math.sin(a) * w
+		__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, u0,1)
+		__vset(dst, px, py, 0.5,1)
+	}
+
+	__vset(dst, px + dlx*w, py + dly*w, u0,1)
+	__vset(dst, px - dlx*w, py - dly*w, u1,1)
+}
+
+__roundCapEnd :: proc(
+	dst: ^[]Vertex,
+	p: ^Point,
+	dx, dy: f32,
+	w: f32,
+	ncap: int,
+	u0: f32,
+	u1: f32,
+) {
+	px := p.x
+	py := p.y
+	dlx := dy
+	dly := -dx
+
+	__vset(dst, px + dlx*w, py + dly*w, u0,1)
+	__vset(dst, px - dlx*w, py - dly*w, u1,1)
+	for i in 0..<ncap {
+		a := f32(i) / f32(ncap - 1) * math.PI
+		ax := math.cos(a) * w
+		ay := math.sin(a) * w
+		__vset(dst, px, py, 0.5,1)
+		__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, u0,1)
+	}
+}
+
+__calculateJoins :: proc(
+	ctx: ^Context,
+	w: f32,
+	lineJoin: LineCapType,
+	miterLimit: f32,
+) {
+	cache := &ctx.cache
+	iw := f32(0)
+
+	if w > 0 {
+		iw = 1.0 / w
+	} 
+
+	// Calculate which joins needs extra vertices to append, and gather vertex count.
+	for path in &cache.paths {
+		pts := cache.points[path.first:]
+		p0 := &pts[path.count-1]
+		p1 := &pts[0]
+		nleft := 0
+		path.nbevel = 0
+
+		for _ in 0..<path.count {
+			dlx0, dly0, dlx1, dly1, dmr2, __cross, limit: f32
+			dlx0 = p0.dy
+			dly0 = -p0.dx
+			dlx1 = p1.dy
+			dly1 = -p1.dx
+			// Calculate extrusions
+			p1.dmx = (dlx0 + dlx1) * 0.5
+			p1.dmy = (dly0 + dly1) * 0.5
+			dmr2 = p1.dmx*p1.dmx + p1.dmy*p1.dmy
+			if (dmr2 > 0.000001) {
+				scale := 1.0 / dmr2
+				if (scale > 600.0) {
+					scale = 600.0
+				}
+				p1.dmx *= scale
+				p1.dmy *= scale
+			}
+
+			// Clear flags, but keep the corner.
+			p1.flags = (.CORNER in p1.flags) ? { .CORNER } : {}
+
+			// Keep track of left turns.
+			__cross = p1.dx * p0.dy - p0.dx * p1.dy
+			if __cross > 0.0 {
+				nleft += 1
+				incl(&p1.flags, PointFlag.LEFT)
+			}
+
+			// Calculate if we should use bevel or miter for inner join.
+			limit = max(1.01, min(p0.len, p1.len) * iw)
+			if (dmr2 * limit * limit) < 1.0 {
+				incl(&p1.flags, PointFlag.INNER_BEVEL)
+			}
+
+			// Check to see if the corner needs to be beveled.
+			if .CORNER in p1.flags {
+				if (dmr2 * miterLimit*miterLimit) < 1.0 || lineJoin == .BEVEL || lineJoin == .ROUND {
+					incl(&p1.flags, PointFlag.BEVEL)
+				}
+			}
+
+			if (.BEVEL in p1.flags) || (.INNER_BEVEL in p1.flags) {
+				path.nbevel += 1
+			}
+
+			p0 = p1
+			p1 = mem.ptr_offset(p1, 1)
+		}
+
+		path.convex = nleft == path.count
+	}
+}
+
+// TODO could be done better? or not need dynamic
+__allocTempVerts :: proc(ctx: ^Context, nverts: int) -> []Vertex {
+	resize(&ctx.cache.verts, nverts)
+	return ctx.cache.verts[:]
+}
+
+__expandStroke :: proc(
+	ctx: ^Context,
+	w: f32,
+	fringe: f32,
+	lineCap: LineCapType,
+	lineJoin: LineCapType,
+	miterLimit: f32,	
+) -> bool {
+	cache := &ctx.cache
+	aa := fringe
+	u0 := f32(0.0)
+	u1 := f32(1.0)
+	ncap := __curveDivs(w, math.PI, ctx.tessTol)	// Calculate divisions per half circle.
+
+	w := w
+	w += aa * 0.5
+
+	// Disable the gradient used for antialiasing when antialiasing is not used.
+	if aa == 0.0 {
+		u0 = 0.5
+		u1 = 0.5
+	}
+
+	__calculateJoins(ctx, w, lineJoin, miterLimit)
+
+	// Calculate max vertex usage.
+	cverts := 0
+	for path in &cache.paths {
+		loop := path.closed
+	
+		// TODO check if f32 calculation necessary?	
+		if lineJoin == .ROUND {
+			cverts += (path.count + path.nbevel * int(ncap + 2) + 1) * 2 // plus one for loop
+		} else {
+			cverts += (path.count + path.nbevel*5 + 1) * 2 // plus one for loop
+		}
+
+		if !loop {
+			// space for caps
+			if lineCap == .ROUND {
+				cverts += int(ncap*2 + 2)*2
+			} else {
+				cverts += (3 + 3)*2
+			}
+		}
+	}
+
+	verts := __allocTempVerts(ctx, cverts)
+	dst_index: int
+
+	for i in 0..<len(cache.paths) {
+		path := &cache.paths[i]
+		pts := cache.points[path.first:]
+		p0, p1: ^Point
+		start, end: int
+		dx, dy: f32
+
+		// nil the fil
+		path.fill = nil
+
+		// Calculate fringe or stroke
+		loop := path.closed
+		dst := verts[dst_index:]
+		dst_start_length := len(dst)
+
+		if loop {
+			// Looping
+			p0 = &pts[path.count-1]
+			p1 = &pts[0]
+			start = 0
+			end = path.count
+		} else {
+			// Add cap
+			p0 = &pts[0]
+			p1 = &pts[1]
+			start = 1
+			end = path.count - 1
+		}
+
+		if !loop {
+			// Add cap
+			dx = p1.x - p0.x
+			dy = p1.y - p0.y
+			__normalize(&dx, &dy)
+
+			if lineCap == .BUTT {
+				__buttCapStart(&dst, p0, dx, dy, w, -aa*0.5, aa, u0, u1)
+			}	else if lineCap == .BUTT || lineCap == .SQUARE {
+				__buttCapStart(&dst, p0, dx, dy, w, w-aa, aa, u0, u1)
+			}	else if lineCap == .ROUND {
+				__roundCapStart(&dst, p0, dx, dy, w, int(ncap), u0, u1)
+			}
+		}
+
+		for j in start..<end {
+			// TODO check this
+			// if ((p1.flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) {
+			if (.BEVEL in p1.flags) || (.INNER_BEVEL in p1.flags) {
+				if lineJoin == .ROUND {
+					__roundJoin(&dst, p0, p1, w, w, u0, u1, int(ncap))
+				} else {
+					__bevelJoin(&dst, p0, p1, w, w, u0, u1)
+				}
+			} else {
+				__vset(&dst, p1.x + (p1.dmx * w), p1.y + (p1.dmy * w), u0, 1)
+				__vset(&dst, p1.x - (p1.dmx * w), p1.y - (p1.dmy * w), u1, 1)
+			}
+
+			p0 = p1 
+			p1 = mem.ptr_offset(p1, 1)
+		}
+
+		if loop {
+			// NOTE use old vertices to loopback!
+			// Loop it
+			__vset(&dst, verts[dst_index + 0].x, verts[dst_index + 0].y, u0, 1)
+			__vset(&dst, verts[dst_index + 1].x, verts[dst_index + 1].y, u1, 1)
+		} else {
+			// Add cap
+			dx = p1.x - p0.x
+			dy = p1.y - p0.y
+			__normalize(&dx, &dy)
+
+			if lineCap == .BUTT {
+				__buttCapEnd(&dst, p1, dx, dy, w, -aa*0.5, aa, u0, u1)
+			}	else if lineCap == .BUTT || lineCap == .SQUARE {
+				__buttCapEnd(&dst, p1, dx, dy, w, w-aa, aa, u0, u1)
+			}	else if lineCap == .ROUND {
+				__roundCapEnd(&dst, p1, dx, dy, w, int(ncap), u0, u1)
+			}
+		}
+
+		// count of vertices pushed
+		dst_diff := dst_start_length - len(dst) 
+		// set stroke to the new region
+		path.stroke = verts[dst_index:dst_index + dst_diff]
+		// move index for next iteration
+		dst_index += dst_diff
+	}
+
+	return true
+}
+
+__expandFill :: proc(
+	ctx: ^Context,
+	w: f32,
+	lineJoin: LineCapType,
+	miterLimit: f32,
+) -> bool {
+	cache := &ctx.cache
+	aa := ctx.fringeWidth
+	fringe := w > 0.0
+	__calculateJoins(ctx, w, lineJoin, miterLimit)
+
+	// Calculate max vertex usage.
+	cverts := 0
+	for path in &cache.paths {
+		cverts += path.count + path.nbevel + 1
+
+		if fringe {
+			cverts += (path.count + path.nbevel*5 + 1) * 2 // plus one for loop
+		}
+	}
+
+	convex := len(cache.paths) == 1 && cache.paths[0].convex
+	verts := __allocTempVerts(ctx, cverts)
+	dst_index: int
+
+	for path in &cache.paths {
+		pts := cache.points[path.first:]
+		p0, p1: ^Point
+		rw, lw, woff: f32
+		ru, lu: f32
+
+		// Calculate shape vertices.
+		woff = 0.5*aa
+		dst := verts[dst_index:]
+		dst_start_length := len(dst)
+
+		if fringe {
+			// Looping
+			p0 = &pts[path.count-1]
+			p1 = &pts[0]
+
+			for j in 0..<path.count {
+				if .BEVEL in p1.flags {
+					dlx0 := p0.dy
+					dly0 := -p0.dx
+					dlx1 := p1.dy
+					dly1 := -p1.dx
+					
+					if .LEFT in p1.flags {
+						lx := p1.x + p1.dmx * woff
+						ly := p1.y + p1.dmy * woff
+						__vset(&dst, lx, ly, 0.5,1)
+					} else {
+						lx0 := p1.x + dlx0 * woff
+						ly0 := p1.y + dly0 * woff
+						lx1 := p1.x + dlx1 * woff
+						ly1 := p1.y + dly1 * woff
+						__vset(&dst, lx0, ly0, 0.5,1)
+						__vset(&dst, lx1, ly1, 0.5,1)
+					}
+				} else {
+					__vset(&dst, p1.x + (p1.dmx * woff), p1.y + (p1.dmy * woff), 0.5,1)
+				}
+
+				p0 = p1
+				p1 = mem.ptr_offset(p1, 1)
+			}
+		} else {
+			for j in 0..<path.count {
+				__vset(&dst, pts[j].x, pts[j].y, 0.5,1)
+			}
+		}
+
+		dst_diff := dst_start_length - len(dst) 
+		path.fill = verts[dst_index:dst_index + dst_diff]
+
+		// advance
+		dst_start_length = len(dst)
+		dst_index += dst_diff
+
+		// Calculate fringe
+		if fringe {
+			lw = w + woff
+			rw = w - woff
+			lu = 0
+			ru = 1
+
+			// Create only half a fringe for convex shapes so that
+			// the shape can be rendered without stenciling.
+			if convex {
+				lw = woff	// This should generate the same vertex as fill inset above.
+				lu = 0.5	// Set outline fade at middle.
+			}
+
+			// Looping
+			p0 = &pts[path.count-1]
+			p1 = &pts[0]
+
+			for j in 0..<path.count {
+				if (.BEVEL in p1.flags) || (.INNER_BEVEL in p1.flags) {
+					__bevelJoin(&dst, p0, p1, lw, rw, lu, ru)
+				} else {
+					__vset(&dst, p1.x + (p1.dmx * lw), p1.y + (p1.dmy * lw), lu,1)
+					__vset(&dst, p1.x - (p1.dmx * rw), p1.y - (p1.dmy * rw), ru,1)
+				}
+
+				p0 = p1
+				p1 = mem.ptr_offset(p1, 1)
+			}
+
+			// Loop it
+			__vset(&dst, verts[dst_index + 0].x, verts[dst_index + 0].y, lu,1)
+			__vset(&dst, verts[dst_index + 1].x, verts[dst_index + 1].y, ru,1)
+
+			dst_diff := dst_start_length - len(dst) 
+			path.stroke = verts[dst_index:dst_index + dst_diff]
+
+			// advance
+			dst_index += dst_diff
+		} else {
+			path.stroke = nil
+		}
+	}
+
+	return true
+}
+
+///////////////////////////////////////////////////////////
+// Paths
+//
+// Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths.
+// Then you define one or more paths and sub-paths which describe the shape. The are functions
+// to draw common shapes like rectangles and circles, and lower level step-by-step functions,
+// which allow to define a path curve by curve.
+//
+// NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
+// winding and holes should have counter clockwise order. To specify winding of a path you can
+// call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW.
+//
+// Finally you can fill the path using current fill style by calling nvgFill(), and stroke it
+// with current stroke style by calling nvgStroke().
+//
+// The curve segments and sub-paths are transformed by the current transform.
+///////////////////////////////////////////////////////////
+
+// NOTE: helper to go from Command to f32
+__cmdf :: #force_inline proc(cmd: Commands) -> f32 {
+	return f32(cmd)
+}
+
+// Clears the current path and sub-paths.
+BeginPath :: proc(ctx: ^Context) {
+	clear(&ctx.commands)
+	__clearPathCache(ctx)
+}
+
+@(deferred_in=Fill)
+FillScoped :: proc(ctx: ^Context) {
+	BeginPath(ctx)
+}
+
+@(deferred_in=Stroke)
+StrokeScoped :: proc(ctx: ^Context) {
+	BeginPath(ctx)
+}
+
+@(deferred_in=Stroke)
+FillStrokeScoped :: proc(ctx: ^Context) {
+	BeginPath(ctx)		
+}
+
+// Starts new sub-path with specified point as first point.
+MoveTo :: proc(ctx: ^Context, x, y: f32) {
+	values := [3]f32 { __cmdf(.MOVE_TO), x, y }
+	__appendCommands(ctx, values[:])
+}
+
+// Adds line segment from the last point in the path to the specified point.
+LineTo :: proc(ctx: ^Context, x, y: f32) {
+	values := [3]f32 { __cmdf(.LINE_TO), x, y }
+	__appendCommands(ctx, values[:])
+}
+
+// Adds cubic bezier segment from last point in the path via two control points to the specified point.
+BezierTo :: proc(
+	ctx: ^Context, 
+	c1x, c1y: f32,
+	c2x, c2y: f32,
+	x, y: f32,
+) {
+	values := [?]f32 { __cmdf(.BEZIER_TO), c1x, c1y, c2x, c2y, x, y }
+	__appendCommands(ctx, values[:])
+}
+
+// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
+QuadTo :: proc(ctx: ^Context, cx, cy, x, y: f32) {
+	x0 := ctx.commandx
+	y0 := ctx.commandy
+	values := [?]f32 {
+		__cmdf(.BEZIER_TO),
+		x0 + 2 / 3 * (cx - x0), 
+		y0 + 2 / 3 * (cy - y0),
+		x + 2 / 3 * (cx - x), 
+		y + 2 / 3 * (cy - y),
+		x,
+		y,
+	}
+	__appendCommands(ctx, values[:])
+}
+
+// Adds an arc segment at the corner defined by the last path point, and two specified points.
+ArcTo :: proc(
+	ctx: ^Context,
+	x1, y1: f32,
+	x2, y2: f32,
+	radius: f32,
+) {
+	if len(ctx.commands) == 0 {
+		return
+	}
+
+	x0 := ctx.commandx
+	y0 := ctx.commandy
+	// Handle degenerate cases.
+	if __ptEquals(x0,y0, x1,y1, ctx.distTol) ||
+		__ptEquals(x1,y1, x2,y2, ctx.distTol) ||
+		__distPtSeg(x1,y1, x0,y0, x2,y2) < ctx.distTol*ctx.distTol ||
+		radius < ctx.distTol {
+		LineTo(ctx, x1, y1)
+		return
+	}
+
+	// Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2).
+	dx0 := x0-x1
+	dy0 := y0-y1
+	dx1 := x2-x1
+	dy1 := y2-y1
+	__normalize(&dx0,&dy0)
+	__normalize(&dx1,&dy1)
+	a := math.acos(dx0*dx1 + dy0*dy1)
+	d := radius / math.tan(a / 2.0)
+
+	if d > 10000 {
+		LineTo(ctx, x1, y1)
+		return
+	}
+
+	a0, a1, cx, cy: f32
+	direction: Winding
+
+	if __cross(dx0,dy0, dx1,dy1) > 0.0 {
+		cx = x1 + dx0*d + dy0*radius
+		cy = y1 + dy0*d + -dx0*radius
+		a0 = math.atan2(dx0, -dy0)
+		a1 = math.atan2(-dx1, dy1)
+		direction = .CW
+	} else {
+		cx = x1 + dx0*d + -dy0*radius
+		cy = y1 + dy0*d + dx0*radius
+		a0 = math.atan2(-dx0, dy0)
+		a1 = math.atan2(dx1, -dy1)
+		direction = .CCW
+	}
+
+	Arc(ctx, cx, cy, radius, a0, a1, direction)
+}
+
+// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r,
+// and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW).
+// Angles are specified in radians.
+Arc :: proc(ctx: ^Context, cx, cy, r, a0, a1: f32, dir: Winding) {
+	move: Commands = .LINE_TO if len(ctx.commands) > 0 else .MOVE_TO
+
+	// Clamp angles
+	da := a1 - a0
+	if dir == .CW {
+		if abs(da) >= math.PI*2 {
+			da = math.PI*2
+		} else {
+			for da < 0.0 {
+				da += math.PI*2
+			}
+		}
+	} else {
+		if abs(da) >= math.PI*2 {
+			da = -math.PI*2
+		} else {
+			for da > 0.0 {
+				da -= math.PI*2
+			} 
+		}
+	}
+
+	// Split arc into max 90 degree segments.
+	ndivs := max(1, min((int)(abs(da) / (math.PI*0.5) + 0.5), 5))
+	hda := (da / f32(ndivs)) / 2.0
+	kappa := abs(4.0 / 3.0 * (1.0 - math.cos(hda)) / math.sin(hda))
+
+	if dir == .CCW {
+		kappa = -kappa
+	}
+
+	values: [3 + 5 * 7 + 100]f32
+	nvals := 0
+
+	px, py, ptanx, ptany: f32
+	for i in 0..=ndivs {
+		a := a0 + da * f32(i) / f32(ndivs)
+		dx := math.cos(a)
+		dy := math.sin(a)
+		x := cx + dx*r
+		y := cy + dy*r
+		tanx := -dy*r*kappa
+		tany := dx*r*kappa
+
+		if i == 0 {
+			values[nvals] = __cmdf(move); nvals += 1
+			values[nvals] = x; nvals += 1
+			values[nvals] = y; nvals += 1
+		} else {
+			values[nvals] = __cmdf(.BEZIER_TO); nvals += 1
+			values[nvals] = px + ptanx; nvals += 1
+			values[nvals] = py + ptany; nvals += 1
+			values[nvals] = x-tanx; nvals += 1
+			values[nvals] = y-tany; nvals += 1
+			values[nvals] = x; nvals += 1
+			values[nvals] = y; nvals += 1
+		}
+		px = x
+		py = y
+		ptanx = tanx
+		ptany = tany
+	}
+
+	// stored internally
+	__appendCommands(ctx, values[:nvals])
+}
+
+// Closes current sub-path with a line segment.
+ClosePath :: proc(ctx: ^Context) {
+	values := [1]f32 { __cmdf(.CLOSE) }
+	__appendCommands(ctx, values[:])
+}
+
+// Sets the current sub-path winding, see NVGwinding and NVGsolidity.
+PathWinding :: proc(ctx: ^Context, direction: Winding) {
+	values := [2]f32 { __cmdf(.WINDING), f32(direction) }
+	__appendCommands(ctx, values[:])	
+}
+
+// same as path_winding but with different enum
+PathSolidity :: proc(ctx: ^Context, solidity: Solidity) {
+	values := [2]f32 { __cmdf(.WINDING), f32(solidity) }
+	__appendCommands(ctx, values[:])	
+}
+
+// Creates new rectangle shaped sub-path.
+Rect :: proc(ctx: ^Context, x, y, w, h: f32) {
+	values := [?]f32 {
+		__cmdf(.MOVE_TO), x, y,
+		__cmdf(.LINE_TO), x, y + h,
+		__cmdf(.LINE_TO), x + w, y + h,
+		__cmdf(.LINE_TO), x + w, y,
+		__cmdf(.CLOSE),
+	}
+	__appendCommands(ctx, values[:])
+}
+
+// Creates new rounded rectangle shaped sub-path.
+RoundedRect :: proc(ctx: ^Context, x, y, w, h, radius: f32) {
+	RoundedRectVarying(ctx, x, y, w, h, radius, radius, radius, radius)
+}
+
+// Creates new rounded rectangle shaped sub-path with varying radii for each corner.
+RoundedRectVarying :: proc(
+	ctx: ^Context,
+	x, y: f32,
+	w, h: f32,
+	radius_top_left: f32,
+	radius_top_right: f32,
+	radius_bottom_right: f32,
+	radius_bottom_left: f32,
+) {
+	if radius_top_left < 0.1 && radius_top_right < 0.1 && radius_bottom_right < 0.1 && radius_bottom_left < 0.1 {
+		Rect(ctx, x, y, w, h)
+	} else {
+		halfw := abs(w) * 0.5
+		halfh := abs(h) * 0.5
+		rxBL := min(radius_bottom_left, halfw) * math.sign(w)
+		ryBL := min(radius_bottom_left, halfh) * math.sign(h)
+		rxBR := min(radius_bottom_right, halfw) * math.sign(w)
+		ryBR := min(radius_bottom_right, halfh) * math.sign(h)
+		rxTR := min(radius_top_right, halfw) * math.sign(w)
+		ryTR := min(radius_top_right, halfh) * math.sign(h)
+		rxTL := min(radius_top_left, halfw) * math.sign(w)
+		ryTL := min(radius_top_left, halfh) * math.sign(h)
+		values := [?]f32 {
+			__cmdf(.MOVE_TO), x, y + ryTL,
+			__cmdf(.LINE_TO), x, y + h - ryBL,
+			__cmdf(.BEZIER_TO), x, y + h - ryBL*(1 - KAPPA), x + rxBL*(1 - KAPPA), y + h, x + rxBL, y + h,
+			__cmdf(.LINE_TO), x + w - rxBR, y + h,
+			__cmdf(.BEZIER_TO), x + w - rxBR*(1 - KAPPA), y + h, x + w, y + h - ryBR*(1 - KAPPA), x + w, y + h - ryBR,
+			__cmdf(.LINE_TO), x + w, y + ryTR,
+			__cmdf(.BEZIER_TO), x + w, y + ryTR*(1 - KAPPA), x + w - rxTR*(1 - KAPPA), y, x + w - rxTR, y,
+			__cmdf(.LINE_TO), x + rxTL, y,
+			__cmdf(.BEZIER_TO), x + rxTL*(1 - KAPPA), y, x, y + ryTL*(1 - KAPPA), x, y + ryTL,
+			__cmdf(.CLOSE),
+		}
+		__appendCommands(ctx, values[:])
+	}
+}
+
+// Creates new ellipse shaped sub-path.
+Ellipse :: proc(ctx: ^Context, cx, cy, rx, ry: f32) {
+	values := [?]f32 {
+		__cmdf(.MOVE_TO), cx-rx, cy,
+		__cmdf(.BEZIER_TO), cx-rx, cy+ry*KAPPA, cx-rx*KAPPA, cy+ry, cx, cy+ry,
+		__cmdf(.BEZIER_TO), cx+rx*KAPPA, cy+ry, cx+rx, cy+ry*KAPPA, cx+rx, cy,
+		__cmdf(.BEZIER_TO), cx+rx, cy-ry*KAPPA, cx+rx*KAPPA, cy-ry, cx, cy-ry,
+		__cmdf(.BEZIER_TO), cx-rx*KAPPA, cy-ry, cx-rx, cy-ry*KAPPA, cx-rx, cy,
+		__cmdf(.CLOSE),
+	}
+	__appendCommands(ctx, values[:])
+}
+
+// Creates new circle shaped sub-path.
+Circle :: #force_inline proc(ctx: ^Context, cx, cy: f32, radius: f32) {
+	Ellipse(ctx, cx, cy, radius, radius)
+}
+
+// Fills the current path with current fill style.
+Fill :: proc(ctx: ^Context) {
+	state := __getState(ctx)
+	fill_paint := state.fill
+
+	__flattenPaths(ctx)
+
+	if ctx.params.edgeAntiAlias && state.shapeAntiAlias {
+		__expandFill(ctx, ctx.fringeWidth, .MITER, 2.4)
+	} else {
+		__expandFill(ctx, 0, .MITER, 2.4)
+	}
+
+	// apply global alpha
+	fill_paint.innerColor.a *= state.alpha
+	fill_paint.outerColor.a *= state.alpha
+
+	assert(ctx.params.renderFill != nil)
+	ctx.params.renderFill(
+		ctx.params.userPtr,
+		&fill_paint,
+		state.compositeOperation,
+		&state.scissor,
+		ctx.fringeWidth,
+		ctx.cache.bounds,
+		ctx.cache.paths[:],
+	)
+
+	for path in &ctx.cache.paths {
+		ctx.fillTriCount += len(path.fill) - 2
+		ctx.fillTriCount += len(path.stroke) - 2
+		ctx.drawCallCount += 2
+	}
+}
+
+// Fills the current path with current stroke style.
+Stroke :: proc(ctx: ^Context) {
+	state := __getState(ctx)
+	scale := __getAverageScale(state.xform[:])
+	strokeWidth := clamp(state.strokeWidth * scale, 0, 200)
+	stroke_paint := state.stroke
+
+	if strokeWidth < ctx.fringeWidth {
+		// If the stroke width is less than pixel size, use alpha to emulate coverage.
+		// Since coverage is area, scale by alpha*alpha.
+		alpha := clamp(strokeWidth / ctx.fringeWidth, 0, 1)
+		stroke_paint.innerColor.a *= alpha * alpha
+		stroke_paint.outerColor.a *= alpha * alpha
+		strokeWidth = ctx.fringeWidth
+	}
+
+	// apply global alpha
+	stroke_paint.innerColor.a *= state.alpha
+	stroke_paint.outerColor.a *= state.alpha
+
+	__flattenPaths(ctx)
+
+	if ctx.params.edgeAntiAlias && state.shapeAntiAlias {
+		__expandStroke(ctx, strokeWidth * 0.5, ctx.fringeWidth, state.lineCap, state.lineJoin, state.miterLimit)
+	} else {
+		__expandStroke(ctx, strokeWidth * 0.5, 0, state.lineCap, state.lineJoin, state.miterLimit)
+	}	
+
+	assert(ctx.params.renderStroke != nil)
+	ctx.params.renderStroke(
+		ctx.params.userPtr,
+		&stroke_paint,
+		state.compositeOperation,
+		&state.scissor,
+		ctx.fringeWidth,
+		strokeWidth,
+		ctx.cache.paths[:],
+	)
+
+	for path in &ctx.cache.paths {
+		ctx.strokeTriCount += len(path.stroke) - 2
+		ctx.drawCallCount += 1
+	}	
+}
+
+DebugDumpPathCache :: proc(ctx: ^Context) {
+	fmt.printf("~~~~~~~~~~~~~Dumping %d cached paths\n", len(ctx.cache.paths))
+	
+	for path, i in &ctx.cache.paths {
+		fmt.printf(" - Path %d\n", i)
+		
+		if len(path.fill) != 0 {
+			fmt.printf("   - fill: %d\n", len(path.fill))
+			
+			for j in 0..<len(path.fill) {
+				fmt.printf("%f\t%f\n", path.fill[j].x, path.fill[j].y)
+			}
+		}
+
+		if len(path.stroke) != 0 {
+			fmt.printf("   - stroke: %d\n", len(path.stroke))
+			
+			for j in 0..<len(path.stroke) {
+				fmt.printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y)
+			}
+		}
+	}
+}
+
+///////////////////////////////////////////////////////////
+// NanoVG allows you to load .ttf files and use the font to render text.
+//
+// The appearance of the text can be defined by setting the current text style
+// and by specifying the fill color. Common text and font settings such as
+// font size, letter spacing and text align are supported. Font blur allows you
+// to create simple text effects such as drop shadows.
+//
+// At render time the font face can be set based on the font handles or name.
+//
+// Font measure functions return values in local space, the calculations are
+// carried in the same resolution as the final rendering. This is done because
+// the text glyph positions are snapped to the nearest pixels sharp rendering.
+//
+// The local space means that values are not rotated or scale as per the current
+// transformation. For example if you set font size to 12, which would mean that
+// line height is 16, then regardless of the current scaling and rotation, the
+// returned line height is always 16. Some measures may vary because of the scaling
+// since aforementioned pixel snapping.
+//
+// While this may sound a little odd, the setup allows you to always render the
+// same way regardless of scaling. I.e. following works regardless of scaling:
+//
+//		const char* txt = "Text me up.";
+//		nvgTextBounds(vg, x,y, txt, nil, bounds);
+//		nvgBeginPath(vg);
+//		nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
+//		nvgFill(vg);
+//
+// Note: currently only solid color fill is supported for text.
+///////////////////////////////////////////////////////////
+
+// Creates font by loading it from the disk from specified file name.
+// Returns handle to the font.
+CreateFont :: proc(ctx: ^Context, name, filename: string) -> int {
+	return fontstash.AddFontPath(&ctx.fs, name, filename)
+}
+
+// Creates font by loading it from the specified memory chunk.
+// Returns handle to the font.
+CreateFontMem :: proc(ctx: ^Context, name: string, slice: []byte, free_loaded_data: bool) -> int {
+	return fontstash.AddFontMem(&ctx.fs, name, slice, free_loaded_data)
+}
+
+// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found.
+FindFont :: proc(ctx: ^Context, name: string) -> int {
+	if name == "" {
+		return -1
+	}
+
+	return fontstash.GetFontByName(&ctx.fs, name)
+}
+
+// Adds a fallback font by handle.
+AddFallbackFontId :: proc(ctx: ^Context, base_font, fallback_font: int) -> bool {
+	if base_font == -1 || fallback_font == -1 {
+		return false
+	}
+
+	return fontstash.AddFallbackFont(&ctx.fs, base_font, fallback_font)
+}
+
+// Adds a fallback font by name.
+AddFallbackFont :: proc(ctx: ^Context, base_font: string, fallback_font: string) -> bool {
+	return AddFallbackFontId(
+		ctx,
+		FindFont(ctx, base_font),
+		FindFont(ctx, fallback_font),
+	)
+}
+
+// Resets fallback fonts by handle.
+ResetFallbackFontsId :: proc(ctx: ^Context, base_font: int) {
+	fontstash.ResetFallbackFont(&ctx.fs, base_font)
+}
+
+// Resets fallback fonts by name.
+ResetFallbackFonts :: proc(ctx: ^Context, base_font: string) {
+	fontstash.ResetFallbackFont(&ctx.fs, FindFont(ctx, base_font))
+}
+
+// Sets the font size of current text style.
+FontSize :: proc(ctx: ^Context, size: f32) {
+	state := __getState(ctx)
+	state.fontSize = size
+}
+
+// Sets the blur of current text style.
+FontBlur :: proc(ctx: ^Context, blur: f32) {
+	state := __getState(ctx)
+	state.fontBlur = blur
+}
+
+// Sets the letter spacing of current text style.
+TextLetterSpacing :: proc(ctx: ^Context, spacing: f32) {
+	state := __getState(ctx)
+	state.letterSpacing = spacing
+}
+
+// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
+TextLineHeight :: proc(ctx: ^Context, lineHeight: f32) {
+	state := __getState(ctx)
+	state.lineHeight = lineHeight
+}
+
+// Sets the horizontal text align of current text style
+TextAlignHorizontal :: proc(ctx: ^Context, align: AlignHorizontal) {
+	state := __getState(ctx)
+	state.alignHorizontal = align
+}
+
+// Sets the vertical text align of current text style
+TextAlignVertical :: proc(ctx: ^Context, align: AlignVertical) {
+	state := __getState(ctx)
+	state.alignVertical = align
+}
+
+// Sets the text align of current text style, see NVGalign for options.
+TextAlign :: proc(ctx: ^Context, ah: AlignHorizontal, av: AlignVertical) {
+	state := __getState(ctx)
+	state.alignHorizontal = ah
+	state.alignVertical = av
+}
+
+// Sets the font face based on specified name of current text style.
+FontFaceId :: proc(ctx: ^Context, font: int) {
+	state := __getState(ctx)
+	state.fontId = font
+}
+
+// Sets the font face based on specified name of current text style.
+FontFace :: proc(ctx: ^Context, font: string) {
+	state := __getState(ctx)
+	state.fontId = fontstash.GetFontByName(&ctx.fs, font)
+}
+
+__quantize :: proc(a, d: f32) -> f32 {
+	return f32(int(a / d + 0.5)) * d
+}
+
+__getFontScale :: proc(state: ^State) -> f32 {
+	return min(__quantize(__getAverageScale(state.xform[:]), 0.01), 4.0)
+}
+
+__flushTextTexture :: proc(ctx: ^Context) {
+	dirty: [4]f32
+	assert(ctx.params.renderUpdateTexture != nil)
+
+
+	if fontstash.ValidateTexture(&ctx.fs, &dirty) {
+		font_image := ctx.fontImages[ctx.fontImageIdx]
+		
+		// Update texture
+		if font_image != 0 {
+			data := ctx.fs.textureData
+			x := dirty[0]
+			y := dirty[1]
+			w := dirty[2] - dirty[0]
+			h := dirty[3] - dirty[1]
+			ctx.params.renderUpdateTexture(ctx.params.userPtr, font_image, int(x), int(y), int(w), int(h), data)
+		}
+	}
+}
+
+__allocTextAtlas :: proc(ctx: ^Context) -> bool {
+	__flushTextTexture(ctx)
+	
+	if ctx.fontImageIdx >= MAX_FONTIMAGES - 1 {
+		return false
+	}
+	
+	// if next fontImage already have a texture
+	iw, ih: int
+	if ctx.fontImages[ctx.fontImageIdx+1] != 0 {
+		iw, ih = ImageSize(ctx, ctx.fontImages[ctx.fontImageIdx+1])
+	} else { // calculate the new font image size and create it.
+		iw, ih = ImageSize(ctx, ctx.fontImages[ctx.fontImageIdx])
+		
+		if iw > ih {
+			ih *= 2
+		}	else {
+			iw *= 2
+		}
+
+		if iw > MAX_FONTIMAGE_SIZE || ih > MAX_FONTIMAGE_SIZE {
+			iw = MAX_FONTIMAGE_SIZE
+			ih = MAX_FONTIMAGE_SIZE
+		}
+
+		ctx.fontImages[ctx.fontImageIdx + 1] = ctx.params.renderCreateTexture(ctx.params.userPtr, .Alpha, iw, ih, {}, nil)
+	}
+
+	ctx.fontImageIdx += 1
+	fontstash.ResetAtlas(&ctx.fs, iw, ih)
+
+	return true
+}
+
+__renderText :: proc(ctx: ^Context, verts: []Vertex) {
+	// disallow 0
+	if len(verts) == 0 {
+		return
+	}
+
+	state := __getState(ctx)
+	paint := state.fill
+
+	// Render triangles.
+	paint.image = ctx.fontImages[ctx.fontImageIdx]
+
+	// Apply global alpha
+	paint.innerColor.a *= state.alpha
+	paint.outerColor.a *= state.alpha
+
+	ctx.params.renderTriangles(ctx.params.userPtr, &paint, state.compositeOperation, &state.scissor, verts, ctx.fringeWidth)
+	
+	ctx.drawCallCount += 1
+	ctx.textTriCount += len(verts) / 3
+}
+
+__isTransformFlipped :: proc(xform: []f32) -> bool {
+	det := xform[0] * xform[3] - xform[2] * xform[1]
+	return det < 0
+}
+
+// draw a single codepoint, useful for icons
+TextIcon :: proc(ctx: ^Context, xpos, ypos: f32, codepoint: rune) -> f32 {
+	state := __getState(ctx)
+	scale := __getFontScale(state) * ctx.devicePxRatio
+	invscale := f32(1.0) / scale
+	is_flipped := __isTransformFlipped(state.xform[:])
+
+	if state.fontId == -1 {
+		return xpos
+	}
+
+	fs := &ctx.fs
+	fontstash.SetSize(fs, state.fontSize * scale)
+	fontstash.SetSpacing(fs, state.letterSpacing * scale)
+	fontstash.SetBlur(fs, state.fontBlur * scale)
+	fontstash.SetAlignHorizontal(fs, state.alignHorizontal)
+	fontstash.SetAlignVertical(fs, state.alignVertical)
+	fontstash.SetFont(fs, state.fontId)
+
+	// fontstash internals
+	fstate := fontstash.__getState(fs)
+	font := fontstash.__getFont(fs, state.fontId)
+	isize := i16(fstate.size * 10)
+	iblur := i16(fstate.blur)
+	glyph := fontstash.__getGlyph(fs, font, codepoint, isize, iblur)
+	fscale := fontstash.__getPixelHeightScale(font, f32(isize) / 10)
+	
+	// transform x / y
+	x := xpos * scale
+	y := ypos * scale
+	switch fstate.ah {
+	case .LEFT: {}
+	
+	case .CENTER: 
+		width := fontstash.CodepointWidth(font, codepoint, fscale)
+		x = math.round(x - width * 0.5)
+
+	case .RIGHT: 
+		width := fontstash.CodepointWidth(font, codepoint, fscale)
+		x -= width
+	}
+
+	// align vertically
+	y = math.round(y + fontstash.__getVerticalAlign(fs, font, fstate.av, isize))
+	nextx := f32(x)
+	nexty := f32(y)
+
+	if glyph != nil {
+		q: fontstash.Quad
+		fontstash.__getQuad(fs, font, -1, glyph, fscale, fstate.spacing, &nextx, &nexty, &q)
+
+		if is_flipped {
+			q.y0, q.y1 = q.y1, q.y0
+			q.t0, q.t1 = q.t1, q.t0
+		}
+
+		// single glyph only
+		verts := __allocTempVerts(ctx, 6)
+		c: [4 * 2]f32
+	
+		// Transform corners.
+		TransformPoint(&c[0], &c[1], state.xform, q.x0 * invscale, q.y0 * invscale)
+		TransformPoint(&c[2], &c[3], state.xform, q.x1 * invscale, q.y0 * invscale)
+		TransformPoint(&c[4], &c[5], state.xform, q.x1 * invscale, q.y1 * invscale)
+		TransformPoint(&c[6], &c[7], state.xform, q.x0 * invscale, q.y1 * invscale)
+		
+		// Create triangles
+		verts[0] = { c[0], c[1], q.s0, q.t0 }
+		verts[1] = { c[4], c[5], q.s1, q.t1 }
+		verts[2] = { c[2], c[3], q.s1, q.t0 }
+		verts[3] = { c[0], c[1], q.s0, q.t0 }
+		verts[4] = { c[6], c[7], q.s0, q.t1 }
+		verts[5] = { c[4], c[5], q.s1, q.t1 }
+
+		ctx.textureDirty = true
+		__renderText(ctx, verts[:])
+	}
+
+	return nextx / scale
+}
+
+// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.
+Text :: proc(ctx: ^Context, x, y: f32, text: string) -> f32 {
+	state := __getState(ctx)
+	scale := __getFontScale(state) * ctx.devicePxRatio
+	invscale := f32(1.0) / scale
+	is_flipped := __isTransformFlipped(state.xform[:])
+
+	if state.fontId == -1 {
+		return x
+	}
+
+	fs := &ctx.fs
+	fontstash.SetSize(fs, state.fontSize * scale)
+	fontstash.SetSpacing(fs, state.letterSpacing * scale)
+	fontstash.SetBlur(fs, state.fontBlur * scale)
+	fontstash.SetAlignHorizontal(fs, state.alignHorizontal)
+	fontstash.SetAlignVertical(fs, state.alignVertical)
+	fontstash.SetFont(fs, state.fontId)
+
+	cverts := max(2, len(text)) * 6 // conservative estimate.
+	verts := __allocTempVerts(ctx, cverts)
+	nverts: int
+
+	iter := fontstash.TextIterInit(fs, x * scale, y * scale, text)
+	prev_iter := iter
+	q: fontstash.Quad
+	for fontstash.TextIterNext(&ctx.fs, &iter, &q) {
+		c: [4 * 2]f32
+		
+		if iter.previousGlyphIndex == -1 { // can not retrieve glyph?
+			if nverts != 0 {
+				__renderText(ctx, verts[:])
+				nverts = 0
+			}
+
+			if !__allocTextAtlas(ctx) {
+				break // no memory :(
+			}
+
+			iter = prev_iter
+			fontstash.TextIterNext(fs, &iter, &q) // try again
+			
+			if iter.previousGlyphIndex == -1 {
+				// still can not find glyph?
+				break
+			} 
+		}
+		
+		prev_iter = iter
+		if is_flipped {
+			q.y0, q.y1 = q.y1, q.y0
+			q.t0, q.t1 = q.t1, q.t0
+		}
+
+		// Transform corners.
+		TransformPoint(&c[0], &c[1], state.xform, q.x0 * invscale, q.y0 * invscale)
+		TransformPoint(&c[2], &c[3], state.xform, q.x1 * invscale, q.y0 * invscale)
+		TransformPoint(&c[4], &c[5], state.xform, q.x1 * invscale, q.y1 * invscale)
+		TransformPoint(&c[6], &c[7], state.xform, q.x0 * invscale, q.y1 * invscale)
+		
+		// Create triangles
+		if nverts + 6 <= cverts {
+			verts[nverts]     = { c[0], c[1], q.s0, q.t0 }
+			verts[nverts + 1] = { c[4], c[5], q.s1, q.t1 }
+			verts[nverts + 2] = { c[2], c[3], q.s1, q.t0 }
+			verts[nverts + 3] = { c[0], c[1], q.s0, q.t0 }
+			verts[nverts + 4] = { c[6], c[7], q.s0, q.t1 }
+			verts[nverts + 5] = { c[4], c[5], q.s1, q.t1 }
+			nverts += 6
+		}
+	}
+
+	ctx.textureDirty = true
+	__renderText(ctx, verts[:nverts])
+
+	return iter.nextx / scale
+}
+
+// Returns the vertical metrics based on the current text style.
+// Measured values are returned in local coordinate space.
+TextMetrics :: proc(ctx: ^Context) -> (ascender, descender, lineHeight: f32) {
+	state := __getState(ctx)
+	scale := __getFontScale(state) * ctx.devicePxRatio
+	invscale := f32(1.0) / scale
+
+	if state.fontId == -1 {
+		return
+	}
+
+	fs := &ctx.fs
+	fontstash.SetSize(fs, state.fontSize*scale)
+	fontstash.SetSpacing(fs, state.letterSpacing*scale)
+	fontstash.SetBlur(fs, state.fontBlur*scale)
+	fontstash.SetAlignHorizontal(fs, state.alignHorizontal)
+	fontstash.SetAlignVertical(fs, state.alignVertical)
+	fontstash.SetFont(fs, state.fontId)
+
+	ascender, descender, lineHeight = fontstash.VerticalMetrics(fs)
+	ascender *= invscale
+	descender *= invscale
+	lineHeight *= invscale
+	return
+}
+
+// Measures the specified text string. Parameter bounds should be a pointer to float[4],
+// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
+// Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
+// Measured values are returned in local coordinate space.
+TextBounds :: proc(
+	ctx: ^Context,
+	x, y: f32,
+	input: string,
+	bounds: ^[4]f32 = nil,
+) -> (advance: f32) {
+	state := __getState(ctx)
+	scale := __getFontScale(state) * ctx.devicePxRatio
+	invscale := f32(1.0) / scale
+
+	if state.fontId == -1 {
+		return {}
+	}
+
+	fs := &ctx.fs
+	fontstash.SetSize(fs, state.fontSize*scale)
+	fontstash.SetSpacing(fs, state.letterSpacing*scale)
+	fontstash.SetBlur(fs, state.fontBlur*scale)
+	fontstash.SetAlignHorizontal(fs, state.alignHorizontal)
+	fontstash.SetAlignVertical(fs, state.alignVertical)
+	fontstash.SetFont(fs, state.fontId)
+
+	width := fontstash.TextBounds(fs, input, x * scale, y * scale, bounds)
+	
+	// Use line bounds for height.
+	one, two := fontstash.LineBounds(fs, y * scale)
+
+	if bounds != nil {
+		bounds[1] = one
+		bounds[3] = two
+		bounds[0] *= invscale
+		bounds[1] *= invscale
+		bounds[2] *= invscale
+		bounds[3] *= invscale
+	}
+
+	return width * invscale
+}
+
+// text row with relative byte offsets into a string
+Text_Row :: struct {
+	start: int,
+	end: int,
+	next: int,
+	width: f32,
+	minx, maxx: f32,
+}
+
+Codepoint_Type :: enum {
+	Space,
+	Newline,
+	Char,
+	CJK,
+}
+
+// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn.
+// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
+// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
+TextBox :: proc(
+	ctx: ^Context, 
+	x, y: f32,
+	break_row_width: f32,
+	input: string,
+) {
+	state := __getState(ctx)
+	rows: [2]Text_Row
+
+	if state.fontId == -1 {
+		return
+	} 
+
+	_, _, lineHeight := TextMetrics(ctx)
+	old_align := state.alignHorizontal
+	defer state.alignHorizontal = old_align
+	state.alignHorizontal = .LEFT
+	rows_mod := rows[:]
+
+	y := y
+	input := input
+	for nrows, input_last in TextBreakLines(ctx, &input, break_row_width, &rows_mod) {
+		for i in 0..<nrows {
+			row := &rows[i]
+			Text(ctx, x, y, input_last[row.start:row.end])		
+			y += lineHeight * state.lineHeight
+		}
+	}
+}
+
+// NOTE text break lines works relative to the string in byte indexes now, instead of on pointers
+// Breaks the specified text into lines
+// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
+// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
+TextBreakLines :: proc(
+	ctx: ^Context,
+	text: ^string,
+	break_row_width: f32,
+	rows: ^[]Text_Row,
+) -> (nrows: int, last: string, ok: bool) {
+	state := __getState(ctx)
+	scale := __getFontScale(state) * ctx.devicePxRatio
+	invscale := 1.0 / scale
+
+	row_start_x, row_width, row_min_x, row_max_x: f32
+	max_rows := len(rows)
+
+	row_start: int = -1
+	row_end: int = -1
+	word_start: int = -1
+	break_end: int = -1
+	word_start_x, word_min_x: f32
+
+	break_width, break_max_x: f32
+	type := Codepoint_Type.Space
+	ptype := Codepoint_Type.Space
+	pcodepoint: rune
+
+	if max_rows == 0 || state.fontId == -1 || len(text) == 0 {
+		return
+	}
+
+	fs := &ctx.fs
+	fontstash.SetSize(fs, state.fontSize * scale)
+	fontstash.SetSpacing(fs, state.letterSpacing * scale)
+	fontstash.SetBlur(fs, state.fontBlur * scale)
+	fontstash.SetAlignHorizontal(fs, state.alignHorizontal)
+	fontstash.SetAlignVertical(fs, state.alignVertical)
+	fontstash.SetFont(fs, state.fontId)
+
+	break_x := break_row_width * scale
+	iter := fontstash.TextIterInit(fs, 0, 0, text^)
+	prev_iter := iter
+	q: fontstash.Quad
+	stopped_early: bool
+
+	for fontstash.TextIterNext(fs, &iter, &q) {
+		if iter.previousGlyphIndex < 0 && __allocTextAtlas(ctx) { // can not retrieve glyph?
+			iter = prev_iter
+			fontstash.TextIterNext(fs, &iter, &q) // try again
+		}
+		prev_iter = iter
+
+		switch iter.codepoint {
+		case '\t', '\v', '\f', ' ', 0x00a0:
+			// NBSP
+			type = .Space
+
+		case '\n':
+			type = pcodepoint == 13 ? .Space : .Newline
+		
+		case '\r':
+			type = pcodepoint == 10 ? .Space : .Newline
+
+		case 0x0085: 
+			// NEL
+			type = .Newline
+
+		case: 
+			if (iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) ||
+				(iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) ||
+				(iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) ||
+				(iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) ||
+				(iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) ||
+				(iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF) {
+				type = .CJK
+			}	else {
+				type = .Char
+			}
+		}
+
+		if type == .Newline {
+			// Always handle new lines.
+			rows[nrows].start = row_start != -1 ? row_start : iter.str
+			rows[nrows].end = row_end != -1 ? row_end : iter.str
+			rows[nrows].width = row_width * invscale
+			rows[nrows].minx = row_min_x * invscale
+			rows[nrows].maxx = row_max_x * invscale
+			rows[nrows].next = iter.next
+			nrows += 1
+			
+			if nrows >= max_rows {
+				stopped_early = true
+				break
+			}
+
+			// Set nil break point
+			break_end = row_start
+			break_width = 0.0
+			break_max_x = 0.0
+			// Indicate to skip the white space at the beginning of the row.
+			row_start = -1
+			row_end = -1
+			row_width = 0
+			row_min_x = 0
+			row_max_x = 0
+		} else {
+			if row_start == -1 {
+				// Skip white space until the beginning of the line
+				if type == .Char || type == .CJK {
+					// The current char is the row so far
+					row_start_x = iter.x
+					row_start = iter.str
+					row_end = iter.next
+					row_width = iter.nextx - row_start_x
+					row_min_x = q.x0 - row_start_x
+					row_max_x = q.x1 - row_start_x
+					word_start = iter.str
+					word_start_x = iter.x
+					word_min_x = q.x0 - row_start_x
+					// Set nil break point
+					break_end = row_start
+					break_width = 0.0
+					break_max_x = 0.0
+				}
+			} else {
+				next_width := iter.nextx - row_start_x
+
+				// track last non-white space character
+				if type == .Char || type == .CJK {
+					row_end = iter.next
+					row_width = iter.nextx - row_start_x
+					row_max_x = q.x1 - row_start_x
+				}
+				// track last end of a word
+				if ((ptype == .Char || ptype == .CJK) && type == .Space) || type == .CJK {
+					break_end = iter.str
+					break_width = row_width
+					break_max_x = row_max_x
+				}
+				// track last beginning of a word
+				if ((ptype == .Space && (type == .Char || type == .CJK)) || type == .CJK) {
+					word_start = iter.str
+					word_start_x = iter.x
+					word_min_x = q.x0
+				}
+
+				// Break to new line when a character is beyond break width.
+				if (type == .Char || type == .CJK) && next_width > break_x {
+					// The run length is too long, need to break to new line.
+					if (break_end == row_start) {
+						// The current word is longer than the row length, just break it from here.
+						rows[nrows].start = row_start
+						rows[nrows].end = iter.str
+						rows[nrows].width = row_width * invscale
+						rows[nrows].minx = row_min_x * invscale
+						rows[nrows].maxx = row_max_x * invscale
+						rows[nrows].next = iter.str
+						nrows += 1
+
+						if nrows >= max_rows {
+							stopped_early = true
+							break
+						}
+
+						row_start_x = iter.x
+						row_start = iter.str
+						row_end = iter.next
+						row_width = iter.nextx - row_start_x
+						row_min_x = q.x0 - row_start_x
+						row_max_x = q.x1 - row_start_x
+						word_start = iter.str
+						word_start_x = iter.x
+						word_min_x = q.x0 - row_start_x
+					} else {
+						// Break the line from the end of the last word, and start new line from the beginning of the new.
+						rows[nrows].start = row_start
+						rows[nrows].end = break_end
+						rows[nrows].width = break_width * invscale
+						rows[nrows].minx = row_min_x * invscale
+						rows[nrows].maxx = break_max_x * invscale
+						rows[nrows].next = word_start
+						nrows += 1
+						if nrows >= max_rows {
+							stopped_early = true
+							break
+						}
+						// Update row
+						row_start_x = word_start_x
+						row_start = word_start
+						row_end = iter.next
+						row_width = iter.nextx - row_start_x
+						row_min_x = word_min_x - row_start_x
+						row_max_x = q.x1 - row_start_x
+					}
+					// Set nil break point
+					break_end = row_start
+					break_width = 0.0
+					break_max_x = 0.0
+				}
+			}
+		}
+
+		pcodepoint = iter.codepoint
+		ptype = type
+	}
+
+	// Break the line from the end of the last word, and start new line from the beginning of the new.
+	if !stopped_early && row_start != -1 {
+		rows[nrows].start = row_start
+		rows[nrows].end = row_end
+		rows[nrows].width = row_width * invscale
+		rows[nrows].minx = row_min_x * invscale
+		rows[nrows].maxx = row_max_x * invscale
+		rows[nrows].next = iter.end
+		nrows += 1
+	}
+
+	// NOTE a bit hacky, row.start / row.end need to work with last string range
+	last = text^
+	// advance early
+	next := rows[nrows - 1].next
+	text^ = text[next:]
+	// terminate the for loop on non ok
+	ok = nrows != 0
+
+	return 
+}
+
+// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4],
+// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
+// Measured values are returned in local coordinate space.
+TextBoxBounds :: proc(
+	ctx: ^Context, 
+	x, y: f32, 
+	breakRowWidth: f32, 
+	input: string, 
+	bounds: ^[4]f32,
+) {
+	state := __getState(ctx)
+	rows: [2]Text_Row
+	scale := __getFontScale(state) * ctx.devicePxRatio
+	invscale := f32(1.0) / scale
+
+	if state.fontId == -1 {
+		if bounds != nil {
+			bounds^ = {}
+		}
+
+		return
+	}
+
+	// alignment
+	halign := state.alignHorizontal
+	old_align := state.alignHorizontal
+	defer state.alignHorizontal = old_align
+	state.alignHorizontal = .LEFT
+
+	_, _, lineh := TextMetrics(ctx)
+	minx, maxx := x, x
+	miny, maxy := y, y
+
+	fs := &ctx.fs
+	fontstash.SetSize(fs, state.fontSize * scale)
+	fontstash.SetSpacing(fs, state.letterSpacing * scale)
+	fontstash.SetBlur(fs, state.fontBlur * scale)
+	fontstash.SetAlignHorizontal(fs, state.alignHorizontal)
+	fontstash.SetAlignVertical(fs, state.alignVertical)
+	fontstash.SetFont(fs, state.fontId)
+	rminy, rmaxy := fontstash.LineBounds(fs, 0)
+	rminy *= invscale
+	rmaxy *= invscale
+
+	input := input
+	rows_mod := rows[:]
+	y := y
+
+	for nrows, input_last in TextBreakLines(ctx, &input, breakRowWidth, &rows_mod) {
+		for i in 0..<nrows {
+			row := &rows[i]
+			rminx, rmaxx, dx: f32
+			
+			// Horizontal bounds
+			switch halign {
+			case .LEFT: dx = 0
+			case .CENTER: dx = breakRowWidth*0.5 - row.width*0.5
+			case .RIGHT: dx = breakRowWidth - row.width
+			}
+
+			rminx = x + row.minx + dx
+			rmaxx = x + row.maxx + dx
+			minx = min(minx, rminx)
+			maxx = max(maxx, rmaxx)
+			// Vertical bounds.
+			miny = min(miny, y + rminy)
+			maxy = max(maxy, y + rmaxy)
+
+			y += lineh * state.lineHeight
+		}
+	}
+
+	if bounds != nil {
+		bounds^ = { minx, miny, maxx, maxy }
+	}
+}
+
+Glyph_Position :: struct {
+	str: int,
+	x: f32,
+	minx, maxx: f32,
+}
+
+// Calculates the glyph x positions of the specified text.
+// Measured values are returned in local coordinate space.
+TextGlyphPositions :: proc(
+	ctx: ^Context, 
+	x, y: f32, 
+	text: string, 
+	positions: ^[]Glyph_Position,
+) -> int {
+	state := __getState(ctx)
+	scale := __getFontScale(state) * ctx.devicePxRatio
+
+	if state.fontId == -1 || len(text) == 0 {
+		return 0
+	}
+
+	fs := &ctx.fs
+	fontstash.SetSize(fs, state.fontSize*scale)
+	fontstash.SetSpacing(fs, state.letterSpacing*scale)
+	fontstash.SetBlur(fs, state.fontBlur*scale)
+	fontstash.SetAlignHorizontal(fs, state.alignHorizontal)
+	fontstash.SetAlignVertical(fs, state.alignVertical)
+	fontstash.SetFont(fs, state.fontId)
+
+	iter := fontstash.TextIterInit(fs, 0, 0, text)
+	prev_iter := iter
+	q: fontstash.Quad
+	npos: int
+	for fontstash.TextIterNext(fs, &iter, &q) {
+		if iter.previousGlyphIndex < 0 && __allocTextAtlas(ctx) { // can not retrieve glyph?
+			iter = prev_iter
+			fontstash.TextIterNext(fs, &iter, &q) // try again
+		}
+
+		prev_iter = iter
+		positions[npos].str = iter.str
+		positions[npos].x = iter.x + x
+		positions[npos].minx = min(iter.x, q.x0) + x
+		positions[npos].maxx = max(iter.nextx, q.x1) + x
+		npos += 1
+		
+		if npos >= len(positions) {
+			break
+		}
+	}
+
+	return npos
+}