#+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.. 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.. 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.. (count: int) { for i in 0.. ^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.. 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.. ^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 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 }