From a1751d8e193687ccfe558cf585ac6a4386963dea Mon Sep 17 00:00:00 2001 From: kasinadhsarma1 <218753575+kasinadhsarma1@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:25:55 +0530 Subject: [PATCH] fix: enforce OpenGL 3.3 Core profile, initialize global VAO, and correct camera mouse tracking --- gui/application.py | 17 ++++++++++++++--- opengl/camera.py | 1 + opengl/main.py | 20 +++++++++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/gui/application.py b/gui/application.py index 32f6e81..aa06067 100644 --- a/gui/application.py +++ b/gui/application.py @@ -32,14 +32,25 @@ class to maintain QT parameters """ def __init__(self, glob, argv): self.env = glob.env - super().__init__(argv) - # Alphacover (if available), is used to use more than one alpha-layer + # QSurfaceFormat must be set BEFORE QApplication is created so that + # the OpenGL widget picks up the correct format on all platforms. + # On Linux/Mesa the system default is OpenGL 2.0 with no depth buffer, + # which causes shaders (#version 330) to fail and the desktop to bleed + # through the viewport. Explicitly request OpenGL 3.3 Core + depth buffer. # self.sformat = QSurfaceFormat() + self.sformat.setVersion(3, 3) + self.sformat.setProfile(QSurfaceFormat.OpenGLContextProfile.CoreProfile) + self.sformat.setDepthBufferSize(24) + self.sformat.setStencilBufferSize(8) + self.sformat.setSwapBehavior(QSurfaceFormat.SwapBehavior.DoubleBuffer) + # Alphacover (if available), is used to use more than one alpha-layer if self.env.noalphacover is False: self.sformat.setSamples(4) - self.sformat.setDefaultFormat(self.sformat) + QSurfaceFormat.setDefaultFormat(self.sformat) + + super().__init__(argv) def getFormat(self): return self.sformat diff --git a/opengl/camera.py b/opengl/camera.py index 1082fe0..42e9653 100644 --- a/opengl/camera.py +++ b/opengl/camera.py @@ -303,6 +303,7 @@ def arcBallRotation(self, x, y): """ xAngle = (self.last_mousex - x) * self.deltaAngleX yAngle = (self.last_mousey - y) * self.deltaAngleY + self.setLastMousePosition(x, y) self.rotation(xAngle, yAngle) def mousePanning(self, x, y): diff --git a/opengl/main.py b/opengl/main.py index c90d5f0..9c7274a 100644 --- a/opengl/main.py +++ b/opengl/main.py @@ -9,7 +9,8 @@ from PySide6.QtOpenGLWidgets import QOpenGLWidget from PySide6.QtWidgets import QSizePolicy from PySide6.QtCore import QSize, Qt, QTimer -from PySide6.QtGui import QMatrix4x4, QVector3D +from PySide6.QtGui import QMatrix4x4, QVector3D, QSurfaceFormat +from PySide6.QtOpenGL import QOpenGLVertexArrayObject # try to keep only constants here # @@ -28,6 +29,10 @@ def __init__(self, glob): self.glob = glob self.env = glob.env super().__init__() + # Explicitly apply the default surface format to this widget so that + # Linux/Mesa always gets OpenGL 3.3 Core + depth buffer regardless of + # the order in which Qt processes the global default format. + self.setFormat(QSurfaceFormat.defaultFormat()) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding) self.setMinimumSize(QSize(300, 560)) self.setMaximumSize(QSize(2000, 2000)) @@ -52,6 +57,7 @@ def __init__(self, glob): self.glfunc = None self.marker = None self.scene = None + self.vao = None def setFPS(self, value, callback=None): self.fps = value @@ -221,6 +227,15 @@ def initializeGL(self): self.glob.openGLBlock = False self.glfunc = self.context().functions() + # OpenGL 3.3 Core Profile requires at least one VAO to be bound before + # any vertex attribute pointer calls (glVertexAttribPointer / + # glEnableVertexAttribArray). Create a single VAO and keep it bound + # for the lifetime of this widget so all existing BindBuffersToShader + # calls work without needing per-object VAOs. + self.vao = QOpenGLVertexArrayObject() + self.vao.create() + self.vao.bind() + deb = GLDebug(self.env.osindex) if deb.checkVersion() is False: self.env.logLine(1, "Shader version is not sufficient, minimum version is " + str(deb.minVersion() + ". Available languages are:") ) @@ -423,3 +438,6 @@ def cleanUp(self): if self.skybox is not None: self.skybox.delete() self.skybox = None + if self.vao is not None: + self.vao.destroy() + self.vao = None