Introduction to OpenGL 3.2 - Tutorial 01

Working with Extensions and GL 3.2 Rendering Context

OpenGL 3.2 is up and running (although currently only with NV 190.56 beta drivers, but it will not stop us to try some of the brand new functionality), and we are ready to start coding. Or... Are we? As we all well know, on MS Windows we have direct access only to GL 1.1 API. Okay, okay, we all no that (I hear your boring yawn). We will go to GLEW or GLee or similar site and download latest library that can handle everything we need. But if we go to those sites we will see that libraries are out of date.

Do we have to wait until new libraries are released? Certainly not! First part of this tutorial will cover direct accessing the extensions. If you already know that, just jump to GL 3.2 Rendering Context Creation section.
Extensions

There are just 4 steps to get access to new functionality...

Step 1. Go to the site http://www.opengl.org/registry/ and download the latest header files (for Windows we just need two of them: glext.h and wglext.h). There is also gl3.h file that could be useful, but we will skip it this time. Downloaded files should be copied into Include/GL folder, or in the local folder of your application.

Step 2. I suggest you to create two new files (for example ogl.h and ogl.cpp) where we can include all OpenGL related .h files and pointers to functions. The ogl.h file, at the moment, should look like this:


// --- ogl.h ---
#include "gl11.h"

#include "glext.h"
#include "wglext.h"

#pragma comment(lib,"opengl32.lib")



As you can see, I’ve copied all .h files into project’s folder. The file gl11.h is in fact gl.h that was shipped with previous versions of Visual Studio (VS2k8 does not include it), but I have to rename it (because of the project name) and copy into project’s folder (because VS2k8 does not include it). I’ve also added 11 to indicate OpenGL version supported by this file (ver.1.1). The file ogl.cpp currently include only this:

// ogl.cpp
#include "stdafx.h"



Step 3. Include ogl.h into StdAfx.h file. Take a look at MSDN to see the purpose of StdAfx.h (Precompiled Header Files). 

//--- OpenGL---
#include "ogl.h"



Step 4. Add new function to GLRenderer class (introduced in previous tutorial). Let’s name it InitAPI(), because it’s purpose will be to grab pointers to all functions that we need. We will explain the whole process on the function glCreateProgram(). This functions serves to create a program that will contain shaders.

   Step 4a. Define a proper function pointer in ogl.h.

extern PFNGLCREATEPROGRAMPROC glCreateProgram;

 The type of that pointer should be created this way:
 PFN + ToUppercase(function_name) + PROC

   Step 4b. Set the pointer to NULL in ogl.cpp.

PFNGLCREATEPROGRAMPROC glCreateProgram = NULL;


   Step 4c. Grab the function pointer in InitAPI().

