Viewing file: vbo.py (13.49 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
"""VertexBufferObject helper class""" from OpenGL import GL from OpenGL.arrays.arraydatatype import ArrayDatatype from OpenGL.arrays.formathandler import FormatHandler from OpenGL.GL.ARB import vertex_buffer_object from OpenGL import constants, error import ctypes,logging log = logging.getLogger( 'OpenGL.arrays.vbo' )
import weakref __all__ = ('VBO','VBOHandler','mapVBO')
class Implementation( object ): """Abstraction point for the various implementations that can be used """ available = False def _arbname( self, name ): return ( (name.startswith( 'gl' ) and name.endswith( 'ARB' )) or (name.startswith( 'GL_' ) and name.endswith( 'ARB' )) ) and (name != 'glInitVertexBufferObjectARB') def basename( self, name ): if name.endswith( '_ARB' ): return name[:-4] elif name.endswith( 'ARB' ): return name[:-3] else: return name def __init__( self ): names = [name for name in dir(vertex_buffer_object) if self._arbname( name )] if not GL.glBufferData: for name in names: setattr( self, self.basename(name), getattr( vertex_buffer_object, name )) self.available = True elif vertex_buffer_object.glBufferDataARB: for name in names: setattr( self, self.basename(name), getattr( GL, self.basename(name) )) self.available = True def __nonzero__( self ): return self.available def deleter( self, buffers, key): """Produce a deleter callback to delete the given buffer""" nfe = error.NullFunctionError gluint = constants.GLuint def doBufferDeletion( *args, **named ): while buffers: try: buffer = buffers.pop() except IndexError, err: break else: try: # Note that to avoid ERROR_ON_COPY issues # we have to pass an array-compatible type here... buf = gluint( buffer ) self.glDeleteBuffers(1, buf) except (AttributeError, nfe), err: pass try: self._DELETERS_.pop( key ) except KeyError, err: pass return doBufferDeletion _DELETERS_ = {}
IMPLEMENTATION = None
def get_implementation( *args ): """Retrieve the appropriate implementation for this machine""" global IMPLEMENTATION if IMPLEMENTATION is None: IMPLEMENTATION = Implementation() return IMPLEMENTATION
from OpenGL import acceleratesupport VBO = None if acceleratesupport.ACCELERATE_AVAILABLE: try: from OpenGL_accelerate.vbo import ( VBO,VBOOffset,VBOHandler,VBOOffsetHandler, ) except ImportError, err: log.warn( "Unable to load VBO accelerator from OpenGL_accelerate" ) if VBO is None: class VBO( object ): """Instances can be passed into array-handling routines You can check for whether VBOs are supported by accessing the implementation attribute of the VBO, which will raise a RuntimeError if there is no available implementation. """ copied = False _no_cache_ = True # do not cache in context data arrays def __init__( self, data, usage='GL_DYNAMIC_DRAW', target='GL_ARRAY_BUFFER', size=None, ): self.usage = usage self.set_array( data, size ) self.target = target self.buffers = [] self._copy_segments = [] _I_ = None implementation = property( get_implementation, ) def resolve( self, value ): """Resolve string constant to constant""" if isinstance( value, (str,unicode)): return getattr( self.implementation, self.implementation.basename( value ) ) return value def set_array( self, data, size=None ): """Update our entire array with new data""" self.data = data self.copied = False if size is not None: self.size = size elif self.data is not None: self.size = ArrayDatatype.arrayByteCount( self.data ) def __setitem__( self, slice, array): """Set slice of data on the array and vbo (if copied already) slice -- the Python slice object determining how the data should be copied into the vbo/array array -- something array-compatible that will be used as the source of the data, note that the data-format will have to be the same as the internal data-array to work properly, if not, the amount of data copied will be wrong. This is a reasonably complex operation, it has to have all sorts of state-aware changes to correctly map the source into the low-level OpenGL view of the buffer (which is just bytes as far as the GL is concerned). """ if slice.step and not slice.step == 1: raise NotImplemented( """Don't know how to map stepped arrays yet""" ) # TODO: handle e.g. mapping character data into an integer data-set data = ArrayDatatype.asArray( array ) start = (slice.start or 0) stop = (slice.stop or len(self.data)) if start < 0: start += len(self.data) start = max((start,0)) if stop < 0: stop += len(self.data) stop = max((stop,0)) self.data[ slice ] = data if self.copied and self.buffers: if start-stop != len(data): self.copied = False elif start-stop == len(self.data): # re-copy the whole data-set self.copied = False elif len(data): # now the fun part, we need to make the array match the # structure of the array we're going to copy into and make # the "size" parameter match the value we're going to copy in, # note that a 2D array (rather than a 1D array) may require # multiple mappings to copy into the memory area... # find the step size from the dimensions and base size... size = ArrayDatatype.arrayByteCount( data ) / len(array) #baseSize = ArrayDatatype.unitSize( data ) # now create the start and distance values... start *= size stop *= size # wait until the last moment (bind) to copy the data... self._copy_segments.append( (start,(stop-start), data) ) def __len__( self ): return len( self.data ) def __getattr__( self, key ): if key not in ('data','usage','target','buffers', 'copied','_I_','implementation','_copy_segments' ): return getattr( self.data, key ) else: raise AttributeError( key ) def create_buffers( self ): """Create the internal buffer(s)""" assert not self.buffers, """Already created the buffer""" self.buffers = [ long(self.implementation.glGenBuffers(1)) ] self.target = self.resolve( self.target ) self.usage = self.resolve( self.usage ) self.implementation._DELETERS_[ id(self) ] = weakref.ref( self, self.implementation.deleter( self.buffers, id(self) )) return self.buffers def copy_data( self ): """Copy our data into the buffer on the GL side""" assert self.buffers, """Should do create_buffers before copy_data""" if self.copied: if self._copy_segments: while self._copy_segments: start,size,data = self._copy_segments.pop(0) dataptr = ArrayDatatype.voidDataPointer( data ) self.implementation.glBufferSubData(self.target, start, size, dataptr) else: if self.data is not None and self.size is None: self.size = ArrayDatatype.arrayByteCount( self.data ) self.implementation.glBufferData( self.target, self.size, self.data, self.usage, ) self.copied = True def delete( self ): """Delete this buffer explicitly""" if self.buffers: while self.buffers: try: self.implementation.glDeleteBuffers(1, self.buffers.pop(0)) except (AttributeError,error.NullFunctionError), err: pass def bind( self ): """Bind this buffer for use in vertex calls""" if not self.buffers: buffers = self.create_buffers() self.implementation.glBindBuffer( self.target, self.buffers[0]) self.copy_data() def unbind( self ): """Unbind the buffer (make normal array operations active)""" self.implementation.glBindBuffer( self.target,0 ) def __add__( self, other ): """Add an integer to this VBO (offset)""" if hasattr( other, 'offset' ): other = other.offset assert isinstance( other, (int,long) ), """Only know how to add integer/long offsets""" return VBOOffset( self, other ) __enter__ = bind def __exit__( self, exc_type=None, exc_val=None, exc_tb=None ): """Context manager exit""" self.unbind() return False # do not supress exceptions...
class VBOOffset( object ): def __init__( self, vbo, offset ): self.vbo = vbo self.offset = offset def __getattr__( self, key ): if key != 'vbo': return getattr( self.vbo, key ) raise AttributeError( 'No %r key in VBOOffset'%(key,)) def __add__( self, other ): if hasattr( other, 'offset' ): other = other.offset return VBOOffset( self.vbo, self.offset + other )
class VBOHandler( FormatHandler ): """Handles VBO instances passed in as array data""" vp0 = ctypes.c_void_p( 0 ) def dataPointer( self, instance ): """Retrieve data-pointer from the instance's data Is always NULL, to indicate use of the bound pointer """ return 0 def from_param( self, instance, typeCode=None ): return self.vp0 def zeros( self, dims, typeCode ): """Not implemented""" raise NotImplemented( """Don't have VBO output support yet""" ) ones = zeros def asArray( self, value, typeCode=None ): """Given a value, convert to array representation""" return value def arrayToGLType( self, value ): """Given a value, guess OpenGL type of the corresponding pointer""" return ArrayDatatype.arrayToGLType( value.data ) def arrayByteCount( self, value ): return ArrayDatatype.arrayByteCount( value.data ) def arraySize( self, value, typeCode = None ): """Given a data-value, calculate dimensions for the array""" return ArrayDatatype.arraySize( value.data ) def unitSize( self, value, typeCode=None ): """Determine unit size of an array (if possible)""" return ArrayDatatype.unitSize( value.data ) def dimensions( self, value, typeCode=None ): """Determine dimensions of the passed array value (if possible)""" return ArrayDatatype.dimensions( value.data )
class VBOOffsetHandler( VBOHandler ): def dataPointer( self, instance ): """Retrieve data-pointer from the instance's data Is always NULL, to indicate use of the bound pointer """ return instance.offset def from_param( self, instance, typeCode=None ): return ctypes.c_void_p( instance.offset )
FormatHandler.loadAll() # otherwise just the VBO would get loaded :) VBO_HANDLER = VBOHandler() VBO_HANDLER.register( [ VBO ] )
VBOOFFSET_HANDLER = VBOOffsetHandler() VBOOFFSET_HANDLER.register( [ VBOOffset ] )
PyBuffer_FromMemory = ctypes.pythonapi.PyBuffer_FromMemory PyBuffer_FromMemory.restype = ctypes.py_object _cleaners = {} def _cleaner( vbo ): def clean( ref ): try: _cleaners.pop( vbo ) except Exception, err: pass else: glUnmapBuffer( vbo.target ) return clean
def mapVBO( vbo, access=GL.GL_READ_WRITE ): """Map the given buffer into a numpy array... Method taken from: http://www.mail-archive.com/numpy-discussion@lists.sourceforge.net/msg01161.html This should be considered an *experimental* API, it is not guaranteed to be available in future revisions of this library! """ vp = glMapBuffer( vbo.target, access ) buffer = PyBuffer_FromMemory( ctypes.c_void_p(vp), vbo.size ) array = frombuffer( buffer, 'B' ) _cleaners[vbo] = weakref.ref( array, _cleaner( vbo )) return array
|