From ce1b46da645fdcac80fc9c71082bb5cb40166576 Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Fri, 27 Sep 2013 12:52:00 +0200
Subject: [PATCH] Add support for OpenGL 3 contexts

This required some further changes:
* GLX backend: Choose FB configs instead of X visuals
* GL2 renderer: Set up a VAO if necessary
---
 .gitignore     |  1 +
 Makefile       |  7 +++-
 eglbackend.cpp | 30 +++++++--------
 glutil_gl1.cpp |  4 +-
 glutil_gl2.cpp | 68 ++++++++++++++++++++++++++++++++++
 glxbackend.cpp | 99 +++++++++++++++++++++++++++++++++++++-------------
 glxbackend.h   |  8 +++-
 7 files changed, 172 insertions(+), 45 deletions(-)

diff --git a/.gitignore b/.gitignore
index 73dd027..8c21785 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,6 @@ glxtest
 glx2test
 egltest
 egl2test
+glx3test
 gles2test
 eglinfo
diff --git a/Makefile b/Makefile
index 39c6780..c69fff7 100644
--- a/Makefile
+++ b/Makefile
@@ -4,13 +4,13 @@ COMMON_SRC = gltest.cpp glwindow.cpp
 COMMON_HDR = glwindow.h glutil.h
 COMMON_LD  = -lX11 -lboost_program_options
 
-BINARIES := glxtest egltest glx2test egl2test gles2test eglinfo
+BINARIES := glxtest egltest glx2test egl2test glx3test gles2test eglinfo
 
 all: $(BINARIES)
 
 # choices (not all combinations are valid)
 # windowing system: WIN_{GLX,EGL}
-# the kind of context: CON_{GL1,GLES2}
+# the kind of context: CON_{GL1,GL3,GLES2}
 # the API used to draw: compile in glutil_gl1.cpp or glutil_gl2.cpp
 
 glxtest: $(COMMON_SRC) $(COMMON_HDR) glutil_gl1.cpp glxbackend.cpp glxbackend.h
@@ -25,6 +25,9 @@ glx2test: $(COMMON_SRC) $(COMMON_HDR) glutil_gl2.cpp glxbackend.cpp glxbackend.h
 egl2test: $(COMMON_SRC) $(COMMON_HDR) glutil_gl2.cpp eglbackend.cpp eglbackend.h
 	g++ $(CFLAGS) -DWIN_EGL -DCON_GL1 $^ -lEGL -lGL $(COMMON_LD) -o $@
 
+glx3test: $(COMMON_SRC) $(COMMON_HDR) glutil_gl2.cpp glxbackend.cpp glxbackend.h
+	g++ $(CFLAGS) -DWIN_GLX -DCON_GL3 $^ -lGL $(COMMON_LD) -o $@
+
 gles2test: $(COMMON_SRC) $(COMMON_HDR) glutil_gl2.cpp eglbackend.cpp eglbackend.h
 	g++ $(CFLAGS) -DWIN_EGL -DCON_GLES2 $^ -lEGL -lGLESv2 $(COMMON_LD) -o $@
 
diff --git a/eglbackend.cpp b/eglbackend.cpp
index ef27997..0d765e9 100644
--- a/eglbackend.cpp
+++ b/eglbackend.cpp
@@ -54,13 +54,7 @@ static void exitEglError(const char *what)
 	exit(1);
 }
 
