# # PyGUI - OpenGL Textures - Generic # from weakref import WeakKeyDictionary from OpenGL.GL import glGenTextures, glBindTexture, glDeleteTextures, \ glTexImage2D, GL_TEXTURE_2D, GL_RGBA from OpenGL.GLU import gluBuild2DMipmaps from GUI.GGLContexts import current_share_group from GUI.GLDisplayLists import call_when_not_compiling_display_list #---------------------------------------------------------------------- class TextureIdMap(WeakKeyDictionary): def __del__(self): #print "GL.TextureIdMap.__del__:", self ### def free_texture(): glDeleteTextures([gl_id]) for share_group, gl_id in self.items(): context = share_group._some_member() if context: #print "...freeing texture id", gl_id, "for", share_group, "using", context ### context.with_context(free_texture) #---------------------------------------------------------------------- class Texture(object): """This class encapsulates an OpenGL texture and maintains a representation of it for each OpenGL context with which it is used. Allocation and maintentance of texture numbers is handled automatically. Constructor: Texture(texture_type) where texture_type is the appropriate GL constant for the type of texture (GL_TEXTURE_2D etc.) """ # # _gl_type int GL_TEXTURE_2D, etc. # _gl_id ShareGroup -> int Mapping from OpenGL share group to texture number def __init__(self, texture_type): self._gl_type = texture_type self._gl_id = TextureIdMap() def deallocate(self): """Deallocate any OpenGL resources that have been allocated for this texture in any context.""" self._gl_id.__del__() def bind(self): """Makes this texture the current texture for the current context by calling glBindTexture. If this texture has not previously been used with the current context, do_setup() is called to allocate and initialise a representation of the texture.""" gl_id = self.gl_id() glBindTexture(self._gl_type, gl_id) def gl_id(self): """Returns the OpenGL texture number corresponding to this texture in the current context. May trigger allocation of a new texture and a call to do_setup(). Does not bind the texture, unless a new texture is allocated, in which case the current texture binding may be changed as a side effect.""" share_group = current_share_group() gl_id = self._gl_id.get(share_group) if gl_id is None: gl_id = glGenTextures(1) #print "GLTexture: assigned id %d for %s in share group %s" % ( # gl_id, self, share_group) ### self._gl_id[share_group] = gl_id call_when_not_compiling_display_list(lambda: self._setup(gl_id)) return gl_id def _setup(self, gl_id): glBindTexture(self._gl_type, gl_id) self.do_setup() def do_setup(self): """Subclasses should override this to make the necessary OpenGL calls to initialise the texture, assuming that glBindTexture has already been called.""" raise NotImplementedError def gl_tex_image_2d(self, image, target = GL_TEXTURE_2D, internal_format = GL_RGBA, border = False, with_mipmaps = False): """Load the currently bound texture with data from an image, with automatic scaling to power-of-2 size and optional mipmap generation.""" border = bool(border) if border and with_mipmaps: raise ValueError("Bordered texture cannot have mipmaps") b2 = 2 * border width, height = image.size twidth = pow2up(width - b2) + b2 theight = pow2up(height - b2) + b2 #print "GUI.GGLTextures.Texture.gl_tex_image_2d: before scaling: size =", (width, height) ### if width <> twidth or height <> theight: #print "GUI.GGLTextures.Texture.gl_tex_image_2d: scaling image to size", (twidth, theight) ### from Pixmaps import Pixmap image2 = Pixmap(twidth, theight) def scale(canvas): image.draw(canvas, (0, 0, width, height), (0, 0, twidth, theight)) image2.with_canvas(scale) image = image2 format, type, data = self._gl_get_texture_data(image) if with_mipmaps: #print "GUI.GGLTextures.Texture.gl_tex_image_2d: loading mipmaps" ### gluBuild2DMipmaps(target, internal_format, twidth, theight, format, type, data) else: #print "GUI.GGLTextures.Texture.gl_tex_image_2d: loading texture" ### glTexImage2D(target, 0, internal_format, twidth, theight, border, format, type, data) #---------------------------------------------------------------------- def pow2up(size): # Round size up to a power of 2 psize = 1 while psize < size: psize <<= 1 return psize