How to change OpenGL Version using QGLFormat on MacOS?

1.2k Views Asked by At

I'm using PyOpenGL on a Mac. By default, OpenGL 2.1 is used. However, according to my research and to OpenGL Extension Viewer, I should be able to use OpenGL 4.1. I'm trying to pass a QGLFormat to my QGLWidget, as I've seen done on many threads here, but the context version does not change. If I try to force it, it tells me that the context is not valid. I have been experimenting a lot - here is a very simple example that shows my problem:

from PyQt4 import QtGui, QtCore, QtOpenGL
import sys

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)

    glFormat = QtOpenGL.QGLFormat()
    glFormat.setVersion(4,1)
    glFormat.setProfile(QtOpenGL.QGLFormat.CoreProfile)
    glFormat.setSampleBuffers(True)
    glFormat.setDefaultFormat(glFormat)
    print("Format version: " + str(glFormat.majorVersion()))
    myW = QtOpenGL.QGLWidget(glFormat)
    print ("Widget context valid: " + str(myW.context().isValid()))
    print ("Widget format version: " + str(myW.format().majorVersion()))
    print ("Widget context format version: " + str(myW.context().format().majorVersion()))
    myW.context().setFormat(glFormat)
    print ("Forced format valid: " + str(myW.context().isValid()))
    print ("Forced format version: " + str(myW.format().majorVersion()))
    myW.show()
    sys.exit(app.exec_())

Here is the output:

Format version: 4
Widget context valid: True
Widget format version: 1
Widget context format version: 1
Forced format valid: False
Forced format version: 4

Any idea where my problem lies? Am I doing something wrong? I'm using macOS Sierra version 10.12.6. I use Qt version 4.8.7, sip version 4.19.3, and pyqt version 4.12, in case that's relevant. Here is what OpenGL Extension Viewer tells me.

Any help would be greatly appreciated!

Edit:

I found what looks like a similar problem with C++ here. I'm not sure how to translate the solution to PyQt, but I will look into this: Changing the OpenGL Context Version for QGLWidgets in Qt 4.8.6 on OS X.

Edit 3:

I get the following output when trying (the second version of) the patch provided by @ekhumoro:

patching file qpy/QtOpenGL/core_profile_attributes.mm
patching file qpy/QtOpenGL/qpyopengl.pro
patching file sip/QtOpenGL/qgl.sip
patch unexpectedly ends in middle of line
Hunk #3 FAILED at 493.
1 out of 3 hunks FAILED -- saving rejects to file sip/QtOpenGL/qgl.sip.rej

qpyopengl.pro is patched correctly now, but it still fails for qgl.sip.

Update:

Here is the content of the reject file for qgl.sip:

***************
*** 478,481 ****

  %ModuleHeaderCode
  #include <qpyopengl_api.h>

--- 493,498 ----

  %ModuleHeaderCode
  #include <qpyopengl_api.h>
+ typedef struct GDevice** GDHandle;
+ void* select_3_2_mac_visual();

Out of curiosity, I ran configure-ng.py and tried to compile. I get the following error:

[...]/PyQt4_gpl_mac-4.12.1/sip/QtOpenGL/qgl.sip:269:5: error: 'virtual' can only appear on non-static member functions
    virtual void* chooseMacVisual(GDHandle handle)
    ^
[...]/PyQt4_gpl_mac-4.12.1/sip/QtOpenGL/qgl.sip:271:16: error: use of undeclared identifier 'select_3_2_mac_visual'
        return select_3_2_mac_visual();
               ^
[...]/PyQt4_gpl_mac-4.12.1/sip/QtOpenGL/qgl.sip:269:44: warning: unused parameter 'handle' [-Wunused-parameter]
    virtual void* chooseMacVisual(GDHandle handle)
2

There are 2 best solutions below

6
CodeSurgeon On

For what it is worth, running your code on my Windows 10 laptop gives the following output:

Format version: 4
Widget context valid: True
Widget format version: 4
Widget context format version: 4
Forced format valid: False
Forced format version: 4

Therefore, I suspect that your issue might not actually be a platform-specific one, but rather with your myW.context().setFormat(glFormat) call. Interestingly, I took a look at the PyQt documentation for QGLWidget and it appears to be missing for setFormat(), which is pretty unhelpful!

Looking instead at the corresponding C++ documentation, it looks like setFormat() can actually be called from either the QGLWidget itself, or it child QGLContext object. Since you are calling it from the QGLContext, it turns out that the context is reset, making it invalid. The solution is to call myW.context().create() after setting the format, thus creating a valid format object with the new parameters! That modification would look like this:

from PyQt4 import QtGui, QtCore, QtOpenGL
import sys

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)

    glFormat = QtOpenGL.QGLFormat()
    glFormat.setVersion(4,1)
    glFormat.setProfile(QtOpenGL.QGLFormat.CoreProfile)
    glFormat.setSampleBuffers(True)
    glFormat.setDefaultFormat(glFormat)
    print("Format version: " + str(glFormat.majorVersion()))
    myW = QtOpenGL.QGLWidget(glFormat)
    print ("Widget context valid: " + str(myW.context().isValid()))
    print ("Widget format version: " + str(myW.format().majorVersion()))
    print ("Widget context format version: " + str(myW.context().format().majorVersion()))
    myW.context().setFormat(glFormat)
    myW.context().create()
    print ("Forced format valid: " + str(myW.context().isValid()))
    print ("Forced format version: " + str(myW.format().majorVersion()))
    myW.show()
    sys.exit(app.exec_())

