gl: check for concurrent use of a gl.Context

It is an error to use a gl.Context concurrently. (Each Context
contains a state machine, so there is no way this can work.)
Up until now only the GL driver could report such a misuse, and
it generally does a poor job of it. Our command buffer adds some
small oppertunity for the race detector to help, but it makes it
harder to debug other kinds of driver crashes, so it is worth
disabling in debug mode.

To make it easy, compiling with -tags gldebug now inserts an
explicit check that there are no other active GL calls outstanding.

Adding something like:

	go func() {
		for {
			glctx.GetInteger(gl.ALPHA_BITS)
		}
	}()

to x/mobile/example/basic now reliably crashes when compiled
with -tags gldebug, providing a stack trace that includes both
misbehaving goroutines.

Change-Id: I3d85d94220bca2a15eaf2742f13b44db1f3428bf
Reviewed-on: https://go-review.googlesource.com/15180
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/gl/gendebug.go b/gl/gendebug.go
index 1c93d99..ff83fa1 100644
--- a/gl/gendebug.go
+++ b/gl/gendebug.go
@@ -201,6 +201,7 @@
 		// Print original body of function.
 		for _, s := range fn.Body.List {
 			if c := enqueueCall(s); c != nil {
+				c.Fun.(*ast.SelectorExpr).Sel.Name = "enqueueDebug"
 				setEnqueueBlocking(c)
 			}
 			printer.Fprint(buf, fset, s)
@@ -280,6 +281,7 @@
 	"fmt"
 	"log"
 	"math"
+	"sync/atomic"
 	"unsafe"
 )
 
@@ -298,6 +300,20 @@
 	return ""
 }
 
