diff options
Diffstat (limited to 'src/silx/gui/plot/backends/glutils/GLTexture.py')
-rw-r--r-- | src/silx/gui/plot/backends/glutils/GLTexture.py | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/src/silx/gui/plot/backends/glutils/GLTexture.py b/src/silx/gui/plot/backends/glutils/GLTexture.py new file mode 100644 index 0000000..cbbe7ac --- /dev/null +++ b/src/silx/gui/plot/backends/glutils/GLTexture.py @@ -0,0 +1,269 @@ +# /*########################################################################## +# +# 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 +# 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 classes wrapping OpenGL texture.""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "03/04/2017" + + +from ctypes import c_void_p +import logging + +import numpy + +from ...._glutils import gl, Texture, numpyToGLType + + +_logger = logging.getLogger(__name__) + + +def _checkTexture2D(internalFormat, shape, format_=None, type_=gl.GL_FLOAT, border=0): + """Check if texture size with provided parameters is supported + + :rtype: bool + """ + height, width = shape + gl.glTexImage2D( + gl.GL_PROXY_TEXTURE_2D, + 0, + internalFormat, + width, + height, + border, + format_ or internalFormat, + type_, + c_void_p(0), + ) + width = gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_2D, 0, gl.GL_TEXTURE_WIDTH) + return bool(width) + + +MIN_TEXTURE_SIZE = 64 + + +def _getMaxSquareTexture2DSize( + internalFormat=gl.GL_RGBA, format_=None, type_=gl.GL_FLOAT, border=0 +): + """Returns a supported size for a corresponding square texture + + :returns: GL_MAX_TEXTURE_SIZE or a smaller supported size (not optimal) + :rtype: int + """ + # Is this useful? + maxTexSize = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE) + while maxTexSize > MIN_TEXTURE_SIZE and not _checkTexture2D( + internalFormat, (maxTexSize, maxTexSize), format_, type_, border + ): + maxTexSize //= 2 + return max(MIN_TEXTURE_SIZE, maxTexSize) + + +class Image(object): + """Image of any size eventually using multiple textures or larger texture""" + + _WRAP = (gl.GL_CLAMP_TO_EDGE, gl.GL_CLAMP_TO_EDGE) + _MIN_FILTER = gl.GL_NEAREST + _MAG_FILTER = gl.GL_NEAREST + + def __init__(self, internalFormat, data, format_=None, texUnit=0): + self.internalFormat = internalFormat + self.height, self.width = data.shape[0:2] + type_ = numpyToGLType(data.dtype) + + if _checkTexture2D(internalFormat, data.shape[0:2], format_, type_): + texture = Texture( + internalFormat, + data, + format_, + texUnit=texUnit, + minFilter=self._MIN_FILTER, + magFilter=self._MAG_FILTER, + wrap=self._WRAP, + ) + texture.prepare() + vertices = numpy.array( + ( + (0.0, 0.0, 0.0, 0.0), + (self.width, 0.0, 1.0, 0.0), + (0.0, self.height, 0.0, 1.0), + (self.width, self.height, 1.0, 1.0), + ), + dtype=numpy.float32, + ) + self.tiles = ( + ( + texture, + vertices, + { + "xOrigData": 0, + "yOrigData": 0, + "wData": self.width, + "hData": self.height, + }, + ), + ) + + else: + # Handle dimension too large: make tiles + maxTexSize = _getMaxSquareTexture2DSize(internalFormat, format_, type_) + + nCols = (self.width + maxTexSize - 1) // maxTexSize + colWidths = [self.width // nCols] * nCols + colWidths[-1] += self.width % nCols + + nRows = (self.height + maxTexSize - 1) // maxTexSize + rowHeights = [self.height // nRows] * nRows + rowHeights[-1] += self.height % nRows + + tiles = [] + yOrig = 0 + for hData in rowHeights: + xOrig = 0 + for wData in colWidths: + if ( + hData < MIN_TEXTURE_SIZE or wData < MIN_TEXTURE_SIZE + ) and not _checkTexture2D( + internalFormat, (hData, wData), format_, type_ + ): + # Ensure texture size is at least MIN_TEXTURE_SIZE + tH = max(hData, MIN_TEXTURE_SIZE) + tW = max(wData, MIN_TEXTURE_SIZE) + + uMax, vMax = float(wData) / tW, float(hData) / tH + + # TODO issue with type_ and alignment + texture = Texture( + internalFormat, + data=None, + format_=format_, + shape=(tH, tW), + texUnit=texUnit, + minFilter=self._MIN_FILTER, + magFilter=self._MAG_FILTER, + wrap=self._WRAP, + ) + # TODO handle unpack + texture.update( + format_, data[yOrig : yOrig + hData, xOrig : xOrig + wData] + ) + # texture.update(format_, type_, data, + # width=wData, height=hData, + # unpackRowLength=width, + # unpackSkipPixels=xOrig, + # unpackSkipRows=yOrig) + else: + uMax, vMax = 1, 1 + # TODO issue with type_ and unpacking tiles + # TODO idea to handle unpack: use array strides + # As it is now, it will make a copy + texture = Texture( + internalFormat, + data[yOrig : yOrig + hData, xOrig : xOrig + wData], + format_, + texUnit=texUnit, + minFilter=self._MIN_FILTER, + magFilter=self._MAG_FILTER, + wrap=self._WRAP, + ) + # TODO + # unpackRowLength=width, + # unpackSkipPixels=xOrig, + # unpackSkipRows=yOrig) + vertices = numpy.array( + ( + (xOrig, yOrig, 0.0, 0.0), + (xOrig + wData, yOrig, uMax, 0.0), + (xOrig, yOrig + hData, 0.0, vMax), + (xOrig + wData, yOrig + hData, uMax, vMax), + ), + dtype=numpy.float32, + ) + texture.prepare() + tiles.append( + ( + texture, + vertices, + { + "xOrigData": xOrig, + "yOrigData": yOrig, + "wData": wData, + "hData": hData, + }, + ) + ) + xOrig += wData + yOrig += hData + self.tiles = tuple(tiles) + + def discard(self): + for texture, vertices, _ in self.tiles: + texture.discard() + del self.tiles + + def updateAll(self, format_, data, texUnit=0): + if not hasattr(self, "tiles"): + raise RuntimeError("No texture, discard has already been called") + + assert data.shape[:2] == (self.height, self.width) + if len(self.tiles) == 1: + self.tiles[0][0].update(format_, data, texUnit=texUnit) + else: + for texture, _, info in self.tiles: + yOrig, xOrig = info["yOrigData"], info["xOrigData"] + height, width = info["hData"], info["wData"] + texture.update( + format_, + data[yOrig : yOrig + height, xOrig : xOrig + width], + texUnit=texUnit, + ) + texture.prepare() + # TODO check + # width=info['wData'], height=info['hData'], + # texUnit=texUnit, unpackAlign=unpackAlign, + # unpackRowLength=self.width, + # unpackSkipPixels=info['xOrigData'], + # unpackSkipRows=info['yOrigData']) + + def render(self, posAttrib, texAttrib, texUnit=0): + try: + tiles = self.tiles + except AttributeError: + raise RuntimeError("No texture, discard has already been called") + + for texture, vertices, _ in tiles: + texture.bind(texUnit) + + stride = vertices.shape[-1] * vertices.itemsize + gl.glEnableVertexAttribArray(posAttrib) + gl.glVertexAttribPointer( + posAttrib, 2, gl.GL_FLOAT, gl.GL_FALSE, stride, vertices + ) + + texCoordsPtr = c_void_p(vertices.ctypes.data + 2 * vertices.itemsize) + gl.glEnableVertexAttribArray(texAttrib) + gl.glVertexAttribPointer( + texAttrib, 2, gl.GL_FLOAT, gl.GL_FALSE, stride, texCoordsPtr + ) + gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, len(vertices)) |