[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] PyGame / PyOpenGL Example



attached is an example of one way to draw pygame surfaces in OpenGL.
It's originally a piece of a larger library, so I tried to rip out
dependencies on that library. It exports a "Display" class which takes
over creating the pygame display and can blit objects which have 4
attributes on them - surface (pygame surface), width, height and
hotspot (2 element sequence saying where the center of the image is).
When things are blit, the Display class adds attributes to the object
to track GL texture info. If you run it, it shows itself working (if
__name__=="__main__": thing)

It doesn't show how to print text (my class for that was too hairy to
extract) but the approach I take is to have pygame.font render
individual characters from strings to surfaces as needed and store
them in dict (so they won't be regenerated all the the time), and then
blit the cached surfaces for each letter in the string to the display

There are 2 somewhat unusual things about this code - first is it
uploads the textures as "premultiplied-alpha" or what it calls
Light-Opacity model, where the blend mode is GL_ONE,
GL_ONE_MINUS_SRC_ALPHA - the reason for that is that its the only way
to get GL's bilinear filtering math to be accurate (so you don't
introduce edge artifacts by blending color's that are supposed to be
transparent - the yellow blocks in the exampleGL.py would have brown
edges if it used Color-Opacity RGBA). The other weird thing is it has
some 2d motion blur stuff.

... Also, I have to say that at first I thought it would be a "Royal
Pain" to have to deal with rendering 2d stuff in openGL - but it
actually turned out to be much easier than I thought and kind of a lot
of fun (although I may have more pain later I suppose). The only thing
that isn't trivially simple, in my opinion, is figuring out how you
want to deal with texture size limitations. In particular, on the
majority of systems out there, textures have to be a power-of-2 in
width and height - so you have to deal with that to upload say a 24x12
image for blitting. The 3 basic approaches are:
1. overallocate textures and render just the portion of the texture
that has an image (so that 24x12 image would be on a 32x16 texture and
would render just texcoords (.0,.0)-(.75,.75)), which will waste about
50% of your video memory and possibly cause extra texture thrashing,
but actually some commercial games ship this way cause it just never
ended up being a problem for their game.
2. spread images across multiple textures and draw all of them (so the
24x12 image would be spread across 4 textures, a 16x8, an 8x8, a 16x4
and an 8x4) which lets you draw more polys in order to not waste so
much video memory
3. pack multiple images into a single texture - the attached example
uses that approach. It can be pathologically bad in terms of memory
thrashing in some cases (like say if you wanted to draw one image each
from 100 large multi-image textures) but if images that are used
together are in a texture together, it is actually the optimal
approach in terms of performance (like it tends to be good to make
sure font characters are all packed in textures together)

I hope the example helps - also I'm interested in ways people think
this code could be improved.


> On Tue, 12 Feb 2008 23:23:49 +1030, fragged <my.old.email.sucked@xxxxxxxxx>
> wrote:
> Hey guys,
>
> I've been searching for some decent examples of PyOpenGL to help me port
> my current application to OpenGL to speed things up on my perty new
> 1920x1200 LCD (Getting about 20fps, using no acceleration whatsoever),
> however I've been unable to find any decent guides on the net that
> simply show me how to do the equivalent of loading an image and some
> text - the small fragments I have found have still been using PyGame to
> render text and this seems to break on my gentoo system whilist working
> on another and I'm not really sure if this is the /propper/ way to do it.
>
> would somebody have one of these laying about, know of one or be able to
> hack one up for me? It'd be a great help and I'd also love to see it go
> in the examples directory on PyGame.org as there seems to be very few
> extreme beguinners guides.
>
"""
Implementation of how to render to the display for gl stuff

(C) Copyright 2005 Hamster Republic Productions - but you are free to do whatever you want with this
"""

########################################################################

import pygame
import math
import OpenGL
from OpenGL.GL import *
import weakref
import types
#from motherhamster.imageassets import ImageAsset
#from motherhamster import editorinfo
import array

########################################################################
def GetHigherPowerOf2(value):
    return 2**int(math.ceil(math.log(value, 2)))

def UnpackImage(image):
    #CHECK: really need to queue up the item for later...
    # in a multithreaded envron. this might happen at a bad time
    if hasattr(image, "GLtexture"):
        texture_pack = image.GLtexture
        texture_pack.lose_image(image)