-static const EGLint context_attribs[] = {
-#ifdef CON_GLES2
-	EGL_CONTEXT_CLIENT_VERSION, 2,
-#endif
-	EGL_NONE
-};
-static const EGLint config_attribs[] = {
+static const EGLint configAttribs[] = {
 	EGL_RED_SIZE,             4,
 	EGL_GREEN_SIZE,           4,
 	EGL_BLUE_SIZE,            4,
@@ -71,6 +65,12 @@ static const EGLint config_attribs[] = {
 #endif
 	EGL_NONE,
 };
+static const EGLint contextAttribs[] = {
+#ifdef CON_GLES2
+	EGL_CONTEXT_CLIENT_VERSION, 2,
+#endif
+	EGL_NONE
+};
 
 VisualID EGLBackend::initialize(Display *xDisplay)
 {
@@ -84,6 +84,7 @@ VisualID EGLBackend::initialize(Display *xDisplay)
 			exitEglError("Failed to initialize EGL");
 		printf("Using EGL version: %d.%d\n", eglMajor, eglMinor);
 		if (eglMajor == 1 && eglMinor < 3) {
+			// Choosing the GL context version requires EGL 1.3
 			fprintf(stderr, "Need at least EGL 1.3 to function properly\n");
 			exit(1);
 		}
@@ -96,11 +97,11 @@ VisualID EGLBackend::initialize(Display *xDisplay)
 		// get an appropriate config
 		EGLConfig configs[1];
 		EGLint count;
-		if (eglChooseConfig(display, config_attribs, configs, 1, &count) == EGL_FALSE){
-			exitEglError("Failed to choose config");
+		if (eglChooseConfig(display, configAttribs, configs, 1, &count) == EGL_FALSE){
+			exitEglError("Failed to choose framebuffer configuration");
 		}
 		if (count == 0) {
-			fprintf(stderr, "Found no matching EGL configuration\n");
+			fprintf(stderr, "Found no matching framebuffer configuration\n");
 			exit(1);
 		}
 		config = configs[0];
@@ -116,7 +117,7 @@ void EGLBackend::createContext(Window window)
 	assert(display != EGL_NO_DISPLAY && context == EGL_NO_CONTEXT);
 	surface = eglCreateWindowSurface(display, config, window, NULL);
 	// create an EGL context and use it with the surface
-	context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attribs);
+	context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
 	if (context == EGL_NO_CONTEXT)
 		exitEglError("Failed to create context");
 	if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
@@ -150,12 +151,11 @@ void EGLBackend::setSwapInterval(int i) const
 {
 	assert(context != EGL_NO_CONTEXT);
 	// check if swap interval value is supported
-	EGLint val;
-	eglGetConfigAttrib(display, config, EGL_MIN_SWAP_INTERVAL, &val);
-	if (i < val) {
-		fprintf(stderr, "Cannot set swap interval to %d, minimum supported value is %d\n", i, val);
+	if (i < 0) {
+		fprintf(stderr, "Cannot set swap interval to %d, must not be negative\n", i);
 		exit(1);
 	}
+	EGLint val;
 	eglGetConfigAttrib(display, config, EGL_MAX_SWAP_INTERVAL, &val);
 	if (i > val) {
 		fprintf(stderr, "Cannot set swap interval to %d, maximum supported value is %d\n", i, val);
diff --git a/glutil_gl1.cpp b/glutil_gl1.cpp
index 1cedfac..0fb04e2 100644
--- a/glutil_gl1.cpp
+++ b/glutil_gl1.cpp
@@ -18,8 +18,8 @@
 
 #include "glutil.h"
 
-#if defined(CON_GLES2)
-#error "GLES2 contexts do not support GL1 functionality"
+#if !defined(CON_GL1)
+#error "Only GL1 contexts support GL1 functionality"
 #endif
 
 void resolveFunctionPointers(T_glGetProcAddress)
diff --git a/glutil_gl2.cpp b/glutil_gl2.cpp
index e8cc190..666ede9 100644
--- a/glutil_gl2.cpp
+++ b/glutil_gl2.cpp
@@ -34,6 +34,7 @@ typedef void (*GLATTACHSHADERPROC) (GLuint program, GLuint shader);
 typedef void (*GLLINKPROGRAMPROC) (GLuint program);
 typedef GLint (*GLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name);
 typedef void (*GLUSEPROGRAMPROC) (GLuint program);
+
 typedef void (*GLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);
 typedef void (*GLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);
 typedef void (*GLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
@@ -41,6 +42,13 @@ typedef void (*GLBINDBUFFERPROC) (GLenum target, GLuint buffer);
 typedef void (*GLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
 typedef void (*GLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
 
+#ifdef CON_GL3
+// VAOs are only supported in GL3+, and not needed in earler contexts
+typedef void (*GLBINDVERTEXARRAYPROC) (GLuint array);
+typedef void (*GLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays);
+typedef void (*GLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);
+#endif
+
 GLGETSHADERIVPROC p_glGetShaderiv = NULL;
 GLGETSHADERINFOLOGPROC p_glGetShaderInfoLog = NULL;
 GLCREATESHADERPROC p_glCreateShader = NULL;
@@ -51,6 +59,7 @@ GLATTACHSHADERPROC p_glAttachShader = NULL;
 GLLINKPROGRAMPROC p_glLinkProgram = NULL;
 GLGETATTRIBLOCATIONPROC p_glGetAttribLocation = NULL;
 GLUSEPROGRAMPROC p_glUseProgram = NULL;
+
 GLVERTEXATTRIBPOINTERPROC p_glVertexAttribPointer = NULL;
 GLENABLEVERTEXATTRIBARRAYPROC p_glEnableVertexAttribArray = NULL;
 GLGENBUFFERSPROC p_glGenBuffers = NULL;
@@ -58,6 +67,12 @@ GLBINDBUFFERPROC p_glBindBuffer = NULL;
 GLBUFFERDATAPROC p_glBufferData = NULL;
 GLDELETEBUFFERSPROC p_glDeleteBuffers = NULL;
 
+#ifdef CON_GL3
+GLBINDVERTEXARRAYPROC p_glBindVertexArray = NULL;
+GLDELETEVERTEXARRAYSPROC p_glDeleteVertexArrays = NULL;
+GLGENVERTEXARRAYSPROC p_glGenVertexArrays = NULL;
+#endif
+
 // resolve function pointers
 static T_proc resolveFunctionPointer(T_glGetProcAddress p_glGetProcAddress, const char *name)
 {
@@ -96,6 +111,13 @@ void resolveFunctionPointers(T_glGetProcAddress p_glGetProcAddress)
 	p_glAttachShader = (GLATTACHSHADERPROC)resolveFunctionPointer(p_glGetProcAddress, "glAttachShader");
 	p_glLinkProgram = (GLLINKPROGRAMPROC)resolveFunctionPointer(p_glGetProcAddress, "glLinkProgram");
 	p_glGetAttribLocation = (GLGETATTRIBLOCATIONPROC)resolveFunctionPointer(p_glGetProcAddress, "glGetAttribLocation");
+
+#ifdef CON_GL3
+	p_glBindVertexArray = (GLBINDVERTEXARRAYPROC)resolveFunctionPointer(p_glGetProcAddress, "glBindVertexArray");
+	p_glDeleteVertexArrays = (GLDELETEVERTEXARRAYSPROC)resolveFunctionPointer(p_glGetProcAddress, "glDeleteVertexArrays");
+	p_glGenVertexArrays = (GLGENVERTEXARRAYSPROC)resolveFunctionPointer(p_glGetProcAddress, "glGenVertexArrays");
+#endif
+
 	p_glUseProgram = (GLUSEPROGRAMPROC)resolveFunctionPointer(p_glGetProcAddress, "glUseProgram");
 	p_glVertexAttribPointer = (GLVERTEXATTRIBPOINTERPROC)resolveFunctionPointer(p_glGetProcAddress, "glVertexAttribPointer");
 	p_glEnableVertexAttribArray = (GLENABLEVERTEXATTRIBARRAYPROC)resolveFunctionPointer(p_glGetProcAddress, "glEnableVertexAttribArray");
@@ -105,6 +127,32 @@ void resolveFunctionPointers(T_glGetProcAddress p_glGetProcAddress)
 	p_glDeleteBuffers = (GLDELETEBUFFERSPROC)resolveFunctionPointer(p_glGetProcAddress, "glDeleteBuffers");
 }
 
+static const char *glErrorToString(GLenum e)
+{
+#define CASE(name) case name: return #name
+	switch (e) {
+		CASE(GL_NO_ERROR);
+		CASE(GL_INVALID_ENUM);
+		CASE(GL_INVALID_VALUE);
+		CASE(GL_INVALID_OPERATION);
+#ifndef CON_GLES2
+		CASE(GL_STACK_OVERFLOW);
+		CASE(GL_STACK_UNDERFLOW);
+#endif
+		CASE(GL_OUT_OF_MEMORY);
+		default: return "<unknown>";
+	}
+#undef CASE
+}
+
+static void checkGlError(const char *what)
+{
+	GLenum e = glGetError();
+	if (e == GL_NO_ERROR) return;
+	fprintf(stderr, "GL error %d (%s): %s\n", e, glErrorToString(e), what);
+	exit(1);
+}
+
 // shaders
 static const char *vertex_shader_source =
 "#version 100 \n\
@@ -155,6 +203,7 @@ static GLuint compile_shader(GLenum shader_type, const char *source)
 		show_info_log(shader);
 		exit(1);
 	}
+	checkGlError("Compiling shader");
 	return shader;
 }
 
@@ -164,6 +213,7 @@ static GLuint createArrayBuffer(GLsizeiptr size, GLfloat *data)
 	p_glGenBuffers(1, &buffer);
 	p_glBindBuffer(GL_ARRAY_BUFFER, buffer);
 	p_glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
+	checkGlError("Creating array buffer");
 	return buffer;
 }
 
@@ -184,6 +234,15 @@ void initialise2dProjection()
 void drawQuad(GLfloat red, GLfloat green, GLfloat blue, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
 {
 	p_glUseProgram(program);
+	checkGlError("Enabling the shaders");
+
+#ifdef CON_GL3
+	// create a vertex array obect and use it
+	GLuint vao;
+	p_glGenVertexArrays(1, &vao);
+	p_glBindVertexArray(vao);
+	checkGlError("Creating a VAO");
+#endif
 	
 	// send vertex data to card with given attribute
 	GLfloat vertex_buffer_data[] = {
@@ -201,7 +260,9 @@ void drawQuad(GLfloat red, GLfloat green, GLfloat blue, GLfloat x1, GLfloat y1,
 		0,                                /* stride */
 		(void*)0                          /* array buffer offset */
 	);
+	checkGlError("Preparing vertex data");
 	p_glEnableVertexAttribArray(attributes.position);
+	checkGlError("Sending vertex data");
 	
 	// same with color data
 	GLfloat color_buffer_data[] = {
@@ -219,12 +280,19 @@ void drawQuad(GLfloat red, GLfloat green, GLfloat blue, GLfloat x1, GLfloat y1,
 		0,                             /* stride */
 		(void*)0                       /* array buffer offset */
 	);
+	checkGlError("Preparing color data");
 	p_glEnableVertexAttribArray(attributes.color);
+	checkGlError("Sending color data");
 	
 	// draw
 	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+	checkGlError("Drawing");
 	
 	// cleanup
 	p_glDeleteBuffers(1, &vertex_buffer);
 	p_glDeleteBuffers(1, &color_buffer);
+#ifdef CON_GL3
+	p_glDeleteVertexArrays(1, &vao);
+#endif
+	checkGlError("Doing cleanup");
 }
diff --git a/glxbackend.cpp b/glxbackend.cpp
index 3ebc878..e40e90c 100644
--- a/glxbackend.cpp
+++ b/glxbackend.cpp
@@ -25,20 +25,32 @@
 #include <GL/glxext.h>
 #include <string>
 
-#if !defined(CON_GL1)
-#error "Valid GL contexts for GLX are: GL1"
+#if !defined(CON_GL1) && !defined(CON_GL3)
+#error "Valid GL contexts for GLX are: GL1, GL3"
 #endif
 
-// attributes for a double buffered visual in RGBA format with at least 4 bits per color
-static int attrList[] =                                             
+// attributes for a double buffered framebuffer in RGBA format with at least 4 bits per color
+static int configAttribs[] =                                             
 {
-	GLX_RGBA, GLX_DOUBLEBUFFER,
+	GLX_RENDER_TYPE, GLX_RGBA_BIT,
+	GLX_DOUBLEBUFFER, True,
 	GLX_RED_SIZE, 4,
 	GLX_GREEN_SIZE, 4,
 	GLX_BLUE_SIZE, 4,
 	None
 };
 
+#ifdef CON_GL3
+// attributes for a GL3 context
+static int contextAttribs[] =
+{
+	GLX_CONTEXT_MAJOR_VERSION_ARB,               3,
+	GLX_CONTEXT_MINOR_VERSION_ARB,               0,
+	GLX_CONTEXT_FLAGS_ARB,                       GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
+	None
+};
+#endif
+
 VisualID GLXBackend::initialize(Display *display)
 {
 	if (this->display == NULL) { // this implies that the context is also unitialized
@@ -47,26 +59,46 @@ VisualID GLXBackend::initialize(Display *display)
 		int glxMajor, glxMinor;
 		glXQueryVersion(display, &glxMajor, &glxMinor);
 		printf("Using GLX version: %d.%d\n", glxMajor, glxMinor);
-		// check for swap control functions
-		const char *extensions = glXQueryExtensionsString(display, DefaultScreen(display));
-		if (std::string(extensions).find("GLX_MESA_swap_control") != std::string::npos) {
-			funSwapIntervalMesa = (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalMESA");
-			if (funSwapIntervalMesa) printf("glXSwapIntervalMESA is present\n");
+		if (glxMajor < 1 || (glxMajor == 1 && glxMinor < 3)) {
+			// glXChooseFBConfig and glXCreateNewContext require GLX 1.3
+			fprintf(stderr, "Need at least GLX 1.3 to function properly\n");
+			exit(1);
 		}
-		else {
-			funSwapIntervalMesa = 0;
+		// check for extension-based functions
+		funSwapIntervalMesa = (PFNGLXSWAPINTERVALMESAPROC)resolveGLXFunction("GLX_MESA_swap_control", "glXSwapIntervalMESA");
+		funSwapIntervalExt = (PFNGLXSWAPINTERVALEXTPROC)resolveGLXFunction("GLX_EXT_swap_control", "glXSwapIntervalEXT");
+		funCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)resolveGLXFunction("GLX_ARB_create_context", "glXCreateContextAttribsARB");
+		// get the first usable framebuffer configuration
+		int count = 0;
+		GLXFBConfig *configs = glXChooseFBConfig(display, DefaultScreen(display), configAttribs, &count);
+		if (count < 1) {
+			fprintf(stderr, "Failed to choose framebuffer configuration\n");
+			exit(1);
 		}
-		if (std::string(extensions).find("GLX_EXT_swap_control") != std::string::npos) {
-			funSwapIntervalExt = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalEXT");
-			if (funSwapIntervalExt) printf("glXSwapIntervalEXT is present\n");
-		}
-		else {
-			funSwapIntervalExt = 0;
-		}
-		// get the visual
-		vi = glXChooseVisual(display, DefaultScreen(display), attrList);
+		config = configs[0];
+		XFree(configs);
 	}
-	return vi->visualid;
+	// return visual ID
+	XVisualInfo *vi = glXGetVisualFromFBConfig(display, config);
+	VisualID visualid = vi->visualid;
+	XFree(vi);
+	return visualid;
+}
+
+bool GLXBackend::haveGLXExtension(const std::string &name)
+{
+	assert(display != NULL);
+	std::string extensions = glXQueryExtensionsString(display, DefaultScreen(display));
+	return (std::string(" "+extensions+" ").find(" "+name+" ") != std::string::npos);
+}
+
+T_proc GLXBackend::resolveGLXFunction(const char *extension, const char *function)
+{
+	if (!haveGLXExtension(extension)) return NULL;
+	T_proc f = glXGetProcAddress((const GLubyte*)function);
+	if (f)
+		printf("%s is supported, using it for %s\n", extension, function);
+	return f;
 }
 
 void GLXBackend::createContext(Window window)
@@ -74,7 +106,19 @@ void GLXBackend::createContext(Window window)
 	assert(display != NULL && context == None);
 	this->window = window;
 	// create context with that window
-	context = glXCreateContext(display, vi, 0, GL_TRUE);                             
+#ifdef CON_GL1
+	context = glXCreateNewContext(display, config, GLX_RGBA_TYPE, NULL, GL_TRUE);
+#else
+	if (!funCreateContextAttribsARB) {
+		fprintf(stderr, "Cannot create GL3 context: GLX_ARB_create_context not supported\n");
+		exit(1);
+	}
+	context = funCreateContextAttribsARB(display, config, NULL, GL_TRUE, contextAttribs);
+#endif
+	if (!context) {
+		fprintf(stderr, "Error creating context\n");
+		exit(1);
+	}
 	glXMakeCurrent(display, window, context);                                      
 	assert(glXIsDirect(display, context));
 	printf("Using GL version: %s\n", glGetString(GL_VERSION));
@@ -89,7 +133,6 @@ GLXBackend::~GLXBackend()
 			glXMakeCurrent(display, None, NULL);
 			glXDestroyContext(display, context);
 		}
-		XFree(vi);
 	}
 }
 
@@ -102,12 +145,18 @@ void GLXBackend::swapBuffers() const
 void GLXBackend::setSwapInterval(int i) const
 {
 	assert(context != None);
+	// check if swap interval value is supported
+	if (i < 0) {
+		fprintf(stderr, "Cannot set swap interval to %d, must not be negative\n", i);
+		exit(1);
+	}
+	// set it
 	if (funSwapIntervalExt)
 		funSwapIntervalExt(display, window, i);
 	else if (funSwapIntervalMesa)
 		funSwapIntervalMesa(i);
 	else {
-		fprintf(stderr, "At least one of glXSwapIntervalMESA, glXSwapIntervalEXT must be provided by the system\n");
+		fprintf(stderr, "At least one of GLX_EXT_swap_control, GLX_MESA_swap_control must be supported by the system\n");
 		abort();
 	}
 }
diff --git a/glxbackend.h b/glxbackend.h
index 1e75cc8..a16f970 100644
--- a/glxbackend.h
+++ b/glxbackend.h
@@ -17,8 +17,10 @@
  */
 
 #include "glwindow.h"
+#include "glutil.h"
 
 #include <GL/glx.h>
+#include <string>
 
 
 /** Make an X window fit for GL. You have to manage the X window yourself! */
@@ -42,10 +44,14 @@ public:
 	virtual void setSwapInterval(int i) const;
 private:
 	Display *display;
-	XVisualInfo *vi;
+	GLXFBConfig config;
 	GLXContext context;
 	Window window;
 	
 	PFNGLXSWAPINTERVALMESAPROC funSwapIntervalMesa;
 	PFNGLXSWAPINTERVALEXTPROC funSwapIntervalExt;
+	PFNGLXCREATECONTEXTATTRIBSARBPROC funCreateContextAttribsARB;
+	
+	bool haveGLXExtension(const std::string &name);
+	T_proc resolveGLXFunction(const char *extension, const char *function);
 };
-- 
2.39.5