May I be lucky enough to pick my ass up and put it back onto my chair, then also say thanks for the well documented and detailed example!


I myself have toyed with OpenGL via perl before, but have never thought of combining it with Win32::GUI myself. Seeing as I'm a  maintainer for a game, and some people have asked for updated tools with animations from said game.. I may someday put this example to a very literal use.

Thanks again,

Jason P

On Thu, May 21, 2009 at 11:18 PM, Kevin Marshall <kejohm88@hotmail.com> wrote:
Hey,
 
This lengthy post is about how to use the Win32::GUI module together with the Perl OpenGL module (POGL).
 
I would first like to thank the developers of Perl-Win32-GUI module. I have been using this module for a while now, and prefer using this module than coding the whole thing in C/C++. Thanks. I have decided to give back to the Win32::GUI community by way of sharing a solution for using OpenGL in conjunction with the Win32::GUI module. I hope that someone out there will find this useful.
 
After reading the book OpenGL Game Programming (Premier Press), I decided to port some of the code examples to Perl. Since the Win32::GUI module makes the creation of windows easy, it was simply a matter of converting the relevant C/C++ code into Perl. A basic knowledge of OpenGL is needed in order to understand the example that I have provided. The example contains comments that should explain what each section does, but I'll provide a brief overview of the process. I'll only be discussing the Windows+OpenGL specific code, so if you don't have a good grasp of OpenGL, I recommend finding a good book or website about OpenGL programming before going any further. Detailed information regarding Windows API functions, and OpenGL functions can be found in the Windows Software Development Kit Documentation.
 
OpenGL is a powerful, low-level rendering and modelling software library. OpenGL does not provide higher functionality, such as windowing, in order to remain platform independent. OpenGL uses a rendering context to store OpenGL commands and settings. This means that each platform must provide functionality to specify a rendering context for OpenGL. For UNIX systems, this is provided by GLX. For Windows this is provided by a set of functions affectionately known as 'wiggle' functions, since most of these start with 'wgl'. There are a number of other Win32 API functions that are also needed. These 'wiggle' and Win32 API functions relate Windows Device Contexts with the OpenGL rendering contexts. The Win32::API module is needed to import these functions for use in the program.
 
Example Program Description: 
 
Lines 21-51 show some setup required for the functions that are to be imported. This includes creating the structure PIXELFORMATDESCRIPTOR used by the SetPixelFormat() function and creating a typedef for the wglMakeCurrent() function. Lines 52-63 show the functions that need to be imported. Info on these functions can be found in the SDK docs.
 
Lines 64-100 creates the main window and sets its icon. This should be fairly basic for anyone experienced with the Win32::GUI module, so I'll just cover the important parts. Line 68 sets the -left of the window to CW_USEDEFAULT. This specifies that the system should position the window. Lines 69-71 add the window styles WS_CLIPCHILDREN  and WS_CLIPSIBLINGS. These affect how the window will be painted. For more info about these styles, see the SDK docs. Lines 72-76 setup an -onTerminate event handler. This will be called when the window is destroyed. At this point the rendering context is deselected from the main window Device Context and is then deleted. More on these functions later. Just know that each takes a handle to a device or rendering context. The sub returns -1 to exit the main loop. For those of you familiar with C/C++ Windows programming, this function roughly corresponds to the WM_CLOSE message. Lines 77-90 setup an -onResize event handler. This is called whenever the window is resized and corresponds to the WM_SIZE message. The functions in the sub are OpenGL specific and basically just reset the viewport to the new dimensions and resets the perspective. Refer to the SDK docs or a good OpenGL resource for info about these functions. Lines 91-95 setup a -onKeyDown event handler which exits the program when the ESC key is pressed.
 