def GetLightOpacityStringForSurface(surface):
    width, height = surface.get_size()
    rgba_buffer = array.array("B", pygame.image.tostring(surface, "RGBA", True))
    for pixel_index in xrange(width*height):
        alpha = rgba_buffer[pixel_index*4 + 3]
        if (alpha < 255):
            if (alpha == 0):
                rgba_buffer[pixel_index*4 + 0] = 0
                rgba_buffer[pixel_index*4 + 1] = 0
                rgba_buffer[pixel_index*4 + 2] = 0
            else:
                rgba_buffer[pixel_index*4 + 0] = rgba_buffer[pixel_index*4 + 0]*alpha/255
                rgba_buffer[pixel_index*4 + 1] = rgba_buffer[pixel_index*4 + 1]*alpha/255
                rgba_buffer[pixel_index*4 + 2] = rgba_buffer[pixel_index*4 + 2]*alpha/255
    return rgba_buffer.tostring()

def glDeleteTextureIfSafeTo(texture_id):
    if texture_id != None:
        if glIsTexture(texture_id):
            glDeleteTextures(texture_id)
            assert(not glIsTexture(texture_id))

########################################################################
class TexturePack:
    """A class that corresponds to an allocated openGL texture
       it tracks what objects are in the texture and what areas are free"""
    
    def __perferred_size(image):
        """get the recommended TexturePack allocation size to hold this image"""
        return (max(GetHigherPowerOf2(image.width+2), 512), max(GetHigherPowerOf2(image.height+2), 256))
    get_perferred_allocation_size = staticmethod(__perferred_size) 
   
    def __init__(self, size):
        """Initialize the TexturePack object. Expects one argument, which is a vector
        representing the width and height"""
        self.texture_id = glGenTextures(1)
        self.free_zones = [[0, 0, size[0], size[1]]]
        self.allocated_zones = []
        self.width = size[0]
        self.height = size[1]
        glBindTexture(GL_TEXTURE_2D, self.texture_id)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        surface = pygame.Surface((size[0], size[1]), pygame.SRCALPHA, 32)
        surface.fill((0,0,0,0))
        pixels = pygame.image.tostring(surface, 'RGBA', True)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)

    def __del__(self):
        self.release_all()
        
    def lose_all_images(self):
        """Erases all the image textures and resets the list of available zones"""
        for zone in self.allocated_zones:
            image = zone[4]()
            if image != None:
                if (image.GLcalllist):
                    glDeleteLists(image.GLcalllist, 1)
                del image.GLtexture
        self.free_zones = [[0,0, self.width, self.height]]

    def release_all(self):
        """Reset this TexturePack object"""
        self.lose_all_images()
        glDeleteTextureIfSafeTo(self.texture_id)
        
    def pack_image(self, image):
        """tries to put the data for an image in this texture pack.
           if successful, it returns true and adds attributes to the image"""
        best_fit = None
        best_score = 0
        width = image.width
        height = image.height
        for zone in self.free_zones:
            # we always put stuff in so it has a 1 pixel buffer (shared) so we don't have edges bleed over
            zone_width = zone[2] - 2
            zone_height = zone[3] - 2
            if (width <= zone_width and height <= zone_height):
                score = (zone_width - width)*(zone_height - height)
                if (score < best_score or best_fit is None):
                    best_score = score
                    best_fit = zone
        if (best_fit):
            self.free_zones.remove(best_fit)
            zone = [best_fit[0] + 1, best_fit[1] + 1, width, height, weakref.ref(image, self.lose_image)]
            width_left = best_fit[2] - width - 1
            height_left = best_fit[3] - height - 1
            # need to track free area - for simplicity we break leftovers into no more than 2 zones
            # also, the free areas contains the shared 1 pixel buffer, always
            if (width_left > 0 and height_left > 0):
                if (width_left < height_left):
                    self.free_zones.append([best_fit[0] + width + 1, best_fit[1], width_left, height + 2])
                    self.free_zones.append([best_fit[0], best_fit[1] + height + 1, best_fit[2], height_left])
                else:
                    self.free_zones.append([best_fit[0] + width + 1, best_fit[1], width_left, best_fit[3]])
                    self.free_zones.append([best_fit[0], best_fit[1] + height + 1, width + 2, height_left])
            elif (width_left > 0):
                self.free_zones.append([best_fit[0] + width + 1, best_fit[1], width_left, best_fit[3]])
            elif (height_left > 0):
                self.free_zones.append([best_fit[0], best_fit[0] + height + 1, best_fit[2], height_left])   
            image.GLtexture = self
            image.GLtexels = [float(zone[0])/self.width, float(zone[1])/self.height, 
                              float(zone[0] + zone[2])/self.width, float(zone[1] + zone[3])/self.height]
            image.GLcalllist = None
            image.image_on_change = UnpackImage
            pixels = GetLightOpacityStringForSurface(image.surface)
            glBindTexture(GL_TEXTURE_2D, self.texture_id)
            glTexSubImage2D(GL_TEXTURE_2D, 0, zone[0], zone[1], zone[2], zone[3], GL_RGBA, GL_UNSIGNED_BYTE, pixels)
            self.allocated_zones.append(zone)
            return True
        return False
                    
    def as_surface(self):
        """gets the contents of the texture as a pygame surface"""
        glBindTexture(GL_TEXTURE_2D, self.texture_id)
        pixels = glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE)
        surface = pygame.image.fromstring(pixels, (self.width, self.height), "RGBA", True)
        for zone in self.free_zones:
            inverted_y = self.height - (zone[1] + zone[3])
            rect = pygame.Rect(zone[0], inverted_y, zone[2], zone[3])
            pygame.draw.rect(surface, (0,255,255,255), rect, 1)
        for zone in self.allocated_zones:
            inverted_y = self.height - (zone[1] + zone[3])
            rect = pygame.Rect(zone[0], inverted_y, zone[2], zone[3])
            pygame.draw.rect(surface, (255,255,0,255), rect, 1)
        return surface

    def get_info(self):
        bytes = self.width*self.height*4
        allocated = 0
        for zone in self.allocated_zones:
            allocated += zone[2]*zone[3]*4
        return "%dx%d for %d bytes, %d images, %.1f%% full" % (self.width, self.height, bytes, len(self.allocated_zones), float(allocated)/bytes)
        
    def lose_image(self, obj):
        """unloads an image from the texture pack"""
        for zone in self.allocated_zones:
            if zone[4] == obj or zone[4]() == obj:
                self.allocated_zones.remove(zone)
                image = zone[4]()
                if image != None:
                    if (image.GLcalllist):
                        glDeleteLists(image.GLcalllist, 1)
                    del image.GLtexture
                #CHECK: should do some kind of thing for combining free zones...
                free_zone = [zone[0] - 1, zone[1] - 1, zone[2] + 2, zone[3] + 2]
                self.free_zones.append(free_zone)
                return True
        return False
    
