This chapter discusses using pyFltk for your OpenGL applications.
The easiest way to make an OpenGL display is to subclass Fl_Gl_Window. Your subclass must implement a draw() method which uses OpenGL calls to draw the display. Your main program should call redraw() when the display needs to change, and (somewhat later) pyFltk will call draw().
With a bit of care you can also use OpenGL to draw into normal pyFltk windows. This allows you to use Gouraud shading for drawing your widgets. To do this you use the gl_start() and gl_finish() functions around your OpenGL code.
You must make sure that pyFltk has been compiled with OpenGL support!
To make a subclass of Fl_Gl_Window, you must provide:
If your subclass provides static controls in the window, they must be redrawn whenever the FL_DAMAGE_ALL bit is set in the value returned by damage(). For double-buffered windows you will need to surround the drawing code with the following code to make sure that both buffers are redrawn:
glDrawBuffer(GL_FRONT_AND_BACK) ... draw stuff here ... glDrawBuffer(GL_BACK)
If you are using the Mesa graphics library, the call to glDrawBuffer() is not required and will slow down drawing considerably. The preprocessor instructions shown above will optimize your code based upon the graphics library used.
To define the subclass you just subclass the Fl_Gl_Window class:
class MyWindow(Fl_Gl_Window): def __init__(self, xpos, ypos, width, height, label): Fl_Gl_Window.__init__(self, xpos, ypos, width, height, label)
The draw() and handle() methods are described below. Like any widget, you can include additional private and public data in your class (such as scene graph information, etc.)
The draw() method is where you actually do your OpenGL drawing:
def draw(self): if not self.valid(): ... set up projection, viewport, etc ... ... window size is in w() and h(). ... valid() is turned on by pyFltk after draw() returns ... draw ...
The handle() method handles mouse and keyboard events for the window:
def handle(self, event): if event == FL_PUSH: ... mouse down event ... ... position in Fl.event_x() and Fl.event_y() return 1 elif event == FL_DRAG: ... mouse moved while down event ... return 1 elif event == FL_RELEASE: ... mouse up event ... return 1 elif event == FL_FOCUS or event == FL_UNFOCUS: ... Return 1 if you want keyboard events, 0 otherwise return 1 elif event == FL_KEYBOARD: ... keypress, key is in Fl.event_key(), ascii in Fl.event_text() ... Return 1 if you understand/use the keyboard event, 0 otherwise... return 1 elif event == FL_SHORTCUT: ... shortcut, key is in Fl.event_key(), ascii in Fl.event_text() ... Return 1 if you understand/use the shortcut event, 0 otherwise... return 1 else: // pass other events to the base class... return Fl_Gl_Window.handle(self, event)
When handle() is called, the OpenGL context is not set up! If your display changes, you should call redraw() and let draw() do the work. Don't call any OpenGL drawing functions from inside handle()!
You can call some OpenGL stuff like hit detection and texture loading functions by doing:
if event == FL_PUSH: self.make_current() # make OpenGL context current if not self.valid(): ... set up projection exactly the same as draw ... self.valid(1) # stop it from doing this next time ... ok to call NON-DRAWING OpenGL code here, such as hit detection, loading textures, etc...
Your main program can now create one of your windows by calling MyWindow(...).
You must put glwindow.show() in your main code after calling show() on the window containing the OpenGL window.
You can put OpenGL code into an Fl_Widget.draw() method or into the code for a boxtype or other places with some care.
Most importantly, before you show any windows, including those that don't have OpenGL drawing, you must initialize pyFltk so that it knows it is going to use OpenGL. You may use any of the symbols described for Fl_Gl_Window.mode() to describe how you intend to use OpenGL:
You can then put OpenGL drawing code anywhere you can draw normally by surrounding it with:
gl_start() ... put your OpenGL code here ... gl_finish()
gl_start() and gl_finish() set up an OpenGL context with an orthographic projection so that 0,0 is the lower-left corner of the window and each pixel is one unit. The current clipping is reproduced with OpenGL glScissor() commands. These functions also synchronize the OpenGL graphics stream with the drawing done by other X, WIN32, or pyFltk functions.
The same context is reused each time. If your code changes the projection transformation or anything else you should use glPushMatrix() and glPopMatrix() functions to put the state back before calling gl_finish().
You may want to use Fl_Window.current()->h() to get the drawable height so that you can flip the Y coordinates.
Unfortunately, there are a bunch of limitations you must adhere to for maximum portability:
Do not call gl_start() or gl_finish() when drawing into an Fl_Gl_Window!
pyFltk provides some useful OpenGL drawing functions, which can be freely mixed with any OpenGL calls.
Sets the current OpenGL color to a pyFltk color. For color-index modes it will use fl_xpixel(c), which is only right if this window uses the default colormap!
Outlines or fills a rectangle with the current color. If Fl_Gl_Window.ortho() has been called, then the rectangle will exactly fill the pixel rectangle passed.
Sets the current OpenGL font to the same font you get by calling fl_font().
Returns information about the current OpenGL font.
Draws a nul-terminated string or an array of n characters in the current OpenGL font at the current raster position.
Draws a nul-terminated string or an array of n characters in the current OpenGL font at the given position.
Draws a string formatted into a box, with newlines and tabs expanded, other control characters changed to ^X, and aligned with the edges or center. Exactly the same output as fl_draw().
Performance of Fl_Gl_Window may be improved on some types of OpenGL implementations, in particular MESA and other software emulators, by setting the GL_SWAP_TYPE environment variable. This variable declares what is in the backbuffer after you do a swapbuffers.
This indicates that the back buffer is copied to the front buffer, and still contains it's old data. This is true of many hardware implementations. Setting this will speed up emulation of overlays, and widgets that can do partial update can take advantage of this as damage() will not be cleared to -1.
This indicates that nothing changes the back buffer except drawing into it. This is true of MESA and Win32 software emulation and perhaps some hardware emulation on systems with lots of memory.
This is easily tested by running the gl_overlay demo program and seeing if the display is correct when you drag another window over it or if you drag the window off the screen and back on. You have to exit and run the program again for it to see any changes to the environment variable.
OpenGL Optimizer is a scene graph toolkit for OpenGL available from Silicon Graphics for IRIX and Microsoft Windows. It allows you to view large scenes without writing a lot of OpenGL code.
To use OpenGL Optimizer with pyFltk you'll need to create a subclass of Fl_Gl_Widget that includes several state variables:
class OptimizerWindow(Fl_Gl_Window): def OptimizerWindow.__init__(self, X, Y, W, H, L): Fl_Gl_Window.__init__(self, X, Y, W, H, L) self.context_ = None self.draw_action_ = None self.scene_ = None self.camera_ = None def scene(self, csGroup): self.scene_ = csGroup self.redraw() def camera(self, csCamera): self-camera_ = csCamera if self.context_ != None: self.draw_action_.setCamera(self.camera_) self.camera_.draw(self.draw_action_) self.redraw()
The camera() method sets the camera (projection and viewpoint) to use when drawing the scene. The scene is redrawn after this call.
The draw() method performs the needed initialization and does the actual drawing:
def draw(self): if self.context_ != None: # This is the first time we've been asked to draw; create the # Optimizer context for the scene... if sys.platform == 'win32': self.context_ = csContext(fl_getHDC()) self.context_.ref() self.context_.makeCurrent(fl_getHDC()) else: self.context_ = csContext(fl_display, fl_visual) self.context_.ref() self.context_.makeCurrent(fl_display, fl_window) ... perform other context setup as desired ... # Then create the draw action to handle drawing things... self.draw_action_ = csDrawAction() if self.camera_ != None: self.draw_action_.setCamera(self.camera_) self.camera_.draw(self.draw_action_) else: if sys.platform == 'win32': self.context_.makeCurrent(fl_getHDC()) else: self.context_.makeCurrent(fl_display, fl_window) if not valid(): # Update the viewport for this context... self.context_.setViewport(0, 0, self.w(), self.h()) } # Clear the window... self.context_.clear(csContext.COLOR_CLEAR | csContext.DEPTH_CLEAR, 0.0, # Red 0.0, # Green 0.0, # Blue 1.0) # Alpha # Then draw the scene (if any)... if self.scene_ != None self.draw_action_.apply(self.scene_)
The scene() method sets the scene to be drawn. The scene is a collection of 3D objects in a csGroup. The scene is redrawn after this call.