| | """VertexBufferObject helper class |
| | |
| | Basic usage: |
| | |
| | my_data = numpy.array( data, 'f') |
| | my_vbo = vbo.VBO( my_data ) |
| | ... |
| | my_vbo.bind() |
| | try: |
| | ... |
| | glVertexPointer( my_vbo, ... ) |
| | ... |
| | glNormalPointer( my_vbo + 12, ... ) |
| | finally: |
| | my_vbo.unbind() |
| | |
| | or |
| | |
| | with my_vbo: |
| | ... |
| | glVertexPointer( my_vbo, ... ) |
| | ... |
| | glNormalPointer( my_vbo + 12, ... ) |
| | |
| | See the OpenGLContext shader tutorials for a gentle introduction on the |
| | usage of VBO objects: |
| | |
| | http://pyopengl.sourceforge.net/context/tutorials/shader_intro.xhtml |
| | |
| | This implementation will choose either the ARB or Core (OpenGL 1.5) |
| | implementation of the VBO functions. |
| | """ |
| | from OpenGL.arrays.arraydatatype import ArrayDatatype |
| | from OpenGL.arrays.formathandler import FormatHandler |
| | from OpenGL.raw.GL import _types |
| | from OpenGL import error |
| | from OpenGL._bytes import bytes,unicode,as_8_bit |
| | import ctypes,logging |
| | _log = logging.getLogger( 'OpenGL.arrays.vbo' ) |
| | from OpenGL._bytes import long, integer_types |
| |
|
| | import weakref |
| | __all__ = ('VBO','VBOHandler','mapVBO') |
| |
|
| | class Implementation( object ): |
| | """Abstraction point for the various implementations that can be used |
| | """ |
| | IMPLEMENTATION_CLASSES = [] |
| | CHOSEN = None |
| | @classmethod |
| | def register( cls ): |
| | cls.IMPLEMENTATION_CLASSES.append( cls ) |
| | |
| | @classmethod |
| | def get_implementation( cls, *args ): |
| | if cls.CHOSEN is None: |
| | for possible in cls.IMPLEMENTATION_CLASSES: |
| | implementation = possible() |
| | if possible: |
| | Implementation.CHOSEN = implementation |
| | break |
| | return cls.CHOSEN |
| |
|
| | EXPORTED_NAMES = '''glGenBuffers |
| | glBindBuffer |
| | glBufferData |
| | glBufferSubData |
| | glDeleteBuffers |
| | glMapBuffer |
| | glUnmapBuffer |
| | GL_STATIC_DRAW |
| | GL_STATIC_READ |
| | GL_STATIC_COPY |
| | GL_DYNAMIC_DRAW |
| | GL_DYNAMIC_READ |
| | GL_DYNAMIC_COPY |
| | GL_STREAM_DRAW |
| | GL_STREAM_READ |
| | GL_STREAM_COPY |
| | GL_ARRAY_BUFFER |
| | GL_ELEMENT_ARRAY_BUFFER |
| | GL_UNIFORM_BUFFER |
| | GL_TEXTURE_BUFFER |
| | GL_TRANSFORM_FEEDBACK_BUFFER'''.split() |
| | 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 __nonzero__( self ): |
| | return self.available |
| | __bool__ = __nonzero__ |
| | def deleter( self, buffers, key): |
| | """Produce a deleter callback to delete the given buffer""" |
| | |
| | |
| | nfe = error.NullFunctionError |
| | gluint = _types.GLuint |
| | def doBufferDeletion( *args, **named ): |
| | while buffers: |
| | try: |
| | buffer = buffers.pop() |
| | except IndexError as err: |
| | break |
| | else: |
| | try: |
| | |
| | |
| | buf = gluint( buffer ) |
| | self.glDeleteBuffers(1, buf) |
| | except (AttributeError, nfe) as err: |
| | pass |
| | try: |
| | self._DELETERS_.pop( key ) |
| | except KeyError as err: |
| | pass |
| | return doBufferDeletion |
| | _DELETERS_ = {} |
| |
|
| | get_implementation = Implementation.get_implementation |
| |
|
| | from OpenGL import acceleratesupport |
| | VBO = None |
| | if acceleratesupport.ACCELERATE_AVAILABLE: |
| | try: |
| | from OpenGL_accelerate.vbo import ( |
| | VBO,VBOOffset,VBOHandler,VBOOffsetHandler, |
| | ) |
| | except ImportError as 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: |
| | |
| | if bool(vbo.get_implementation()): |
| | # vbo version of code |
| | else: |
| | # fallback version of code |
| | """ |
| | copied = False |
| | _no_cache_ = True |
| | def __init__( |
| | self, data, usage='GL_DYNAMIC_DRAW', |
| | target='GL_ARRAY_BUFFER', size=None, |
| | ): |
| | """Initialize the VBO object |
| | |
| | data -- PyOpenGL-compatible array-data structure, numpy arrays, ctypes arrays, etc. |
| | usage -- OpenGL usage constant describing expected data-flow patterns (this is a hint |
| | to the GL about where/how to cache the data) |
| | |
| | GL_STATIC_DRAW_ARB |
| | GL_STATIC_READ_ARB |
| | GL_STATIC_COPY_ARB |
| | GL_DYNAMIC_DRAW_ARB |
| | GL_DYNAMIC_READ_ARB |
| | GL_DYNAMIC_COPY_ARB |
| | GL_STREAM_DRAW_ARB |
| | GL_STREAM_READ_ARB |
| | GL_STREAM_COPY_ARB |
| | |
| | DRAW constants suggest to the card that the data will be primarily used to draw |
| | on the card. READ that the data will be read back into the GL. COPY means that |
| | the data will be used both for DRAW and READ operations. |
| | |
| | STATIC suggests that the data will only be written once (or a small number of times). |
| | DYNAMIC suggests that the data will be used a small number of times before being |
| | discarded. |
| | STREAM suggests that the data will be updated approximately every time that it is |
| | used (that is, it will likely only be used once). |
| | |
| | target -- VBO target to which to bind (array or indices) |
| | GL_ARRAY_BUFFER -- array-data binding |
| | GL_ELEMENT_ARRAY_BUFFER -- index-data binding |
| | GL_UNIFORM_BUFFER -- used to pass mid-size arrays of data packed into a buffer |
| | GL_TEXTURE_BUFFER -- used to pass large arrays of data as a pseudo-texture |
| | GL_TRANSFORM_FEEDBACK_BUFFER -- used to receive transformed vertices for processing |
| | |
| | size -- if not provided, will use arrayByteCount to determine the size of the data-array, |
| | thus this value (number of bytes) is required when using opaque data-structures, |
| | (such as ctypes pointers) as the array data-source. |
| | """ |
| | 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, (bytes,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 |
| | |
| | data -- PyOpenGL-compatible array-data structure, numpy arrays, ctypes arrays, etc. |
| | size -- if not provided, will use arrayByteCount to determine the size of the data-array, |
| | thus this value (number of bytes) is required when using opaque data-structures, |
| | (such as ctypes pointers) as the array data-source. |
| | """ |
| | 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""" ) |
| | |
| | data = ArrayDatatype.asArray( array ) |
| | data_length = ArrayDatatype.arrayByteCount( 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(self.data): |
| | |
| | self.copied = False |
| | elif len(data): |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | size = ArrayDatatype.arrayByteCount( self.data[0] ) |
| | |
| | |
| | start *= size |
| | stop *= size |
| | |
| | self._copy_segments.append( |
| | (start,(stop-start), data) |
| | ) |
| | def __len__( self ): |
| | """Delegate length/truth checks to our data-array""" |
| | return len( self.data ) |
| | def __getattr__( self, key ): |
| | """Delegate failing attribute lookups to our data-array""" |
| | 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 (if required) |
| | |
| | Ensures that the GL's version of the data in the VBO matches our |
| | internal view of the data, either by copying the entire data-set |
| | over with glBufferData or by updating the already-transferred |
| | data with glBufferSubData. |
| | """ |
| | 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) as err: |
| | pass |
| | def __int__( self ): |
| | """Get our VBO id""" |
| | if not self.buffers: |
| | self.create_buffers() |
| | return self.buffers[0] |
| | def bind( self ): |
| | """Bind this buffer for use in vertex calls |
| | |
| | If we have not yet created our implementation-level VBO, then we |
| | will create it before binding. Once bound, calls self.copy_data() |
| | """ |
| | 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 (create a VBOOffset)""" |
| | if hasattr( other, 'offset' ): |
| | other = other.offset |
| | assert isinstance( other, integer_types ), """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 |
| |
|
| | class VBOOffset( object ): |
| | """Offset into a VBO instance |
| | |
| | This class is normally instantiated by doing a my_vbo + int operation, |
| | it can be passed to VBO requiring operations and will generate the |
| | appropriate integer offset value to be passed in. |
| | """ |
| | def __init__( self, vbo, offset ): |
| | """Initialize the offset with vbo and offset (unsigned integer)""" |
| | self.vbo = vbo |
| | self.offset = offset |
| | def __getattr__( self, key ): |
| | """Delegate any undefined attribute save vbo to our vbo""" |
| | if key != 'vbo': |
| | return getattr( self.vbo, key ) |
| | raise AttributeError( 'No %r key in VBOOffset'%(key,)) |
| | def __add__( self, other ): |
| | """Allow adding integers or other VBOOffset instances |
| | |
| | returns a VBOOffset to the this VBO with other.offset + self.offset |
| | or, if other has no offset, returns VBOOffset with self.offset + 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 |
| | |
| | This FormatHandler is registered with PyOpenGL on import of this module |
| | to provide handling of VBO objects as array data-sources |
| | """ |
| | 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 ): |
| | """Always returns c_void_p(0)""" |
| | 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 ): |
| | """Handles VBOOffset instances passed in as array data |
| | |
| | Registered on module import to provide support for VBOOffset instances |
| | as sources for array data. |
| | """ |
| | def dataPointer( self, instance ): |
| | """Retrieve data-pointer from the instance's data |
| | |
| | returns instance' offset |
| | """ |
| | return instance.offset |
| | def from_param( self, instance, typeCode=None ): |
| | """Returns a c_void_p( instance.offset )""" |
| | return ctypes.c_void_p( instance.offset ) |
| |
|
| | _cleaners = {} |
| | def _cleaner( vbo ): |
| | """Construct a mapped-array cleaner function to unmap vbo.target""" |
| | def clean( ref ): |
| | try: |
| | _cleaners.pop( vbo ) |
| | except Exception as err: |
| | pass |
| | else: |
| | vbo.implementation.glUnmapBuffer( vbo.target ) |
| | return clean |
| |
|
| | def mapVBO( vbo, access=0x88BA ): |
| | """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! |
| | |
| | Simplification to use ctypes cast from comment by 'sashimi' on my blog... |
| | """ |
| | from numpy import frombuffer |
| | vp = vbo.implementation.glMapBuffer( vbo.target, access ) |
| | |
| | |
| | vp_array = ctypes.cast(vp, ctypes.POINTER(ctypes.c_byte*vbo.size) ) |
| | |
| | array = frombuffer( vp_array, 'B' ) |
| | _cleaners[vbo] = weakref.ref( array, _cleaner( vbo )) |
| | return array |
| |
|