def offscreen_context(format): """Provide a temporary QOpenGLContext with the given QSurfaceFormat on an QOffscreenSurface.""" surface = QOffscreenSurface() surface.create() context = QOpenGLContext() context.setFormat(format) context.create() context.makeCurrent(surface) try: yield context finally: context.doneCurrent()
class GLWindow(QWindow): def __init__(self): super(GLWindow, self).__init__() self.title= "GLWindow" self.setGeometry(0, 0, 640, 480) self.setupUI() def setupUI(self): self.setSurfaceType(QWindow.OpenGLSurface) self.renderctxt = QOpenGLContext(self) self.renderctxt.setFormat(self.requestedFormat()) self.renderctxt.create() self.renderctxt.makeCurrent(self) self.glfunc = self.renderctxt.versionFunctions() self.glfunc.initializeOpenGLFunctions()
def setContext(cls, major_version: int, minor_version: int, core=False, profile=None): new_format = QSurfaceFormat() new_format.setMajorVersion(major_version) new_format.setMinorVersion(minor_version) if core: profile_ = QSurfaceFormat.CoreProfile else: profile_ = QSurfaceFormat.CompatibilityProfile if profile is not None: profile_ = profile new_format.setProfile(profile_) new_context = QOpenGLContext() new_context.setFormat(new_format) success = new_context.create() if success: return new_context else: Logger.log( "e", "Failed creating OpenGL context (%d, %d, core=%s)" % (major_version, minor_version, core)) return None
def setContext(cls, major_version: int, minor_version: int, core=False, profile=None): """Set OpenGL context, given major, minor version + core using QOpenGLContext Unfortunately, what you get back does not have to be the requested version. """ new_format = QSurfaceFormat() new_format.setMajorVersion(major_version) new_format.setMinorVersion(minor_version) if core: profile_ = QSurfaceFormat.CoreProfile else: profile_ = QSurfaceFormat.CompatibilityProfile if profile is not None: profile_ = profile new_format.setProfile(profile_) new_context = QOpenGLContext() new_context.setFormat(new_format) success = new_context.create() if success: return new_context else: Logger.log( "e", "Failed creating OpenGL context (%d, %d, core=%s)" % (major_version, minor_version, core)) return None
def opengl_info() -> Optional[OpenGLInfo]: # pragma: no cover """Get the OpenGL vendor used. This returns a string such as 'nouveau' or 'Intel Open Source Technology Center'; or None if the vendor can't be determined. """ assert QApplication.instance() override = os.environ.get('QUTE_FAKE_OPENGL') if override is not None: log.init.debug("Using override {}".format(override)) vendor, version = override.split(', ', maxsplit=1) return OpenGLInfo.parse(vendor=vendor, version=version) old_context = cast(Optional[QOpenGLContext], QOpenGLContext.currentContext()) old_surface = None if old_context is None else old_context.surface() surface = QOffscreenSurface() surface.create() ctx = QOpenGLContext() ok = ctx.create() if not ok: log.init.debug("Creating context failed!") return None ok = ctx.makeCurrent(surface) if not ok: log.init.debug("Making context current failed!") return None try: if ctx.isOpenGLES(): # Can't use versionFunctions there return OpenGLInfo(gles=True) vp = QOpenGLVersionProfile() vp.setVersion(2, 0) try: vf = ctx.versionFunctions(vp) except ImportError as e: log.init.debug("Importing version functions failed: {}".format(e)) return None if vf is None: log.init.debug("Getting version functions failed!") return None vendor = vf.glGetString(vf.GL_VENDOR) version = vf.glGetString(vf.GL_VERSION) return OpenGLInfo.parse(vendor=vendor, version=version) finally: ctx.doneCurrent() if old_context and old_surface: old_context.makeCurrent(old_surface)
def opengl_vendor(): # pragma: no cover """Get the OpenGL vendor used. This returns a string such as 'nouveau' or 'Intel Open Source Technology Center'; or None if the vendor can't be determined. """ assert QApplication.instance() override = os.environ.get('QUTE_FAKE_OPENGL_VENDOR') if override is not None: log.init.debug("Using override {}".format(override)) return override old_context = QOpenGLContext.currentContext() old_surface = None if old_context is None else old_context.surface() surface = QOffscreenSurface() surface.create() ctx = QOpenGLContext() ok = ctx.create() if not ok: log.init.debug("Creating context failed!") return None ok = ctx.makeCurrent(surface) if not ok: log.init.debug("Making context current failed!") return None try: if ctx.isOpenGLES(): # Can't use versionFunctions there return None vp = QOpenGLVersionProfile() vp.setVersion(2, 0) try: vf = ctx.versionFunctions(vp) except ImportError as e: log.init.debug("Importing version functions failed: {}".format(e)) return None if vf is None: log.init.debug("Getting version functions failed!") return None return vf.glGetString(vf.GL_VENDOR) finally: ctx.doneCurrent() if old_context and old_surface: old_context.makeCurrent(old_surface)
class WindowGL(QWindow): def __init__(self): super().__init__() fmt = QSurfaceFormat() fmt.setVersion(2, 0) fmt.setProfile(QSurfaceFormat.CompatibilityProfile) fmt.setStereo(False) fmt.setSwapBehavior(QSurfaceFormat.DoubleBuffer) self.setSurfaceType(QWindow.OpenGLSurface) self.context = QOpenGLContext() self.context.setFormat(fmt) if not self.context.create(): raise Exception("Unable to create context") self.create() self.setTitle("OpenGL") def exposeEvent(self, e): e.accept() if self.isExposed() and self.isVisible(): self.update() def makeCurrent(self): self.context.makeCurrent(self) def swapBuffers(self): self.context.swapBuffers(self) def resize(self, w, h): super().resize(w, h) self.makeCurrent() GL.glViewport(0, 0, w, h) def update(self): tri_size = 0.7 self.makeCurrent() GL.glClearColor(0, 0, 0, 0) GL.glClearDepth(1) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) GL.glBegin(GL.GL_TRIANGLES) GL.glVertex2f(0, tri_size) GL.glColor3f(1, 0, 0) GL.glVertex2f(-tri_size, -tri_size) GL.glColor3f(0, 1, 0) GL.glVertex2f(tri_size, -tri_size) GL.glColor3f(0, 0, 1) GL.glEnd() GL.glFlush() self.swapBuffers()
def opengl_vendor(): # pragma: no cover """Get the OpenGL vendor used. This returns a string such as 'nouveau' or 'Intel Open Source Technology Center'; or None if the vendor can't be determined. """ assert QApplication.instance() old_context = QOpenGLContext.currentContext() old_surface = None if old_context is None else old_context.surface() surface = QOffscreenSurface() surface.create() ctx = QOpenGLContext() ok = ctx.create() if not ok: log.init.debug("opengl_vendor: Creating context failed!") return None ok = ctx.makeCurrent(surface) if not ok: log.init.debug("opengl_vendor: Making context current failed!") return None try: if ctx.isOpenGLES(): # Can't use versionFunctions there return None vp = QOpenGLVersionProfile() vp.setVersion(2, 0) try: vf = ctx.versionFunctions(vp) except ImportError as e: log.init.debug("opengl_vendor: Importing version functions " "failed: {}".format(e)) return None if vf is None: log.init.debug("opengl_vendor: Getting version functions failed!") return None return vf.glGetString(vf.GL_VENDOR) finally: ctx.doneCurrent() if old_context and old_surface: old_context.makeCurrent(old_surface)
def opengl_vendor(): # pragma: no cover """Get the OpenGL vendor used. This returns a string such as 'nouveau' or 'Intel Open Source Technology Center'; or None if the vendor can't be determined. """ # We're doing those imports here because this is only available with Qt 5.4 # or newer. from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile, QOffscreenSurface) assert QApplication.instance() old_context = QOpenGLContext.currentContext() old_surface = None if old_context is None else old_context.surface() surface = QOffscreenSurface() surface.create() ctx = QOpenGLContext() ok = ctx.create() if not ok: log.init.debug("opengl_vendor: Creating context failed!") return None ok = ctx.makeCurrent(surface) if not ok: log.init.debug("opengl_vendor: Making context current failed!") return None try: if ctx.isOpenGLES(): # Can't use versionFunctions there return None vp = QOpenGLVersionProfile() vp.setVersion(2, 0) vf = ctx.versionFunctions(vp) if vf is None: log.init.debug("opengl_vendor: Getting version functions failed!") return None return vf.glGetString(vf.GL_VENDOR) finally: ctx.doneCurrent() if old_context and old_surface: old_context.makeCurrent(old_surface)
def setContext(cls, major_version, minor_version, core = False, profile = None): new_format = QSurfaceFormat() new_format.setMajorVersion(major_version) new_format.setMinorVersion(minor_version) if core: profile_ = QSurfaceFormat.CoreProfile else: profile_ = QSurfaceFormat.CompatibilityProfile if profile is not None: profile_ = profile new_format.setProfile(profile_) new_context = QOpenGLContext() new_context.setFormat(new_format) success = new_context.create() if success: return new_context else: Logger.log("e", "Failed creating OpenGL context (%d, %d, core=%s)" % (major_version, minor_version, core)) return None
def opengl_vendor(): # pragma: no cover """Get the OpenGL vendor used. This returns a string such as 'nouveau' or 'Intel Open Source Technology Center'; or None if the vendor can't be determined. """ # We're doing those imports here because this is only available with Qt 5.4 # or newer. from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile, QOffscreenSurface) assert QApplication.instance() assert QOpenGLContext.currentContext() is None surface = QOffscreenSurface() surface.create() ctx = QOpenGLContext() ok = ctx.create() assert ok ok = ctx.makeCurrent(surface) assert ok if ctx.isOpenGLES(): # Can't use versionFunctions there return None vp = QOpenGLVersionProfile() vp.setVersion(2, 0) vf = ctx.versionFunctions(vp) vendor = vf.glGetString(vf.GL_VENDOR) ctx.doneCurrent() return vendor
class OpenGLWindow(QWindow): def __init__(self, parent=None): super(OpenGLWindow, self).__init__(parent) self.m_update_pending = False self.m_animating = False self.m_context = None self.m_gl = None self.setSurfaceType(QWindow.OpenGLSurface) def initialize(self): pass def setAnimating(self, animating): self.m_animating = animating if animating: self.renderLater() def renderLater(self): if not self.m_update_pending: self.m_update_pending = True QGuiApplication.postEvent(self, QEvent(QEvent.UpdateRequest)) def renderNow(self): if not self.isExposed(): return self.m_update_pending = False needsInitialize = False if self.m_context is None: self.m_context = QOpenGLContext(self) self.m_context.setFormat(self.requestedFormat()) self.m_context.create() needsInitialize = True self.m_context.makeCurrent(self) if needsInitialize: self.m_gl = self.m_context.versionFunctions() self.m_gl.initializeOpenGLFunctions() self.initialize() self.render(self.m_gl) self.m_context.swapBuffers(self) if self.m_animating: self.renderLater() def event(self, event): if event.type() == QEvent.UpdateRequest: self.renderNow() return True return super(OpenGLWindow, self).event(event) def exposeEvent(self, event): self.renderNow() def resizeEvent(self, event): self.renderNow()
class OpenGLWindow(QWindow): def __init__(self, parent=None): super(OpenGLWindow, self).__init__(parent) self.m_update_pending = False self.m_animating = False self.m_context = None self.m_gl = None self.setSurfaceType(QWindow.OpenGLSurface) def initialize(self): pass def setAnimating(self, animating): self.m_animating = animating if animating: self.renderLater() def renderLater(self): if not self.m_update_pending: self.m_update_pending = True QGuiApplication.postEvent(self, QEvent(QEvent.UpdateRequest)) def renderNow(self): if not self.isExposed(): return self.m_update_pending = False needsInitialize = False if self.m_context is None: self.m_context = QOpenGLContext(self) self.m_context.setFormat(self.requestedFormat()) self.m_context.create() needsInitialize = True self.m_context.makeCurrent(self) if needsInitialize: version_profile = QOpenGLVersionProfile() version_profile.setVersion(2, 0) self.m_gl = self.m_context.versionFunctions(version_profile) self.m_gl.initializeOpenGLFunctions() self.initialize() self.render(self.m_gl) self.m_context.swapBuffers(self) if self.m_animating: self.renderLater() def event(self, event): if event.type() == QEvent.UpdateRequest: self.renderNow() return True return super(OpenGLWindow, self).event(event) def exposeEvent(self, event): self.renderNow() def resizeEvent(self, event): self.renderNow()
class OFQtGraphicsContextCallback(PyOF.GraphicsContextCallback): """ A callback that interfaces OpenFrames with PyQt's OpenGL rendering context Attributes ---------- _context : QOpenGLContext Context used to render the window contents _surface : QSurface Surface (window) on which contents will be drawn """ def __init__(self, surface): super().__init__() self._surface = surface self._context = None def swapBuffers(self): """ Swaps the front/back rendering buffer """ if self._context is not None: self._context.swapBuffers(self._surface) def makeCurrent(self): """ Makes _context current for the surface of this window Returns ------- bool True if successful False if an error occurs """ success = False if self._context is None: self._context = QOpenGLContext() self._context.create() success = self._context.makeCurrent(self._surface) if success: self._context.doneCurrent() else: return success if self._context is not None: success = self._context.makeCurrent(self._surface) # err = glGetError() return success def updateContext(self): """ Updates _context when it becomes invalid, e.g. when resizing the window Returns ------- bool True if successful False if an error occurs """ return self.makeCurrent()
def detectBestOpenGLVersion(cls): Logger.log("d", "Trying OpenGL context 4.1...") ctx = cls.setContext(4, 1, core=True) if ctx is not None: fmt = ctx.format() profile = fmt.profile() # First test: we hope for this if ((fmt.majorVersion() == 4 and fmt.minorVersion() >= 1) or (fmt.majorVersion() > 4)) and profile == QSurfaceFormat.CoreProfile: Logger.log( "d", "Yay, we got at least OpenGL 4.1 core: %s", cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) # https://riverbankcomputing.com/pipermail/pyqt/2017-January/038640.html # PyQt currently only implements 2.0, 2.1 and 4.1Core # If eg 4.5Core would be detected and used here, PyQt would not be able to handle it. major_version = 4 minor_version = 1 # CURA-6092: Check if we're not using software backed 4.1 context; A software 4.1 context # is much slower than a hardware backed 2.0 context gl_window = QWindow() gl_window.setSurfaceType(QWindow.OpenGLSurface) gl_window.showMinimized() gl_format = QSurfaceFormat() gl_format.setMajorVersion(major_version) gl_format.setMinorVersion(minor_version) gl_format.setProfile(profile) gl_context = QOpenGLContext() gl_context.setFormat(gl_format) gl_context.create() gl_context.makeCurrent(gl_window) gl_profile = QOpenGLVersionProfile() gl_profile.setVersion(major_version, minor_version) gl_profile.setProfile(profile) gl = gl_context.versionFunctions( gl_profile ) # type: Any #It's actually a protected class in PyQt that depends on the requested profile and the implementation of your graphics card. gpu_type = "Unknown" # type: str result = None if gl: result = gl.initializeOpenGLFunctions() if not result: Logger.log("e", "Could not initialize OpenGL to get gpu type") else: # WORKAROUND: Cura/#1117 Cura-packaging/12 # Some Intel GPU chipsets return a string, which is not undecodable via PyQt5. # This workaround makes the code fall back to a "Unknown" renderer in these cases. try: gpu_type = gl.glGetString(gl.GL_RENDERER) #type: str except UnicodeDecodeError: Logger.log( "e", "DecodeError while getting GL_RENDERER via glGetString!" ) Logger.log("d", "OpenGL renderer type for this OpenGL version: %s", gpu_type) if "software" in gpu_type.lower(): Logger.log( "w", "Unfortunately OpenGL 4.1 uses software rendering") else: return major_version, minor_version, QSurfaceFormat.CoreProfile else: Logger.log("d", "Failed to create OpenGL context 4.1.") # Fallback: check min spec Logger.log("d", "Trying OpenGL context 2.0...") ctx = cls.setContext(2, 0, profile=QSurfaceFormat.NoProfile) if ctx is not None: fmt = ctx.format() profile = fmt.profile() if fmt.majorVersion() >= 2 and fmt.minorVersion() >= 0: Logger.log( "d", "We got at least OpenGL context 2.0: %s", cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) return 2, 0, QSurfaceFormat.NoProfile else: Logger.log( "d", "Current OpenGL context is too low: %s" % cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) return None, None, None else: Logger.log("d", "Failed to create OpenGL context 2.0.") return None, None, None
class Window(QWindow): """ A QWindow for rendering of WindowProxy Attributes ---------- _context : QOpenGLContext Context used to render the window contents _window_proxy_id : int The WindowProxy responsible for drawing _saved_size : QSize If a resize event occurs before WindowProxy is started, then the size is saved here so that it can be set when WindowProxy is started _proxy_started_once : bool True after the first time that _of has been started """ def __init__(self, nrow=1, ncol=1, id=0): """ Create an instance of OFWindow Attributes ---------- nrow : int, optional Number of rows in WindowProxy grid, default = 1 ncol : int, optional Number of columns in WindowProxy grid, default = 1 id : int, optional Identifier for the WindowProxy (each WindowProxy should have a unique identifier), default = 0 """ super().__init__() self._context = None self._proxy_started = False self._saved_size = None self.setSurfaceType(QWindow.OpenGLSurface) self._window_proxy_id = id ofwin_createproxy(0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, nrow, ncol, True, self._window_proxy_id, False) ofwin_setmakecurrentfunction(self.make_current) ofwin_setupdatecontextfunction(self.make_current) ofwin_setswapbuffersfunction(self.swap_buffers) def exposeEvent(self, event): """ Overrides QWindow.exposeEvent() """ if not self._proxy_started: self._proxy_started = True ofwin_activate(self._window_proxy_id) ofwin_start() if self._saved_size is not None: ofwin_resizewindow(0, 0, self._saved_size.width(), self._saved_size.height()) self._saved_size = None def hideEvent(self, event): """ Overrides QWindow.exposeEvent() """ ofwin_activate(self._window_proxy_id) ofwin_signalstop() while ofwin_isrunning() == 1: QCoreApplication.processEvents(QEventLoop.AllEvents, 100) self._proxy_started = False def resizeEvent(self, event): """ Overrides QWindow.resizeEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: ofwin_resizewindow(0, 0, event.size().width(), event.size().height()) else: self._saved_size = event.size() def mousePressEvent(self, event): """ Overrides QWindow.mousePressEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: button = Window._map_qt_button_to_of_button(event.button()) if button != 0: ofwin_buttonpress(event.x(), event.y(), button) def mouseReleaseEvent(self, event): """ Overrides QWindow.mouseReleaseEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: button = Window._map_qt_button_to_of_button(event.button()) if button != 0: ofwin_buttonrelease(event.x(), event.y(), button) def mouseMoveEvent(self, event): """ Overrides QWindow.mouseMoveEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: ofwin_mousemotion(event.x(), event.y()) def keyPressEvent(self, event): """ Overrides QWindow.keyPressEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: key = Window._map_qt_key_event_to_osg_key(event) ofwin_keypress(key) # TODO call glGetError() to print any errors that may have occurred def make_current(self): """ Makes _context current for the surface of this window Returns ------- bool True if successful False if an error occurs """ success = False if self._context is None: self._context = QOpenGLContext() self._context.create() success = self._context.makeCurrent(self) if success: # self.initializeOpenGLFunctions() self._context.doneCurrent() else: return success if self._context is not None: success = self._context.makeCurrent(self) # err = glGetError() return success def swap_buffers(self): """ Swaps the buffer from _context to the surface of this window """ if self._context is not None: self._context.swapBuffers(self) @staticmethod def _map_qt_button_to_of_button(qt_button): """ Maps a Qt.MouseButton enumeration to an int for OpenFrames Parameters ---------- qt_button : Qt.MouseButton The button to map Returns ------- int The corresponding button for OpenFrames """ if qt_button == Qt.LeftButton: return 1 elif qt_button == Qt.RightButton: return 3 elif qt_button == Qt.MiddleButton: return 2 elif qt_button == Qt.BackButton: return 6 elif qt_button == Qt.ForwardButton: return 7 else: return 0 @staticmethod def _map_qt_key_event_to_osg_key(event): """ Maps a QKeyEvent to an int for OpenFrames Parameters ---------- event : PyQt5.QtGui.QKeyEvent.QKeyEvent The key event to map Returns ------- int The corresponding key code for OpenFrames """ if Qt.Key_A <= event.key() <= Qt.Key_Z: if event.modifiers() & Qt.ShiftModifier: key = event.key() else: key = event.key() + 0x20 else: key = event.key() return key
class ViewerWindow(QWindow): instructions = """ --Key controls 0-9 - toggle data layers r - reset view parameters c - clip view t - view from top l - light/dark background = - increase point size - - decrease point size --Mouse controls drag shift + move - translate dataset scroll - scale dataset shift + scroll - change field of view ctrl + scroll - move far clipping plane alt + scroll - move near clipping plane ctrl + alt + scroll - move far and near clipping plane simultaniously """ def __init__(self, parent=None, **kwargs): super(ViewerWindow, self).__init__(parent) self.setSurfaceType(QWindow.OpenGLSurface) format = QSurfaceFormat() format.setVersion(3, 3) format.setProfile(QSurfaceFormat.CoreProfile) format.setStereo(False) format.setSwapBehavior(QSurfaceFormat.DoubleBuffer) format.setDepthBufferSize(24) format.setSamples(16) self.context = QOpenGLContext(self) self.context.setFormat(format) if not self.context.create(): raise Exception('self.context.create() failed') self.create() size = 720, 720 self.resize(*size) self.context.makeCurrent(self) self.hud_program = CrossHairProgram() self.default_view = np.eye(4, dtype=np.float32) self.view = self.default_view self.model = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) self.layer_manager = LayerManager() self.visibility_toggle_listeners = [] self.multiview = True self.rotation = q.quaternion() self.scale = 0.6 self.translation = np.zeros(3) self.radius = 0.5 * min(*size) self.fov = 5. self.camera_position = -12. self.near_clip = .1 if 'near_clip' in kwargs: self.near_clip = kwargs['near_clip'] self.far_clip = 100. if 'far_clip' in kwargs: self.far_clip = kwargs['far_clip'] self.projection_mode = 'perspective' # 'orthographic' self.size = size self.bg_white = False self.viewpoint_dict = {} print((self.instructions)) def add_layer(self, layer): self.layer_manager.add_layer(layer) return layer # keeping this compatibility with older scripts def add_data_source(self, name, opts, points, normals=None, radii=None, intensity=None, category=None, zrange=None, **kwargs): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source( name, opts, points, normals=normals, radii=radii, intensity=intensity, category=category, zrange=zrange, **kwargs) def add_data_source_line(self, name, coords_start, coords_end, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_line( name, coords_start, coords_end, **args) def add_data_source_triangle(self, name, coords, normals, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_triangle( name, coords, normals, **args) def run(self): self.initialize() self.show() def set_layer_visibility(self, name, is_visible): for listener in self.visibility_toggle_listeners: listener(name, is_visible) def center_view(self, center=None): if center is None: center = self.layer_manager.first.get_center() data_range = self.layer_manager.first.data_range self.data_width = data_range[0] self.data_height = data_range[2] self.translation = np.zeros(3) self.model = np.eye(4, dtype=np.float32) translate(self.model, -center[0], -center[1], -center[2]) for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) self.update_view_matrix() def initialize(self): self.context.makeCurrent(self) gl.glDepthMask(gl.GL_TRUE) gl.glEnable(gl.GL_DEPTH_TEST) gl.glDepthFunc(gl.GL_LESS) self.set_bg() view_width, view_height = [x / self.radius for x in self.size] self.center_view() for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) program.setUniform('u_view', self.view) program.setUniform('u_projection', self.projection) self.modelscale = .6 * 2 * min(2. * view_width / self.data_width, 2. * view_height / self.data_height) self.scale = self.modelscale self.update_view_matrix() self.last_mouse_pos = 0, 0 self.on_resize(*self.size) def render(self): if not self.isExposed(): return self.context.makeCurrent(self) bits = 0 bits |= gl.GL_COLOR_BUFFER_BIT bits |= gl.GL_DEPTH_BUFFER_BIT bits |= gl.GL_STENCIL_BUFFER_BIT gl.glClear(bits) gl.glEnable(gl.GL_PROGRAM_POINT_SIZE) for program in self.layer_manager.programs(): if program.do_blending: if self.bg_white: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_SRC_ALPHA) else: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_ONE, gl.GL_SRC_ALPHA) else: gl.glDisable(gl.GL_BLEND) self.layer_manager.draw() if self.hud_program.is_visible: self.hud_program.draw() self.context.swapBuffers(self) def event(self, event): # print event.type() if event.type() == QEvent.UpdateRequest: self.render() return True return super(ViewerWindow, self).event(event) def exposeEvent(self, event): self.render() def resizeEvent(self, event): size = event.size() self.on_resize(size.width(), size.height()) self.render() def update_view_matrix(self): self.view = np.eye(4, dtype=np.float32) translate(self.view, self.translation[0], self.translation[1], self.translation[2]) scale(self.view, self.scale, self.scale, self.scale) self.view = self.view.dot( np.array(q.matrix(self.rotation), dtype=np.float32)) # translate(self.view, -self.translation[0], -self.translation[1], -self.translation[2] ) translate(self.view, 0, 0, self.camera_position) for program in self.layer_manager.programs(): program.setUniform('u_view', self.view) if program.draw_type == 'points': program.setUniform('u_model_scale', self.scale) def update_projection_matrix(self): view_width, view_height = [x / self.radius for x in self.size] if self.projection_mode == 'orthographic': self.projection = ortho(-view_width, view_width, -view_height, view_height, self.near_clip, self.far_clip) elif self.projection_mode == 'perspective': self.projection = perspective(self.fov, view_width / float(view_height), self.near_clip, self.far_clip) for program in self.layer_manager.programs(): program.setUniform('u_projection', self.projection) def screen2view(self, x, y): width, height = self.size # print (x-width/2.)/self.radius, ((height-y)-height/2.)/self.radius return (x - width / 2.) / self.radius, ( (height - y) - height / 2.) / self.radius def set_bg(self): gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) if self.bg_white: gl.glClearColor(0.95, 0.95, 0.95, 1) # gloo.set_state('translucent', clear_color=np.array([1,1,1,1]) ) else: gl.glClearColor(0.05, 0.05, 0.05, 1) # gloo.set_state('translucent', clear_color=np.array([0.15,0.15,0.15,1]) ) def keyPressEvent(self, event): key = event.key() repeat = event.isAutoRepeat() if key == Qt.Key_R: self.view = np.eye(4, dtype=np.float32) self.rotation = q.quaternion() self.scale = self.modelscale self.camera_position = -12. self.near_clip = 2. self.far_clip = 100. self.center_view() self.update_projection_matrix() elif key == Qt.Key_Minus: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform( 'u_point_size', program.uniforms['u_point_size'] / 1.2) elif key == Qt.Key_Equal: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform( 'u_point_size', program.uniforms['u_point_size'] * 1.2) elif key == Qt.Key_B: for program in self.layer_manager.programs(): if program.is_visible and program.draw_type == 'points': program.do_blending = not program.do_blending elif key == Qt.Key_C: self.near_clip = -self.camera_position - 0.1 self.far_clip = -self.camera_position + 0.1 self.update_projection_matrix() elif key == Qt.Key_T: self.rotation = q.quaternion() self.update_view_matrix() elif key == Qt.Key_P: if self.projection_mode == 'perspective': self.projection_mode = 'orthographic' else: self.projection_mode = 'perspective' self.update_projection_matrix() elif key == Qt.Key_L: self.bg_white = not self.bg_white self.set_bg() elif Qt.Key_0 <= key <= Qt.Key_9: i = int(chr(key)) - 1 layers = [item for item in self.layer_manager.layers.items()] if i < len(layers): name, layer = layers[i] self.set_layer_visibility(name, layer.toggle()) # if self.multiview: # self.set_layer_visibility(list(self.data_programs.keys())[i], not list(self.data_programs.values())[i].is_visible) # else: # for pi, prog in enumerate(self.data_programs.values()): # prog.is_visible = False # if pi == i: # prog.is_visible = True self.update() def on_resize(self, size_x, size_y): gl.glViewport(int(0), int(0), int(size_x), int(size_y)) self.radius = 0.5 * min(size_x, size_y) self.size = size_x, size_y self.update_projection_matrix() def wheelEvent(self, event): # def on_mouse_wheel(self, window, offset_x, offset_y): ticks = float(event.angleDelta().y() + event.angleDelta().x()) / 50 modifiers = event.modifiers() if modifiers == Qt.ControlModifier | Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 self.near_clip -= ticks self.far_clip -= ticks self.update_projection_matrix() elif modifiers == Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = max(0.1, self.near_clip - ticks) if new <= self.far_clip: self.near_clip = new self.update_projection_matrix() elif modifiers == Qt.ControlModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = min(1000, self.far_clip - ticks) if new >= self.near_clip: self.far_clip = new self.update_projection_matrix() elif modifiers == Qt.ShiftModifier: if self.projection_mode == 'perspective': old_fov = self.fov # do `dolly zooming` so that world appears at same size after canging fov self.fov = max(5., self.fov + ticks) self.fov = min(120., self.fov) self.camera_position = self.camera_position * (math.tan( math.radians(old_fov) / 2.)) / (math.tan( math.radians(self.fov) / 2.)) self.update_projection_matrix() self.update_view_matrix() elif modifiers == Qt.MetaModifier: self.camera_position += ticks / 10 self.update_view_matrix() else: self.scale *= ticks / 10 + 1. # self.camera_position += ticks/10 self.update_view_matrix() self.update() def mouseMoveEvent(self, event): modifiers = event.modifiers() buttons = event.buttons() pos_x, pos_y = event.x(), event.y() if Qt.ShiftModifier == modifiers: x0, y0 = self.last_mouse_pos x1, y1 = pos_x, pos_y dx, dy = (x1 - x0), (y1 - y0) #scale to zero plane in projection frustrum if self.projection_mode == 'perspective': scale = -self.camera_position * math.tan( math.radians(self.fov / 2.)) dx, dy = scale * dx, scale * dy #multiply with inverse view matrix and apply translation in world coordinates self.translation += np.array( [dx / self.radius, -dy / self.radius, 0., 0.]).dot(np.linalg.inv(self.view))[:3] elif self.projection_mode == 'orthographic': # this is not fully correct self.translation += self.modelscale * np.array( [dx, -dy, 0., 0.]).dot(np.linalg.inv(self.view))[:3] self.hud_program.is_visible = True elif Qt.LeftButton == buttons: x0, y0 = self.screen2view(*self.last_mouse_pos) x1, y1 = self.screen2view(pos_x, pos_y) v0 = q.arcball(x0, y0) v1 = q.arcball(x1, y1) self.rotation = q.product(v1, v0, self.rotation) self.hud_program.is_visible = True else: self.hud_program.is_visible = False self.update_view_matrix() self.update() self.last_mouse_pos = pos_x, pos_y def update(self): self.render()
# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. """Show information about the OpenGL setup.""" from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile, QOffscreenSurface, QGuiApplication) app = QGuiApplication([]) surface = QOffscreenSurface() surface.create() ctx = QOpenGLContext() ok = ctx.create() assert ok ok = ctx.makeCurrent(surface) assert ok print(f"GLES: {ctx.isOpenGLES()}") vp = QOpenGLVersionProfile() vp.setVersion(2, 0) vf = ctx.versionFunctions(vp) print(f"Vendor: {vf.glGetString(vf.GL_VENDOR)}") print(f"Renderer: {vf.glGetString(vf.GL_RENDERER)}") print(f"Version: {vf.glGetString(vf.GL_VERSION)}") print(
class OpenGLWindow(QWindow): def __init__(self, parent=None): super(OpenGLWindow, self).__init__(parent) self.m_update_pending = False self.m_animating = False self.m_context = None self.m_device = None self.m_gl = None self.logger = None self.setSurfaceType(QWindow.OpenGLSurface) def initialize(self, gl): pass def setAnimating(self, animating): self.m_animating = animating if animating: self.renderLater() def renderLater(self): if not self.m_update_pending: self.m_update_pending = True QGuiApplication.postEvent(self, QEvent(QEvent.UpdateRequest)) def paint(self, painter): pass def render(self, gl): pass def addGlFunctuins(self, GL, functions): for function, arguments in functions.items(): GL[function].restype = None GL[function].argtypes = arguments setattr(self.m_gl, function, GL[function]) @exitOnKeyboardInterrupt def renderNow(self): if not self.isExposed(): return self.m_update_pending = False needsInitialize = False if self.m_context is None: self.m_context = QOpenGLContext(self) self.m_context.setFormat(self.requestedFormat()) self.m_context.create() needsInitialize = True self.m_context.makeCurrent(self) if needsInitialize: # Sorry, no support for higher versions for now. profile = QOpenGLVersionProfile() profile.setVersion(2, 0) self.m_gl = self.m_context.versionFunctions(profile) self.m_gl.initializeOpenGLFunctions() #print(self.m_context.hasExtension('GL_EXT_framebuffer_object')) #print(self.m_context.hasExtension('GL_ARB_texture_float')) #print(*sorted(self.m_context.extensions()), sep='\n') # Small hack. Guess noone mind? import ctypes import ctypes.util GL = ctypes.CDLL(ctypes.util.find_library('GL')) self.addGlFunctuins(GL, { 'glFramebufferTexture2D': (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_int) }) self.logger = QOpenGLDebugLogger() self.logger.initialize() self.logger.loggedMessages() self.logger.messageLogged.connect(self.handleLoggedMassage) self.logger.startLogging() self.initialize(self.m_gl) if not self.m_device: self.m_device = QOpenGLPaintDevice() self.m_gl.glClear(self.m_gl.GL_COLOR_BUFFER_BIT | self.m_gl.GL_DEPTH_BUFFER_BIT); self.m_device.setSize(self.size()) painter = QPainter(self.m_device) painter.beginNativePainting() self.render(self.m_gl) painter.endNativePainting() self.paint(painter) self.m_context.swapBuffers(self) if self.m_animating: self.renderLater() def handleLoggedMassage(self, message): # This three really annoyng and brings no useful info =\ if not (message.message().find('Use glDrawRangeElements() to avoid this.') > -1 or message.message().find('CPU mapping a busy miptree') > -1 or message.message().find('Flushing before mapping a referenced bo.') > -1 ): print(message.message().strip()) def event(self, event): if event.type() == QEvent.UpdateRequest: self.renderNow() return True return super(OpenGLWindow, self).event(event) def exposeEvent(self, event): self.renderNow() def resizeEvent(self, event): self.renderNow()
class ViewerWindow(QWindow): instructions = """ --Key controls 0-9 - toggle data layers r - reset view parameters c - clip view t - view from top l - light/dark background = - increase point size - - decrease point size --Mouse controls drag shift + move - translate dataset scroll - scale dataset shift + scroll - change field of view ctrl + scroll - move far clipping plane alt + scroll - move near clipping plane ctrl + alt + scroll - move far and near clipping plane simultaniously """ def __init__(self, parent=None, **kwargs): super(ViewerWindow, self).__init__(parent) self.setSurfaceType(QWindow.OpenGLSurface) format = QSurfaceFormat() format.setVersion(3, 3) format.setProfile(QSurfaceFormat.CoreProfile) format.setStereo(False) format.setSwapBehavior(QSurfaceFormat.DoubleBuffer) format.setDepthBufferSize(24) format.setSamples(16) self.context = QOpenGLContext(self) self.context.setFormat(format) if not self.context.create(): raise Exception('self.context.create() failed') self.create() size = 720, 720 self.resize(*size) self.context.makeCurrent(self) self.hud_program = CrossHairProgram() self.default_view = np.eye(4, dtype=np.float32) self.view = self.default_view self.model = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) self.layer_manager = LayerManager() self.visibility_toggle_listeners = [] self.multiview = True self.rotation = q.quaternion() self.scale = 0.6 self.translation = np.zeros(3) self.radius = 0.5 * min(*size) self.fov = 5. self.camera_position = -12. self.near_clip = .1 if 'near_clip' in kwargs: self.near_clip = kwargs['near_clip'] self.far_clip = 100. if 'far_clip' in kwargs: self.far_clip = kwargs['far_clip'] self.projection_mode = 'perspective' # 'orthographic' self.size = size self.bg_white = False self.viewpoint_dict = {} print((self.instructions)) def add_layer(self, layer): self.layer_manager.add_layer(layer) return layer # keeping this compatibility with older scripts def add_data_source(self, name, opts, points, normals=None, radii=None, intensity=None, category=None, zrange=None, **kwargs): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source(name, opts, points, normals=normals, radii=radii, intensity=intensity, category=category, zrange=zrange, **kwargs) def add_data_source_line(self, name, coords_start, coords_end, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_line(name, coords_start, coords_end, **args) def add_data_source_triangle(self, name, coords, normals, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_triangle(name, coords, normals, **args) def run(self): self.initialize() self.show() def set_layer_visibility(self, name, is_visible): for listener in self.visibility_toggle_listeners: listener(name, is_visible) def center_view(self, center=None): if center is None: center = self.layer_manager.first.get_center() data_range = self.layer_manager.first.data_range self.data_width = data_range[0] self.data_height = data_range[2] self.translation = np.zeros(3) self.model = np.eye(4, dtype=np.float32) translate(self.model, -center[0], -center[1], -center[2]) for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) self.update_view_matrix() def initialize(self): self.context.makeCurrent(self) gl.glDepthMask(gl.GL_TRUE) gl.glEnable(gl.GL_DEPTH_TEST) gl.glDepthFunc(gl.GL_LESS) self.set_bg() view_width, view_height = [x/self.radius for x in self.size] self.center_view() for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) program.setUniform('u_view', self.view) program.setUniform('u_projection', self.projection) self.modelscale = .6* 2*min(2.*view_width/self.data_width, 2.*view_height/self.data_height) self.scale = self.modelscale self.update_view_matrix() self.last_mouse_pos = 0,0 self.on_resize(*self.size) def render(self): if not self.isExposed(): return self.context.makeCurrent(self) bits = 0 bits |= gl.GL_COLOR_BUFFER_BIT bits |= gl.GL_DEPTH_BUFFER_BIT bits |= gl.GL_STENCIL_BUFFER_BIT gl.glClear(bits) gl.glEnable(gl.GL_PROGRAM_POINT_SIZE) for program in self.layer_manager.programs(): if program.do_blending: if self.bg_white: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_SRC_ALPHA) else: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_ONE, gl.GL_SRC_ALPHA) else: gl.glDisable(gl.GL_BLEND) self.layer_manager.draw() if self.hud_program.is_visible: self.hud_program.draw() self.context.swapBuffers(self) def event(self, event): # print event.type() if event.type() == QEvent.UpdateRequest: self.render() return True return super(ViewerWindow, self).event(event) def exposeEvent(self, event): self.render() def resizeEvent(self, event): size = event.size() self.on_resize(size.width(), size.height()) self.render() def update_view_matrix(self): self.view = np.eye(4, dtype=np.float32) translate(self.view, self.translation[0], self.translation[1], self.translation[2] ) scale(self.view, self.scale, self.scale, self.scale) self.view = self.view.dot( np.array(q.matrix(self.rotation), dtype=np.float32) ) # translate(self.view, -self.translation[0], -self.translation[1], -self.translation[2] ) translate(self.view, 0,0, self.camera_position) for program in self.layer_manager.programs(): program.setUniform('u_view', self.view) if program.draw_type == 'points': program.setUniform('u_model_scale', self.scale) def update_projection_matrix(self): view_width, view_height = [x/self.radius for x in self.size] if self.projection_mode == 'orthographic': self.projection = ortho(-view_width, view_width, -view_height, view_height, self.near_clip, self.far_clip) elif self.projection_mode == 'perspective': self.projection = perspective(self.fov, view_width / float(view_height), self.near_clip, self.far_clip) for program in self.layer_manager.programs(): program.setUniform('u_projection', self.projection) def screen2view(self, x,y): width, height = self.size # print (x-width/2.)/self.radius, ((height-y)-height/2.)/self.radius return (x-width/2.)/self.radius, ((height-y)-height/2.)/self.radius def set_bg(self): gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) if self.bg_white: gl.glClearColor(0.95,0.95,0.95,1) # gloo.set_state('translucent', clear_color=np.array([1,1,1,1]) ) else: gl.glClearColor(0.05,0.05,0.05,1) # gloo.set_state('translucent', clear_color=np.array([0.15,0.15,0.15,1]) ) def keyPressEvent(self, event): key = event.key() repeat = event.isAutoRepeat() if key == Qt.Key_R: self.view = np.eye(4, dtype=np.float32) self.rotation = q.quaternion() self.scale = self.modelscale self.camera_position = -12. self.near_clip = 2. self.far_clip = 100. self.center_view() self.update_projection_matrix() elif key == Qt.Key_Minus: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform('u_point_size', program.uniforms['u_point_size']/1.2) elif key == Qt.Key_Equal: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform('u_point_size', program.uniforms['u_point_size']*1.2) elif key == Qt.Key_B: for program in self.layer_manager.programs(): if program.is_visible and program.draw_type == 'points': program.do_blending = not program.do_blending elif key == Qt.Key_C: self.near_clip = -self.camera_position - 0.1 self.far_clip = -self.camera_position + 0.1 self.update_projection_matrix() elif key == Qt.Key_T: self.rotation = q.quaternion() self.update_view_matrix() elif key == Qt.Key_P: if self.projection_mode == 'perspective': self.projection_mode = 'orthographic' else: self.projection_mode = 'perspective' self.update_projection_matrix() elif key == Qt.Key_L: self.bg_white = not self.bg_white self.set_bg() elif Qt.Key_0 <= key <= Qt.Key_9: i = int(chr(key))-1 layers = [item for item in self.layer_manager.layers.items()] if i < len(layers): name, layer = layers[i] self.set_layer_visibility(name,layer.toggle()) # if self.multiview: # self.set_layer_visibility(list(self.data_programs.keys())[i], not list(self.data_programs.values())[i].is_visible) # else: # for pi, prog in enumerate(self.data_programs.values()): # prog.is_visible = False # if pi == i: # prog.is_visible = True self.update() def on_resize(self, size_x, size_y): gl.glViewport(int(0), int(0), int(size_x), int(size_y)) self.radius = 0.5 * min(size_x, size_y) self.size = size_x, size_y self.update_projection_matrix() def wheelEvent(self, event): # def on_mouse_wheel(self, window, offset_x, offset_y): ticks = float(event.angleDelta().y()+event.angleDelta().x())/50 modifiers = event.modifiers() if modifiers == Qt.ControlModifier | Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 self.near_clip -= ticks self.far_clip -= ticks self.update_projection_matrix() elif modifiers == Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = max(0.1,self.near_clip - ticks) if new <= self.far_clip: self.near_clip = new self.update_projection_matrix() elif modifiers == Qt.ControlModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = min(1000,self.far_clip - ticks) if new >= self.near_clip: self.far_clip = new self.update_projection_matrix() elif modifiers == Qt.ShiftModifier: if self.projection_mode == 'perspective': old_fov = self.fov # do `dolly zooming` so that world appears at same size after canging fov self.fov = max(5.,self.fov + ticks) self.fov = min(120.,self.fov) self.camera_position = self.camera_position * (math.tan(math.radians(old_fov)/2.)) / (math.tan(math.radians(self.fov)/2.)) self.update_projection_matrix() self.update_view_matrix() elif modifiers == Qt.MetaModifier: self.camera_position += ticks/10 self.update_view_matrix() else: self.scale *= ticks/10 + 1. # self.camera_position += ticks/10 self.update_view_matrix() self.update() def mouseMoveEvent(self, event): modifiers = event.modifiers() buttons = event.buttons() pos_x, pos_y = event.x(), event.y() if Qt.ShiftModifier == modifiers: x0,y0 = self.last_mouse_pos x1,y1 = pos_x, pos_y dx, dy = (x1-x0), (y1-y0) #scale to zero plane in projection frustrum if self.projection_mode == 'perspective': scale = -self.camera_position * math.tan(math.radians(self.fov/2.)) dx, dy = scale*dx, scale*dy #multiply with inverse view matrix and apply translation in world coordinates self.translation += np.array([dx/self.radius, -dy/self.radius, 0., 0.]).dot( np.linalg.inv(self.view)) [:3] elif self.projection_mode == 'orthographic': # this is not fully correct self.translation += self.modelscale * np.array([dx, -dy, 0., 0.]).dot( np.linalg.inv(self.view)) [:3] self.hud_program.is_visible = True elif Qt.LeftButton == buttons: x0,y0 = self.screen2view(*self.last_mouse_pos) x1,y1 = self.screen2view(pos_x, pos_y) v0 = q.arcball(x0, y0) v1 = q.arcball(x1, y1) self.rotation = q.product(v1, v0, self.rotation) self.hud_program.is_visible = True else: self.hud_program.is_visible = False self.update_view_matrix() self.update() self.last_mouse_pos = pos_x, pos_y def update(self): self.render()
class OffscreenModelRendererThread(QThread): TEX_SIZE = 128 renderedTexture = pyqtSignal(str, QImage) def __init__(self, parent_screen): super().__init__() self.parent_screen = parent_screen self.started.connect(self.started_) self.workspace = None self.offscreen_surface = QOffscreenSurface() self.offscreen_surface.requestedFormat().setVersion(4, 3) self.offscreen_surface.requestedFormat().setProfile( QSurfaceFormat.CoreProfile) self.offscreen_surface.requestedFormat().setDepthBufferSize(24) self.offscreen_surface.setFormat( self.offscreen_surface.requestedFormat()) self.offscreen_surface.create() @pyqtSlot() def started_(self): self.ctx = QOpenGLContext(self.offscreen_surface) self.ctx.setFormat(self.offscreen_surface.requestedFormat()) self.ctx.create() self.fbo = -1 self.tex = -1 self.rbuf = -1 self.setup_fbo() self.renderer = ModelRenderer(self.workspace, self.offscreen_surface) @pyqtSlot(Workspace) def setWorkspace(self, w): if hasattr(self, "renderer"): self.renderer.set_workspace(w) else: self.workspace = w def setup_fbo(self): self.ctx.makeCurrent(self.offscreen_surface) self.tex = GL.glGenTextures(1) self.fbo = GL.glGenFramebuffers(1) self.rbuf = GL.glGenRenderbuffers(1) GL.glBindTexture(GL.GL_TEXTURE_2D, self.tex) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, None) GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, self.rbuf) GL.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL.GL_DEPTH_COMPONENT24, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self.fbo) GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, GL.GL_RENDERBUFFER, self.rbuf) GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, self.tex, 0) if GL.glCheckFramebufferStatus( GL.GL_FRAMEBUFFER) != GL.GL_FRAMEBUFFER_COMPLETE: raise RuntimeError("Framebuffer is not complete!") GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0) GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0) @pyqtSlot(str, BlockModel) def queue_render_order(self, order_name, model): """ Render a block model in item format. Subscribing (connecting) to the renderedTexture signal allows you to get the rendered texture back. Pass in an order name so you know which image you got back :param order_name: the order name. passed to renderedTexture :param model: the blockmodel :return: """ self.ctx.makeCurrent(self.offscreen_surface) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self.fbo) GL.glViewport(0, 0, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE) GL.glClearColor(0, 0, 0, 0) GL.glClear(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT) self.renderer.setup_data_for_block_model(model) self.renderer.resize(OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE) self.renderer.draw_loaded_model( glm.lookAt(glm.vec3(15, 5, 5), glm.vec3(5, 5, 5), glm.vec3(0, 1, 0)), "gui", glm.ortho(-10, 10, 10, -10, 0.1, 50)) tex_str = GL.glReadPixels(0, 0, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, outputType=bytes) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0) qimage = QImage(tex_str, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE * 4, QImage.Format_RGBA8888) qimage = qimage.mirrored(vertical=True) self.renderedTexture.emit(order_name, qimage)
format.setSwapBehavior(QSurfaceFormat.DoubleBuffer) format.setSamples(16) format.setStencilBufferSize(8) if core: format.setProfile(QSurfaceFormat.CoreProfile) window.setSurfaceType(QWindow.OpenGLSurface) window.setFormat(format) window.setTitle(name) pixel_ratio = window.devicePixelRatio() window.resize(*(u/pixel_ratio for u in window_size)) gl_context = QOpenGLContext(window) gl_context.setFormat(window.requestedFormat()) gl_context.create() # asynchronous redisplay def redisplay(): gl_display(scene, feedback) gl_context.swapBuffers(window) waiting_redisplay = False def post_redisplay(): global waiting_redisplay if not waiting_redisplay: waiting_redisplay = True app.postEvent(app, QEvent(QEvent.UpdateRequest))