Lines 101-106 are where the device and rendering contexts are setup. Line 101 gets the device context of the main window and stores it in a global variable. Lines 102-104 calls the SetupPixelFormat() function, passing it the handle to the main window device context. If this sub fails the program exits. Lines 116-157 show the SetupPixelFormat() function. Line 119 creates a new PIXELFORMATDESCRIPTOR structure and lines 120-147 fills the structure with appropriate data. See the SDK docs for more info about this structure. Lines 148-151 calls the ChoosePixelFormat() function passing the handle to the DC and the PIXELFORMATDESCRIPTOR structure. This function chooses the best matching pixel format for the DC from the data specified in the PIXELFORMATDESCRIPTOR structure. The function returns 0 if not pixel format can be found. Lines 152-155 set the pixel format of the DC to the format returned from the ChoosePixelFormat() function and returns 0 if it fails. The sub returns 1 to show that it succeeded.  Line 105 creates an OpenGL rendering context from the specified DC using the wglCreateContext() function. The function is passed the handle to the main window DC and returns the handle to a rendering context. Line 106 selects the rendering context into the device context using the wglMakeCurrent() function. This functions is passed the handle to the main window DC and the handle to the rendering context created with wglCreateContext(). Passing 0 as the rendering context causes the rendering context to be deselected, such as in the -onTerminate event handler above. The wglDeleteContext() function is used to delete a rendering context and should be used after the rendering context has been deselected. The deselection and deletion of a rendering context should be performed when a window is destroyed, which is why this is done in the -onTerminate event handler above. Lines 101-106 could be thought of as equivalent to the WM_CREATE message (you could even use the Hook() method to implement this).
 
Lines 107-111 show some basic setup of OpenGL before any rendering is done. These are OpenGL specific, so if you are unsure of what these do, refer to the docs.
 
Lines 113-115 setup the main message loop. Win32::GUI::Dialog() can't be used here because the Render() function has to be called every frame. This loop is essentially the same, but a few differences are present which may affect applications with multiple windows, since the Win32::GUI::Dialog() functions does more behind the scenes. It would be nice if the Win32::GUI::Dialog() function accepted a sub ref which could be called every frame, but I'm not sure how easy that would be to create. Anyway, this does the job fine, but there are probably better methods.
 
Lines 158-172 and 173-188 are the DrawCube() and Render() functions, respectively. These are used to draw the cube every frame and are OpenGL specific. Refer to the docs about what the OpenGL functions do if you are unsure.
 
Line 189-193 creates a Handle() method in the Win32::GUI::DC package. This method returns the handle of the object passed in. Putting it in the DC package allows both windows and DCs to access it. This method is used when the OpenGL/Win32API function requires a handle. Since the objects store in handle internally, it makes it really easy to pass this value to the functions that need it.
 
Here is my code example:
 
