如何在 MacOS 上使用 QGLFormat 更改 OpenGL 版本?

Posted

技术标签:

【中文标题】如何在 MacOS 上使用 QGLFormat 更改 OpenGL 版本?【英文标题】:How to change OpenGL Version using QGLFormat on MacOS? 【发布时间】:2017-10-19 16:43:28 【问题描述】:

我在 Mac 上使用 PyOpenGL。默认情况下,使用 OpenGL 2.1。但是,根据我的研究和 OpenGL Extension Viewer,我应该能够使用 OpenGL 4.1。 我正在尝试将 QGLFormat 传递给我的 QGLWidget,正如我在这里的许多线程上看到的那样,但是上下文版本没有改变。如果我尝试强制它,它会告诉我上下文无效。 我一直在做很多实验——这里有一个非常简单的例子来说明我的问题:

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_())

这是输出:

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

知道我的问题出在哪里吗?难道我做错了什么? 我使用的是 macOS Sierra 版本 10.12.6。 我使用 Qt 版本 4.8.7、sip 版本 4.19.3 和 pyqt 版本 4.12,以防万一。 这里是what OpenGL Extension Viewer tells me。

任何帮助将不胜感激!

编辑

我在这里发现了与 C++ 类似的问题。我不确定如何将解决方案翻译成 PyQt,但我会研究一下: Changing the OpenGL Context Version for QGLWidgets in Qt 4.8.6 on OS X.

编辑 3

在尝试@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 现在已正确修补,但 qgl.sip 仍然失败。

更新

这是 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();

出于好奇,我运行了 configure-ng.py 并尝试编译。我收到以下错误:

[...]/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)

【问题讨论】:

您需要在 OSX 上创建 forward-compatible 上下文。 @derhass:你能说得更具体点吗? 【参考方案1】:

无论如何,在我的 Windows 10 笔记本电脑上运行您的代码会产生以下输出:

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

因此,我怀疑您的问题实际上可能不是特定于平台的问题,而是与您的 myW.context().setFormat(glFormat) 通话有关。有趣的是,我查看了PyQt documentation for QGLWidget,它似乎缺少 setFormat(),这非常无用!

查看相应的C++ 文档,看起来setFormat() 实际上可以从QGLWidget itself 或其子QGLContext 对象中调用。由于您是从QGLContext 调用它,因此上下文是reset,使其无效。解决方法是在设置格式后调用myW.context().create(),从而创建一个带有新参数的有效格式对象!该修改如下所示:

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_())

另一种选择是在QGLWidget 级别调用setFormat(),这会自动创建一个新上下文,结果如下:

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_())

话虽如此,所有这些都是猜测,因为我没有可以在其上测试此代码的 Mac。 如果这能解决您的问题,请告诉我,希望对您有所帮助!

【讨论】:

