OpenGL+programming

//by Richard Russell, August 2006//

Introduction
This article explains how to interface with the **OpenGL** 3D graphics standard from //BBC BASIC for Windows//. It is //not// an OpenGL tutorial; you will need to refer to the [|OpenGL Reference Manual] and the [|OpenGL Programming Guide] in order to write OpenGL programs. **OpenGL** provides similar facilities to Microsoft's **Direct3D** (accessible from BBC BASIC using the [|D3DLIB] library) but, being an open standard, it is easier to port OpenGL programs from or to other platforms.

Declarations
In order to use OpenGL from BBC BASIC you will need to access the **OPENGL32.DLL** library supplied with Windows. You should include code similar to the following in your program (e.g. in an initialisation routine):

code format="bb4w" SYS "LoadLibrary", "OPENGL32.DLL" TO opengl% SYS "GetProcAddress", opengl%, "wglCreateContext" TO `wglCreateContext` SYS "GetProcAddress", opengl%, "wglDeleteContext" TO `wglDeleteContext` SYS "GetProcAddress", opengl%, "wglMakeCurrent"  TO `wglMakeCurrent` SYS "GetProcAddress", opengl%, "glClear"         TO `glClear` code Here only one standard OpenGL function **glClear** has been listed, but you should include every function that your program requires (the full list runs to more than 100 functions so you probably won't want to include every one). In this example each function address is assigned to a variable whose name is the function name enclosed in 'back quotes' (grave accent or CHR$96 characters). You need not follow this convention if you prefer not to, so long as you adhere to BBC BASIC's variable naming rules; for example you could use **_glClear** or **glClear%** as the variable name.

As well as the standard OpenGL functions you are quite likely to need one or more of the routines from the **OpenGL Utility Library** (GLU). These are contained in the file **GLU32.DLL**:

code format="bb4w" SYS "LoadLibrary", "GLU32.DLL" TO glu% SYS "GetProcAddress", glu%, "gluPerspective" TO `gluPerspective` code Again only one function has been listed in this example; there are nearly 40 in all.

Note that in many cases there are two alternative functions, one accepting //single-precision// (32-bit) floating-point numbers and the other accepting //double-precision// (64-bit) floating-point numbers. For example there are two variants of the glTranslate function: **glTranslatef** and **glTranslated**. When you have a choice, and assuming you don't need the extra accuracy of the //double// version, it is slightly easier to pass single-precision floats (by value) from BBC BASIC for Windows. See below for more details.

Some functions offer even more options, for example the **glColor3** function comes in 8 varieties with its parameters having different data types (float, double, byte, short, int, unsigned byte, unsigned short and unsigned int). Since the **SYS** statement only passes integers (signed 32-bit values) your code will be made most straightforward by choosing the **glColor3i** variant.

Initialisation
Once you have declared the OpenGL functions you need to use, you can start writing your program proper. The first step is to set up your main output window to the size you require. You can do that using any of the normal methods provided in BBC BASIC: the **MODE** statement, the **VDU 23,22** command or via the Windows API using **SYS**.

The next step is to select the wanted **pixel format**, for example the number of bits-per-pixel for the colour and for the //depth map//. This is a two-stage process: you first request a //preferred// format using **ChoosePixelFormat** in response to which Windows offers the nearest match to that format available with the current configuration (graphics card etc.). Assuming the offered format is acceptable you then select that format using **SetPixelFormat**. To achieve this you will need to incorporate code similar to the following:

code format="bb4w" _PFD_MAIN_PLANE = 0 _PFD_TYPE_RGBA = 0 _PFD_TYPE_COLORINDEX = 1 _PFD_DOUBLEBUFFER = 1 _PFD_DRAW_TO_WINDOW = 4 _PFD_SUPPORT_OPENGL = &20 DIM pfd{nSize{l&,h&}, nVersion{l&,h&}, dwFlags%, iPixelType&, cColorBits&, \ \      cRedBits&, cRedShift&, cGreenBits&, cGreenShift&, cBlueBits&, cBlueShift&, \ \      cAlphaBits&, cAlphaShift&, cAccumBits&, cAccumRedBits&, cAccumGreenBits&, \ \      cAccumBlueBits&, cAccumAlphaBits&, cDepthBits&, cStencilBits&, cAuxBuffers&, \ \      iLayerType&, bReserved&, dwLayerMask%, dwVisibleMask%, dwDamageMask%} pfd.nSize.l& = DIM(pfd{}) : REM sizeof(PIXELFORMATDESCRIPTOR) pfd.nVersion.l& = 1 pfd.dwFlags% = _PFD_DRAW_TO_WINDOW OR _PFD_SUPPORT_OPENGL OR _PFD_DOUBLEBUFFER pfd.dwLayerMask% = _PFD_MAIN_PLANE pfd.iPixelType& = _PFD_TYPE_COLORINDEX pfd.cColorBits& = 8 pfd.cDepthBits& = 16 SYS "GetDC", @hwnd% TO ghDC% SYS "ChoosePixelFormat", ghDC%, pfd{} TO pixelformat% IF pixelformat% = 0 ERROR 100, "ChoosePixelFormat failed" SYS "SetPixelFormat", ghDC%, pixelformat%, pfd{} TO res% IF res% = 0 ERROR 100, "SetPixelFormat failed" SYS `wglCreateContext`, ghDC% TO ghRC% SYS `wglMakeCurrent`, ghDC%, ghRC% code This code requests an //indexed// (paletted) colour selection with 8 bits per pixel, i.e. 256 different colours in all. Alternatively it could have requested, for example, a pixel type of **_PFD_TYPE_RGBA** and a **pfd.cColorBits&** value of **24**.

Cleanup
When you exit your program you should delete the **rendering context** and **device context** created above. You can do that using code similar to the following:

code format="bb4w" DEF PROCcleanup ghRC% += 0 : IF ghRC% SYS `wglDeleteContext`, ghRC% : ghRC% = 0 ghDC% += 0 : IF ghDC% SYS "ReleaseDC", @hwnd%, ghDC% : ghDC% = 0 ENDPROC code You should place this procedure out of the way, for example at the end of your program after the **END** statement.

You will want to incorporate **ON ERROR** and **ON CLOSE** statements to ensure that PROCcleanup is executed even if your program is terminated unexpectedly:

code format="bb4w" ON CLOSE PROCcleanup : QUIT ON ERROR PROCcleanup : SYS "MessageBox", @hwnd%, REPORT$, 0, 48 : QUIT code

OpenGL code
Once you have executed the Windows and BBC BASIC specific code above, you can commence the OpenGL code proper. The main considerations in making OpenGL calls from //BBC BASIC for Windows// are related to passing floating-point values, since normally only integer values can be passed //by value// using the **SYS** statement.

To pass a single-precision (32-bit) floating-point value use the **FN_f4** function in the [|D3DLIB] library. For example when calling the **glTranslatef** function, which takes three single-precision floating-point values, you would use code similar to the following:

code format="bb4w" SYS `glTranslatef`, FN_f4(x), FN_f4(y), FN_f4(z) code To pass a double-precision (64-bit) floating-point value use the **FN_dl** and **FN_dh** functions listed at the end of this article. For example when calling the **glTranslated** function, which takes three double-precision floating-point values, you would use code similar to the following:

code format="bb4w" SYS `glTranslated`, FN_dl(x), FN_dh(x), FN_dl(y), FN_dh(y), FN_dl(z), FN_dh(z) code Note particularly that each //double// parameter must be passed as a pair of values, the first using **FN_dl** and the second using **FN_dh**. When there is a choice, passing single-precision values will usually be easier.

Some OpenGL functions require an array of values. In this case it is easier to pass a double-precision array, since BBC BASIC for Windows supports this data type natively (in ***FLOAT64** mode). For example to call **glLoadMatrixd** you would use code similar to the following:

code format="bb4w" DIM matrix(3,3) matrix = a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 matrix *= 1.0 SYS `glLoadMatrixd`, ^matrix(0,0) code Note that for this to work your program must be in ***FLOAT64** mode.

Rendering
OpenGL under Windows uses a //double-buffering// scheme. Objects are first rendered to an off-screen bitmap and then, when the entire frame is complete, //swapped// onto the display. The rendering loop will typically be of the following form:

code format="bb4w" _GL_DEPTH_BUFFER_BIT = &0100 _GL_COLOR_BUFFER_BIT = &4000

REPEAT WAIT 2 SYS `glClear`, _GL_COLOR_BUFFER_BIT OR _GL_DEPTH_BUFFER_BIT

REM OpenGL code to render the scene here...

SYS "SwapBuffers", ghDC% UNTIL FALSE code

Support functions
The functions listed below are used when passing double-precision floating-point values:

code format="bb4w" REM Convert to 8-byte double (low 4 bytes) DEF FN_dl(A#) A#*=1.0# =!^A# REM Convert to 8-byte double (high 4 bytes) DEF FN_dh(A#) A#*=1.0# =!(^A#+4) code