void CGLRenderer::InitAPI()
{
      glCreateProgram =  (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
}



   Step 4d. Call InitAPI() inside CreateGLContext() function.


bool CGLRenderer::CreateGLContext(CDC* pDC)
{
     PIXELFORMATDESCRIPTOR pfd ;
     memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
     pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
     pfd.nVersion = 1; 
     pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;  
     pfd.iPixelType = PFD_TYPE_RGBA; 
     pfd.cColorBits = 32;
     pfd.cDepthBits = 32; 
     pfd.iLayerType = PFD_MAIN_PLANE;
 
     int nPixelFormat = ChoosePixelFormat(pDC->m_hDC, &pfd);
 
     if (nPixelFormat == 0) return false; 

     BOOL bResult = SetPixelFormat (pDC->m_hDC, nPixelFormat, &pfd);
   
     if (!bResult) return false; 

     // --- OpenGL 3.x ---
     HGLRC tempContext = wglCreateContext(pDC->m_hDC); 
     wglMakeCurrent(pDC->m_hDC,tempContext);

     InitAPI();
     //...
}



Steps 4a, 4b ad 4c have to be repeated for all functions we want to call inside our code. Just to enable shaders and draw a triangle using a single VBO we have to add plenty of functions. After some time of writing and copy/pasting, the function InitAPI() should look like this:

void CGLRenderer::InitAPI()
{
      // Program
      glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
      glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram");
      glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
      glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
      glDetachShader = (PFNGLDETACHSHADERPROC)wglGetProcAddress("glDetachShader");
      glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
      glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
      glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
      glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
      glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
      glUniform1iv = (PFNGLUNIFORM1IVPROC)wglGetProcAddress("glUniform1iv");
      glUniform2iv = (PFNGLUNIFORM2IVPROC)wglGetProcAddress("glUniform2iv");
      glUniform3iv = (PFNGLUNIFORM3IVPROC)wglGetProcAddress("glUniform3iv");
      glUniform4iv = (PFNGLUNIFORM4IVPROC)wglGetProcAddress("glUniform4iv");
      glUniform1f = (PFNGLUNIFORM1FPROC)wglGetProcAddress("glUniform1f");
      glUniform1fv = (PFNGLUNIFORM1FVPROC)wglGetProcAddress("glUniform1fv");
      glUniform2fv = (PFNGLUNIFORM2FVPROC)wglGetProcAddress("glUniform2fv");
      glUniform3fv = (PFNGLUNIFORM3FVPROC)wglGetProcAddress("glUniform3fv");
      glUniform4fv = (PFNGLUNIFORM4FVPROC)wglGetProcAddress("glUniform4fv");
      glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
      glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)wglGetProcAddress("glGetAttribLocation");
      glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)wglGetProcAddress("glVertexAttrib1f");
      glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)wglGetProcAddress("glVertexAttrib1fv");
      glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)wglGetProcAddress("glVertexAttrib2fv");
      glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)wglGetProcAddress("glVertexAttrib3fv");
      glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)wglGetProcAddress("glVertexAttrib4fv");
      glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray");
      glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)wglGetProcAddress("glBindAttribLocation");

      // Shader
      glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
      glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
      glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
      glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
      glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");

      // VBO
      glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
      glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
      glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
      glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
 
}



After acquiring each pointer we should test if it is NULL. If it is NULL, specified function is not implemented and we shouldn’t continue to execute program.
GL 3.2 Rendering Context Creation

As we have already learnt in the previous tutorial, to create a GL 3.x rendering context we have to: 
     1. define pixel format (as for old rendering context),
     2. create an old context and activate it (make it current),
     3. define attributes of the new (3.x) context,
     4. create new (3.x) context using function wglCreateContextAttribsARB(),
     5. deactivate and delete the old context.

Everything should be done in CreateGLContext() function.

bool CGLRenderer::CreateGLContext(CDC* pDC)
{
      PIXELFORMATDESCRIPTOR pfd ;
       memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
       pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
       pfd.nVersion = 1; 
       pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;  
       pfd.iPixelType = PFD_TYPE_RGBA; 
       pfd.cColorBits = 32;
       pfd.cDepthBits = 32; 
       pfd.iLayerType = PFD_MAIN_PLANE;
 
      int nPixelFormat = ChoosePixelFormat(pDC->m_hDC, &pfd);
 
      if (nPixelFormat == 0) return false; 

      BOOL bResult = SetPixelFormat (pDC->m_hDC, nPixelFormat, &pfd);
   
      if (!bResult) return false; 

      // --- OpenGL 3.x ---
      HGLRC tempContext = wglCreateContext(pDC->m_hDC); 
      wglMakeCurrent(pDC->m_hDC,tempContext);

      InitAPI();

      int major, minor;
      GetGLVersion(&major, &minor);

      if( major < 3 || ( major == 3 && minor < 2 ) )
            AfxMessageBox(_T("OpenGL 3.2 is not supported!"));

      int attribs[] =
       {
            WGL_CONTEXT_MAJOR_VERSION_ARB, major,
            WGL_CONTEXT_MINOR_VERSION_ARB, minor, 
            WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
            WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
            0
       };

      PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
      wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
      if(wglCreateContextAttribsARB != NULL)
      {
            m_hrc = wglCreateContextAttribsARB(pDC->m_hDC,0, attribs);
      }

      wglMakeCurrent(NULL,NULL); 
      wglDeleteContext(tempContext);

 
      if (!m_hrc) 
      {
            AfxMessageBox(_T("OpenGL 3.x RC was not created!"));
            return false; 
      }

      return true; 
}



