Source code for ccpn.ui.gui.lib.OpenGL.CcpnOpenGLGlobal

"""
Module Documentation here
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2022"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
               "Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
                 "CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
                 "J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2022-01-27 15:24:37 +0000 (Thu, January 27, 2022) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Ed Brooksbank $"
__date__ = "$Date: 2018-12-20 13:28:13 +0000 (Thu, December 20, 2018) $"
#=========================================================================================
# Start of code
#=========================================================================================

import numpy as np
from PyQt5 import QtWidgets, QtGui
from ccpn.ui.gui.lib.OpenGL import GL
from ccpn.util.decorators import singleton
from ccpn.framework.PathsAndUrls import openGLFontsPath
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLFonts import CcpnGLFont
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLShader import ShaderProgramABC
from ccpn.util.Logging import getLogger


GLFONT_DEFAULT = 'OpenSans-Regular'
GLFONT_SUBSTITUTE = 'OpenSans-Regular'
GLFONT_DEFAULTSIZE = 13  # moved to preferences.appearance
_OLDGLFONT_SIZES = [10, 11, 12, 13, 14, 16, 18, 20, 22, 24]
GLFONT_DICT = {}

GLFONT_FILE = 0
GLFONT_NAME = 1
GLFONT_SIZE = 2
GLFONT_SCALE = 3

GLFONT_TRANSPARENT = 'Transparent'
GLFONT_DEFAULTFONTFILE = 'glAllFonts.fnt'
GLPIXELSHADER = 'GLPixelShader'
GLTEXTSHADER = 'GLTextShader'


[docs]@singleton class GLGlobalData(QtWidgets.QWidget): """ Class to handle the common information between all the GL widgets """ def __init__(self, parent=None, mainWindow=None): """ Initialise the global data :param parent: :param mainWindow: """ super(GLGlobalData, self).__init__() self._parent = parent self.mainWindow = mainWindow self.fonts = {} self.shaders = None _ver = QtGui.QOpenGLVersionProfile() self._GLVersion = GL.glGetString(GL.GL_VERSION) self._GLShaderVersion = GL.glGetString(GL.GL_SHADING_LANGUAGE_VERSION) getLogger().debug(f"OpenGL: {self._GLVersion.decode('utf-8')}") getLogger().debug(f"GLSL: {self._GLShaderVersion.decode('utf-8')}") _format = QtGui.QSurfaceFormat() getLogger().debug(f"Surface: {_format.version()}") self.loadFonts() self.initialiseShaders() self._texturesBound = False
[docs] def loadFonts(self): """Load all the necessary GLFonts """ self.fonts[GLFONT_DEFAULT] = CcpnGLFont(openGLFontsPath / GLFONT_DEFAULTFONTFILE, activeTexture=0, scale=1.0) _foundFonts = self.fonts[GLFONT_DEFAULT].fontGlyph # find all the fonts ion the list that have a matching 2* size, for double resolution retina displays self.GLFONT_SIZES = [_size for _size in _foundFonts.keys() if _size * 2 in _foundFonts.keys()] # set the current size from the preferences _size = self.mainWindow.application.preferences.appearance.spectrumDisplayFontSize if _size in self.GLFONT_SIZES: self.glSmallFontSize = _size else: self.glSmallFontSize = GLFONT_DEFAULTSIZE
[docs] def bindFonts(self): """Bind the font textures to the GL textures MUST be called inside GL current context, i.e., after GL.makeCurrent or inside initializeGL, paintGL """ if not self._texturesBound: for name, fnt in self.fonts.items(): fnt._bindFontTexture() self._texturesBound = True
[docs] def initialiseShaders(self): """Initialise the shaders """ # add some shaders to the global data self.shaders = {} for _shader in (PixelShader, TextShader, AliasedPixelShader, AliasedTextShader): _new = _shader() self.shaders[_new.name] = _new self._shaderProgram1 = self.shaders['pixelShader'] self._shaderProgramTex = self.shaders['textShader'] self._shaderProgramAlias = self.shaders['aliasedPixelShader'] self._shaderProgramTexAlias = self.shaders['aliasedTextShader']
[docs]class PixelShader(ShaderProgramABC): """ Pixel shader for contour plotting A very simple shader, uses the projection/viewport matrices to calculate the gl_Position, and passes through the gl_Color to set the pixel. """ name = 'pixelShader' CCPNSHADER = True # vertex shader to determine the co-ordinates vertexShader = """ #version 120 uniform mat4 pMatrix; uniform mat4 mvMatrix; varying vec4 _FC; void main() { // calculate the position gl_Position = pMatrix * mvMatrix * gl_Vertex; _FC = gl_Color; } """ # fragment shader to set the colour fragmentShader = """ #version 120 varying vec4 _FC; void main() { // set the pixel colour gl_FragColor = _FC; } """ # attribute list for shader attributes = {'pMatrix' : (16, np.float32), 'mvMatrix': (16, np.float32), } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # methods available for shader
[docs] def setPMatrix(self, matrix): """Set the contents of projection pMatrix :param matrix: consisting of 16 float32 elements """ self.setGLUniformMatrix4fv('pMatrix', 1, GL.GL_FALSE, matrix)
[docs] def setMVMatrix(self, matrix): """Set the contents of viewport mvMatrix :param matrix: consisting of 16 float32 elements """ self.setGLUniformMatrix4fv('mvMatrix', 1, GL.GL_FALSE, matrix)
[docs]class AliasedPixelShader(PixelShader): """ Pixel shader for aliased peak plotting Uses the projection/viewport matrices to calculate the gl_Position, and passes through the gl_Color to set the pixel. gl_Color is modified for peaks at different aliased positions """ name = 'aliasedPixelShader' CCPNSHADER = True # vertex shader to determine the co-ordinates vertexShader = """ #version 120 uniform mat4 pMatrix; uniform mat4 mvMatrix; attribute float alias; uniform float aliasPosition; varying float _aliased; varying vec4 _FC; void main() { // calculate the position, set shading value gl_Position = pMatrix * mvMatrix * vec4(gl_Vertex.xy, 0.0, 1.0); _FC = gl_Color; _aliased = (aliasPosition - alias); } """ # fragment shader to set the colour fragmentShader = """ #version 120 uniform vec4 background; uniform float aliasShade; uniform int aliasEnabled; varying vec4 _FC; varying float _aliased; void main() { // set the pixel colour if (abs(_aliased) < 0.5) { gl_FragColor = _FC; } else if (aliasEnabled != 0) { // set the colour if aliasEnabled (set opaque or set the alpha) gl_FragColor = (aliasShade * _FC) + (1 - aliasShade) * background; //gl_FragColor = vec4(_FC.xyz, _FC.w * aliasShade); } else { // skip the pixel discard; } } """ # additional attribute list for shader _attributes = { 'aliasPosition': (1, np.float32), 'background' : (4, np.float32), 'aliasShade' : (1, np.float32), 'aliasEnabled' : (1, np.uint32), } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # methods available for shader def __init__(self): self.attributes.update(self._attributes) super().__init__()
[docs] def setAliasPosition(self, aliasX, aliasY): """Set the alias position: Used to calculate whether the current peak is in the aliased region :param aliasX: X alias region :param aliasY: Y alias region """ self.setGLUniform1f('aliasPosition', getAliasSetting(aliasX, aliasY))
[docs] def setBackground(self, colour): """Set the background colour, for use with the solid text colour is tuple/list of 4 float/np.float32 elements in range 0.0-1.0 values outside range will be clipped :param colour: tuple/list """ if len(colour) != 4 and not isinstance(colour, (list, tuple, type(np.array))): raise TypeError('colour must tuple/list/numpy.array of 4 elements') if not all(isinstance(col, (float, np.float32)) for col in colour): raise TypeError('colour must be tuple/list/numpy.array of float/np.float32') self.setGLUniform4fv('background', 1, np.clip(colour, [0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0]))
[docs] def setAliasShade(self, aliasShade): """Set the alias shade: a single float in range [0.0, 1.0] Used to determine visibility of aliased peaks, 0.0 -> background colour :param aliasShade: single float32 """ if not isinstance(aliasShade, (float, np.float32)): raise TypeError('aliasShade must be a float') value = float(np.clip(aliasShade, 0.0, 1.0)) self.setGLUniform1f('aliasShade', value)
[docs] def setAliasEnabled(self, aliasEnabled): """Set the alias enabled: bool True/False Used to determine visibility of aliased peaks, using aliasShade False = disable visibility of aliased peaks :param aliasEnabled: bool """ if not isinstance(aliasEnabled, bool): raise TypeError('aliasEnabled must be a bool') value = 1 if aliasEnabled else 0 self.setGLUniform1i('aliasEnabled', value)
[docs]def getAliasSetting(aliasX, aliasY): """Return the alias setting for alias value (aliasX, aliasY) for insertion into shader """ if not isinstance(aliasX, int): raise TypeError('aliasX must be an int') if not isinstance(aliasY, int): raise TypeError('aliasY must be an int') # arbitrary value to pack into a single float return (256 * aliasX) + aliasY
[docs]class TextShader(ShaderProgramABC): """ Main Text shader Shader for plotting text, uses a billboard technique by using an _offset to determine pixel positions Colour of the pixel is set by glColorPointer array. Alpha value is grabbed from the texture to give anti-aliasing and modified by the 'alpha' attribute to affect overall transparency. """ name = 'textShader' CCPNSHADER = True # shader for plotting anti-aliased text to the screen vertexShader = """ #version 120 uniform mat4 pTexMatrix; uniform vec4 axisScale; uniform vec2 stackOffset; varying vec4 _FC; varying vec2 _texCoord; attribute vec3 _offset; void main() { gl_Position = pTexMatrix * ((gl_Vertex * axisScale) + vec4(_offset.xy + stackOffset, 0.0, 0.0)); _texCoord = gl_MultiTexCoord0.st; _FC = gl_Color; } """ # fragment shader to determine shading from the texture alpha value and the 'alpha' attribute fragmentShader = """ #version 120 uniform sampler2D texture; uniform vec4 background; uniform int blendEnabled; uniform float alpha; varying vec4 _FC; varying vec2 _texCoord; vec4 _texFilter; float _opacity; void main() { _texFilter = texture2D(texture, _texCoord); // colour for blending enabled _opacity = _texFilter.w * alpha; if (blendEnabled != 0) // multiply the character fade by the color fade to give the actual transparency gl_FragColor = vec4(_FC.xyz, _FC.w * _opacity); else // plot a background box around the character gl_FragColor = vec4((_FC.xyz * _opacity) + (1.0 - _opacity) * background.xyz, 1.0); } """ # attribute list for shader attributes = {'pTexMatrix' : (16, np.float32), 'axisScale' : (4, np.float32), 'stackOffset' : (2, np.float32), 'texture' : (1, np.uint32), 'background' : (4, np.float32), 'blendEnabled': (1, np.uint32), 'alpha' : (1, np.float32), } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # methods available for shader
[docs] def setPTexMatrix(self, matrix): """Set the contents of projection pTexMatrix :param matrix: consisting of 16 float32 elements """ self.setGLUniformMatrix4fv('pTexMatrix', 1, GL.GL_FALSE, matrix)
[docs] def setAxisScale(self, axisScale): """Set the axisScale values :param axisScale: consisting of 4 float32 elements """ if len(axisScale) != 4 and not isinstance(axisScale, (list, tuple, type(np.array))): raise TypeError('axisScale must tuple/list/numpy.array of 4 elements') if not all(isinstance(val, (float, np.float32)) for val in axisScale): raise TypeError('axisScale must be tuple/list/numpy.array of float/np.float32') self.setGLUniform4fv('axisScale', 1, axisScale)
[docs] def setStackOffset(self, stackOffset): """Set the stacking value for the 1d widget :param stackOffset: consisting of 2 float32 elements, the stacking offset in thje X, Y dimensions """ if len(stackOffset) != 2 and not isinstance(stackOffset, (list, tuple, type(np.array))): raise TypeError('stackOffset must tuple/list/numpy.array of 2 elements') if not all(isinstance(val, (float, np.float32)) for val in stackOffset): raise TypeError('stackOffset must be tuple/list/numpy.array of float/np.float32') self.setGLUniform2fv('stackOffset', 1, stackOffset)
[docs] def setTextureID(self, textureID): """Set the texture ID, determines which texture the text bitmaps are taken from :param textureID: uint32 """ self.setGLUniform1i('texture', textureID)
[docs] def setBackground(self, colour): """Set the background colour, for use with the solid text :param colour: consisting of 4 float32 elements """ if len(colour) != 4 and not isinstance(colour, (list, tuple, type(np.array))): raise TypeError('colour must tuple/list/numpy.array of 4 elements') if not all(isinstance(col, (float, np.float32)) for col in colour): raise TypeError('colour must be tuple/list/numpy.array of float/np.float32') self.setGLUniform4fv('background', 1, colour)
[docs] def setBlendEnabled(self, blendEnabled): """Set the blend enabled flag, determines whether the characters are surrounded with a solid background block :param blendEnabled: single uint32 """ if not isinstance(blendEnabled, bool): raise TypeError('blendEnabled must be a bool') value = 1 if blendEnabled else 0 self.setGLUniform1i('blendEnabled', value)
[docs] def setAlpha(self, alpha): """Set the alpha value, a multiplier to the transparency 0 - completely transparent; 1 - solid alpha to clipped to value [0.0, 1.0] :param alpha: single float32 """ if not isinstance(alpha, (float, np.float32)): raise TypeError('value must be a float') value = float(np.clip(alpha, 0.0, 1.0)) self.setGLUniform1f('alpha', value)
[docs]class AliasedTextShader(TextShader): """ Text shader for displaying text in aliased regions Shader for plotting text, uses a billboard technique by using an _offset to determine pixel positions Colour of the pixel is set by glColorPointer array. Alpha value is grabbed from the texture to give anti-aliasing and modified by the 'alpha' attribute to affect overall transparency. """ name = 'aliasedTextShader' CCPNSHADER = True # shader for plotting smooth text to the screen in aliased Regions vertexShader = """ #version 120 uniform mat4 pTexMatrix; uniform mat4 mvMatrix; uniform vec4 axisScale; uniform vec2 stackOffset; uniform float aliasPosition; varying float _aliased; varying vec4 _FC; varying vec2 _texCoord; attribute vec3 _offset; void main() { gl_Position = pTexMatrix * mvMatrix * ((gl_Vertex * axisScale) + vec4(_offset.xy + stackOffset, 0.0, 0.0)); //gl_Position = pTexMatrix * mvMatrix * (gl_Vertex + vec4(_offset.xy + stackOffset, 0.0, 0.0)); _texCoord = gl_MultiTexCoord0.st; _FC = gl_Color; _aliased = (aliasPosition - _offset.z); } """ # fragment shader to determine shading from the texture alpha value and the 'alpha' attribute fragmentShader = """ #version 120 uniform sampler2D texture; uniform vec4 background; uniform int blendEnabled; uniform float alpha; uniform float aliasShade; uniform int aliasEnabled; varying vec4 _FC; varying vec2 _texCoord; vec4 _texFilter; float _opacity; varying float _aliased; void main() { _texFilter = texture2D(texture, _texCoord); // colour for blending enabled _opacity = _texFilter.w * alpha; // set the pixel colour if (abs(_aliased) < 0.5) if (blendEnabled != 0) // multiply the character fade by the color fade to give the actual transparency gl_FragColor = vec4(_FC.xyz, _FC.w * _opacity); else // plot a background box around the character gl_FragColor = vec4((_FC.xyz * _opacity) + (1.0 - _opacity) * background.xyz, 1.0); else if (aliasEnabled != 0) { // modify the opacity _opacity *= aliasShade; if (blendEnabled != 0) // multiply the character fade by the color fade to give the actual transparency gl_FragColor = vec4(_FC.xyz, _FC.w * _opacity); else // plot a background box around the character gl_FragColor = vec4((_FC.xyz * _opacity) + (1.0 - _opacity) * background.xyz, 1.0); } else // skip the pixel discard; } """ """ """ # additional attribute list for shader _attributes = { 'mvMatrix' : (16, np.float32), 'aliasPosition': (1, np.float32), 'aliasShade' : (1, np.float32), 'aliasEnabled' : (1, np.uint32), } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # methods available for shader def __init__(self): self.attributes.update(self._attributes) super().__init__()
[docs] def setMVMatrix(self, matrix): """Set the contents of viewport mvMatrix :param matrix: consisting of 16 float32 elements """ self.setGLUniformMatrix4fv('mvMatrix', 1, GL.GL_FALSE, matrix)
[docs] def setAliasPosition(self, aliasX, aliasY): """Set the alias position: Used to calculate whether the current peak is in the aliased region :param aliasX: X alias region :param aliasY: Y alias region """ self.setGLUniform1f('aliasPosition', getAliasSetting(aliasX, aliasY))
[docs] def setAliasShade(self, aliasShade): """Set the alias shade: a single float in range [0.0, 1.0] Used to determine visibility of aliased peaks, 0.0 -> background colour :param aliasShade: single float32 """ if not isinstance(aliasShade, (float, np.float32)): raise TypeError('aliasShade must be a float') value = float(np.clip(aliasShade, 0.0, 1.0)) self.setGLUniform1f('aliasShade', value)
[docs] def setAliasEnabled(self, aliasEnabled): """Set the alias enabled: bool True/False Used to determine visibility of aliased peaks, using aliasShade False = disable visibility of aliased peaks :param aliasEnabled: bool """ if not isinstance(aliasEnabled, bool): raise TypeError('aliasEnabled must be a bool') value = 1 if aliasEnabled else 0 self.setGLUniform1i('aliasEnabled', value)