The other alternative is to just call setFormat() at the QGLWidget level, which automatically creates a new context, resulting in this:

from PyQt4 import QtGui, QtCore, QtOpenGL
import sys

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)

    glFormat = QtOpenGL.QGLFormat()
    glFormat.setVersion(4,1)
    glFormat.setProfile(QtOpenGL.QGLFormat.CoreProfile)
    glFormat.setSampleBuffers(True)
    glFormat.setDefaultFormat(glFormat)
    print("Format version: " + str(glFormat.majorVersion()))
    myW = QtOpenGL.QGLWidget(glFormat)
    print ("Widget context valid: " + str(myW.context().isValid()))
    print ("Widget format version: " + str(myW.format().majorVersion()))
    print ("Widget context format version: " + str(myW.context().format().majorVersion()))
    myW.setFormat(glFormat)
    print ("Forced format valid: " + str(myW.context().isValid()))
    print ("Forced format version: " + str(myW.format().majorVersion()))
    myW.show()
    sys.exit(app.exec_())

That being said, all of this is conjecture since I do not have a Mac on which I can test this code. Let me know if this fixes the problem for you, and hope it helps!

10
ekhumoro On

To answer the specific question regarding translating the Mac-specific solution into Python: this is not possible to do directly, because the PyQt bindings do not wrap the virtual method chooseMacVisual. Without this, there is no way to reimplement it, because Qt just cannot see any Python methods that PyQt has not pre-defined as being virtual.

So the only way to get it to work will be to patch PyQt4 and re-compile it. Fortunately, someone has already done this, and provided the code here:

However, the build instructions aren't entirely clear, and are aimed at PyQt-4.9.4. So I have prepared a diff which should be easier to work with. I have not attempted to compile it because I don't have access to a Mac system.

To apply the patch, unpack the latest PyQt4 OSX sources, and cd into the directory. Then copy the diff below and save it as macgl.diff, and run this command:

patch -Np1 -i macgl.diff

You can then compile your patched PyQt4 using the instructions shown here:

UPDATE:

I previously thought a diff against PyQt-4.9.4 would apply cleanly against all newer PyQt4 versions, but I was wrong about that. I have attempted to remedy this with the revised diff below, which has been adapted for the PyQt-4.12.1 mac source code.

Here is the diff:

diff -Naur c/qpy/QtOpenGL/core_profile_attributes.mm d/qpy/QtOpenGL/core_profile_attributes.mm
--- c/qpy/QtOpenGL/core_profile_attributes.mm   1970-01-01 01:00:00.000000000 +0100
+++ d/qpy/QtOpenGL/core_profile_attributes.mm   2017-10-22 20:11:53.000000000 +0100
@@ -0,0 +1,22 @@
+#include <QGLContext>
+ 
+ 
+void* select_3_2_mac_visual()
+{
+    static const int Max = 40;
+    NSOpenGLPixelFormatAttribute attribs[Max];
+    int cnt = 0;
+   
+    attribs[cnt++] = NSOpenGLPFAOpenGLProfile;
+    attribs[cnt++] = NSOpenGLProfileVersion3_2Core;
+   
+    attribs[cnt++] = NSOpenGLPFADoubleBuffer;
+   
+    attribs[cnt++] = NSOpenGLPFADepthSize;
+    attribs[cnt++] = (NSOpenGLPixelFormatAttribute)16;
+   
+    attribs[cnt] = 0;
+    Q_ASSERT(cnt < Max);
+   
+    return [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
+}
diff -Naur c/qpy/QtOpenGL/qpyopengl.pro d/qpy/QtOpenGL/qpyopengl.pro
--- c/qpy/QtOpenGL/qpyopengl.pro    2017-06-30 09:47:26.000000000 +0100
+++ d/qpy/QtOpenGL/qpyopengl.pro    2017-10-31 17:07:37.694805459 +0000
@@ -24,6 +24,11 @@
 TARGET      = qpyopengl
 TEMPLATE    = lib

+mac {
+     OBJECTIVE_SOURCES  +=  core_profile_attributes.mm
+     LIBS += -framework Foundation -framework Cocoa
+ }
+
 SOURCES   = \
             qpyopengl_attribute_array.cpp \
             qpyopengl_uniform_value_array.cpp
diff -Naur c/sip/QtOpenGL/qgl.sip d/sip/QtOpenGL/qgl.sip
--- c/sip/QtOpenGL/qgl.sip  2017-06-30 09:47:26.000000000 +0100
+++ d/sip/QtOpenGL/qgl.sip  2017-10-31 17:30:26.025295693 +0000
@@ -20,6 +20,14 @@
 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.


+%ModuleCode
+#include <qgl.h>
+
+typedef struct GDevice** GDHandle;
+void* select_3_2_mac_visual();
+
+%End
+
 namespace QGL
 {
 %TypeHeaderCode
@@ -257,6 +265,13 @@
 %End
 %End

+%TypeCode
+    virtual void* chooseMacVisual(GDHandle handle)
+    {
+        return select_3_2_mac_visual();
+    }
+%End
+
 public:
     void deleteTexture(GLuint tx_id);
     static void setTextureCacheLimit(int size);
@@ -478,4 +493,6 @@

 %ModuleHeaderCode
 #include <qpyopengl_api.h>
+typedef struct GDevice** GDHandle;
+void* select_3_2_mac_visual();
 %End