################################################################################
#
# Win32::GUI + OpenGL example
#
# This program demonstrates a basic example of using the Perl Win32::GUI
#  module in conjunction with the Perl OpenGL module to render a spinning
#  cube of points in the window.
#
# Requirements:
#  Perl,
#  Win32::GUI,
#  Win32::GUI::Carp,
#  Win32::API, and
#  OpenGL
#
# This program was written using ActiveState Perl 5.8.8 Build 820 running on
#  Windows XP and using Win32::GUI v1.06, Win32::GUI::Carp v1.01,
#  Win32::API v0.46, and OpenGL v0.56
#
# Parts of this program are based on example code from the book OpenGL Game
#  Programming (Premier Press, 2004) from the Premier Press Game
#  Development Series. I recommend this book for anyone interested in using
#  OpenGL for developing games on Windows. The book is written for the
#  development of games written in C/C++ and assumes an advanced knowledge
#  of the language but provides an in-depth look at OpenGL programming on
#  Windows platforms, as well as a look at using DirectInput and
#  DirectX Audio.
#
################################################################################

 
1: use strict;
2: use warnings;
#these constants are needed for SetPixelFormat() but aren't defined in Win32::GUI
3: use constant {
4:  PFD_TYPE_RGBA => 0,
5:  PFD_DOUBLEBUFFER => 0x00000001,
6:  PFD_DRAW_TO_WINDOW => 0x00000004,
7:  PFD_SUPPORT_OPENGL => 0x00000020,
8:  PFD_MAIN_PLANE => 0,
9: };

 
10: use OpenGL qw(:glfunctions :glconstants :glufunctions);
11: use Win32::API;
12: use Win32::GUI qw();
13: use Win32::GUI::Carp qw(warningsToDialog fatalsToDialog immediateWarnings winwarn windie);
14: use Win32::GUI::Constants qw(IDI_APPLICATION WS_CLIPCHILDREN WS_CLIPSIBLINGS
15:  WM_CREATE WM_SIZE WM_CLOSE VK_ESCAPE CW_USEDEFAULT MB_OK MB_ICONEXCLAMATION);

 
16: my $g_HDC;  #global handle to device context
17: my $hRC;  #handle to rendering context

 
#keep track of rotation of cube
18: my $objectXRot = 0.0;
19: my $objectYRot = 0.0;
20: my $objectZRot = 0.0;

 
#define PIXELFORMATDESCRIPTOR struct used for the SetPixelFormat function
#refer to the Windows SDK documentation for more info about this structure
21: Win32::API::Struct->typedef(
22:  PIXELFORMATDESCRIPTOR => qw(
23:   WORD nSize;
24:   WORD nVersion;
25:   DWORD dwFlags;
26:   BYTE iPixelType;
27:   BYTE cColorBits;
28:   BYTE cRedBits;
29:   BYTE cRedShift;
30:   BYTE cGreenBits;
31:   BYTE cGreenShift;
32:   BYTE cBlueBits;
33:   BYTE cBlueShift;
34:   BYTE cAlphaBits;
35:   BYTE cAlphaShift;
36:   BYTE cAccumBits;
37:   BYTE cAccumRedBits;
38:   BYTE cAccumGreenBits;
39:   BYTE cAccumBlueBits;
40:   BYTE cAccumAlphaBits;
41:   BYTE cDepthBits;
42:   BYTE cStencilBits;
43:   BYTE cAuxBuffers;
44:   BYTE iLayerType;
45:   BYTE bReserved;
46:   DWORD dwLayerMask;
47:   DWORD dwVisibleMask;
48:   DWORD dwDamageMask;
49:  )
50: );

 
#needed for the wglMakeCurrent functions
51: Win32::API::Type->typedef('HGLRC', 'HANDLE');

 
#import some extra functions
#more info can be found in the Windows SDK documentation

 
#exchanges the front and back buffers of the current pixel format
52: Win32::API->Import('gdi32', 'BOOL SwapBuffers(HDC hdc);')
53:  or windie "Win32::API->Import(SwapBuffers): $^E";

 
#attempts to match an appropriate pixel format supported by a device context to
# a given pixel format specification.
54: Win32::API->Import('gdi32', 'int ChoosePixelFormat(HDC hdc, PIXELFORMATDESCRIPTOR * ppfd);')
55:  or windie "Win32::API->Import(ChoosePixelFormat): $^E";

 
#sets the pixel format of the specified device context to the format specified
# by the iPixelFormat index returned from ChoosePixelFormat().
56: Win32::API->Import('gdi32', 'BOOL SetPixelFormat(HDC hdc, int iPixelFormat, PIXELFORMATDESCRIPTOR * ppfd);')
57:  or windie "Win32::API->Import(SetPixelFormat): $^E";

 
#creates a new OpenGL rendering context, which is suitable for drawing on the
# device referenced by hdc.
58: Win32::API->Import('opengl32', 'HGLRC wglCreateContext(HDC hdc);')
59:  or windie "Win32::API->Import(wglCreateContext): $^E";

 
#makes a specified OpenGL rendering context the calling thread's current
# rendering context.
60: Win32::API->Import('opengl32', 'BOOL wglMakeCurrent(HDC hdc, HGLRC hglrc);')
61:  or windie "Win32::API->Import(wglMakeCurrent): $^E";

 
#deletes a specified OpenGL rendering context.
62: Win32::API->Import('opengl32', 'BOOL wglDeleteContext(HGLRC hglrc);')
63:  or windie "Win32::API->Import(wglDeleteContext): $^E";


