Setting up OpenGL in C++Builder

By: John Ray Thomas

Abstract: This article by JT is a quick introduction to setting up OpenGL drawing on a TForm's TCanvas

Setting up OpenGL in C++Builder

This article discusses setting up a VCL application to allow OpenGL drawing on the main form's TCanvas. To set up OpenGL for use in Windows there are specific Windows functions that need to be called to associate OpenGLs renderer, the HRC or "rendering context", to its needed drawing medium, the Windows HDC. In the VCL, a Windows HDC is encapsulated by a TCanvas component.

The required OpenGL setup functions are referred to as "wiggle" because of their function name prefix, wgl. Take a look at the following listing which shows the minimum amount of code to make your C++Builder program OpenGL ready. We will then discuss each part of this example. This source code, along with a C++Builder 4 project file, can be downloaded from CodeCentral, article 14011.

An OpenGL Skeleton for C++Builder

glskeleton.h
//--------------------------------------------------------------------------- 
#include <vclvcl.h> 

//--------------------------------------------------------------------------- 
#ifndef GLSkeletonH 
#define GLSkeletonH 
//--------------------------------------------------------------------------- 
#include <vclClasses.hpp> 
#include <vclControls.hpp> 
#include <vclStdCtrls.hpp> 
#include <vclForms.hpp> 
#include <gl/gl.h> 
#include <gl/glu.h> 
//--------------------------------------------------------------------------- 
class TForm1 : public TForm 
{ 
published:    // IDE-managed Components 
    void __fastcall FormCreate(TObject *Sender); 
    void __fastcall FormDestroy(TObject *Sender); 
    void __fastcall FormResize(TObject *Sender); 
    void __fastcall FormPaint(TObject *Sender); 
private:    // User declarations 
    HDC hdc;
    HGLRC hrc; 
    int PixelFormat; 
public:        // User declarations 
    fastcall TForm1(TComponent* Owner); 
    void __fastcall IdleLoop(TObject*, bool&); 
    void __fastcall RenderGLScene(); 
    void __fastcall SetPixelFormatDescriptor(); 
}; 

glskeleton.cpp
//--------------------------------------------------------------------------- 
extern TForm1 *Form1; 
//--------------------------------------------------------------------------- 
#endif 

//--------------------------------------------------------------------------- 
#include <vclvcl.h> 
#pragma hdrstop 

#include "GLSkeleton.h" 
//--------------------------------------------------------------------------- 
#pragma resource "*.dfm" 

TForm1 *Form1; 
//--------------------------------------------------------------------------- 
__fastcall TForm1::TForm1(TComponent* Owner) 
    : TForm(Owner) 
{ 
    Application->OnIdle = IdleLoop; 
    _control87(MCW_EM, MCW_EM);
} 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::IdleLoop(TObject*, bool& done) 
{ 
     done = false; 
     RenderGLScene(); 
     SwapBuffers(hdc); 
} 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::RenderGLScene() 
{ 
     //Place your OpenGL drawing code here
} 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::FormCreate(TObject *Sender) 
{ 
    hdc = GetDC(Handle); 
    SetPixelFormatDescriptor(); 
    hrc = wglCreateContext(hdc); 
    wglMakeCurrent(hdc, hrc); 
    SetupRC(); 
} 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::SetupRC() 
{ 
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 
    glClear(GL_COLOR_BUFFER_BIT); 
    glFlush(); 
} 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::FormDestroy(TObject *Sender) 
{ 
    ReleaseDC(hdc);
    wglMakeCurrent(hdc, NULL); 
    wglDeleteContext(hrc); 
} 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::SetPixelFormatDescriptor() 
{ 
    PIXELFORMATDESCRIPTOR pfd = { 
        sizeof(PIXELFORMATDESCRIPTOR), 
        1, 
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, 
        PFD_TYPE_RGBA, 
        24, 
        0,0,0,0,0,0, 
        0,0, 
        0,0,0,0,0, 
        32, 
        0, 
        0, 
        PFD_MAIN_PLANE, 
        0, 
        0,0,0 
        }; 
    PixelFormat = ChoosePixelFormat(hdc, &pfd); 
    SetPixelFormat(hdc, PixelFormat, &pfd); 
} 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::FormResize(TObject *Sender) 
{ 
    GLfloat nRange = 200.0f; 
    glViewport(0, 0, ClientWidth, ClientHeight); 
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity(); 
	
    
    if (ClientWidth <= ClientHeight) 
       glOrtho(-nRange, nRange, -nRange*ClientHeight/ClientWidth, 
       			nRange*ClientHeight/ClientWidth, -nRange, nRange); 
    else 
       glOrtho(-nRange*ClientWidth/ClientHeight, nRange*ClientWidth/ClientHeight, 
       			-nRange, nRange, -nRange, nRange); 

    glMatrixMode(GL_MODELVIEW); 
    glLoadIdentity(); 
} 
//--------------------------------------------------------------------------- 

