This code makes OpenGL simpler to get started with. It provides these classes:

GL_Texture - Loads texture data and provides a draw function
Textureset - Container for textures
GL_Image - Bootstraps off of Textureset; it provides a more sophisticated draw method and contains default values for said method
CImage - Multiple GLImages cached together, for ex. a tiled landscape
DCImage - Dynamic version of Cimage; much lower performance
LDCImage - Simplified version of DCImage with midrange performance but no special drawing arguments

The test code will run if you add a 32x32 .bmp image to "/data". It demos all of the featured classes and functions, but not every included method

import pygame import os from OpenGL.GL import * from OpenGL.GLU import * def initializeDisplay(w, h): pygame.display.set_mode((w,h), pygame.OPENGL|pygame.DOUBLEBUF) glClearColor(0.0, 0.0, 0.0, 1.0) glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_PROJECTION) glLoadIdentity(); # this puts us in quadrant 1, rather than quadrant 4 gluOrtho2D(0, w, h, 0) glMatrixMode(GL_MODELVIEW) # set up texturing glEnable(GL_TEXTURE_2D) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) def render_init(w,h): """Finds the smallest available resolution that fits the desired viewfield.""" pygame.init() modelist = pygame.display.list_modes() nextmode = [l for l in modelist if l[0]>=w and l[1]>=h] bestx, besty = -1,-1 for l in nextmode: if (bestx==-1 or bestx>=l[0]) and (besty==-1 or besty>=l[1]): bestx, besty = l[0],l[1] print "resolution: ",bestx, besty initializeDisplay(bestx, besty) def loadImage(image): textureSurface = pygame.image.load(image) textureData = pygame.image.tostring(textureSurface, "RGBA", 1) width = textureSurface.get_width() height = textureSurface.get_height() texture = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData) return texture, width, height def SurfaceClip(surface, rect): textureSurface = surface.subsurface(rect) textureData = pygame.image.tostring(textureSurface, "RGBA", 1) width = textureSurface.get_width() height = textureSurface.get_height() texture = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData) return texture, width, height def delTexture(texture): glDeleteTextures(texture) def createTexDL(texture, width, height): newList = glGenLists(1) glNewList(newList,GL_COMPILE); glBindTexture(GL_TEXTURE_2D, texture) glBegin(GL_QUADS) # Bottom Left Of The Texture and Quad glTexCoord2f(0, 0); glVertex2f(0, 0) # Top Left Of The Texture and Quad glTexCoord2f(0, 1); glVertex2f(0, height) # Top Right Of The Texture and Quad glTexCoord2f(1, 1); glVertex2f( width, height) # Bottom Right Of The Texture and Quad glTexCoord2f(1, 0); glVertex2f(width, 0) glEnd() glEndList() return newList def delDL(list): glDeleteLists(list, 1) def render(layers): for l in layers: l.render() class GL_Texture: def __init__(s, texname=None, texappend=".png"): filename = os.path.join('data', texname) filename += texappend s.texture, s.width, s.height = loadImage(filename) s.displaylist = createTexDL(s.texture, s.width, s.height) def __del__(self): if self.texture != None: delTexture(self.texture) self.texture = None if self.displaylist != None: delDL(self.displaylist) self.displaylist = None def __repr__(s): return s.texture.__repr__() class Textureset: """Texturesets contain and name textures.""" def __init__(s): s.textures = {} def load(s, texname=None, texappend=".png"): s.textures[texname] = GL_Texture(texname, texappend) def set(s, texname, data): s.textures[texname] = data def delete(s, texname): del s.textures[texname] def __del__(s): s.textures.clear() del s.textures def get(s, name): return s.textures[name] class GL_Image: def __init__(self, texset, texname): self.texture = texset.get(texname) self.width = self.texture.width self.height = self.texture.height self.abspos=None self.relpos=None self.color=(1,1,1,1) self.rotation=0 self.rotationCenter=None def draw(self, abspos=None, relpos=None, width=None, height=None, color=None, rotation=None, rotationCenter=None): if color==None: color = self.color glColor4fv(color) if abspos: glLoadIdentity() glTranslate(abspos[0],abspos[1],0) elif relpos: glTranslate(relpos[0],relpos[1],0) if rotation==None: rotation=self.rotation if rotation != 0: if rotationCenter == None: rotationCenter = (self.width / 2, self.height / 2) # (w,h) = rotationCenter glTranslate(rotationCenter[0],rotationCenter[1],0) glRotate(rotation,0,0,-1) glTranslate(-rotationCenter[0],-rotationCenter[1],0) if width or height: if not width: width = self.width elif not height: height = self.height glScalef(width/(self.width*1.0), height/(self.height*1.0), 1.0) glCallList(self.texture.displaylist) if rotation != 0: # reverse glTranslate(rotationCenter[0],rotationCenter[1],0) glRotate(-rotation,0,0,-1) glTranslate(-rotationCenter[0],-rotationCenter[1],0) class CImage: """CImage is a "composed image" that refs multiple GLImages. format is [(GLImage,argstoimage)...()..()] Cimage is fast but immutable - it has to recreate the display list to be changed.""" def __init__(s, ilist): newlist = glGenLists(1) glNewList(newlist,GL_COMPILE) # see GL_Image.draw for i in ilist: if i[1][0] == None: i[0].draw(i[1][0], i[1][1], i[1][2], i[1][3], i[1][4], i[1][5], i[1][6]) else: # absolute positioning normally resets the identity i[0].draw(None,i[1][0], i[1][2], i[1][3], i[1][4], i[1][5], i[1][6]) glTranslate(-i[1][0][0], -i[1][0][1],0) glEndList() s.displaylist = newlist def __del__(s): if s.displaylist != None: delDL(s.displaylist) s.displaylist = None def draw(s, abspos=None,relpos=None): if abspos: glLoadIdentity() glTranslate(abspos[0],abspos[1],0) elif relpos: glTranslate(relpos[0],relpos[1],0) glCallList(s.displaylist) class DCImage: """Dynamic Composite Image - elements are mutable, at the caveat of runtime performance.""" def __init__(s, ilist): s.ilist = ilist def draw(s, abspos): glLoadIdentity() glTranslate(abspos[0],abspos[1],0) for i in s.ilist: i[0].draw(i[1]) class LDCImage: """Limited Dynamic Composite Image. LDCImage uses only the texture display lists for drawing, which makes it useful for simpler applications like text and tiles that don't need the features of DCImage. Remember not to mistake this for *LCD* Image!""" def __init__(s, cache): """cache format is: (texture ref, (absx, absy))""" s.cache = cache def draw(s, abspos): glLoadIdentity() glTranslate(abspos[0],abspos[1],0) for c in s.cache: glTranslate(c[1][0], c[1][1],0) glCallList(c[0].displaylist) glTranslate(-c[1][0], -c[1][1],0) def main(): render_init(640,480) tset = Textureset() tset.load('foo','.bmp') fooimage = GL_Image(tset, 'foo') rawfootex = tset.get('foo') compositelist = [] examplegrid = [] for x in xrange(4): for y in xrange(4): examplegrid.append((rawfootex,(x*32,y*32))) compositelist.append((fooimage, ((x*32,y*32), None, None, None, (1,1,1,1), 1, None))) ldcimg = LDCImage(examplegrid) foocomposite = CImage(compositelist) clock = pygame.time.Clock() t = 0 glLoadIdentity() for count in range(100): glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) if t==0: ldcimg.draw((10,10)) fooimage.draw((0,0)) t=1 else: t=0 foocomposite.draw((320,200)) clock.tick() pygame.display.flip() pygame.event.pump() print "result: "+str(clock.get_fps())+" FPS" if __name__=="__main__": main()