#create main window
64: my $main = Win32::GUI::Window->new(
65:  -name => "main",
66:  -text => "OpenGL Example: Colour Cube",
67:  -size => [640,480],
68:  -left => CW_USEDEFAULT,      #let system position window
69:  -pushstyle =>
  #Excludes the area occupied by child windows when drawing occurs
  # within the parent window.
70:   WS_CLIPCHILDREN |
  #When a particular child window needs to be painted, all other overlapping
  # child windows are clipped out of the region of the child window to be updated.
71:   WS_CLIPSIBLINGS,
72:  -onTerminate => sub {      #WM_CLOSE
73:   wglMakeCurrent($g_HDC->Handle(), 0); #deselect rendering context from $hDC
74:   wglDeleteContext($hRC);     #delete rendering context $hRC
75:   return -1;        #exit main loop
76:  },
77:  -onResize => sub {       #WM_SIZE
78:   my $self = shift;
79:   return 0 unless $self;
80:   my $height = $self->Height();   #get height and width
81:   my $width = $self->Width();
82:   $height = 1 if $height == 0;   #don't divide by 0
83:   glViewport(0,0,$width,$height);   #set viewport to new dimensions
84:   glMatrixMode(GL_PROJECTION);   #set matrix mode to projection matrix
85:   glLoadIdentity();      #reset projection matrix
86:   gluPerspective(54.0, $width / $height, 1.0, 1000.0); #calculate aspect ratio of window
87:   glMatrixMode(GL_MODELVIEW);    #set modelview matrix
88:   glLoadIdentity();      #reset modelview matrix
89:   return 1;
90:  },
91:  -onKeyDown => sub {       #WM_KEYDOWN
92:   my ($self, $flags, $key) = @_;
93:   return -1 if $key == VK_ESCAPE;  #exit if escape key pressed
94:   return 1;
95:  },
96: );
97: unless($main){
98:  windie("Cannot create window: $^E");
99: }
100: $main->SetIcon(Win32::GUI::Icon->new(IDI_APPLICATION)); #set window icon
 
#WM_CREATE
101: $g_HDC = $main->GetDC();      #set global device context to device context of main window
102: unless(SetupPixelFormat($g_HDC->Handle())){ #setup pixel format for device context
103:  exit 1;          #exit if setup fails
104: }
105: $hRC = wglCreateContext($g_HDC->Handle());  #create rendering context used by OpenGL to draw
106: wglMakeCurrent($g_HDC->Handle(), $hRC);   #select rendering context $hRC into $g_HDC
 
#Initialize OpenGL
107: glShadeModel(GL_SMOOTH);      #set shading to smooth
108: glEnable(GL_DEPTH_TEST);      #do depth comparisons and update the depth buffer
109: glEnable(GL_CULL_FACE);       #cull polygons based on their winding in window coordinates
110: glFrontFace(GL_CCW);       #The orientation of front-facing polygons
111: glClearColor(0.0, 0.0, 0.0, 0.0);    #values that glClear uses to clear the colour buffers
 
112: $main->Show();         #show window
 
#message event
#This is necessary because Render() needs to be called every frame.
#This can produce interesting results in more complex applications (with more
# than one window) since the Win32::GUI::Dialog() function does more than just
# check if a sub has returned -1 (perhaps the ability to call a code block every
# iteration of Win32::GUI::Dialog() would be useful)
113: while(Win32::GUI::DoEvents() != -1){
114:  Render();
115: }
 
