aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Schickel2016-01-02 14:09:41 +0100
committerJohannes Schickel2016-03-16 20:29:26 +0100
commite66e9e44d358b0cc90d128c31e695a8ace4177fa (patch)
tree0833ca6a0c8956ba7c8ec01eb0d64c19aa6ec9b4
parentde3846923c9a00ff6a8563e33858e12a72bfebda (diff)
downloadscummvm-rg350-e66e9e44d358b0cc90d128c31e695a8ace4177fa.tar.gz
scummvm-rg350-e66e9e44d358b0cc90d128c31e695a8ace4177fa.tar.bz2
scummvm-rg350-e66e9e44d358b0cc90d128c31e695a8ace4177fa.zip
OPENGL: Accelerate palette lookups with shaders.
This currently is limited to GL contexts.
-rw-r--r--backends/graphics/opengl/context.cpp9
-rw-r--r--backends/graphics/opengl/opengl-defs.h13
-rw-r--r--backends/graphics/opengl/opengl-func.h11
-rw-r--r--backends/graphics/opengl/opengl-graphics.cpp6
-rw-r--r--backends/graphics/opengl/opengl-sys.h9
-rw-r--r--backends/graphics/opengl/shader.cpp10
-rw-r--r--backends/graphics/opengl/shader.h18
-rw-r--r--backends/graphics/opengl/texture.cpp270
-rw-r--r--backends/graphics/opengl/texture.h73
9 files changed, 416 insertions, 3 deletions
diff --git a/backends/graphics/opengl/context.cpp b/backends/graphics/opengl/context.cpp
index 209c38e5f3..10468f3700 100644
--- a/backends/graphics/opengl/context.cpp
+++ b/backends/graphics/opengl/context.cpp
@@ -45,6 +45,9 @@ void Context::reset() {
NPOTSupported = false;
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
shadersSupported = false;
+ multitextureSupported = false;
+ framebufferObjectSupported = false;
+ textureRGSupported = false;
#endif
#define GL_FUNC_DEF(ret, name, param) name = nullptr;
@@ -216,6 +219,12 @@ void OpenGLGraphicsManager::initializeGLContext() {
ARBVertexShader = true;
} else if (token == "GL_ARB_fragment_shader") {
ARBFragmentShader = true;
+ } else if (token == "GL_ARB_multitexture") {
+ g_context.multitextureSupported = true;
+ } else if (token == "GL_ARB_texture_rg") {
+ g_context.textureRGSupported = true;
+ } else if (token == "GL_EXT_framebuffer_object") {
+ g_context.framebufferObjectSupported = true;
#endif
}
}
diff --git a/backends/graphics/opengl/opengl-defs.h b/backends/graphics/opengl/opengl-defs.h
index 4de73d3476..733fc2933c 100644
--- a/backends/graphics/opengl/opengl-defs.h
+++ b/backends/graphics/opengl/opengl-defs.h
@@ -183,6 +183,9 @@ typedef GLhandleARB GLshader;
#define GL_BGR 0x80E0
#define GL_BGRA 0x80E1
+#define GL_RED 0x1903
+#define GL_R8 0x8229
+
/* PixelStoreParameter */
#define GL_UNPACK_ALIGNMENT 0x0CF5
#define GL_PACK_ALIGNMENT 0x0D05
@@ -242,8 +245,18 @@ typedef GLhandleARB GLshader;
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_INFO_LOG_LENGTH 0x8B84
+#define GL_CURRENT_PROGRAM 0x8B8D
/* Textures */
#define GL_TEXTURE0 0x84C0
+#define GL_TEXTURE1 0x84C1
+
+/* GetPName */
+#define GL_VIEWPORT 0x0BA2
+#define GL_FRAMEBUFFER_BINDING 0x8CA6
+
+/* Framebuffer objects */
+#define GL_COLOR_ATTACHMENT0 0x8CE0
+#define GL_FRAMEBUFFER 0x8D40
#endif
diff --git a/backends/graphics/opengl/opengl-func.h b/backends/graphics/opengl/opengl-func.h
index fb0877913e..763d5e9143 100644
--- a/backends/graphics/opengl/opengl-func.h
+++ b/backends/graphics/opengl/opengl-func.h
@@ -77,6 +77,7 @@
GL_FUNC_DEF(void, glEnable, (GLenum cap));
GL_FUNC_DEF(void, glDisable, (GLenum cap));
+GL_FUNC_DEF(GLboolean, glIsEnabled, (GLenum cap));
GL_FUNC_DEF(void, glClear, (GLbitfield mask));
GL_FUNC_DEF(void, glColor4f, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha));
GL_FUNC_DEF(void, glViewport, (GLint x, GLint y, GLsizei width, GLsizei height));
@@ -128,10 +129,16 @@ GL_FUNC_2_DEF(void, glGetShaderiv, glGetObjectParameterivARB, (GLshader shader,
GL_FUNC_2_DEF(void, glGetShaderInfoLog, glGetInfoLogARB, (GLshader shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog));
GL_FUNC_2_DEF(void, glShaderSource, glShaderSourceARB, (GLshader shader, GLsizei count, const GLchar *const *string, const GLint *length));
GL_FUNC_2_DEF(void, glCompileShader, glCompileShaderARB, (GLshader shader));
+
+#if !USE_FORCED_GLES2
+GL_FUNC_2_DEF(void, glBindFramebuffer, glBindFramebufferEXT, (GLenum target, GLuint renderbuffer));
+GL_FUNC_2_DEF(void, glDeleteFramebuffers, glDeleteFramebuffersEXT, (GLsizei n, const GLuint *framebuffers));
+GL_FUNC_2_DEF(void, glGenFramebuffers, glGenFramebuffersEXT, (GLsizei n, GLuint *renderbuffers));
+GL_FUNC_2_DEF(void, glFramebufferTexture2D, glFramebufferTexture2DEXT, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level));
+GL_FUNC_2_DEF(GLenum, glCheckFramebufferStatus, glCheckFramebufferStatusEXT, (GLenum target));
#endif
-#if !USE_FORCED_GL && !USE_FORCED_GLES
-GL_FUNC_DEF(void, glActiveTexture, (GLenum texture));
+GL_FUNC_2_DEF(void, glActiveTexture, glActiveTextureARB, (GLenum texture));
#endif
#ifdef DEFINED_GL_EXT_FUNC_DEF
diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index 6784bfe6ab..df939fb3d8 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -1031,6 +1031,12 @@ void OpenGLGraphicsManager::setMousePosition(int x, int y) {
Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &format, bool wantAlpha) {
GLenum glIntFormat, glFormat, glType;
if (format.bytesPerPixel == 1) {
+#if !USE_FORCED_GLES && !USE_FORCED_GLES2
+ if (TextureCLUT8GPU::isSupportedByContext()) {
+ return new TextureCLUT8GPU();
+ }
+#endif
+
const Graphics::PixelFormat &virtFormat = wantAlpha ? _defaultFormatAlpha : _defaultFormat;
const bool supported = getGLPixelFormat(virtFormat, glIntFormat, glFormat, glType);
if (!supported) {
diff --git a/backends/graphics/opengl/opengl-sys.h b/backends/graphics/opengl/opengl-sys.h
index 4d143485b6..9df5efc38c 100644
--- a/backends/graphics/opengl/opengl-sys.h
+++ b/backends/graphics/opengl/opengl-sys.h
@@ -111,6 +111,15 @@ struct Context {
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
/** Whether shader support is available or not. */
bool shadersSupported;
+
+ /** Whether multi texture support is available or not. */
+ bool multitextureSupported;
+
+ /** Whether (GLES2) RG texture formats are supported. */
+ bool textureRGSupported;
+
+ /** Whether FBO support is available or not. */
+ bool framebufferObjectSupported;
#endif
#define GL_FUNC_DEF(ret, name, param) ret (GL_CALL_CONV *name)param
diff --git a/backends/graphics/opengl/shader.cpp b/backends/graphics/opengl/shader.cpp
index 9e106a81b8..c251ec1ed9 100644
--- a/backends/graphics/opengl/shader.cpp
+++ b/backends/graphics/opengl/shader.cpp
@@ -171,6 +171,16 @@ void Shader::activate(const GLfloat *projectionMatrix) {
GL_CALL(glUniform1i(_textureLocation, 0));
}
+GLint Shader::getUniformLocation(const char *name) const {
+ GLint result = -1;
+ GL_ASSIGN(result, glGetUniformLocation(_program, name));
+ return result;
+}
+
+void Shader::setUniformI(GLint location, GLint value) {
+ GL_CALL(glUniform1i(location, value));
+}
+
GLshader Shader::compileShader(const char *source, GLenum shaderType) {
GLshader handle;
GL_ASSIGN(handle, glCreateShader(shaderType));
diff --git a/backends/graphics/opengl/shader.h b/backends/graphics/opengl/shader.h
index f9dcbb0c61..458ecb9f16 100644
--- a/backends/graphics/opengl/shader.h
+++ b/backends/graphics/opengl/shader.h
@@ -71,6 +71,24 @@ public:
* @param projectionMatrix Projection matrix to use.
*/
void activate(const GLfloat *projectionMatrix);
+
+ /**
+ * Return location for uniform with given name.
+ *
+ * @param name Name of the uniform to look up in the shader.
+ * @return The location or -1 if uniform was not found.
+ */
+ GLint getUniformLocation(const char *name) const;
+
+ /**
+ * Bind value to uniform.
+ *
+ * Note: this only works when the shader is actived by activate.
+ *
+ * @param location Location of the uniform.
+ * @param value The value to be set.
+ */
+ void setUniformI(GLint location, GLint value);
protected:
/**
* Vertex shader sources.
diff --git a/backends/graphics/opengl/texture.cpp b/backends/graphics/opengl/texture.cpp
index e9713eeb40..5ae4987874 100644
--- a/backends/graphics/opengl/texture.cpp
+++ b/backends/graphics/opengl/texture.cpp
@@ -504,4 +504,274 @@ void TextureRGB555::updateTexture() {
}
#endif // !USE_FORCED_GL
+#if !USE_FORCED_GLES && !USE_FORCED_GLES2
+namespace {
+const char *const g_lookUpFragmentShaderGL =
+ "varying vec2 texCoord;\n"
+ "varying vec4 blendColor;\n"
+ "\n"
+ "uniform sampler2D texture;\n"
+ "uniform sampler2D palette;\n"
+ "\n"
+ "const float adjustFactor = 255.0 / 256.0 + 1.0 / (2.0 * 256.0);"
+ "\n"
+ "void main(void) {\n"
+ "\tvec4 index = texture2D(texture, texCoord);\n"
+ "\tgl_FragColor = blendColor * texture2D(palette, vec2(index.x * adjustFactor, 0.0));\n"
+ "}\n";
+} // End of anonymous namespace
+
+TextureCLUT8GPU::TextureCLUT8GPU()
+ : _clut8Texture(GL_R8, GL_RED, GL_UNSIGNED_BYTE),
+ _paletteTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE),
+ _glTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE),
+ _glFBO(0), _clut8Vertices(), _projectionMatrix(),
+ _lookUpShader(nullptr), _paletteLocation(-1),
+ _clut8Data(), _userPixelData(), _palette(), _paletteDirty(false) {
+ // Allocate space for 256 colors.
+ _paletteTexture.setSize(256, 1);
+
+ _lookUpShader = new Shader(g_defaultVertexShader, g_lookUpFragmentShaderGL);
+ _lookUpShader->recreate();
+ _paletteLocation = _lookUpShader->getUniformLocation("palette");
+
+ setupFBO();
+}
+
+TextureCLUT8GPU::~TextureCLUT8GPU() {
+ delete _lookUpShader;
+ GL_CALL_SAFE(glDeleteFramebuffers, (1, &_glFBO));
+ _clut8Data.free();
+}
+
+void TextureCLUT8GPU::destroy() {
+ _clut8Texture.destroy();
+ _paletteTexture.destroy();
+ _glTexture.destroy();
+ _lookUpShader->destroy();
+
+ GL_CALL(glDeleteFramebuffers(1, &_glFBO));
+ _glFBO = 0;
+}
+
+void TextureCLUT8GPU::recreate() {
+ _clut8Texture.create();
+ _paletteTexture.create();
+ _glTexture.create();
+ _lookUpShader->recreate();
+ _paletteLocation = _lookUpShader->getUniformLocation("palette");
+ setupFBO();
+
+ // In case image date exists assure it will be completely refreshed next
+ // time.
+ if (_clut8Data.getPixels()) {
+ flagDirty();
+ _paletteDirty = true;
+ }
+}
+
+void TextureCLUT8GPU::enableLinearFiltering(bool enable) {
+ _glTexture.enableLinearFiltering(enable);
+}
+
+void TextureCLUT8GPU::allocate(uint width, uint height) {
+ // Assure the texture can contain our user data.
+ _clut8Texture.setSize(width, height);
+ _glTexture.setSize(width, height);
+
+ // In case the needed texture dimension changed we will reinitialize the
+ // texture data buffer.
+ if (_clut8Texture.getWidth() != _clut8Data.w || _clut8Texture.getHeight() != _clut8Data.h) {
+ // Create a buffer for the texture data.
+ _clut8Data.create(_clut8Texture.getWidth(), _clut8Texture.getHeight(), Graphics::PixelFormat::createFormatCLUT8());
+ }
+
+ // Create a sub-buffer for raw access.
+ _userPixelData = _clut8Data.getSubArea(Common::Rect(width, height));
+
+ // Setup structures for internal rendering to _glTexture.
+ _clut8Vertices[0] = 0;
+ _clut8Vertices[1] = 0;
+
+ _clut8Vertices[2] = width;
+ _clut8Vertices[3] = 0;
+
+ _clut8Vertices[4] = 0;
+ _clut8Vertices[5] = height;
+
+ _clut8Vertices[6] = width;
+ _clut8Vertices[7] = height;
+
+ _projectionMatrix[ 0] = 2.0f / _glTexture.getWidth();
+ _projectionMatrix[ 1] = 0.0f;
+ _projectionMatrix[ 2] = 0.0f;
+ _projectionMatrix[ 3] = 0.0f;
+
+ _projectionMatrix[ 4] = 0.0f;
+ _projectionMatrix[ 5] = 2.0f / _glTexture.getHeight();
+ _projectionMatrix[ 6] = 0.0f;
+ _projectionMatrix[ 7] = 0.0f;
+
+ _projectionMatrix[ 8] = 0.0f;
+ _projectionMatrix[ 9] = 0.0f;
+ _projectionMatrix[10] = 0.0f;
+ _projectionMatrix[11] = 0.0f;
+
+ _projectionMatrix[12] = -1.0f;
+ _projectionMatrix[13] = -1.0f;
+ _projectionMatrix[14] = 0.0f;
+ _projectionMatrix[15] = 1.0f;
+}
+
+void TextureCLUT8GPU::draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h) {
+ // Only do any processing when the Texture is initialized.
+ if (!_clut8Data.getPixels()) {
+ return;
+ }
+
+ // First update any potentional changes.
+ updateTextures();
+
+ // Set the texture.
+ _glTexture.bind();
+
+ // Calculate the screen rect where the texture will be drawn.
+ const GLfloat vertices[4*2] = {
+ x, y,
+ x + w, y,
+ x, y + h,
+ x + w, y + h
+ };
+
+ // Setup coordinates for drawing.
+ g_context.setDrawCoordinates(vertices, _glTexture.getTexCoords());
+
+ // Draw the texture to the screen buffer.
+ GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
+}
+
+Graphics::PixelFormat TextureCLUT8GPU::getFormat() const {
+ return Graphics::PixelFormat::createFormatCLUT8();
+}
+
+void TextureCLUT8GPU::setColorKey(uint colorKey) {
+ _palette[colorKey * 4 + 3] = 0x00;
+
+ _paletteDirty = true;
+}
+
+void TextureCLUT8GPU::setPalette(uint start, uint colors, const byte *palData) {
+ byte *dst = _palette + start * 4;
+
+ while (colors-- > 0) {
+ memcpy(dst, palData, 3);
+ dst[3] = 0xFF;
+
+ dst += 4;
+ palData += 3;
+ }
+
+ _paletteDirty = true;
+}
+
+void TextureCLUT8GPU::updateTextures() {
+ const bool needLookUp = Surface::isDirty() || _paletteDirty;
+
+ // Update CLUT8 texture if necessary.
+ if (Surface::isDirty()) {
+ _clut8Texture.updateArea(getDirtyArea(), _clut8Data);
+ clearDirty();
+ }
+
+ // Update palette if necessary.
+ if (_paletteDirty) {
+ Graphics::Surface palSurface;
+ palSurface.init(256, 1, 256, _palette,
+#ifdef SCUMM_LITTLE_ENDIAN
+ Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24) // ABGR8888
+#else
+ Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0) // RGBA8888
+#endif
+ );
+
+ _paletteTexture.updateArea(Common::Rect(256, 1), palSurface);
+ _paletteDirty = false;
+ }
+
+ // In case any data changed, do color look up and save result in _glTexture.
+ if (needLookUp) {
+ lookUpColors();
+ }
+}
+
+void TextureCLUT8GPU::lookUpColors() {
+ // Save old state.
+ GLint oldProgram = 0;
+ GL_CALL(glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgram));
+
+ GLint viewport[4];
+ GL_CALL(glGetIntegerv(GL_VIEWPORT, viewport));
+
+ GLboolean scissorState;
+ GL_ASSIGN(scissorState, glIsEnabled(GL_SCISSOR_TEST));
+ GLboolean blendState;
+ GL_ASSIGN(blendState, glIsEnabled(GL_BLEND));
+
+ GLint oldFBO = 0;
+ GL_CALL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFBO));
+
+ // Update state.
+ GL_CALL(glViewport(0, 0, _glTexture.getWidth(), _glTexture.getHeight()));
+ GL_CALL(glDisable(GL_SCISSOR_TEST));
+ GL_CALL(glDisable(GL_BLEND));
+
+ // Bind framebuffer.
+ GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, _glFBO));
+
+ // Set the palette texture.
+ GL_CALL(glActiveTexture(GL_TEXTURE1));
+ _paletteTexture.bind();
+
+ // Set the clut8 texture.
+ GL_CALL(glActiveTexture(GL_TEXTURE0));
+ _clut8Texture.bind();
+
+ // Do color look up.
+ _lookUpShader->activate(_projectionMatrix);
+ _lookUpShader->setUniformI(_paletteLocation, 1);
+ g_context.setDrawCoordinates(_clut8Vertices, _clut8Texture.getTexCoords());
+ GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
+
+ // Restore old state.
+ GL_CALL(glUseProgram(oldProgram));
+ GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, oldFBO));
+
+ if (blendState) {
+ GL_CALL(glEnable(GL_BLEND));
+ }
+ if (scissorState) {
+ GL_CALL(glEnable(GL_SCISSOR_TEST));
+ }
+ GL_CALL(glViewport(viewport[0], viewport[1], viewport[2], viewport[3]));
+}
+
+void TextureCLUT8GPU::setupFBO() {
+ // Allocate framebuffer object if necessary.
+ if (!_glFBO) {
+ GL_CALL(glGenFramebuffers(1, &_glFBO));
+ }
+
+ // Save old FBO.
+ GLint oldFBO = 0;
+ GL_CALL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFBO));
+
+ // Attach destination texture to FBO.
+ GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, _glFBO));
+ GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _glTexture.getGLTexture(), 0));
+
+ // Restore old FBO.
+ GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, oldFBO));
+}
+#endif // !USE_FORCED_GLES && !USE_FORCED_GLES2
+
} // End of namespace OpenGL
diff --git a/backends/graphics/opengl/texture.h b/backends/graphics/opengl/texture.h
index 343a32b341..06fe47a241 100644
--- a/backends/graphics/opengl/texture.h
+++ b/backends/graphics/opengl/texture.h
@@ -32,6 +32,8 @@
namespace OpenGL {
+class Shader;
+
/**
* A simple GL texture object abstraction.
*
@@ -104,6 +106,14 @@ public:
* Obtain texture coordinates for rectangular drawing.
*/
const GLfloat *getTexCoords() const { return _texCoords; }
+
+ /**
+ * Obtain texture name.
+ *
+ * Beware that the texture name changes whenever create is used.
+ * destroy will invalidate the texture name.
+ */
+ GLuint getGLTexture() const { return _glTexture; }
private:
const GLenum _glIntFormat;
const GLenum _glFormat;
@@ -175,7 +185,7 @@ public:
virtual void draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h) = 0;
void flagDirty() { _allDirty = true; }
- bool isDirty() const { return _allDirty || !_dirtyArea.isEmpty(); }
+ virtual bool isDirty() const { return _allDirty || !_dirtyArea.isEmpty(); }
virtual uint getWidth() const = 0;
virtual uint getHeight() const = 0;
@@ -307,6 +317,67 @@ private:
};
#endif // !USE_FORCED_GL
+#if !USE_FORCED_GLES && !USE_FORCED_GLES2
+class TextureCLUT8GPU : public Surface {
+public:
+ TextureCLUT8GPU();
+ virtual ~TextureCLUT8GPU();
+
+ virtual void destroy();
+
+ virtual void recreate();
+
+ virtual void enableLinearFiltering(bool enable);
+
+ virtual void allocate(uint width, uint height);
+
+ virtual void draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h);
+
+ virtual bool isDirty() const { return _paletteDirty || Surface::isDirty(); }
+
+ virtual uint getWidth() const { return _userPixelData.w; }
+ virtual uint getHeight() const { return _userPixelData.h; }
+
+ virtual Graphics::PixelFormat getFormat() const;
+
+ virtual bool hasPalette() const { return true; }
+
+ virtual void setColorKey(uint colorKey);
+ virtual void setPalette(uint start, uint colors, const byte *palData);
+
+ virtual Graphics::Surface *getSurface() { return &_userPixelData; }
+ virtual const Graphics::Surface *getSurface() const { return &_userPixelData; }
+
+ static bool isSupportedByContext() {
+ return g_context.shadersSupported
+ && g_context.multitextureSupported
+ && g_context.textureRGSupported
+ && g_context.framebufferObjectSupported;
+ }
+private:
+ void updateTextures();
+ void lookUpColors();
+
+ GLTexture _clut8Texture;
+ GLTexture _paletteTexture;
+ GLTexture _glTexture;
+
+ void setupFBO();
+ GLuint _glFBO;
+ GLfloat _clut8Vertices[4*2];
+ GLfloat _projectionMatrix[4*4];
+
+ Shader *_lookUpShader;
+ GLint _paletteLocation;
+
+ Graphics::Surface _clut8Data;
+ Graphics::Surface _userPixelData;
+
+ byte _palette[4 * 256];
+ bool _paletteDirty;
+};
+#endif // !USE_FORCED_GLES && !USE_FORCED_GLES2
+
} // End of namespace OpenGL
#endif