diff options
Diffstat (limited to 'silx/gui/_glutils/VertexBuffer.py')
-rw-r--r-- | silx/gui/_glutils/VertexBuffer.py | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/silx/gui/_glutils/VertexBuffer.py b/silx/gui/_glutils/VertexBuffer.py new file mode 100644 index 0000000..689b543 --- /dev/null +++ b/silx/gui/_glutils/VertexBuffer.py @@ -0,0 +1,266 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2014-2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""This module provides a class managing an OpenGL vertex buffer.""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "10/01/2017" + + +import logging +from ctypes import c_void_p +import numpy + +from . import gl +from .utils import numpyToGLType, sizeofGLType + + +_logger = logging.getLogger(__name__) + + +class VertexBuffer(object): + """Object handling an OpenGL vertex buffer object + + :param data: Data used to fill the vertex buffer + :type data: numpy.ndarray or None + :param int size: Size in bytes of the buffer or None for data size + :param usage: OpenGL vertex buffer expected usage pattern: + GL_STREAM_DRAW, GL_STATIC_DRAW (default) or GL_DYNAMIC_DRAW + :param target: Target buffer: + GL_ARRAY_BUFFER (default) or GL_ELEMENT_ARRAY_BUFFER + """ + # OpenGL|ES 2.0 subset: + _USAGES = gl.GL_STREAM_DRAW, gl.GL_STATIC_DRAW, gl.GL_DYNAMIC_DRAW + _TARGETS = gl.GL_ARRAY_BUFFER, gl.GL_ELEMENT_ARRAY_BUFFER + + def __init__(self, + data=None, + size=None, + usage=None, + target=None): + if usage is None: + usage = gl.GL_STATIC_DRAW + assert usage in self._USAGES + + if target is None: + target = gl.GL_ARRAY_BUFFER + assert target in self._TARGETS + + self._target = target + self._usage = usage + + self._name = gl.glGenBuffers(1) + self.bind() + + if data is None: + assert size is not None + self._size = size + gl.glBufferData(self._target, + self._size, + c_void_p(0), + self._usage) + else: + data = numpy.array(data, copy=False, order='C') + if size is not None: + assert size <= data.nbytes + + self._size = size or data.nbytes + gl.glBufferData(self._target, + self._size, + data, + self._usage) + + gl.glBindBuffer(self._target, 0) + + @property + def target(self): + """The target buffer of the vertex buffer""" + return self._target + + @property + def usage(self): + """The expected usage of the vertex buffer""" + return self._usage + + @property + def name(self): + """OpenGL Vertex Buffer object name (int)""" + if self._name is not None: + return self._name + else: + raise RuntimeError("No OpenGL buffer resource, \ + discard has already been called") + + @property + def size(self): + """Size in bytes of the Vertex Buffer Object (int)""" + if self._size is not None: + return self._size + else: + raise RuntimeError("No OpenGL buffer resource, \ + discard has already been called") + + def bind(self): + """Bind the vertex buffer""" + gl.glBindBuffer(self._target, self.name) + + def update(self, data, offset=0, size=None): + """Update vertex buffer content. + + :param numpy.ndarray data: The data to put in the vertex buffer + :param int offset: Offset in bytes in the buffer where to put the data + :param int size: If provided, size of data to copy + """ + data = numpy.array(data, copy=False, order='C') + if size is None: + size = data.nbytes + assert offset + size <= self.size + with self: + gl.glBufferSubData(self._target, offset, size, data) + + def discard(self): + """Delete the vertex buffer""" + if self._name is not None: + gl.glDeleteBuffers(self._name) + self._name = None + self._size = None + else: + _logger.warning("Discard has already been called") + + # with statement + + def __enter__(self): + self.bind() + + def __exit__(self, exctype, excvalue, traceback): + gl.glBindBuffer(self._target, 0) + + +class VertexBufferAttrib(object): + """Describes data stored in a vertex buffer + + Convenient class to store info for glVertexAttribPointer calls + + :param VertexBuffer vbo: The vertex buffer storing the data + :param int type_: The OpenGL type of the data + :param int size: The number of data elements stored in the VBO + :param int dimension: The number of `type_` element(s) in [1, 4] + :param int offset: Start offset of data in the vertex buffer + :param int stride: Data stride in the vertex buffer + """ + + _GL_TYPES = gl.GL_UNSIGNED_BYTE, gl.GL_FLOAT, gl.GL_INT + + def __init__(self, + vbo, + type_, + size, + dimension=1, + offset=0, + stride=0, + normalisation=False): + self.vbo = vbo + assert type_ in self._GL_TYPES + self.type_ = type_ + self.size = size + assert 1 <= dimension <= 4 + self.dimension = dimension + self.offset = offset + self.stride = stride + self.normalisation = bool(normalisation) + + @property + def itemsize(self): + """Size in bytes of a vertex buffer element (int)""" + return self.dimension * sizeofGLType(self.type_) + + itemSize = itemsize # Backward compatibility + + def setVertexAttrib(self, attribute): + """Call glVertexAttribPointer with objects information""" + normalisation = gl.GL_TRUE if self.normalisation else gl.GL_FALSE + with self.vbo: + gl.glVertexAttribPointer(attribute, + self.dimension, + self.type_, + normalisation, + self.stride, + c_void_p(self.offset)) + + def copy(self): + return VertexBufferAttrib(self.vbo, + self.type_, + self.size, + self.dimension, + self.offset, + self.stride, + self.normalisation) + + +def vertexBuffer(arrays, prefix=None, suffix=None, usage=None): + """Create a single vertex buffer from multiple 1D or 2D numpy arrays. + + It is possible to reserve memory before and after each array in the VBO + + :param arrays: Arrays of data to store + :type arrays: Iterable of numpy.ndarray + :param prefix: If given, number of elements to reserve before each array + :type prefix: Iterable of int or None + :param suffix: If given, number of elements to reserve after each array + :type suffix: Iterable of int or None + :param int usage: vertex buffer expected usage or None for default + :returns: List of VertexBufferAttrib objects sharing the same vertex buffer + """ + info = [] + vbosize = 0 + + if prefix is None: + prefix = (0,) * len(arrays) + if suffix is None: + suffix = (0,) * len(arrays) + + for data, pre, post in zip(arrays, prefix, suffix): + data = numpy.array(data, copy=False, order='C') + shape = data.shape + assert len(shape) <= 2 + type_ = numpyToGLType(data.dtype) + size = shape[0] + pre + post + dimension = 1 if len(shape) == 1 else shape[1] + sizeinbytes = size * dimension * sizeofGLType(type_) + sizeinbytes = 4 * ((sizeinbytes + 3) >> 2) # 4 bytes alignment + copyoffset = vbosize + pre * dimension * sizeofGLType(type_) + info.append((data, type_, size, dimension, + vbosize, sizeinbytes, copyoffset)) + vbosize += sizeinbytes + + vbo = VertexBuffer(size=vbosize, usage=usage) + + result = [] + for data, type_, size, dimension, offset, sizeinbytes, copyoffset in info: + copysize = data.shape[0] * dimension * sizeofGLType(type_) + vbo.update(data, offset=copyoffset, size=copysize) + result.append( + VertexBufferAttrib(vbo, type_, size, dimension, offset, 0)) + return result |