########################################################################
class Display:
    """pygame window with openGL"""
    def __init__(self):
        self.packs = []
        self.circle_list = None
#        self.src_blend = {ImageAsset.blend_modes.alpha_blend:GL_ONE,
#                          ImageAsset.blend_modes.additive:GL_ONE,
#                          ImageAsset.blend_modes.lighting:GL_DST_COLOR}
#        self.dest_blend = {ImageAsset.blend_modes.alpha_blend:GL_ONE_MINUS_SRC_ALPHA,
#                           ImageAsset.blend_modes.additive:GL_ONE,
#                           ImageAsset.blend_modes.lighting:GL_SRC_ALPHA}
#        editorinfo.RegisterImageSource("GL Image Textures", self.get_num_packs, self.get_pack_image, self.get_pack_info)
    
    def get_num_packs(self):
        return len(self.packs)
    
    def get_pack_image(self, index):
        return self.packs[index].as_surface()
    
    def get_pack_info(self, index):
        return self.packs[index].get_info()
        
    def pack_image(self, image):
        """Packs an image into an OpenGL texture"""
        # If this image has already been packed, don't pack it again
        if hasattr(image, "GLtexture") and image.GLtexture != None:
            return image.GLtexture
        # Try to pack the image into the first texture that has room for it
        for pack in self.packs:
            if pack.pack_image(image):
                return pack
        # If no existing texture has room, add another one
        pack = TexturePack(TexturePack.get_perferred_allocation_size(image))
        pack.pack_image(image)
        self.packs.append(pack)
        return pack

    def rect(self, corner0, corner1, color = (1.0,1.0,1.0,1.0)):
        """Draws a rectangle from corner0 to corner1 in the specified color.
           The corners are supplied as vectors"""
        glDisable(GL_TEXTURE_2D)
        glLoadIdentity()
        glColor4f(color[0], color[1], color[2], color[3])
        glBegin(GL_QUADS)
        glVertex2f(corner0[0], corner0[1])
        glVertex2f(corner1[0], corner0[1])
        glVertex2f(corner1[0], corner1[1])
        glVertex2f(corner0[0], corner1[1])
        glEnd()

    def line(self, point_list, color = (1.0,1.0,1.0,1.0)):
        """Display a line in the specified color. The line is supplied as a
           list of vectors representing points on the line."""
        glDisable(GL_TEXTURE_2D)
        glLoadIdentity()
        glColor4f(color[0], color[1], color[2], color[3])
        glBegin(GL_LINE_STRIP)
        for point in point_list:
            glVertex2f(point[0], point[1])
        glEnd()

    def polygon(self, position, polygon, angle = 0, scale = 1.0, color = (1.0,1.0,1.0,1.0)):
        """Draw the outline of a polygon as lines"""
        glDisable(GL_TEXTURE_2D)
        glLoadIdentity()
        glTranslatef(position[0], position[1], 0)
        glScalef(scale, scale, 1)
        glRotatef(angle, 0, 0, 1)
        glColor4f(color[0], color[1], color[2], color[3])
        glBegin(GL_LINE_LOOP)
        angle = 0
        for point in polygon:
            glVertex2f(point[0], point[1])
        glEnd()

    def circle(self, position, radius, color = (1.0,1.0,1.0,1.0), filled = False):
        """Draw a circle. Specify center position as a vector, radius in pixels. Can optionally be drawn solid"""
        glDisable(GL_TEXTURE_2D)
        if (self.circle_list is None):
            self.circle_list = glGenLists(1)
            glNewList(self.circle_list, GL_COMPILE)
            step = 2*math.pi/30
            angle = -math.pi*2 - step/2
            while angle < 0:
                glVertex2f(math.cos(angle), math.sin(angle))
                angle += step
            glEndList()
        glLoadIdentity()
        glTranslatef(position[0], position[1], 0)
        if hasattr(radius, "__getitem__"):
            glScalef(radius[0], radius[1], 1)
        else:
            glScalef(radius, radius, 1)
        glColor4f(color[0], color[1], color[2], color[3])
        if (filled):
            glBegin(GL_POLYGON)
        else:
            glBegin(GL_LINE_LOOP)
        glCallList(self.circle_list)
        glEnd()
    
    def render_patch(self, patch, image = None, color = (1.0,1.0,1.0,1.0), subdiv = 20):
        """ renders a bezier patch, which may or may not have an image mapped. 
            patch should be a grid of control points for a 2d bezier path.
            patch can be of any x & y dimension, but should be a list of equal-length rows
            the patch is rendered with linear approximations, sudiv is the number of points"""
        glLoadIdentity()
        patch_3D = map(lambda x : map(lambda y : (y[0], y[1], 0), x), patch)
        glMap2f(GL_MAP2_VERTEX_3, 0, 1, 0, 1, patch_3D)
        glEnable(GL_MAP2_VERTEX_3)
        if image:
            if (not hasattr(image, "GLtexture")):
                self.pack_image(image)
            tex_rect = [[[image.GLtexels[0], image.GLtexels[1]], [image.GLtexels[2], image.GLtexels[1]]], 
                        [[image.GLtexels[0], image.GLtexels[3]], [image.GLtexels[2], image.GLtexels[3]]]]
            glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 0, 1, tex_rect)
            glEnable(GL_MAP2_TEXTURE_COORD_2)
            glEnable(GL_TEXTURE_2D)
            glBindTexture(GL_TEXTURE_2D, image.GLtexture.texture_id)
        else:
            glDisable(GL_MAP2_TEXTURE_COORD_2)
            glDisable(GL_TEXTURE_2D)
        if isinstance(color[0], (types.FloatType, types.IntType)):
            glDisable(GL_MAP2_COLOR_4)
            glColor4f(color[0], color[1], color[2], color[3])
        else:
            glMap2f(GL_MAP2_COLOR_4, 0, 1, 0, 1, color)
            glEnable(GL_MAP2_COLOR_4)

        glMapGrid2f(subdiv, 0, 1, subdiv, 0, 1)
        glEvalMesh2(GL_FILL, 0, subdiv, 0, subdiv)
        
    def render_motion(self, image, position_list, angle = 0.0, scale = 1.0, color = (1.0, 1.0, 1.0, 1.0), blur_start = .4, blur_end = .02, max_steps = 15):
        """ renders a motion trail, intended to be called after a normal render call
            position_list must be a list of vec2d's
            scale and angle may be lists as well
            blur_start and blur_end define what alpha levels to use """
        pos_stops = len(position_list)
        if (pos_stops == 0):
            return
        elif pos_stops == 1:
            pos_steps = [[position_list[0], 0, 0]]
        elif not hasattr(position_list[0], "__getitem__"):
            pos_steps = [[position_list, 0, 0]]
        else:
            pos_steps = []
            index = pos_stops - 2
            last_pos = position_list[-1]
            while index >= 0:
                this_pos = position_list[index]
                path = (this_pos[0] - last_pos[0], this_pos[1] - last_pos[1])
                pos_steps.append([last_pos, path, math.sqrt(path[0]**2 + path[1]**2)])
                last_pos = this_pos
                index -= 1
                
        total_length = 0
        for step in pos_steps:
            total_length += step[2]
            
        try:
            scale_base = scale[-1]
            scale_range = scale[0] - scale[-1]
            scale_magnitude = max(abs(image.width*scale_range), abs(image.height*scale_range))
            if hasattr(scale_magnitude, "__getitem__"):
                scale_magnitude = max(scale_magnitude[0], scale_magnitude[1])
        except:
            scale_base = scale
            scale_range = 0
            scale_magnitude = 0
        try:
            angle_base = angle[-1]
            angle_range = angle[0] - angle[-1]
        except:
            angle_base = angle
            angle_range = 0
        steps = min(max(int(total_length/2), int(abs(angle_range)/2), int(scale_magnitude/3)), max_steps)
        
        if (steps > 0):
            color_interp = [color[0]*blur_start, color[1]*blur_start, color[2]*blur_start, color[3]*blur_start]
            interpolation = 0
            factor = math.pow(blur_end/blur_start, 1.0/steps)
            step = 1.0
            
            dist = .0
            dist_step = min(2.0, total_length/steps)
            pos_index = 0
            pos_step = pos_steps[pos_index]
            while step <= steps:
                dist += dist_step
                while (dist > pos_step[2] and pos_index < len(pos_steps) - 1):
                    dist -= pos_step[2]
                    pos_index += 1
                    pos_step = pos_steps[pos_index]
                    
                if (pos_step[2] > 0):
                    x = pos_step[0][0] + pos_step[1][0]*(dist/pos_step[2])
                    y = pos_step[0][1] + pos_step[1][1]*(dist/pos_step[2])
                    pos = [x, y]
                else:
                    pos = pos_step[0]

                interpolation = step/steps
                self.render(image, pos, angle_base + angle_range*interpolation, scale_base + scale_range*interpolation, color_interp)
                step += 1.0
                color_interp[0] *= factor
                color_interp[1] *= factor
                color_interp[2] *= factor
                color_interp[3] *= factor
        
    def render(self, image, position, angle = 0.0, scale = 1.0, color = (1.0,1.0,1.0,1.0), src_rect = None):
        """Draw an image to the specified position with optional scale and angle and cropping source rectangle"""
        if (not hasattr(image, "GLtexture")):
            self.pack_image(image)
        glLoadIdentity()
        glTranslatef(position[0], position[1], 0)
        if (angle != 0):
            glRotatef(angle, 0, 0, 1)
        if (scale != 1):
            if hasattr(scale, "__getitem__"):
                glScalef(scale[0], scale[1], 1)
            else:
                glScalef(scale, scale, 1)
        glColor4f(color[0], color[1], color[2], color[3])
        if src_rect != None:
            self._build_verts_clipped(image, src_rect)
        else:
            if (image.GLcalllist is None):
                image.GLcalllist = glGenLists(1)
                glNewList(image.GLcalllist, GL_COMPILE)
                self._build_verts(image)
                glEndList()
            glCallList(image.GLcalllist)
                    
    def _build_verts(self, image):
        #if image.blend_mode != ImageAsset.blend_modes.alpha_blend:
        #    glBlendFunc(self.src_blend[image.blend_mode], self.dest_blend[image.blend_mode])
        x0 = -image.hotspot[0]
        x1 = x0 + image.width
        y0 = -image.hotspot[1]
        y1 = y0 + image.height
        glBindTexture(GL_TEXTURE_2D, image.GLtexture.texture_id)
        glEnable(GL_TEXTURE_2D)
        glBegin(GL_QUADS)
        glTexCoord2d(image.GLtexels[0], image.GLtexels[1]); glVertex2f(x0, y0)
        glTexCoord2d(image.GLtexels[0], image.GLtexels[3]); glVertex2f(x0, y1)
        glTexCoord2d(image.GLtexels[2], image.GLtexels[3]); glVertex2f(x1, y1)
        glTexCoord2d(image.GLtexels[2], image.GLtexels[1]); glVertex2f(x1, y0)
        glEnd()
        #if image.blend_mode != ImageAsset.blend_modes.alpha_blend:
        #    glBlendFunc(self.src_blend[ImageAsset.blend_modes.alpha_blend], self.dest_blend[ImageAsset.blend_modes.alpha_blend])
    
    def _build_verts_clipped(self, image, src_rect):
        #if image.blend_mode != ImageAsset.blend_modes.alpha_blend:
        #    glBlendFunc(self.src_blend[image.blend_mode], self.dest_blend[image.blend_mode])
        x = max(src_rect[0], 0)
        y = max(src_rect[1], 0)
        w = min(image.width - x, src_rect[2])
        h = min(image.height - y, src_rect[3])
        texw = image.GLtexels[2] - image.GLtexels[0]
        texh = image.GLtexels[3] - image.GLtexels[1]
        # we need to make sure the texels don't go out of the right zone, 
        # they'll grab other images if we don't
        s0 = max(image.GLtexels[0] + texw*x/image.width, image.GLtexels[0])
        s1 = min(s0 + texw*w/image.width, image.GLtexels[2])
        t0 = max(image.GLtexels[1] + texh*y/image.height, image.GLtexels[1])
        t1 = min(t0 + texh*h/image.height, image.GLtexels[3])
        x0 = -image.hotspot[0] + x
        x1 = x0 + w
        y0 = -image.hotspot[1] + y
        y1 = y0 + h
        glBindTexture(GL_TEXTURE_2D, image.GLtexture.texture_id)
        glEnable(GL_TEXTURE_2D)
        glBegin(GL_QUADS)
        glTexCoord2d(s0, t0); glVertex2f(x0, y0)
        glTexCoord2d(s0, t1); glVertex2f(x0, y1)
        glTexCoord2d(s1, t1); glVertex2f(x1, y1)
        glTexCoord2d(s1, t0); glVertex2f(x1, y0)
        glEnd()
        #if image.blend_mode != ImageAsset.blend_modes.alpha_blend:
        #    glBlendFunc(self.src_blend[ImageAsset.blend_modes.alpha_blend], self.dest_blend[ImageAsset.blend_modes.alpha_blend])

    def set_mode(self, width, height, fullscreen = False):
        """Change the screen resolution and fullscreen/windowed state"""
        # OpenGL will drop all your textures and lists, so we need to forget about them
        for pack in self.packs:
            pack.release_all()
        self.packs = []
        if self.circle_list != None:
            glDeleteLists(self.circle_list)
            self.circle_list = None
        
        flags = pygame.OPENGL | pygame.DOUBLEBUF
        if (fullscreen):
            flags |= pygame.FULLSCREEN
        pygame.display.gl_set_attribute(pygame.GL_ALPHA_SIZE, 0)
        pygame.display.gl_set_attribute(pygame.GL_DEPTH_SIZE, 0)
        pygame.display.gl_set_attribute(pygame.GL_STENCIL_SIZE, 0)
        #pygame.display.gl_set_attribute(pygame.GL_SWAP_CONTROL, 1)
        pygame.display.set_mode([width, height], flags, 24)
        glViewport(0,0,width,height)
        self._init_gl_2d(width, height)
        self.width = width
        self.height = height
        
    def _init_gl_2d(self, width, height):
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(0, width, 0, height, 1, 0)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glShadeModel(GL_SMOOTH)
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
        glDisable(GL_CULL_FACE)
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
        glDisable(GL_LIGHTING)
        glDisable(GL_DEPTH_TEST)
        glEnable(GL_LINE_SMOOTH)
        glDisable(GL_POLYGON_SMOOTH)
    
    def erase(self, color=(0,0,0)):
        #CHECK: This erases the whole screen, yes?
        glClearColor(color[0],color[1],color[2],255)
        glClear(GL_COLOR_BUFFER_BIT)
              
    def flip(self):
        pygame.display.flip()

