OpenGL: Transition to Core Profile
Significant groups of OpenGL features were marked as deprecated when the 3.2 spec was published in December 2009, resulting in two different OpenGL profiles:
- The Core Profile, which contains only non-deprecated features.
- The Compatibility Profile, which contains all the features.
Graphics vendors still widely support the Compatibility Profile, so there has not been much pressure on developers to adopt the Core Profile. My impression is that there is still a lot of code in active use, or even still being developed, that uses the Compatibility Profile, including deprecated features.
For developers interested in transitioning to the Core Profile, I hope that this article will be helpful in understanding what is typically needed to complete the transition. Most of the content is based on my experience from completing the work for my Volume Rendering Library, which is a moderately sized project consisting of about 25,000 lines of source code, including around 300 OpenGL API calls and 300 lines of GLSL shader code. Full details about the deprecated features can be found in the OpenGL specs.
Depending on the amount of code, and the feature set used, the effort can be fairly extensive. My recommendation is to perform the work step by step, while testing that the code is still functional after each step. This means that you will not enable use of the Core Profile until being close to completing all the work. If you enable use of the Core Profile from the start, you will not be able to run your code until the entire process is completed. Chances are that it will be broken at this point, and it will be difficult to track down where things went wrong. The step by step approach allows you to verify success of each change. It also makes sense to save the state in your source control system after successfully completing a step, which allows you to easily go back to the last good state if you run into problems.
The following sections list some of the most important tasks in the Core Profile transition. Most of the steps can be completed in any order. The order used in this article is based on getting some of the big chunks out of the way early.
Arguably the most significant feature deprecated in the Core Profile is the fixed pipeline. Depending on where your current code stands, this can be a big deal. If your code is at least on an OpenGL 2.0 level, and already uses shaders, this will not affect you much. If you currently use the fixed pipeline, which typically means that you let OpenGL do shading calculations based on light sources and material properties specified with API calls like glLight*() and glMaterial*(), you may have a substantial amount of work to replace all that fixed pipeline functionality with vertex and fragment shaders written in GLSL.
If you are new to GLSL shader programming, you can learn about it from various resources, like the Orange Book. The book also contains shader code to match most of the previous fixed function pipeline.
There are some changes between older GLSL versions and the latest GLSL versions. The Core Profile requires using the newer versions in these cases. Most of the changes are very simple and quick to apply.
GLSL shaders need to start with a #version directive specifying the version of GLSL used by the shader. Once you transition to the Core Profile, this is also indicated as part of the version. For Core Profile code, you will typically use one of the following versions:
OpenGL 3.2: #version 150 core
OpenGL 3.3: #version 330 core
OpenGL 4.0: #version 400 core
OpenGL 4.1: #version 410 core
OpenGL 4.2: #version 420 core
OpenGL 4.3: #version 430 core
OpenGL 4.4: #version 440 core
You may have to defer adding the core part to the version until you completed a couple more of the following steps.
The attribute and varying storage qualifiers have been deprecated. Simple substitution can be used to update them to the latest standard:
- Vertex shader: Replace attribute by in.
- Vertex shader: Replace varying by out.
- Fragment shader: Replace varying by in.
With the Core Profile, GLSL has a much smaller set of built-in uniform variables, mostly corresponding to fixed pipeline state being removed. For example, gl_ModelViewMatrix and gl_ProjectionMatrix are not available anymore.
You will typically need to add your own uniform declarations if you previously used any of these pre-defined variables. This is easy to do, but will also need corresponding changes in your C/C++ code to set these variables. For the example of transformation matrices, this will be covered in a section below.
Pre-defined vertex attributes like gl_Vertex, gl_Normal and gl_Color are gone. You need to define your own generic vertex attributes as in variables in the vertex shader to replace them.
gl_FragColor is not pre-define anymore. You need to define your own out variable in the fragment shader, and assign the fragment color to that variable instead of gl_FragColor. The declaration will typically look like this:
out vec4 FragColor;
Renamed Built-in Functions
Some built-in functions have been renamed. Most notably, the texture sampling functions are now overloaded with the same name for different sampler types, instead of having specific names per sampler type. For example, what used to be texture2D() is now simply texture().
Transformations and Matrix Stack
The matrix stack, which was previously used with glPushMatrix() and glPopMatrix(), is not available anymore in the Core Profile. If you previously used the matrix stack, the necessary changes can be much more intrusive than expected, depending on your code design and structure. You could maintain your own stack, and make it available globally (using something like a global variable or a singleton). If that sounds too ugly for your sense of design, you may need to start passing around transformation state between software components, which is cleaner, but can require far reaching changes.
In the Core Profile, the entire concept of transformations has basically been removed. What was previously treated as transformations are now just matrices, which are typically defined as uniform variables in the vertex shader, and applied as needed in the shader code. Where your application previously manipulated transformations with calls like glLoadMatrix(), it now needs to set the values of these matrix variables with glUniform*() calls.
Related to this, API calls to build transformation matrices, like glRotate*(), have also been removed. If you previously used them, you now have to build the matrices in your own code. The matrices for common transformations are listed in Appendix E/F of the Red Book.
The attribute stack, which was previously used with glPushAttrib() and glPopAttrib(), is not available anymore in the Core Profile. Similar to the matrix stack, the necessary code changes to eliminate previous use of the attribute stack can be more complex than one would expect, particularly if you want to minimize redundant state changes.
The OpenGL state API does not match up very well with software that uses a scene graph or similar structures. Without the attribute stack, clean and efficient state management can get even trickier. This topic might be worth a separate post at some point. Without going into more detail about the various options, one possible approach is that you define a “standard” set of state across your rendering code. When the rendering method for any object is invoked, it can rely on this state being set. If it changes any state to different values, it is responsible to setting this state back to the “standard” value at the end.
Various ways of specifying vertex data were introduced into OpenGL over time. Starting with immediate mode and display lists, moving over vertex arrays (VA), vertex buffer objects (VBO), to vertex array objects (VAO). With the Core Profile, all of these options except for VAO, with vertex data stored in VBOs, is deprecated. A VAO always needs to be bound for any rendering.
If your code is moderately recent, and used VBOs, the changes needed for adopting VAO are very simple. If you used any of the older mechanisms, the effort might be bigger, but should still be fairly straightforward.
Corresponding to pre-defined attributes like gl_Vertex not existing anymore in the vertex shader, the corresponding API entry points like glVertexPointer() , glNormalPointer() and glColorPointer() are not present anymore in the Core Profile. Use glVertexAttribPointer() to set the generic vertex attributes you used to replace the pre-defined attributes in the shader.
One somewhat interesting case comes up if you geometry is highly dynamic, e.g. if your geometry changes for each frame. In this case, you may want to experiment with various options, like the use of glBufferSubData() and glMapBufferRange(), to find out what performs best for your usage patterns and target platforms.
GL_QUADS and GL_QUAD_STRIP primitive types are not available in the Core Profile. They can fairly easily be replaced by the corresponding triangle primitive types. You need to be careful about the order of vertices, though. For example, if you were drawing a single quad with GL_QUADS, and want to use the same 4 vertices to draw the quad with a GL_TRIANGLE_STRIP, the 3rd and 4th vertex need to be swapped.
Some redundant texture formats are not available in the Core Profile. They can easily be replaced by different formats. For example, if you previously used GL_LUMINANCE or GL_INTENSITY types for one-component textures, replace them with the corresponding GL_RED formats. For two-component textures, use GL_RG instead of GL_LUMINANCE_ALPHA.
Rarely Used Features
Wide lines are still supported in the Core Profile, but marked as deprecated. If you choose your context attribute to also disable deprecated core features, setting the line width to a value greater than 1.0 will result in an error.
Texture borders are not supported anymore.
Related, the GL_CLAMP texture attribute has been removed. Use GL_CLAMP_TO_EDGE instead.
Long considered an obsolete features, display lists are (finally) gone.
The alpha test is gone in the Core Profile. It can be replaced with a discard statement in the fragment shader, conditional on the alpha value of the fragment.
Enable Core Profile, Test and Iterate
Once you have completed the changes to eliminate the use of deprecated features, and confirmed that your software is still functional, you are now ready to enable the Core Profile. This is done while creating and configuring your OpenGL rendering context. The exact mechanism is platform dependent:
- Under Windows, you first need to retrieve the WGL entry point that allows you to create an OpenGL context with extended attributes by calling wglGetProcAddress(“wglCreateContextAttribsARB”). Once you have this entry point, call it similarly to how you previously called wglCreateContext(), except that you pass a set of attributes as a additional 3rd argument. For those attributes, use values like the following (using OpenGL 3.2 for the example):
int ctxAttribs =
- Under Mac OS, add the following attribute value to your NSOpenGLPixelFormatAttribute array:
Also remember to add core to your shader version if you have not done so earlier.
You can now test your application for Core Profile compliance. If you see any problems, or just to be safe, add glGetError() calls in strategic places of your code, e.g. at the end of rendering a frame. One easy approach to do this is by having asserts for glGetError() == GL_NO_ERROR that are only active in debug builds in your code. For debug builds, I also recommend to test the result of all shader compile/link steps, and trigger asserts if they fail. This will allow you to pinpoint problems more quickly.