谢谢!至少,我知道为什么上下文变得无效,多亏了你。实际上,我已经尝试在小部件级别调用 setFormat(),但它没有改变任何内容:即上下文有效,但版本保持 1。这就是我直接在上下文上尝试的原因。 :( 我也尝试了你的第一个建议,但我得到了相同的结果:我最终得到了一个有效的上下文,但仍然是错误的 OpenGL 版本。 @Elensil 查看了您在问题编辑中添加的链接。如果升级到 PyQt5 不是一个选项(这可能会解决您的问题),那么您似乎需要以某种方式翻译该 C++ 选项。有很多选项(ctypes、cython 或 C 扩展),但最简单的可能是首先让它与 ctypes 一起工作。希望增加赏金会引起对这个问题的关注,因为如果没有 Mac,我无法真正测试任何东西,哈哈…… 我试过 PyQt5,它解决了这个问题。这是一个备份解决方案,但是将我的应用程序升级到 PyQt5 需要大量的工作。主要是因为混合了一些遗留的 OpenGL,当我切换到 PyQt5 时,它不再起作用了。我不是最初的开发者,我对 OpenGL 及其不同版本的了解有限。另一方面,我想为我的用户安装一些易于安装的东西。下载和运行补丁可能会阻止很多人。 :( @Elensil 定义遗留代码的含义。是否有固定功能的东西,如glBegin(GL_TRIANGLES)、显示列表或像glTranslate 这样的调用转换堆栈?您使用的是什么版本的着色器(例如#version120)?如果您的目标是 OpenGL 中已弃用的功能,您可能根本不需要现代的 opengl 上下文。另外,如果你走 Qt5 路线,请记住 QGLWidget 实际上是 not recommended in new code。 是的,我已经用QOpenGLWidget 替换了我所有的QGLWidget 等等。看来我让那部分工作了。确实有一些固定功能的东西,比如glBegin。对于我的着色器,我不确定我使用的是什么版本。我没有指定版本,但我使用了像varying/uniform/attribute 这样的限定符。使用 PyQt5,它强制我指定版本。不支持版本 120,所以我通过更改限定符尝试使用 330,但我仍然有一个 undeclared identifier 'gl_Vertex'。我试图弄清楚如何使用顶点缓冲区。我最终会到达那里。【参考方案2】:

回答有关将 Mac 特定解决方案翻译成 Python 的具体问题:这是不可能的直接,因为 PyQt 绑定不包装虚拟方法 chooseMacVisual。没有这个,就没有办法重新实现它,因为 Qt 无法看到 PyQt 没有预定义为虚拟的任何 Python 方法。

所以让它工作的唯一方法是修补 PyQt4 并重新编译它。幸运的是,有人已经这样做了,并在此处提供了代码:

https://github.com/prideout/coregl-python

但是,构建说明并不完全清楚,并且针对的是 PyQt-4.9.4。所以我准备了一个应该更容易使用的差异。我没有尝试编译它,因为我无法访问 Mac 系统。

要应用补丁,解压latest PyQt4 OSX sources,然后 cd 进入目录。然后复制下面的差异并保存为macgl.diff,然后运行这个命令:

patch -Np1 -i macgl.diff

然后,您可以使用此处显示的说明编译已修补的 PyQt4:

Installing PyQt4

更新

我以前认为针对 PyQt-4.9.4 的差异将完全适用于所有较新的 PyQt4 版本,但我错了。我试图用下面的修改后的 diff 来解决这个问题,它已经适应了 PyQt-4.12.1 mac source code。

这里是差异:

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

【讨论】:

这看起来很棒,感谢您的彻底回答!我也没有办法对此进行测试,所以希望 OP 可以回复并验证它是否适用于较新版本的 PyQt4。 @CodeSurgeon。看起来我们在这方面都一无所知,但希望补丁仍然有效...... @ekhumoro 感谢您的回答,这看起来很有希望!我已经离开一段时间了,刚刚回到这个问题上。为了清楚起见,您的补丁完全替换了您提供的 coregl-python 链接,对吗?我尝试了您的说明,但收到一条错误消息(请参阅问题中的第二次编辑)。知道是什么原因造成的吗?不幸的是,我只是在这里盲目地按照您的指示进行操作,并没有太多帮助。 @Elensil。是的,它完全取代了它。但是,似乎我没有针对 PyQt-4.12.1 正确测试补丁,因为我现在发现它只适用于 PyQt-4.9.4。我现在已经用 PyQt-4.12.1 的修订补丁更新了我的答案 - 但是,与第一个一样,我不知道它是否会真正正确编译,因为我无法访问 mac。 @ekhumoro。谢谢!我再次尝试了您的补丁,但仍然出现错误(有关详细信息,请参阅问题)。你认为你能解决它吗?

以上是关于如何在 MacOS 上使用 QGLFormat 更改 OpenGL 版本?的主要内容,如果未能解决你的问题,请参考以下文章

刚开始接触苹果Mac,如何认识桌面上的基本功能?

Applescript - 在 MacOS Monterey 上设置系统偏好设置底座大小滑块的值

macOS四种Finder视图,所有这些视图都值得研究

SwiftUI:如何在 macOS 11 上使用 NSSearchToolBarItem?

如何在 MacOS 上使用 OpenCV 链接 C++ 程序 [重复]

如何在 MacOS 上获取文件类型的图标?