gl: support for GL ES 3.0

This CL covers the basic structure for supporting ES 3.0 where the
platform provides it, and includes one ES 3.0 function as a
proof-of-concept. The rest of the functions and constant values will
follow in later CLs.

ES 3.0 is available everywhere except Android older than
version 4.3, approximately half of Android devices today:

https://developer.android.com/about/dashboards/index.html#OpenGL

Change-Id: Ief7714131227c447a0c603dadad0bd5285999bb3
Reviewed-on: https://go-review.googlesource.com/23821
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/gl/doc.go b/gl/doc.go
index 1ea6efb..1eb1d77 100644
--- a/gl/doc.go
+++ b/gl/doc.go
@@ -3,7 +3,15 @@
 // license that can be found in the LICENSE file.
 
 /*
-Package gl implements Go bindings for OpenGL ES 2.
+Package gl implements Go bindings for OpenGL ES 2.0 and ES 3.0.
+
+The GL functions are defined on a Context object that is responsible for
+tracking a GL context. Typically a windowing system package (such as
+golang.org/x/exp/shiny/screen) will call NewContext and provide
+a gl.Context for a user application.
+
+If the gl package is compiled on a platform capable of supporting ES 3.0,
+the gl.Context object also implements gl.Context3.
 
 The bindings are deliberately minimal, staying as close the C API as
 possible. The semantics of each function maps onto functions
diff --git a/gl/fn.go b/gl/fn.go
index b4de7d5..a3fe0a6 100644
--- a/gl/fn.go
+++ b/gl/fn.go
@@ -23,6 +23,8 @@
 	a5 uintptr
 	a6 uintptr
 	a7 uintptr
+	a8 uintptr
+	a9 uintptr
 }
 
 type glfn int
@@ -41,6 +43,7 @@
 	glfnBlendEquationSeparate
 	glfnBlendFunc
 	glfnBlendFuncSeparate
+	glfnBlitFramebuffer
 	glfnBufferData
 	glfnBufferSubData
 	glfnCheckFramebufferStatus
diff --git a/gl/gendebug.go b/gl/gendebug.go
index 861ca19..57b966e 100644
--- a/gl/gendebug.go
+++ b/gl/gendebug.go
@@ -103,6 +103,13 @@
 		if fn.Recv == nil || fn.Recv.List[0].Names[0].Name != "ctx" {
 			continue
 		}
+		tname := "<unknown>"
+		t := fn.Recv.List[0].Type
+		if star, ok := t.(*ast.StarExpr); ok {
+			tname = "*" + star.X.(*ast.Ident).Name
+		} else if t, ok := t.(*ast.Ident); ok {
+			tname = t.Name
+		}
 
 		var (
 			params      []string
@@ -112,7 +119,7 @@
 		)
 
 		// Print function signature.
-		fmt.Fprintf(buf, "func (ctx *context) %s(", fn.Name.Name)
+		fmt.Fprintf(buf, "func (ctx %s) %s(", tname, fn.Name.Name)
 		for i, p := range fn.Type.Params.List {
 			if i > 0 {
 				fmt.Fprint(buf, ", ")
diff --git a/gl/gl.go b/gl/gl.go
index 71b0ee3..d9d0184 100644
--- a/gl/gl.go
+++ b/gl/gl.go
@@ -1679,3 +1679,21 @@
 		},
 	})
 }
+
+func (ctx context3) BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1 int, mask uint, filter Enum) {
+	ctx.enqueue(call{
+		args: fnargs{
+			fn: glfnBlitFramebuffer,
+			a0: uintptr(srcX0),
+			a1: uintptr(srcY0),
+			a2: uintptr(srcX1),
+			a3: uintptr(srcY1),
+			a4: uintptr(dstX0),
+			a5: uintptr(dstY0),
+			a6: uintptr(dstX1),
+			a7: uintptr(dstY1),
+			a8: uintptr(mask),
+			a9: filter.c(),
+		},
+	})
+}
diff --git a/gl/gldebug.go b/gl/gldebug.go
index 2c718ff..8dde6df 100644
--- a/gl/gldebug.go
+++ b/gl/gldebug.go
@@ -2858,3 +2858,25 @@
 		},
 		blocking: true})
 }
+
+func (ctx context3) BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1 int, mask uint, filter Enum) {
+	defer func() {
+		errstr := ctx.errDrain()
+		log.Printf("gl.BlitFramebuffer(%v, %v, %v, %v, %v, %v, %v, %v, %v, %v) %v", srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter, errstr)
+	}()
+	ctx.enqueueDebug(call{
+		args: fnargs{
+			fn: glfnBlitFramebuffer,
+			a0: uintptr(srcX0),
+			a1: uintptr(srcY0),
+			a2: uintptr(srcX1),
+			a3: uintptr(srcY1),
+			a4: uintptr(dstX0),
+			a5: uintptr(dstY0),
+			a6: uintptr(dstX1),
+			a7: uintptr(dstY1),
+			a8: uintptr(mask),
+			a9: filter.c(),
+		},
+		blocking: true})
+}
diff --git a/gl/interface.go b/gl/interface.go
index cf02587..8c93954 100644
--- a/gl/interface.go
+++ b/gl/interface.go
@@ -4,7 +4,15 @@
 
 package gl
 
-// Context is an OpenGL context.
+// Context is an OpenGL ES context.
+//
+// A Context has a method for every GL function supported by ES 2 or later.
+// In a program compiled with ES 3 support, a Context is also a Context3.
+// For example, a program can:
+//
+//	func f(glctx gl.Context) {
+//		glctx.(gl.Context3).BlitFramebuffer(...)
+//	}
 //
 // Calls are not safe for concurrent use. However calls can be made from
 // any goroutine, the gl package removes the notion of thread-local
@@ -816,6 +824,19 @@
 	Viewport(x, y, width, height int)
 }
 
+// Context3 is an OpenGL ES 3 context.
+//
+// When the gl package is compiled with GL ES 3 support, the produced
+// Context object also implements the Context3 interface.
+type Context3 interface {
+	Context
+
+	// BlitFramebuffer copies a block of pixels between framebuffers.
+	//
+	// https://www.khronos.org/opengles/sdk/docs/man3/html/glBlitFramebuffer.xhtml
+	BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1 int, mask uint, filter Enum)
+}
+
 // Worker is used by display driver code to execute OpenGL calls.
 //
 // Typically display driver code creates a gl.Context for an application,
diff --git a/gl/work.c b/gl/work.c
index 6e7f76d..8f259f7 100644
--- a/gl/work.c
+++ b/gl/work.c
@@ -8,6 +8,15 @@
 #include "_cgo_export.h"
 #include "work.h"
 
+#if defined(GL_ES_VERSION_3_0) && GL_ES_VERSION_3_0
+#else
+#include <stdio.h>
+void glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
+	printf("GLES3 function is missing\n");
+	exit(2);
+}
+#endif
+
 uintptr_t processFn(struct fnargs* args, char* parg) {
 	uintptr_t ret = 0;
 	switch (args->fn) {
@@ -50,6 +59,9 @@
 	case glfnBlendFuncSeparate:
 		glBlendFuncSeparate((GLenum)args->a0, (GLenum)args->a1, (GLenum)args->a2, (GLenum)args->a3);
 		break;
+	case glfnBlitFramebuffer:
+		glBlitFramebuffer((GLint)args->a0, (GLint)args->a1, (GLint)args->a2, (GLint)args->a3, (GLint)args->a4, (GLint)args->a5, (GLint)args->a6, (GLint)args->a7, (GLbitfield)args->a8, (GLenum)args->a9);
+		break;
 	case glfnBufferData:
 		glBufferData((GLenum)args->a0, (GLsizeiptr)args->a1, (GLvoid*)parg, (GLenum)args->a2);
 		break;
diff --git a/gl/work.go b/gl/work.go
index 65fdc42..b6b8c0d 100644
--- a/gl/work.go
+++ b/gl/work.go
@@ -12,6 +12,7 @@
 #cgo darwin,arm64  LDFLAGS: -framework OpenGLES
 #cgo linux         LDFLAGS: -lGLESv2
 
+#cgo android       CFLAGS: -Dos_android
 #cgo darwin,amd64  CFLAGS: -Dos_osx
 #cgo darwin,arm    CFLAGS: -Dos_ios
 #cgo darwin,arm64  CFLAGS: -Dos_ios
@@ -66,6 +67,10 @@
 
 func (ctx *context) WorkAvailable() <-chan struct{} { return ctx.workAvailable }
 
+type context3 struct {
+	*context
+}
+
 // NewContext creates a cgo OpenGL context.
 //
 // See the Worker interface for more details on how it is used.
@@ -75,7 +80,16 @@
 		work:          make(chan call, workbufLen),
 		retvalue:      make(chan C.uintptr_t),
 	}
-	return glctx, glctx
+	if C.GLES_VERSION == "GL_ES_2_0" {
+		return glctx, glctx
+	}
+	return context3{glctx}, glctx
+}
+
+// Version returns a GL ES version string, either "GL_ES_2_0" or "GL_ES_3_0".
+// Future versions of the gl package may return "GL_ES_3_1".
+func Version() string {
+	return C.GLES_VERSION
 }
 
 func (ctx *context) enqueue(c call) uintptr {
diff --git a/gl/work.h b/gl/work.h
index 1a090f3..3d9a81b 100644
--- a/gl/work.h
+++ b/gl/work.h
@@ -2,16 +2,30 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-#ifdef os_linux
-#include <GLES2/gl2.h> // install on Ubuntu with: sudo apt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev
+#ifdef os_android
+// TODO(crawshaw): We could include <android/api-level.h> and
+// condition on __ANDROID_API__ to get GLES3 headers. However
+// we also need to add -lGLESv3 to LDFLAGS, which we cannot do
+// from inside an ifdef.
+#include <GLES2/gl2.h>
+#elif os_linux
+#include <GLES3/gl3.h> // install on Ubuntu with: sudo apt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev
 #endif
+
 #ifdef os_ios
 #include <OpenGLES/ES2/glext.h>
 #endif
+
 #ifdef os_osx
 #include <OpenGL/gl3.h>
 #endif
 
+#if defined(GL_ES_VERSION_3_0) && GL_ES_VERSION_3_0
+#define GLES_VERSION "GL_ES_3_0"
+#else
+#define GLES_VERSION "GL_ES_2_0"
+#endif
+
 #include <stdint.h>
 #include <stdlib.h>
 
@@ -31,6 +45,7 @@
 	glfnBlendEquationSeparate,
 	glfnBlendFunc,
 	glfnBlendFuncSeparate,
+	glfnBlitFramebuffer,
 	glfnBufferData,
 	glfnBufferSubData,
 	glfnCheckFramebufferStatus,
@@ -173,6 +188,8 @@
 	uintptr_t a5;
 	uintptr_t a6;
 	uintptr_t a7;
+	uintptr_t a8;
+	uintptr_t a9;
 };
 
 extern uintptr_t processFn(struct fnargs* args, char* parg);