#This function is used to set the pixel format for the device context.
# Accepts a handle to the device context of the window and returns true if succeeds
# or false if fails.
#Adapted from code from OpenGL Game Programming (Premier Press, 2004)
116: sub SetupPixelFormat {
117:  my $hDC = shift;       #is a handle to DC
118:  my $nPixelFormat;
119:  my $pfd = Win32::API::Struct->new('PIXELFORMATDESCRIPTOR'); #create new structure
120:  $pfd->{nSize} = $pfd->sizeof();    #return sizeof structure
121:  $pfd->{nVersion} = 1;      #default version
122:  $pfd->{dwFlags} = PFD_DRAW_TO_WINDOW |  #window drawing support
123:   PFD_SUPPORT_OPENGL |      #OpenGL support
124:   PFD_DOUBLEBUFFER;      #double buffering support
125:  $pfd->{iPixelType} = PFD_TYPE_RGBA;   #rgba colour mode
126:  $pfd->{cColorBits} = 32;     #32 bit colour mode
127:  $pfd->{cRedBits} = 0;      #ignore colour bits
128:  $pfd->{cRedShift} = 0;      #
129:  $pfd->{cGreenBits} = 0;      #
130:  $pfd->{cGreenShift} = 0;     #
131:  $pfd->{cBlueBits} = 0;      #
132:  $pfd->{cBlueShift} = 0;      #
133:  $pfd->{cAlphaBits} = 0;      #not alpha buffer
134:  $pfd->{cAlphaShift} = 0;     #ignore alpha shift bit
135:  $pfd->{cAccumBits} = 0;      #no accumulation buffer
136:  $pfd->{cAccumRedBits} = 0;     #ignore accumulation bits
137:  $pfd->{cAccumGreenBits} = 0;    #
138:  $pfd->{cAccumBlueBits} = 0;     #
139:  $pfd->{cAccumAlphaBits} = 0;    #
140:  $pfd->{cDepthBits} = 16;     #16 bit z-buffer size
141:  $pfd->{cStencilBits} = 0;     #no stencil buffer
142:  $pfd->{cAuxBuffers} = 0;     #no auxiliary buffer
143:  $pfd->{iLayerType} = PFD_MAIN_PLANE;  #main drawing plane
144:  $pfd->{bReserved} = 0;      #reserved
145:  $pfd->{dwLayerMask} = 0;     #layer masks ignored
146:  $pfd->{dwVisibleMask} = 0;     #
147:  $pfd->{dwDamageMask} = 0;     #
 # choose best matching pixel format
148:  unless( $nPixelFormat = ChoosePixelFormat($hDC, $pfd) ){
149:   winwarn("Can't find an appropriate pixel format");
150:   return 0;
151:  }
 # set pixel format to device context
152:  unless( SetPixelFormat($hDC, $nPixelFormat, $pfd) ){
153:   winwarn("Unable to set pixel format");
154:   return 0;
155:  }
156:  return 1;
157: }
 
#This function draws the cube of points. The colour of each point is based on its position
#Adapted from code from OpenGL Game Programming (Premier Press, 2004)
158: sub DrawCube {
159:  glPointSize(2.0); #set size of points drawn
160:  glPushMatrix();
161:   glBegin(GL_POINTS);
162:    for(my $x = 0.0; $x <= 1.0; $x += 0.1){
163:     for(my $y = 0.0; $y <= 1.0; $y += 0.1){
164:      for(my $z = 0.0; $z <= 1.0; $z += 0.1){
165:       glColor3f($x,$y,$z);
      #move and scale vertexes so that cube rotates about centre
166:       glVertex3f($x*50-25,$y*50-25,$z*50-25);
167:      }
168:     }
169:    }
170:   glEnd();
171:  glPopMatrix();
172: }
 
