//+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
}