Before creating the new context it is not a bad idea to check the version of OpenGL our driver supports. For that reason, we will create function GetGLVersion() which will retrieve the major and the minor version of supported OpenGL. If we have to deal OpenGL 3.x, than version should be retrieved calling function glGetIntegerv() with GL_MAJOR_VERSION and GL_MINOR_VERSION.

// for GL 3.x
glGetIntegerv(GL_MAJOR_VERSION, major);
// major = 3
glGetIntegerv(GL_MINOR_VERSION, minor);
// minor = 2


But to be sure that we will read OpenGL versions correctly if older than 3.x is supported, we should use glGetString(GL_VERSION). The next code demonstrates mixed technique to retrieve GL version.

void CGLRenderer::GetGLVersion(int* major, int* minor)
{
      // for all versions
      char* ver = (char*)glGetString(GL_VERSION); // ver = "3.2.0"

      *major = ver[0] - '0';
      if( *major >= 3)
      {
       // for GL 3.x
       glGetIntegerv(GL_MAJOR_VERSION, major); // major = 3
       glGetIntegerv(GL_MINOR_VERSION, minor); // minor = 2
      }
      else
      {
            *minor = ver[2] - '0';
      }

      // GLSL
      ver = (char*)glGetString(GL_SHADING_LANGUAGE_VERSION); // ver = "1.50 NVIDIA via Cg compiler"
}


It is also demonstrated how to read OpenGL Shading Language (GLSL) version. The function does not retrieve GLSL version. The comment shows how the returned string looks like.

Let’s go back to attributes. The attributes are defined as a list of pairs (name, value) terminated with 0. If certain attributes are not defined in the list, the default values are used.

WGL_CONTEXT_MAJOR_VERSION_ARB and WGL_CONTEXT_MINOR_VERSION_ARB define version of GL context we want to create. If not defined the default values are 1 and 0 respectively. That means "the latest version that also supports GL 1.0". In the case of GL 3.2 it is a GL 3.2 compatibility profile.

WGL_CONTEXT_PROFILE_MASK_ARB defines which “profile” the context should support. There are two profiles:
   - core and
   - compatibility.

If we create the core profile, only undeprecated functions can be used. The compatibility profile enables using all functionality, from GL 1.0 to GL 3.2. It is the superset of core profile. 