Let's first discuss the GLSkeleton class declaration. It should be very familiar to regular C++Builder users. There were three member variables declared, of type HDC, HRC and int to store a pixel format, in the private section of our class.
Three user defined functions have also been declared.

  • IdleLoop is used to handle the TApplication::OnIdle event.
  • RenderGLScene is called from the idle loop and is where all OpenGL drawing code should be placed.
  • SetPixelFormatDescriptor is used to setup Windows for the OpenGL drawing that will be performed on the TCanvas (HDC) .
You should also notice some standard TForm events declared.

O.K. Let's look at the implementation now. We will talk about these in the order that they appear in GLSkeleton.cpp.

In the constuctor the IdleLoop function is assigned the Application->OnIdle event. Also a call is made to the RTL to mask all floating point exceptions.

A few quick notes on _control87. This function is used to control the behavior of the FPU. The flags that I am passing (MCW_EM) in this call tell the FPU not to throw any exceptions. This is useful when calling into MS libraries that perform FP calculations partly due to the difference in how floating point values are stored. The flag MCW_EM suppresses all FPU exceptions but this can be fine tuned. See float.h for more info.

The next function is the idle loop. The first thing is setting the done variable to false to keep the loop going. Then a call is made to RenderGLScene. Finally, the drawing buffer is flipped into view with the call SwapBuffers. A double buffer is used to minimize screen flicker.

The RenderGLScene is next and this is where OpenGL code that must be called for every frame is placed. For instance, all drawing and transformation code would go here.

The first call in the OnCreate event handler is to the Windows API call GetDC. This is necessary to ensure that a good copy of the form's HDC is around at all times. SetPixelFormatDescriptor tells windows that the HDC will support OpenGL. More on this shortly. Next we start seeing some "wiggle" calls being made. The first one wglCreateContext takes an HDC and returns an HRC, OpenGL's rendering context. The next call wglMakeCurrent(hdc, hrc) associates the HRC to a specific HDC. This is useful in MDI apps where many children will be using OpenGL code, for instance. The last "initialization" call is SetupRC. This sets the background color of the HRC and clears the color buffer with that color.

The OnDestroy event handler does cleanup. It calls ReleaseDC and two wiggle functions, wglMakeCurrent and wglDeleteContext.

SetPixelFormatDescriptor is probably the most important function in the setup of OpenGL in a WIndows application. It is here that we create an HDC capable of supporting OpenGL and that is double buffered. This entails creating a PIXELFORMATDESCRIPTOR structure and setting the PFD_SUPPORT_OPENGL and PFD_DOUBLE_BUFFERED flags. Next call two functions, ChoosePixelFormat and SetPixelFormat, to set the passed HDC up with these capabilities. More info on this structure and all "wiggle" calls can be found in the OpenGL Reference help file (opengl.hlp) under the MSHelp directory in the Borland Shared directory.

The OnResize event handler simply sets up an orthographic projection for the camera based on the aspect ratio of the client area.

One of the cool things about C++Builder is its code repository which saves you from all the work involved in setting up a new project for the correct library support and all the code and event handling that will need to be rewritten. To save the OpenGL Skeleton to the repository, copy GLSkeleton.bpr from the CodeCentral database download to a base directory of your OpenGL projects, open it in the editor and choose Projects | Add to Repository. In the ensuing dialog set the title to OpenGL skeleton and choose Forms for the page. To reuse it choose File | New | Forms and choose OpenGL Skeleton. In future OpenGL articles I will be using this skeleton as starting point for new projects.

In this article I have shown how to setup OpenGL in a VCL application. If you have specific requests regarding OpenGL or comments about this article please email me.



Server Response from: ETNASC03