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:
QOpenGLWidget
uses a framebuffer, and not a double buffering asQGLWidget
was 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.