home *** CD-ROM | disk | FTP | other *** search
- """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
-
-
- import weakref
- __all__ = ('VBO','VBOHandler')
-
- 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 get_implementation( *args ):
- """Retrieve the appropriate implementation for this machine"""
- if VBO._I_ is None:
- VBO.implementation = VBO._I_ = Implementation()
- return VBO._I_
-
-
- 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.
- """
- _DELETERS_ = {}
- copied = False
- _no_cache_ = True # do not cache in context data arrays
- def __init__(
- self, data, usage='GL_DYNAMIC_DRAW',
- target='GL_ARRAY_BUFFER',
- ):
- self.data = data
- self.usage = usage
- 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 ):
- """Update our entire array with new data"""
- self.data = data
- self.copied = False
- 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._DELETERS_[ id(self) ] = weakref.ref( self, deleter( self.buffers, id(self), self.implementation ))
- 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:
- self.implementation.glBufferData(
- self.target,
- 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, 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 ), """Only know how to add integer offsets"""
- return VBOOffset( self, other )
-
- 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 )
-
- def deleter( buffers, key, implementation ):
- """Produce a deleter callback to delete the given buffer"""
- def doBufferDeletion( *args, **named ):
- for buffer in buffers:
- try:
- implementation.glDeleteBuffers(1, buffer)
- except AttributeError, err:
- pass
- try:
- VBO._DELETERS_.pop( key )
- except KeyError, err:
- pass
- return doBufferDeletion
-
- class VBOHandler( FormatHandler ):
- """Handles VBO instances passed in as array data"""
- 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 ctypes.c_void_p( 0 )
- 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 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 )
-
- HANDLER = VBOHandler()
- HANDLER.loadAll() # otherwise just the VBO would get loaded :)
- HANDLER.register( [ VBO ] )
-
- HANDLER = VBOOffsetHandler()
- HANDLER.register( [ VBOOffset ] )
-