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.
|
Connect with Us