summaryrefslogtreecommitdiff
path: root/silx/gui/_glutils
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@debian.org>2021-01-06 14:10:12 +0100
committerPicca Frédéric-Emmanuel <picca@debian.org>2021-01-06 14:10:12 +0100
commitb3bea947efa55d2c0f198b6c6795b3177be27f45 (patch)
tree4116758aafe4483bf472c1d54b519e685737fd77 /silx/gui/_glutils
parent5ad425ff4e62f5e003178813ebd073577679a00e (diff)
New upstream version 0.14.0+dfsg
Diffstat (limited to 'silx/gui/_glutils')
-rw-r--r--silx/gui/_glutils/FramebufferTexture.py3
-rw-r--r--silx/gui/_glutils/OpenGLWidget.py14
-rw-r--r--silx/gui/_glutils/Texture.py319
-rw-r--r--silx/gui/_glutils/utils.py30
4 files changed, 200 insertions, 166 deletions
diff --git a/silx/gui/_glutils/FramebufferTexture.py b/silx/gui/_glutils/FramebufferTexture.py
index cc05080..e065030 100644
--- a/silx/gui/_glutils/FramebufferTexture.py
+++ b/silx/gui/_glutils/FramebufferTexture.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2014-2017 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
@@ -62,6 +62,7 @@ class FramebufferTexture(object):
**kwargs):
self._texture = Texture(internalFormat, shape=shape, **kwargs)
+ self._texture.prepare()
self._previousFramebuffer = 0 # Used by with statement
diff --git a/silx/gui/_glutils/OpenGLWidget.py b/silx/gui/_glutils/OpenGLWidget.py
index 1f7bfae..5e3fcb8 100644
--- a/silx/gui/_glutils/OpenGLWidget.py
+++ b/silx/gui/_glutils/OpenGLWidget.py
@@ -329,6 +329,20 @@ class OpenGLWidget(qt.QWidget):
else:
return self.__openGLWidget.getDevicePixelRatio()
+ def getDotsPerInch(self):
+ """Returns current screen resolution as device pixels per inch.
+
+ :rtype: float
+ """
+ screen = self.window().windowHandle().screen()
+ if screen is not None:
+ # TODO check if this is correct on different OS/screen
+ # OK on macOS10.12/qt5.13.2
+ dpi = screen.physicalDotsPerInch() * self.getDevicePixelRatio()
+ else: # Fallback
+ dpi = 96. * self.getDevicePixelRatio()
+ return dpi
+
def getOpenGLVersion(self):
"""Returns the available OpenGL version.
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))
diff --git a/silx/gui/_glutils/utils.py b/silx/gui/_glutils/utils.py
index 35cf819..d5627ef 100644
--- a/silx/gui/_glutils/utils.py
+++ b/silx/gui/_glutils/utils.py
@@ -29,45 +29,25 @@ __authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "10/01/2017"
-from . import gl
import numpy
-
-_GL_TYPE_SIZES = {
- gl.GL_FLOAT: 4,
- gl.GL_BYTE: 1,
- gl.GL_SHORT: 2,
- gl.GL_INT: 4,
- gl.GL_UNSIGNED_BYTE: 1,
- gl.GL_UNSIGNED_SHORT: 2,
- gl.GL_UNSIGNED_INT: 4,
-}
+from OpenGL.constants import BYTE_SIZES as _BYTE_SIZES
+from OpenGL.constants import ARRAY_TO_GL_TYPE_MAPPING as _ARRAY_TO_GL_TYPE_MAPPING
def sizeofGLType(type_):
"""Returns the size in bytes of an element of type `type_`"""
- return _GL_TYPE_SIZES[type_]
-
-
-_TYPE_CONVERTER = {
- numpy.dtype(numpy.float32): gl.GL_FLOAT,
- numpy.dtype(numpy.int8): gl.GL_BYTE,
- numpy.dtype(numpy.int16): gl.GL_SHORT,
- numpy.dtype(numpy.int32): gl.GL_INT,
- numpy.dtype(numpy.uint8): gl.GL_UNSIGNED_BYTE,
- numpy.dtype(numpy.uint16): gl.GL_UNSIGNED_SHORT,
- numpy.dtype(numpy.uint32): gl.GL_UNSIGNED_INT,
-}
+ return _BYTE_SIZES[type_]
def isSupportedGLType(type_):
"""Test if a numpy type or dtype can be converted to a GL type."""
- return numpy.dtype(type_) in _TYPE_CONVERTER
+ return numpy.dtype(type_).char in _ARRAY_TO_GL_TYPE_MAPPING
def numpyToGLType(type_):
"""Returns the GL type corresponding the provided numpy type or dtype."""
- return _TYPE_CONVERTER[numpy.dtype(type_)]
+ return _ARRAY_TO_GL_TYPE_MAPPING[numpy.dtype(type_).char]
def segmentTrianglesIntersection(segment, triangles):