########################################################################
## Quick Testing                                                       ##
########################################################################
if __name__ == "__main__":
    #from motherhamster.mathobj import vec2d
    class SimpleImage(object):
        def __init__(self, surface):
            self.surface = surface
            self.width = surface.get_width()
            self.height = surface.get_height()
            #self.blend_mode = blend_mode
            self.hotspot = (0,0)
            
    try:
        print glGetString(GL_EXTENSIONS)
    except:
        pass
    display = Display()
    pygame.init()
    display.set_mode(800,600)
    running = True
    circle_surface = pygame.Surface((40,40), pygame.SRCALPHA, 32)
    pygame.draw.circle(circle_surface, (255,0,0,255), (20,20), 15)
    pygame.draw.circle(circle_surface, (0,0,0,255), (20,20), 16, 2)
    pygame.draw.circle(circle_surface, (255,255,255,255), (14,15), 1)
    pygame.draw.circle(circle_surface, (255,255,255,255), (26,15), 1)
    pygame.draw.arc(circle_surface, (255,255,255,255), (13,20,14,8), 3.14, 6.28)
    circle_image = SimpleImage(circle_surface)
    tile_surface = pygame.Surface((32,32), pygame.SRCALPHA, 32)
    pygame.draw.rect(tile_surface, (216,255,128,255), pygame.Rect(0,0,32,32))
    pygame.draw.line(tile_surface, (0,255,0,255), (31,0), (31,31))
    pygame.draw.line(tile_surface, (255,0,0,255), (0,0), (31,0))
    pygame.draw.line(tile_surface, (0,0,0,0), (0,0), (0,31))
    pygame.draw.line(tile_surface, (0,0,255,255), (0,31), (31,31))
    tile_image = SimpleImage(tile_surface)
    blend_surface = pygame.Surface((32,32), pygame.SRCALPHA, 32)
    pygame.draw.rect(blend_surface, (255,255,128,255), pygame.Rect(0,0,32,32))    
    pygame.draw.rect(blend_surface, (255,255,255,0), pygame.Rect(8,8,16,16))    
    pygame.draw.rect(blend_surface, (0,0,255,1), pygame.Rect(12,12,8,8))    
    blend_image = SimpleImage(blend_surface)
    
    test_surface = pygame.Surface((1,1), pygame.SRCALPHA, 32)
    test_surface.fill((255,255,255,0))
    
    while running:
        display.erase((255,255,255))
        display.render(circle_image, (0,0), 3)
        display.render(circle_image, (200,100))
        display.render_motion(circle_image, ((300,100), (200,100)))
        display.render(circle_image, (300,200))
        display.render_motion(circle_image, (300,200), scale = (2, 1))
        display.render(tile_image, (0,600-32))
        display.render(tile_image, (32,600-32))
        display.render(tile_image, (0,600-64))
        display.render(tile_image, (32,600-64))
        display.render(blend_image, (400,400))
        display.render(blend_image, (450,400), scale=1.1)
        display.render(blend_image, (500,400), angle=5)
        display.rect((550,200), (650,300), (0,0,0,255))
        display.render_motion(circle_image, (550,200), (2, 1))
        display.render(blend_image, (550,300-32))
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                running = False
        display.flip()