When GL 3.0 came to us, I thought that, so called deprecated functions, will die (and I was not very happy, because some of them were very useful). But, by default, GL 3.0 was backward compatible. Then GL 3.1 has come, and we have learnt that if we want to use deprecated functions we have to use extensions! Wow, it was something challenging, because drivers do not have to implement extensions. Everything out of the core is optional. So, we have to orient to core functions, reimplement geometric transformations, projections, and everything else. I thought the new age of GL has begun. GL 3.2 returned things where they stood almost a year ago. Now we have two profiles cohabitating without problems. Maybe the core profile will be better optimized, but ... maybe. After reading some posts of Mark Kilgard (http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=258516#Post258516 and http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=258666#Post258666) I'm sure that compatibility mode/profile/however-it-is-called will not be removed in foreseeable future.
 
For the core profile a predefined value WGL_CONTEXT_CORE_PROFILE_BIT_ARB (or hexadecimal value 0x00000001) should be used. This is the default value for WGL_CONTEXT_PROFILE_MASK_ARB attribute (GL 3.2)! To use compatibility mode set WGL_CONTEXT_PROFILE_MASK_ARB attribute to WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB (0x00000002). Previous implementations of OpenGL should ignore WGL_CONTEXT_PROFILE_MASK_ARB attribute. (I have tried WGL_CONTEXT_PROFILE_MASK_ARB on two older (3.0) versions of NV drivers and saw only trouble.)

It is very interesting that the profile attribute is implemented as a bitmask rather than an enumeration. Currently, only a single profile can be specified, but it is not unlikely that eventually there will be profiles defined than can be implemented in the same context.

The attribute WGL_CONTEXT_FLAGS_ARB specifies a set of flags affecting the rendering context.
   - If WGL_CONTEXT_DEBUG_BIT_ARB flag is set a “debug” context should be created. But currently (after three versions of GL) it still does not have any effects.
   - If WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB is set a “forward-compatible” context will be created. The “forward-compatible” context must not support functionality marked as “deprecated” by defined version of GL, while a non-forward-compatible context must support all functionality in that version, deprecated or not.

WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB had a predominant function in GL 3.0 and 3.1 contexts, but now when profiles are defined its function is pretty unclear.

Citation:
If the forward compatibility bit is set, the returned context must not implement deprecated functionality in that profile (nothing is deprecated from the OpenGL 3.2 compatibility profile, so a forward-compatible compatibility profile has exactly the same functionality as a non-forward-compatible compatibility profile, while a forward-compatible core profile would leave out only the handful of features - wide lines and two queries - deprecated from core OpenGL 3.2).

Drawing a Triangle

Now we are ready to set up other functions so that we can see something meaningful on the screen.

The function PrepareScene() creates shaders, loads code, compiles shaders, attaches them to a program, binds IDs to attributes locations, links and activates the program.

void CGLRenderer::PrepareScene(CDC *pDC)
{
      wglMakeCurrent(pDC->m_hDC, m_hrc);
      //---------------------------------
       glClearColor (1.0, 1.0, 1.0, 0.0);
 
       m_pProgram = new CGLProgram();
       m_pVertSh = new CGLShader(GL_VERTEX_SHADER);
       m_pFragSh = new CGLShader(GL_FRAGMENT_SHADER);
 
       if(!m_pVertSh->Load(_T("minimal.vert")))
              AfxMessageBox(_T("Vertex shader loading error!"));
  
       if(!m_pFragSh->Load(_T("minimal.frag")))
              AfxMessageBox(_T("Fragment shader loading error!"));
  
       if(!m_pVertSh->Compile())
              AfxMessageBox(_T("Vertex shader compiling error!"));
 
       if(!m_pFragSh->Compile())
              AfxMessageBox(_T("Fragment shader compiling error!"));
 
       m_pProgram->AttachShader(m_pVertSh);
       m_pProgram->AttachShader(m_pFragSh);
 
       m_pProgram->BindAttribLocation(0, "in_Position");
       m_pProgram->BindAttribLocation(1, "in_Color");
  
       if(!m_pProgram->Link())
        AfxMessageBox(_T("Program linking error!"));
 
       m_pProgram->Use();
 
       SetData();
       //---------------------------------
       wglMakeCurrent(NULL, NULL);
}

The function SetData() creates and fills VBOs with data, and enables vertex attribute arrays.

void CGLRenderer::SetData()
{
      float* vert = new float[9]; // vertex array
      float* col = new float[9]; // color array
 
      vert[0] = 0.0f; vert[1] = 0.8f; vert[2] =-1.0f;
      vert[3] =-0.8f; vert[4] =-0.8f; vert[5] =-1.0f;
      vert[6] = 0.8f; vert[7] =-0.8f; vert[8]= -1.0f;
 
      col[0] = 1.0f; col[1] = 0.0f; col[2] = 0.0f;
      col[3] = 0.0f; col[4] = 1.0f; col[5] = 0.0f;
      col[6] = 0.0f; col[7] = 0.0f; col[8] = 1.0f;
 
 
      glGenBuffers(2, &m_vboID[0]);
 
      glBindBuffer(GL_ARRAY_BUFFER, m_vboID[0]);
      glBufferData(GL_ARRAY_BUFFER, 9*sizeof(GLfloat), vert, GL_STATIC_DRAW);
      glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, 0, 0); 
      glEnableVertexAttribArray(0);
 
      glBindBuffer(GL_ARRAY_BUFFER, m_vboID[1]);
      glBufferData(GL_ARRAY_BUFFER, 9*sizeof(GLfloat), col, GL_STATIC_DRAW);
      glVertexAttribPointer((GLuint)1, 3, GL_FLOAT, GL_FALSE, 0, 0);
      glEnableVertexAttribArray(1);
 
      delete [] vert;
      delete [] col;
}

