File: OpenGLHelpers.py

package info (click to toggle)
python-pyqtgraph 0.14.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 8,168 kB
  • sloc: python: 54,831; makefile: 128; ansic: 40; sh: 2
file content (107 lines) | stat: -rw-r--r-- 4,063 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import importlib
import warnings

import numpy as np

from . import QT_LIB, QtGui, QtWidgets
from . import OpenGLConstants as GLC

if QT_LIB in ["PyQt5", "PySide2"]:
    QtOpenGL = QtGui
    QtOpenGLWidgets = QtWidgets
else:
    QtOpenGL = importlib.import_module(f'{QT_LIB}.QtOpenGL')
    QtOpenGLWidgets = importlib.import_module(f"{QT_LIB}.QtOpenGLWidgets")

__all__ = ["getFunctions", "GraphicsViewGLWidget"]

def getFunctions(context):
    glfn = None
    format = context.format()

    if QT_LIB in ["PySide2", "PySide6"]:
        glfn = context.extraFunctions()

    elif QT_LIB in ["PyQt5", "PyQt6"]:
        # PyQt5 has context.versionFunctions().
        #    however, when there are multiple GraphicsItems, the following bug occurs:
        #    all except one of the C++ objects of the returned versionFunctions() get
        #    deleted. i.e. in PyQt5, we are not able to cache the return value.
        # Qt6 has QOpenGLVersionFunctionsFactory().
        #    however with OpenGL ES: "versionFunctions: Not supported on OpenGL ES."
        # To overcome the above listed issues, we load the modules directly.

        # PyQt{5,6} only provides 2.0, 2.1, 4.1_Core, ES2.
        # ES2 module is present only if PyQt was compiled for GLES.
        # On Debian packaged PyQt5, python3-pyqt5.qtopengl needs to be installed.
        if context.isOpenGLES():
            # PyQt could have been compiled against OpenGL Desktop
            # but an OpenGL ES context was requested, so we use "2_0"
            # as a fallback.
            suffixes = ["ES2", "2_0"]
        elif format.version() >= (4, 1):
            suffixes = ["4_1_Core"]
        else:
            suffixes = ["2_1"]
        for suffix in suffixes:
            glfnname = f"QOpenGLFunctions_{suffix}"
            try:
                modname = f"_{glfnname}" if QT_LIB == "PyQt5" else "QtOpenGL"
                QtOpenGLFunctions = importlib.import_module(f"{QT_LIB}.{modname}")
                glfn = getattr(QtOpenGLFunctions, glfnname)()
            except (ModuleNotFoundError, AttributeError):
                continue
            glfn.initializeOpenGLFunctions()
            break

    if glfn is None:
        kind = "ES" if context.isOpenGLES() else "Desktop"
        raise RuntimeError(f"failed to obtain functions for OpenGL {kind} {format.version()} {format.profile()}")

    return glfn

def setUniformValue(program, key, value):
    # convenience function to mask the warnings
    with warnings.catch_warnings():
        # PySide2 : RuntimeWarning: SbkConverter: Unimplemented C++ array type.
        warnings.simplefilter("ignore")
        program.setUniformValue(key, value)

class GraphicsViewGLWidget(QtOpenGLWidgets.QOpenGLWidget):
    def __init__(self):
        super().__init__()
        self._programs = {}
        self._functions = None

    def initializeGL(self):
        # initializeGL gets called again when the context changes.
        # so we start off by destroying old resources.
        for program in self._programs.values():
            program.setParent(None)
        self._programs.clear()
        self._functions = None

    def retrieveProgram(self, key):
        return self._programs.get(key)

    def storeProgram(self, key, program):
        if (olditem := self._programs.get(key)) is not None:
            olditem.setParent(None)
        program.setParent(self)
        self._programs[key] = program

    def getFunctions(self):
        if self._functions is None:
            self._functions = getFunctions(self.context())
        return self._functions

    def setViewboxClip(self, view):
        rect = view.sceneBoundingRect()
        dpr = self.devicePixelRatioF()
        # glScissor wants the bottom-left corner and is Y-up
        x, y = rect.left(), self.height() - rect.bottom()
        w, h = rect.width(), rect.height()
        glfn = self.getFunctions()
        glfn.glScissor(*[round(v * dpr) for v in [x, y, w, h]])
        glfn.glEnable(GLC.GL_SCISSOR_TEST)
        # the test will be disabled by QPainter.endNativePainting().