I am porting code from QGLWidget to QOpenGLWidget and I encounter a different behavior: using QOpenGLWidget some swapBuffers() occur in some window events (like Enter or Leave events), but paintGL() is not called, which ends up showing the wrong buffer.
To demonstrate I use a QMainWindow with a menu (opening or even hovering the menu triggers the events), and a very simple example, here in python/PyQt (but it's the same in C++). I use PyQt5 in order to compare to QGLWidget but the behavior is the same in Qt6.
#!/usr/bin/env python
from PyQt5 import Qt
from OpenGL.GL import *
class GLW(Qt.QOpenGLWidget):
def initializeGL(self):
print('initializeGL')
glClearColor(0, 0, 1, 1)
self.timer = Qt.QTimer(self)
self.timer.timeout.connect(self.prepare_sel)
def paintGL(self):
print('paintGL')
glClearColor(1, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0, 0, 1, 1)
self.timer.stop()
self.timer.setSingleShot(True)
self.timer.start(500)
def resizeGL(self, w, h):
print('resizeGL')
glClearColor(0, 1, 0, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0, 0, 1, 1)
def buffers_swapped(self):
print('buffers_swapped')
def prepare_sel(self):
print('prepare_sel')
self.makeCurrent()
glClearColor(0, 1, 1, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0, 0, 1, 1)
class GLW2(Qt.QGLWidget):
def initializeGL(self):
print('initializeGL (2)')
glClearColor(0, 0, 1, 1)
self.timer = Qt.QTimer(self)
self.timer.timeout.connect(self.prepare_sel)
def paintGL(self):
print('paintGL (2)')
glClearColor(1, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0, 0, 1, 1)
self.timer.stop()
self.timer.setSingleShot(True)
self.timer.start(500)
def resizeGL(self, w, h):
print('resizeGL (2)')
glClearColor(0, 1, 0, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0, 0, 1, 1)
def buffers_swapped(self):
print('buffers_swapped (2)')
def prepare_sel(self):
print('prepare_sel (2)')
self.makeCurrent()
glClearColor(0, 1, 1, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glClearColor(0, 0, 1, 1)
app = Qt.QApplication([])
w = Qt.QMainWindow()
glw = GLW()
w.setCentralWidget(glw)
glw.frameSwapped.connect(glw.buffers_swapped)
qa = Qt.QAction('Quit')
m = Qt.QMenu('File')
m.addAction(qa)
w.menuBar().addMenu(m)
qa.triggered.connect(w.close)
w.show()
w2 = Qt.QMainWindow()
glw2 = GLW2()
w2.setCentralWidget(glw2)
#glw2.frameSwapped.connect(glw2.buffers_swapped)
qa2 = Qt.QAction('Quit')
m2 = Qt.QMenu('File')
m2.addAction(qa2)
w2.menuBar().addMenu(m2)
qa2.triggered.connect(w2.close)
w2.show()
app.exec()
w uses the QOpenGLWidget implementation whereas w2 is the same, using legacy QGLWidget. The widget normally just displays a red background in paintGL(), but also triggers a timer to prepare another buffer (let's say for future mouse selection, whatever), which is done 500ms later in prepare_sel(). Here the buffer is cyan, but is not displayed, and is not intended to be visible at any time.
In w2 (QGLWidget) things are as expected, the window is always red.
in w (QOpenGLWidget) the window is red, at first, but when I open the menu, or just move the mouse over it, then the window becomes cyan: swapBuffers() has been called here (as confirmed by the print in the debug callback buffers_swapped(), but paintGL() has not, thus a new buffer has not been prepared before being displayed.
In comparison, in w2, the redrawing is not called when I move the mouse over the menu, but when I actually open or close it, the scene is completely refreshed, paintGL() is called. It is not in w.
Is this behavior normal ?
How can I prevent this: is there a way to avoid the additional swapBuffers() which does not occur using QGLWidget ? Or is there a way to force a paintGL() before it ?
Or is this way of doing things really wrong ? I mean, the "selection" buffer (cyan here) is perhaps not done the way it should, how could I do the same another way (do I need to use an additional framebuffer for this) ? But even then, the swapBuffers() occuring without paintGL() will at some point, when in a moving scene, display a buffer "from the past" and show a rendering which is not up-to-date, right ?
I just want to ensure I can call paintGL() before any swapBuffers() happens.
I found a solution, using separate framebuffers for selection or other off-screen renderings (probably using more memory resources but I had no choice). The problem was actually that:
QOpenGLWidgetuses a framebuffer, and not a double buffering asQGLWidgetwas doing. Thus swapBuffers() is not actually a swap but a copy to visible screen, andSo we cannot avoid creating a second framebuffer for selection in order to leave the widget's one untouched. When doing so, everything looks as expected.