+func (ctx *context) enqueueDebug(c call) C.uintptr_t {
+	numCalls := atomic.AddInt32(&ctx.debug, 1)
+	if numCalls > 1 {
+		panic("concurrent calls made to the same GL context")
+	}
+	defer func() {
+		if atomic.AddInt32(&ctx.debug, -1) > 0 {
+			select {} // block so you see us in the panic
+		}
+	}()
+
+	return ctx.enqueue(c)
+}
+
 `
 
 type entry struct {
diff --git a/gl/gldebug.go b/gl/gldebug.go
index 6b7ab93..f019d65 100644
--- a/gl/gldebug.go
+++ b/gl/gldebug.go
@@ -17,6 +17,7 @@
 	"fmt"
 	"log"
 	"math"
+	"sync/atomic"
 	"unsafe"
 )
 
@@ -35,6 +36,20 @@
 	return ""
 }
 
+func (ctx *context) enqueueDebug(c call) C.uintptr_t {
+	numCalls := atomic.AddInt32(&ctx.debug, 1)
+	if numCalls > 1 {
+		panic("concurrent calls made to the same GL context")
+	}
+	defer func() {
+		if atomic.AddInt32(&ctx.debug, -1) > 0 {
+			select {} // block so you see us in the panic
+		}
+	}()
+
+	return ctx.enqueue(c)
+}
+
 func (v Enum) String() string {
 	switch v {
 	case 0x0:
@@ -635,7 +650,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ActiveTexture(%v) %v", texture, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnActiveTexture,
 			a0: texture.c(),
@@ -648,7 +663,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.AttachShader(%v, %v) %v", p, s, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnAttachShader,
 			a0: p.c(),
@@ -662,7 +677,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BindAttribLocation(%v, %v, %v) %v", p, a, name, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBindAttribLocation,
 			a0: p.c(),
@@ -677,7 +692,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BindBuffer(%v, %v) %v", target, b, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBindBuffer,
 			a0: target.c(),
@@ -691,7 +706,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BindFramebuffer(%v, %v) %v", target, fb, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBindFramebuffer,
 			a0: target.c(),
@@ -705,7 +720,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BindRenderbuffer(%v, %v) %v", target, rb, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBindRenderbuffer,
 			a0: target.c(),
@@ -719,7 +734,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BindTexture(%v, %v) %v", target, t, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBindTexture,
 			a0: target.c(),
@@ -733,7 +748,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BlendColor(%v, %v, %v, %v) %v", red, green, blue, alpha, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBlendColor,
 			a0: C.uintptr_t(math.Float32bits(red)),
@@ -749,7 +764,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BlendEquation(%v) %v", mode, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBlendEquation,
 			a0: mode.c(),
@@ -762,7 +777,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BlendEquationSeparate(%v, %v) %v", modeRGB, modeAlpha, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBlendEquationSeparate,
 			a0: modeRGB.c(),
@@ -776,7 +791,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BlendFunc(%v, %v) %v", sfactor, dfactor, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBlendFunc,
 			a0: sfactor.c(),
@@ -790,7 +805,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BlendFuncSeparate(%v, %v, %v, %v) %v", sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBlendFuncSeparate,
 			a0: sfactorRGB.c(),
@@ -810,7 +825,7 @@
 	if len(src) > 0 {
 		parg = unsafe.Pointer(&src[0])
 	}
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBufferData,
 			a0: target.c(),
@@ -827,7 +842,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BufferInit(%v, %v, %v) %v", target, size, usage, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBufferData,
 			a0: target.c(),
@@ -843,7 +858,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.BufferSubData(%v, %v, len(%d)) %v", target, offset, len(data), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnBufferSubData,
 			a0: target.c(),
@@ -874,7 +889,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Clear(%v) %v", mask, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnClear,
 			a0: C.uintptr_t(mask),
@@ -887,7 +902,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ClearColor(%v, %v, %v, %v) %v", red, green, blue, alpha, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnClearColor,
 			a0: C.uintptr_t(math.Float32bits(red)),
@@ -903,7 +918,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ClearDepthf(%v) %v", d, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnClearDepthf,
 			a0: C.uintptr_t(math.Float32bits(d)),
@@ -916,7 +931,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ClearStencil(%v) %v", s, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnClearStencil,
 			a0: C.uintptr_t(s),
@@ -929,7 +944,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ColorMask(%v, %v, %v, %v) %v", red, green, blue, alpha, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnColorMask,
 			a0: glBoolean(red),
@@ -945,7 +960,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.CompileShader(%v) %v", s, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnCompileShader,
 			a0: s.c(),
@@ -958,7 +973,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.CompressedTexImage2D(%v, %v, %v, %v, %v, %v, len(%d)) %v", target, level, internalformat, width, height, border, len(data), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnCompressedTexImage2D,
 			a0: target.c(),
@@ -979,7 +994,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.CompressedTexSubImage2D(%v, %v, %v, %v, %v, %v, %v, len(%d)) %v", target, level, xoffset, yoffset, width, height, format, len(data), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnCompressedTexSubImage2D,
 			a0: target.c(),
@@ -1001,7 +1016,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.CopyTexImage2D(%v, %v, %v, %v, %v, %v, %v, %v) %v", target, level, internalformat, x, y, width, height, border, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnCopyTexImage2D,
 			a0: target.c(),
@@ -1021,7 +1036,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.CopyTexSubImage2D(%v, %v, %v, %v, %v, %v, %v, %v) %v", target, level, xoffset, yoffset, x, y, width, height, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnCopyTexSubImage2D,
 			a0: target.c(),
@@ -1120,7 +1135,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.CullFace(%v) %v", mode, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnCullFace,
 			a0: mode.c(),
@@ -1133,7 +1148,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DeleteBuffer(%v) %v", v, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDeleteBuffer,
 			a0: C.uintptr_t(v.Value),
@@ -1146,7 +1161,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DeleteFramebuffer(%v) %v", v, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDeleteFramebuffer,
 			a0: C.uintptr_t(v.Value),
@@ -1159,7 +1174,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DeleteProgram(%v) %v", p, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDeleteProgram,
 			a0: p.c(),
@@ -1172,7 +1187,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DeleteRenderbuffer(%v) %v", v, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDeleteRenderbuffer,
 			a0: v.c(),
@@ -1185,7 +1200,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DeleteShader(%v) %v", s, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDeleteShader,
 			a0: s.c(),
@@ -1198,7 +1213,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DeleteTexture(%v) %v", v, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDeleteTexture,
 			a0: v.c(),
@@ -1211,7 +1226,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DepthFunc(%v) %v", fn, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDepthFunc,
 			a0: fn.c(),
@@ -1224,7 +1239,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DepthMask(%v) %v", flag, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDepthMask,
 			a0: glBoolean(flag),
@@ -1237,7 +1252,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DepthRangef(%v, %v) %v", n, f, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDepthRangef,
 			a0: C.uintptr_t(math.Float32bits(n)),
@@ -1251,7 +1266,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DetachShader(%v, %v) %v", p, s, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDetachShader,
 			a0: p.c(),
@@ -1265,7 +1280,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Disable(%v) %v", cap, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDisable,
 			a0: cap.c(),
@@ -1278,7 +1293,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DisableVertexAttribArray(%v) %v", a, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDisableVertexAttribArray,
 			a0: a.c(),
@@ -1291,7 +1306,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DrawArrays(%v, %v, %v) %v", mode, first, count, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDrawArrays,
 			a0: mode.c(),
@@ -1306,7 +1321,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.DrawElements(%v, %v, %v, %v) %v", mode, count, ty, offset, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnDrawElements,
 			a0: mode.c(),
@@ -1322,7 +1337,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Enable(%v) %v", cap, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnEnable,
 			a0: cap.c(),
@@ -1335,7 +1350,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.EnableVertexAttribArray(%v) %v", a, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnEnableVertexAttribArray,
 			a0: a.c(),
@@ -1348,7 +1363,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Finish() %v", errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnFinish,
 		},
@@ -1361,7 +1376,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Flush() %v", errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnFlush,
 		},
@@ -1374,7 +1389,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.FramebufferRenderbuffer(%v, %v, %v, %v) %v", target, attachment, rbTarget, rb, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnFramebufferRenderbuffer,
 			a0: target.c(),
@@ -1390,7 +1405,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.FramebufferTexture2D(%v, %v, %v, %v, %v) %v", target, attachment, texTarget, t, level, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnFramebufferTexture2D,
 			a0: target.c(),
@@ -1407,7 +1422,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.FrontFace(%v) %v", mode, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnFrontFace,
 			a0: mode.c(),
@@ -1420,7 +1435,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GenerateMipmap(%v) %v", target, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGenerateMipmap,
 			a0: target.c(),
@@ -1438,7 +1453,7 @@
 	defer C.free(buf)
 	var cSize C.GLint
 	var cType C.GLenum
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetActiveAttrib,
 			a0: p.c(),
@@ -1464,7 +1479,7 @@
 	defer C.free(buf)
 	var cSize C.GLint
 	var cType C.GLenum
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetActiveUniform,
 			a0: p.c(),
@@ -1491,7 +1506,7 @@
 	}
 	var n C.GLsizei
 	buf := make([]C.GLuint, shadersLen)
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetAttachedShaders,
 			a0: p.c(),
@@ -1531,7 +1546,7 @@
 		log.Printf("gl.GetBooleanv(%v, %v) %v", dst, pname, errstr)
 	}()
 	buf := make([]C.GLboolean, len(dst))
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetBooleanv,
 			a0: pname.c(),
@@ -1549,7 +1564,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GetFloatv(len(%d), %v) %v", len(dst), pname, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetFloatv,
 			a0: pname.c(),
@@ -1565,7 +1580,7 @@
 		log.Printf("gl.GetIntegerv(%v, %v) %v", dst, pname, errstr)
 	}()
 	buf := make([]C.GLint, len(dst))
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetIntegerv,
 			a0: pname.c(),
@@ -1651,7 +1666,7 @@
 	infoLen := ctx.GetProgrami(p, INFO_LOG_LENGTH)
 	buf := C.malloc(C.size_t(infoLen))
 	defer C.free(buf)
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetProgramInfoLog,
 			a0: p.c(),
@@ -1702,7 +1717,7 @@
 	infoLen := ctx.GetShaderi(s, INFO_LOG_LENGTH)
 	buf := C.malloc(C.size_t(infoLen))
 	defer C.free(buf)
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetShaderInfoLog,
 			a0: s.c(),
@@ -1722,7 +1737,7 @@
 	}()
 	var cRange [2]C.GLint
 	var cPrecision C.GLint
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetShaderPrecisionFormat,
 			a0: shadertype.c(),
@@ -1746,7 +1761,7 @@
 	}
 	buf := C.malloc(C.size_t(sourceLen))
 	defer C.free(buf)
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetShaderSource,
 			a0: s.c(),
@@ -1779,7 +1794,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GetTexParameterfv(len(%d), %v, %v) %v", len(dst), target, pname, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetTexParameterfv,
 			a0: target.c(),
@@ -1795,7 +1810,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GetTexParameteriv(%v, %v, %v) %v", dst, target, pname, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetTexParameteriv,
 			a0: target.c(),
@@ -1810,7 +1825,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GetUniformfv(len(%d), %v, %v) %v", len(dst), src, p, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetUniformfv,
 			a0: p.c(),
@@ -1826,7 +1841,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GetUniformiv(%v, %v, %v) %v", dst, src, p, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetUniformiv,
 			a0: p.c(),
@@ -1868,7 +1883,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GetVertexAttribfv(len(%d), %v, %v) %v", len(dst), src, pname, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetVertexAttribfv,
 			a0: src.c(),
@@ -1894,7 +1909,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.GetVertexAttribiv(%v, %v, %v) %v", dst, src, pname, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnGetVertexAttribiv,
 			a0: src.c(),
@@ -1910,7 +1925,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Hint(%v, %v) %v", target, mode, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnHint,
 			a0: target.c(),
@@ -2022,7 +2037,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.LineWidth(%v) %v", width, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnLineWidth,
 			a0: C.uintptr_t(math.Float32bits(width)),
@@ -2035,7 +2050,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.LinkProgram(%v) %v", p, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnLinkProgram,
 			a0: p.c(),
@@ -2048,7 +2063,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.PixelStorei(%v, %v) %v", pname, param, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnPixelStorei,
 			a0: pname.c(),
@@ -2062,7 +2077,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.PolygonOffset(%v, %v) %v", factor, units, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnPolygonOffset,
 			a0: C.uintptr_t(math.Float32bits(factor)),
@@ -2076,7 +2091,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ReadPixels(len(%d), %v, %v, %v, %v, %v, %v) %v", len(dst), x, y, width, height, format, ty, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnReadPixels,
 
@@ -2097,7 +2112,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ReleaseShaderCompiler() %v", errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnReleaseShaderCompiler,
 		},
@@ -2109,7 +2124,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.RenderbufferStorage(%v, %v, %v, %v) %v", target, internalFormat, width, height, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnRenderbufferStorage,
 			a0: target.c(),
@@ -2125,7 +2140,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.SampleCoverage(%v, %v) %v", value, invert, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnSampleCoverage,
 			a0: C.uintptr_t(math.Float32bits(value)),
@@ -2139,7 +2154,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Scissor(%v, %v, %v, %v) %v", x, y, width, height, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnScissor,
 			a0: C.uintptr_t(x),
@@ -2158,7 +2173,7 @@
 	cstr := C.CString(src)
 	cstrp := (**C.char)(C.malloc(C.size_t(unsafe.Sizeof(cstr))))
 	*cstrp = cstr
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnShaderSource,
 			a0: s.c(),
@@ -2173,7 +2188,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.StencilFunc(%v, %v, %v) %v", fn, ref, mask, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnStencilFunc,
 			a0: fn.c(),
@@ -2188,7 +2203,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.StencilFuncSeparate(%v, %v, %v, %v) %v", face, fn, ref, mask, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnStencilFuncSeparate,
 			a0: face.c(),
@@ -2204,7 +2219,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.StencilMask(%v) %v", mask, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnStencilMask,
 			a0: C.uintptr_t(mask),
@@ -2217,7 +2232,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.StencilMaskSeparate(%v, %v) %v", face, mask, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnStencilMaskSeparate,
 			a0: face.c(),
@@ -2231,7 +2246,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.StencilOp(%v, %v, %v) %v", fail, zfail, zpass, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnStencilOp,
 			a0: fail.c(),
@@ -2246,7 +2261,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.StencilOpSeparate(%v, %v, %v, %v) %v", face, sfail, dpfail, dppass, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnStencilOpSeparate,
 			a0: face.c(),
@@ -2266,7 +2281,7 @@
 	if len(data) > 0 {
 		parg = unsafe.Pointer(&data[0])
 	}
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnTexImage2D,
 
@@ -2288,7 +2303,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.TexSubImage2D(%v, %v, %v, %v, %v, %v, %v, %v, len(%d)) %v", target, level, x, y, width, height, format, ty, len(data), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnTexSubImage2D,
 
@@ -2311,7 +2326,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.TexParameterf(%v, %v, %v) %v", target, pname, param, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnTexParameterf,
 			a0: target.c(),
@@ -2326,7 +2341,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.TexParameterfv(%v, %v, len(%d)) %v", target, pname, len(params), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnTexParameterfv,
 			a0: target.c(),
@@ -2342,7 +2357,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.TexParameteri(%v, %v, %v) %v", target, pname, param, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnTexParameteri,
 			a0: target.c(),
@@ -2357,7 +2372,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.TexParameteriv(%v, %v, %v) %v", target, pname, params, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnTexParameteriv,
 			a0: target.c(),
@@ -2373,7 +2388,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform1f(%v, %v) %v", dst, v, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform1f,
 			a0: dst.c(),
@@ -2387,7 +2402,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform1fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform1fv,
 			a0: dst.c(),
@@ -2403,7 +2418,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform1i(%v, %v) %v", dst, v, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform1i,
 			a0: dst.c(),
@@ -2417,7 +2432,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform1iv(%v, %v) %v", dst, src, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform1iv,
 			a0: dst.c(),
@@ -2433,7 +2448,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform2f(%v, %v, %v) %v", dst, v0, v1, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform2f,
 			a0: dst.c(),
@@ -2448,7 +2463,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform2fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform2fv,
 			a0: dst.c(),
@@ -2464,7 +2479,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform2i(%v, %v, %v) %v", dst, v0, v1, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform2i,
 			a0: dst.c(),
@@ -2479,7 +2494,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform2iv(%v, %v) %v", dst, src, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform2iv,
 			a0: dst.c(),
@@ -2495,7 +2510,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform3f(%v, %v, %v, %v) %v", dst, v0, v1, v2, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform3f,
 			a0: dst.c(),
@@ -2511,7 +2526,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform3fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform3fv,
 			a0: dst.c(),
@@ -2527,7 +2542,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform3i(%v, %v, %v, %v) %v", dst, v0, v1, v2, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform3i,
 			a0: dst.c(),
@@ -2543,7 +2558,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform3iv(%v, %v) %v", dst, src, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform3iv,
 			a0: dst.c(),
@@ -2559,7 +2574,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform4f(%v, %v, %v, %v, %v) %v", dst, v0, v1, v2, v3, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform4f,
 			a0: dst.c(),
@@ -2576,7 +2591,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform4fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform4fv,
 			a0: dst.c(),
@@ -2592,7 +2607,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform4i(%v, %v, %v, %v, %v) %v", dst, v0, v1, v2, v3, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform4i,
 			a0: dst.c(),
@@ -2609,7 +2624,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Uniform4iv(%v, %v) %v", dst, src, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniform4iv,
 			a0: dst.c(),
@@ -2625,7 +2640,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.UniformMatrix2fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniformMatrix2fv,
 
@@ -2642,7 +2657,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.UniformMatrix3fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniformMatrix3fv,
 			a0: dst.c(),
@@ -2658,7 +2673,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.UniformMatrix4fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUniformMatrix4fv,
 			a0: dst.c(),
@@ -2674,7 +2689,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.UseProgram(%v) %v", p, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnUseProgram,
 			a0: p.c(),
@@ -2687,7 +2702,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.ValidateProgram(%v) %v", p, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnValidateProgram,
 			a0: p.c(),
@@ -2700,7 +2715,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib1f(%v, %v) %v", dst, x, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib1f,
 			a0: dst.c(),
@@ -2714,7 +2729,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib1fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib1fv,
 			a0: dst.c(),
@@ -2729,7 +2744,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib2f(%v, %v, %v) %v", dst, x, y, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib2f,
 			a0: dst.c(),
@@ -2744,7 +2759,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib2fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib2fv,
 			a0: dst.c(),
@@ -2759,7 +2774,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib3f(%v, %v, %v, %v) %v", dst, x, y, z, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib3f,
 			a0: dst.c(),
@@ -2775,7 +2790,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib3fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib3fv,
 			a0: dst.c(),
@@ -2790,7 +2805,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib4f(%v, %v, %v, %v, %v) %v", dst, x, y, z, w, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib4f,
 			a0: dst.c(),
@@ -2807,7 +2822,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttrib4fv(%v, len(%d)) %v", dst, len(src), errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttrib4fv,
 			a0: dst.c(),
@@ -2822,7 +2837,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.VertexAttribPointer(%v, %v, %v, %v, %v, %v) %v", dst, size, ty, normalized, stride, offset, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnVertexAttribPointer,
 			a0: dst.c(),
@@ -2840,7 +2855,7 @@
 		errstr := ctx.errDrain()
 		log.Printf("gl.Viewport(%v, %v, %v, %v) %v", x, y, width, height, errstr)
 	}()
-	ctx.enqueue(call{
+	ctx.enqueueDebug(call{
 		args: C.struct_fnargs{
 			fn: C.glfnViewport,
 			a0: C.uintptr_t(x),
diff --git a/gl/work.go b/gl/work.go
index eda47ce..f7191fc 100644
--- a/gl/work.go
+++ b/gl/work.go
@@ -41,7 +41,8 @@
 const workbufLen = 3
 
 type context struct {
-	cptr uintptr
+	cptr  uintptr
+	debug int32
 
 	workAvailable chan struct{}