The function Reshape() sets a viewport.


void CGLRenderer::Reshape(CDC *pDC, int w, int h)
{
      wglMakeCurrent(pDC->m_hDC, m_hrc);
      //---------------------------------
      glViewport (0, 0, (GLsizei) w, (GLsizei) h); 
      //---------------------------------
      wglMakeCurrent(NULL, NULL);
}


The function DrawScene() actually draws the scene.

void CGLRenderer::DrawScene(CDC *pDC)
{
      wglMakeCurrent(pDC->m_hDC, m_hrc);
      //--------------------------------
      glClear(GL_COLOR_BUFFER_BIT);

      glDrawArrays(GL_TRIANGLES, 0, 3);
      //--------------------------------
      glFlush (); 
      SwapBuffers(pDC->m_hDC);
      wglMakeCurrent(NULL, NULL);
}


The function DestroyScene() cleans everything up.

void CGLRenderer::DestroyScene(CDC *pDC)
{
      wglMakeCurrent(pDC->m_hDC, m_hrc);
      //--------------------------------
      m_pProgram->DetachShader(m_pVertSh);
      m_pProgram->DetachShader(m_pFragSh);

      delete m_pProgram;
      m_pProgram = NULL;

      delete m_pVertSh;
      m_pVertSh = NULL;
      delete m_pFragSh;
      m_pFragSh = NULL;

      wglMakeCurrent(NULL,NULL); 
      //--------------------------------
      if(m_hrc) 
      {
            wglDeleteContext(m_hrc);
            m_hrc = NULL;
      }
}
And the shaders are exactly the same as in previous tutorial.

// minimal.vert
#version 150 core

in vec3 in_Position;
in vec3 in_Color;
out vec3 ex_Color;

void main(void)
{
      gl_Position = vec4(in_Position, 1.0);
      ex_Color = in_Color;
}


// minimal.frag
#version 150 core

// precision highp float;

in vec3 ex_Color;
out vec4 out_Color;

void main(void)
{
      out_Color = vec4(ex_Color,1.0);
}


Only version is changed to 150 and the core profile is selected. More about GLSL 1.5 will be covered in the next tutorial.

If everything is correct we will see a triangle eventually.


The first triangle 

Fig.1.1 The First Triangle


 
Visual Studio 6 project is available for downloading.

Next: GLSL 1.5 and the Input Blocks

All questions and/or suggestions can be posted on http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=262493&page=1 or to my e-mail (adimitrijevic73@gmail.com).


Free Hit Counters
ċ
GL3.2-Tut01.zip
(116k)
Aleksandar Dimitrijevic,
Mar 28, 2010, 5:39 AM
ċ
GL3.2-Tut01Exe.zip
(10k)
Aleksandar Dimitrijevic,
Mar 29, 2010, 1:19 PM
ą
Aleksandar Dimitrijevic,
Aug 17, 2009, 7:13 AM
Ċ
Aleksandar Dimitrijevic,
Aug 21, 2009, 2:46 AM
Comments