#This function is used to draw the cube and is called every frame
#Adapted from code from OpenGL Game Programming (Premier Press, 2004)
173: sub Render {
174:  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #clear color and depth buffer
175:  glLoadIdentity();         #replaces the current matrix with the identity matrix.
176:  glTranslatef(0.0, 0.0, -150.0);      #move to 0,0,-150
177:  glPushMatrix();          #save current matrix
178:   glRotatef($objectXRot, 1.0, 0.0, 0.0);   #rotate around x axis
179:   glRotatef($objectYRot, 0.0, 1.0, 0.0);   #rotate around y axis
180:   glRotatef($objectZRot, 0.0, 0.0, 1.0);   #rotate around z axis
181:   DrawCube();          #draw cube
182:  glPopMatrix();          #restore matrix
183:  glFlush();           #clear buffers
184:  SwapBuffers($g_HDC->Handle());      #exchange front and back buffers of device context
185:  $objectXRot += 0.01;        #increment rotation
186:  $objectYRot += 0.02;
187:  $objectZRot += 0.01;
188: }
 
#Conveniently, the Windows specific functions for setup of OpenGL accept and return
# handles to windows or contexts, which are just numbers, and the handles to
# these are stored in the Window and DC objects created by Win32::GUI. This method
# provides an easy access to this value. Placing the method in the Win32::GUI::DC
# package allows both Windows and DCs to use it.
189: package Win32::GUI::DC;
190: sub Handle {
191:  return shift->{-handle};
192: }
193: __END__
 
Here are some tips and tricks regarding using Win32::GUI module and the OpenGL module that I have come across on my travels:
 
Win32::GUI:

  • It is possible to create child windows that are rendered to using OpenGL instead of rendering to the parent window, but requires a lot more setup. One of the tricks needed is to specify the right styles for the window. I have yet to perfect this technique, but perhaps when I get it working correctly I'll post an example.
 
OpenGL:
  • try to use the *_p variants of functions, if they exist. The function has been tweaked to behave more like a regular Perl function, such as the ability to accept and return arrays. This is a lot more convenient than having to pack() and unpack() your a arguments. Some functions don't have a *_p variant, so the next best thing is to use the *_c variant, which accept OpenGL::Array objects. The use of OpenGL::Array is not documented in the module, but docs can be found on the website for the module (just search the web for POGL). I haven't included an example of this here, since it requires more knowledge of OpenGL, but experienced OpenGL programmers should have no problems using them.
 
As an alternative to creating windows using Win32::GUI, windows can be created using the GLUT(OpenGL Utility Toolkit) functions supplied with OpenGL. These can create windows that a platform-independent, as well as a lot of other stuff. A lot of the examples supplied for the OpenGL module use the GLUT, making them more portable, but OpenGL needs to be compiled with support for GLUT, requiring the GLUT libraries. Since I can't seem to get XS-implemented modules to compile properly on my machine (I use PPM instead), I just stick with Win32::GUI. Its all about personal preference.
 
Well, that's it for my Win32::GUI+OpenGL example. I hope someone finds it useful. I'm no expert at OpenGL or the Win32 API, so there is probably a better way of doing this. So far this model has worked for basic implementations but don't expect to be able to make anything to big, such as games, but you never know. I'd love to hear any questions or comments about this example, as well as any examples of anything anyone else has done.
 
As a side note, I'm new to posting messages on the mailing lists. I was wondering whether I can send pictures attached (I was hoping to show a screen shot of my program). Also, how do I post a reply to an existing thread. Any help would be much appreciated.
 
Sorry about any typos in advance. Contact me if you find any errors with this post (such as with the sample program).
 
Thanks.
 
Kevin Marshall
 
(kejohm88 AT hotmail DOT com)
 



Click Here View photos of singles in your area

------------------------------------------------------------------------------
Register Now for Creativity and Technology (CaT), June 3rd, NYC. CaT
is a gathering of tech-side developers & brand creativity professionals. Meet
the minds behind Google Creative Lab, Visual Complexity, Processing, &
iPhoneDevCamp asthey present alongside digital heavyweights like Barbarian
Group, R/GA, & Big Spaceship. http://www.creativitycat.com
_______________________________________________
Perl-Win32-GUI-Users mailing list
Perl-Win32-GUI-Users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/perl-win32-gui-users
http://perl-win32-gui.sourceforge.net/



--
Maximus*
WarheadsSE
MaxSource