diff options
author | Picca Frédéric-Emmanuel <picca@debian.org> | 2021-01-06 14:10:12 +0100 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@debian.org> | 2021-01-06 14:10:12 +0100 |
commit | b3bea947efa55d2c0f198b6c6795b3177be27f45 (patch) | |
tree | 4116758aafe4483bf472c1d54b519e685737fd77 /silx/gui/_glutils/Texture.py | |
parent | 5ad425ff4e62f5e003178813ebd073577679a00e (diff) |
New upstream version 0.14.0+dfsg
Diffstat (limited to 'silx/gui/_glutils/Texture.py')
-rw-r--r-- | silx/gui/_glutils/Texture.py | 319 |
1 files changed, 179 insertions, 140 deletions
diff --git a/silx/gui/_glutils/Texture.py b/silx/gui/_glutils/Texture.py index a7fd44b..c72135a 100644 --- a/silx/gui/_glutils/Texture.py +++ b/silx/gui/_glutils/Texture.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2014-2019 European Synchrotron Radiation Facility +# Copyright (c) 2014-2020 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 @@ -81,20 +81,23 @@ class Texture(object): else: shape = data.shape + self._deferredUpdates = [(format_, data, None)] + assert len(shape) in (2, 3) self._shape = tuple(shape) self._ndim = len(shape) self.texUnit = texUnit - self._name = gl.glGenTextures(1) - self.bind(self.texUnit) + self._texParameterUpdates = {} # Store texture params to update + + self._minFilter = minFilter if minFilter is not None else gl.GL_NEAREST + self._texParameterUpdates[gl.GL_TEXTURE_MIN_FILTER] = self._minFilter - self._minFilter = None - self.minFilter = minFilter if minFilter is not None else gl.GL_NEAREST + self._magFilter = magFilter if magFilter is not None else gl.GL_LINEAR + self._texParameterUpdates[gl.GL_TEXTURE_MAG_FILTER] = self._magFilter - self._magFilter = None - self.magFilter = magFilter if magFilter is not None else gl.GL_LINEAR + self._name = None # Store texture ID if wrap is not None: if not isinstance(wrap, abc.Iterable): @@ -102,69 +105,10 @@ class Texture(object): assert len(wrap) == self.ndim - gl.glTexParameter(self.target, - gl.GL_TEXTURE_WRAP_S, - wrap[-1]) - gl.glTexParameter(self.target, - gl.GL_TEXTURE_WRAP_T, - wrap[-2]) + self._texParameterUpdates[gl.GL_TEXTURE_WRAP_S] = wrap[-1] + self._texParameterUpdates[gl.GL_TEXTURE_WRAP_T] = wrap[-2] if self.ndim == 3: - gl.glTexParameter(self.target, - gl.GL_TEXTURE_WRAP_R, - wrap[0]) - - gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) - - # This are the defaults, useless to set if not modified - # gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, 0) - # gl.glPixelStorei(gl.GL_UNPACK_SKIP_PIXELS, 0) - # gl.glPixelStorei(gl.GL_UNPACK_SKIP_ROWS, 0) - # gl.glPixelStorei(gl.GL_UNPACK_IMAGE_HEIGHT, 0) - # gl.glPixelStorei(gl.GL_UNPACK_SKIP_IMAGES, 0) - - if data is None: - data = c_void_p(0) - type_ = gl.GL_UNSIGNED_BYTE - else: - type_ = utils.numpyToGLType(data.dtype) - - if self.ndim == 2: - _logger.debug( - 'Creating 2D texture shape: (%d, %d),' - ' internal format: %s, format: %s, type: %s', - self.shape[0], self.shape[1], - str(self.internalFormat), str(format_), str(type_)) - - gl.glTexImage2D( - gl.GL_TEXTURE_2D, - 0, - self.internalFormat, - self.shape[1], - self.shape[0], - 0, - format_, - type_, - data) - else: - _logger.debug( - 'Creating 3D texture shape: (%d, %d, %d),' - ' internal format: %s, format: %s, type: %s', - self.shape[0], self.shape[1], self.shape[2], - str(self.internalFormat), str(format_), str(type_)) - - gl.glTexImage3D( - gl.GL_TEXTURE_3D, - 0, - self.internalFormat, - self.shape[2], - self.shape[1], - self.shape[0], - 0, - format_, - type_, - data) - - gl.glBindTexture(self.target, 0) + self._texParameterUpdates[gl.GL_TEXTURE_WRAP_R] = wrap[0] @property def target(self): @@ -188,12 +132,11 @@ class Texture(object): @property def name(self): - """OpenGL texture name""" - if self._name is not None: - return self._name - else: - raise RuntimeError( - "No OpenGL texture resource, discard has already been called") + """OpenGL texture name. + + It is None if not initialized or already discarded. + """ + return self._name @property def minFilter(self): @@ -204,10 +147,7 @@ class Texture(object): def minFilter(self, minFilter): if minFilter != self.minFilter: self._minFilter = minFilter - self.bind() - gl.glTexParameter(self.target, - gl.GL_TEXTURE_MIN_FILTER, - self.minFilter) + self._texParameterUpdates[gl.GL_TEXTURE_MIN_FILTER] = minFilter @property def magFilter(self): @@ -218,20 +158,112 @@ class Texture(object): def magFilter(self, magFilter): if magFilter != self.magFilter: self._magFilter = magFilter - self.bind() - gl.glTexParameter(self.target, - gl.GL_TEXTURE_MAG_FILTER, - self.magFilter) + self._texParameterUpdates[gl.GL_TEXTURE_MAG_FILTER] = magFilter - def discard(self): - """Delete associated OpenGL texture""" - if self._name is not None: - gl.glDeleteTextures(self._name) - self._name = None - else: - _logger.warning("Discard as already been called") + def _isPrepareRequired(self) -> bool: + """Returns True if OpenGL texture needs to be updated. - def bind(self, texUnit=None): + :rtype: bool + """ + return (self._name is None or + self._texParameterUpdates or + self._deferredUpdates) + + def _prepareAndBind(self, texUnit=None): + """Synchronizes the OpenGL texture""" + if self._name is None: + self._name = gl.glGenTextures(1) + + self._bind(texUnit) + + # Synchronizes texture parameters + for pname, param in self._texParameterUpdates.items(): + gl.glTexParameter(self.target, pname, param) + self._texParameterUpdates = {} + + # Copy data to texture + for format_, data, offset in self._deferredUpdates: + gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) + + # This are the defaults, useless to set if not modified + # gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, 0) + # gl.glPixelStorei(gl.GL_UNPACK_SKIP_PIXELS, 0) + # gl.glPixelStorei(gl.GL_UNPACK_SKIP_ROWS, 0) + # gl.glPixelStorei(gl.GL_UNPACK_IMAGE_HEIGHT, 0) + # gl.glPixelStorei(gl.GL_UNPACK_SKIP_IMAGES, 0) + + if data is None: + data = c_void_p(0) + type_ = gl.GL_UNSIGNED_BYTE + else: + type_ = utils.numpyToGLType(data.dtype) + + if offset is None: # Initialize texture + if self.ndim == 2: + _logger.debug( + 'Creating 2D texture shape: (%d, %d),' + ' internal format: %s, format: %s, type: %s', + self.shape[0], self.shape[1], + str(self.internalFormat), str(format_), str(type_)) + + gl.glTexImage2D( + gl.GL_TEXTURE_2D, + 0, + self.internalFormat, + self.shape[1], + self.shape[0], + 0, + format_, + type_, + data) + + else: + _logger.debug( + 'Creating 3D texture shape: (%d, %d, %d),' + ' internal format: %s, format: %s, type: %s', + self.shape[0], self.shape[1], self.shape[2], + str(self.internalFormat), str(format_), str(type_)) + + gl.glTexImage3D( + gl.GL_TEXTURE_3D, + 0, + self.internalFormat, + self.shape[2], + self.shape[1], + self.shape[0], + 0, + format_, + type_, + data) + + else: # Update already existing texture + if self.ndim == 2: + gl.glTexSubImage2D(gl.GL_TEXTURE_2D, + 0, + offset[1], + offset[0], + data.shape[1], + data.shape[0], + format_, + type_, + data) + + else: + gl.glTexSubImage3D(gl.GL_TEXTURE_3D, + 0, + offset[2], + offset[1], + offset[0], + data.shape[2], + data.shape[1], + data.shape[0], + format_, + type_, + data) + + self._deferredUpdates = [] + + def _bind(self, texUnit=None): """Bind the texture to a texture unit. :param int texUnit: The texture unit to use @@ -241,73 +273,80 @@ class Texture(object): gl.glActiveTexture(gl.GL_TEXTURE0 + texUnit) gl.glBindTexture(self.target, self.name) + def _unbind(self, texUnit=None): + """Reset texture binding to a texture unit. + + :param int texUnit: The texture unit to use + """ + if texUnit is None: + texUnit = self.texUnit + gl.glActiveTexture(gl.GL_TEXTURE0 + texUnit) + gl.glBindTexture(self.target, 0) + + def prepare(self): + """Synchronizes the OpenGL texture. + + This method must be called with a current OpenGL context. + """ + if self._isPrepareRequired(): + self._prepareAndBind() + self._unbind() + + def bind(self, texUnit=None): + """Bind the texture to a texture unit. + + The OpenGL texture is updated if needed. + + This method must be called with a current OpenGL context. + + :param int texUnit: The texture unit to use + """ + if self._isPrepareRequired(): + self._prepareAndBind(texUnit) + else: + self._bind(texUnit) + + def discard(self): + """Delete associated OpenGL texture. + + This method must be called with a current OpenGL context. + """ + if self._name is not None: + gl.glDeleteTextures(self._name) + self._name = None + else: + _logger.warning("Texture not initialized or already discarded") + # with statement def __enter__(self): self.bind() def __exit__(self, exc_type, exc_val, exc_tb): - gl.glActiveTexture(gl.GL_TEXTURE0 + self.texUnit) - gl.glBindTexture(self.target, 0) + self._unbind() - def update(self, - format_, - data, - offset=(0, 0, 0), - texUnit=None): + def update(self, format_, data, offset=(0, 0, 0), copy=True): """Update the content of the texture. Texture is not resized, so data must fit into texture with the given offset. + This update is performed lazily during next call to + :meth:`prepare` or :meth:`bind`. + Data MUST not be changed until then. + :param format_: The OpenGL format of the data :param data: The data to use to update the texture - :param offset: The offset in the texture where to copy the data - :type offset: List[int] - :param int texUnit: - The texture unit to use (default: the one provided at init) + :param List[int] offset: Offset in the texture where to copy the data + :param bool copy: + True (default) to copy data, False to use as is (do not modify) """ - data = numpy.array(data, copy=False, order='C') + data = numpy.array(data, copy=copy, order='C') + offset = tuple(offset) assert data.ndim == self.ndim assert len(offset) >= self.ndim for i in range(self.ndim): assert offset[i] + data.shape[i] <= self.shape[i] - gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) - - # This are the defaults, useless to set if not modified - # gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, 0) - # gl.glPixelStorei(gl.GL_UNPACK_SKIP_PIXELS, 0) - # gl.glPixelStorei(gl.GL_UNPACK_SKIP_ROWS, 0) - # gl.glPixelStorei(gl.GL_UNPACK_IMAGE_HEIGHT, 0) - # gl.glPixelStorei(gl.GL_UNPACK_SKIP_IMAGES, 0) - - self.bind(texUnit) - - type_ = utils.numpyToGLType(data.dtype) - - if self.ndim == 2: - gl.glTexSubImage2D(gl.GL_TEXTURE_2D, - 0, - offset[1], - offset[0], - data.shape[1], - data.shape[0], - format_, - type_, - data) - gl.glBindTexture(gl.GL_TEXTURE_2D, 0) - else: - gl.glTexSubImage3D(gl.GL_TEXTURE_3D, - 0, - offset[2], - offset[1], - offset[0], - data.shape[2], - data.shape[1], - data.shape[0], - format_, - type_, - data) - gl.glBindTexture(gl.GL_TEXTURE_3D, 0) + self._deferredUpdates.append((format_, data, offset)) |