macOS下基于Qt/C++的OpenGL开发环境的搭建
系统配置
- MacBook Pro 2015 Intel macOS 12
- Xcode 14
Qt开发环境搭建
Qt Creator的下载与安装
在Qt官网的下载页面上下载,即Download Qt Online Installer for macOS。下载完成就得到一个文件名类似于qt-online-installer-macOS-x64-x.y.z.dmg的安装包。
下一步
选中自定义安装,下一步
点击右上角显示下拉框,并选中Archive
按上方图示选中对应组件,下一步
安装。这里需要花费较长的一段时间。
新建Qt Widget项目
打开已经安装完成的Qt Creator IDE
点击右上角Create Project…
选中Application(Qt)–>Qt Widgets Application
按上方图示,Base class选择QWidget,不选中Generate form
持续下一步
点击左下角绿色三角形,即运行按钮
生成了一个空白窗体。好,现在关闭这个空白窗体。至此,macOS下基于Qt/c++的开发环境搭建完成。
OpenGL开发环境搭建
基本配置
打开并编辑first_project.pro,添加如下代码:
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 5): QT += opengl openglwidgets
保存。
打开并编辑widget.h,变更代码为如下所示:
#ifndef WIDGET_H
#define WIDGET_H#include <QOpenGLWidget>
#include <QWidget>class Widget : public QOpenGLWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();
};
#endif // WIDGET_H
保存。
打开并编辑widget.cpp,变更代码为如下所示:
#include "widget.h"Widget::Widget(QWidget *parent): QOpenGLWidget(parent)
{}Widget::~Widget() {}
保存。运行一下。
生成了一个空白窗体,效果比上一次的空白窗体更黑,因为现在它是一个OpenGL的运行环境了。好,现在关闭这个空白窗体。
OpenGL版本选择
根据使用 OpenCL 和 OpenGL 图形处理程序的 Mac 电脑这篇Apple官方文章可知,我的MacBook Pro支持OpenGL 4.1。既然如此,那么我就选择OpenGL 4.1作为我的OpenGL学习版本。具体如何操作呢?
回顾一下刚才那个更黑的空白窗体,它已经是一个OpenGL的运行环境了。既然如此,这个OpenGL运行环境运行的是哪一个OpenGL版本呢?是OpenGL 4.1吗?不知道。怎么办呢?
打开并编辑main.cpp,变更代码为如下所示:
#include "widget.h"#include <QApplication>
#include <QOpenGLContext>
#include <QSurfaceFormat>int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();QOpenGLContext *cxt = w.context();QSurfaceFormat fmt{ cxt->format() };qDebug("%d.%d", fmt.majorVersion(), fmt.minorVersion());return a.exec();
}
保存。运行一下。还是刚才那个更黑的空白窗体。好,现在关闭这个空白窗体。
点击一下Qt Creator IDE最下方的Application Output
现在看来,当前使用的是OpenGL 2.1版本。
现在尝鲜,运行第1条OpenGL 2.1的命令,即const GLubyte *glGetString( GLenum name);
。
打开并编辑widget.h,变更代码为如下所示:
#ifndef WIDGET_H
#define WIDGET_H#include <QOpenGLFunctions_2_1>
#include <QOpenGLWidget>
#include <QWidget>class Widget : public QOpenGLWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();protected:virtual void initializeGL() override;virtual void paintGL() override;private:QOpenGLFunctions_2_1 *glf;
};
#endif // WIDGET_H
保存。
打开并编辑widget.cpp,变更代码为如下所示:
#include "widget.h"#include <QOpenGLContext>
#include <QOpenGLVersionFunctionsFactory>Widget::Widget(QWidget *parent): QOpenGLWidget(parent)
{}Widget::~Widget() {}void Widget::initializeGL()
{QOpenGLContext *cxt = context();glf = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_2_1>(cxt);
}void Widget::paintGL()
{const GLubyte *version = glf->glGetString(GL_VERSION);QString versionStr;for (size_t i = 0; version[i] != 0; ++i){versionStr.append(QChar(version[i]));}qDebug() << versionStr;
}
保存。运行一下。
好,现在关闭这个空白窗体。
如何将当前的OpenGL运行环境从OpenGL 2.1切换到OpenGL 4.1?
打开并编辑main.cpp,变更代码为如下所示:
#include "widget.h"#include <QApplication>
#include <QOpenGLContext>
#include <QSurfaceFormat>int main(int argc, char *argv[])
{QSurfaceFormat default_fmt{ QSurfaceFormat::defaultFormat() };default_fmt.setVersion(4, 1);QSurfaceFormat::setDefaultFormat(default_fmt);QApplication a(argc, argv);Widget w;w.show();QOpenGLContext *cxt = w.context();QSurfaceFormat fmt{ cxt->format() };qDebug("%d.%d", fmt.majorVersion(), fmt.minorVersion());return a.exec();
}
保存。运行一下。好,现在关闭这个空白窗体。发现效果和上一张图片完全一样。为什么没有做到将当前的OpenGL运行环境从OpenGL 2.1切换到OpenGL 4.1?哦!原来widget.cpp文件中的那个工厂得到的就是OpenGL 2.1的函数对象,代码如下所示:
glf = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_2_1>(cxt);
打开并编辑widget.h,变更代码为如下所示:
#ifndef WIDGET_H
#define WIDGET_H#include <QOpenGLFunctions_4_1_Core>
#include <QOpenGLWidget>
#include <QWidget>class Widget : public QOpenGLWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();protected:virtual void initializeGL() override;virtual void paintGL() override;private:QOpenGLFunctions_4_1_Core *glf;
};
#endif // WIDGET_H
保存。
打开并编辑widget.cpp,变更代码为如下所示:
#include "widget.h"#include <QOpenGLContext>
#include <QOpenGLVersionFunctionsFactory>Widget::Widget(QWidget *parent): QOpenGLWidget(parent)
{}Widget::~Widget() {}void Widget::initializeGL()
{QOpenGLContext *cxt = context();glf = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_4_1_Core>(cxt);
}void Widget::paintGL()
{const GLubyte *version = glf->glGetString(GL_VERSION);QString versionStr;for (size_t i = 0; version[i] != 0; ++i){versionStr.append(QChar(version[i]));}qDebug() << versionStr;
}
保存。运行一下。
程序崩溃了!怎么办?调试一下。
图中右下角指出:函数对象glf
是空指针。说明在当前这个OpenGL环境上下文下没有正常地拿到函数对象。怎么办?
从OpenGL 2.1切换到OpenGL 4.1相关的所有代码如下所示:
widget.h
QOpenGLFunctions_4_1_Core *glf;
widget.cpp
glf = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_4_1_Core>(cxt);
main.cpp
QSurfaceFormat default_fmt{ QSurfaceFormat::defaultFormat() };
default_fmt.setVersion(4, 1);
QSurfaceFormat::setDefaultFormat(default_fmt);
我们先来查看一下已经下载到本地的Qt QSurfaceFormat的源代码。
qsurfaceformat.cpp
explicit QSurfaceFormatPrivate(QSurfaceFormat::FormatOptions _opts = { })// 已省略, profile(QSurfaceFormat::NoProfile), major(2), minor(0)// 已省略 {}
qsurfaceformat.h
// 已省略
enum OpenGLContextProfile {NoProfile,CoreProfile,CompatibilityProfile};
// 已省略
是不是端倪已出?打开并编辑main.cpp,变更代码为如下所示:
// 已省略
QSurfaceFormat default_fmt{ QSurfaceFormat::defaultFormat() };
default_fmt.setVersion(4, 1);
default_fmt.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
QSurfaceFormat::setDefaultFormat(default_fmt);
// 已省略
保存。运行一下。
好,现在关闭这个空白窗体。至此,macOS下基于Qt/c++的OpenGL 4.1开发环境搭建完成。
细枝末节
细心的话会发现一个现象:
qsurfaceformat.cpp源代码如下所示:
explicit QSurfaceFormatPrivate(QSurfaceFormat::FormatOptions _opts = { })// 已省略, profile(QSurfaceFormat::NoProfile), major(2), minor(0)// 已省略 {}
其中,major
是 2,minor
是 0 。但是前文所述,默认情况下,得到的是major
是 2,minor
是 1 。为什么?而且这是我的机器运行的结果,别人的机器呢?
Qt是一个支持跨平台的大型c++类库。那么,它是如何跨进macOS的呢?以OpenGL为例:在Mac平台上,通过objective-c编写基于OpenGL的应用,相关的API会涉及到NSOpenGLContext这个类,即OpenGL运行环境上下文,而Qt就可以通过插件的形式调用到此API 。
我们再来查看一下已经下载到本地的qcocoaglcontext.mm的源代码。
// 已省略
m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:m_shareContext];
// 已省略
NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurfaceFormat &format)
{QVector<NSOpenGLPixelFormatAttribute> attrs;attrs << NSOpenGLPFAOpenGLProfile;if (format.profile() == QSurfaceFormat::CoreProfile) {if (format.version() >= qMakePair(4, 1))attrs << NSOpenGLProfileVersion4_1Core;else if (format.version() >= qMakePair(3, 2))attrs << NSOpenGLProfileVersion3_2Core;elseattrs << NSOpenGLProfileVersionLegacy;} else {attrs << NSOpenGLProfileVersionLegacy;}// 已省略
根据上方代码可知,对于Qt来说,原来所有Mac系的OpenGL都支持OpenGL 4.1 Core、OpenGL 3.2 Core、OpenGL Legacy,根据前文描述的运行结果可知,此处的OpenGL Legacy应该就是指OpenGL 2.1了。不过还是不要忘记前文所示的使用 OpenCL 和 OpenGL 图形处理程序的 Mac 电脑这篇Apple官方文章。
另外一个问题是为什么在main.cpp中,以下部分代码:
QSurfaceFormat default_fmt{ QSurfaceFormat::defaultFormat() };
default_fmt.setVersion(4, 1);
default_fmt.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
QSurfaceFormat::setDefaultFormat(default_fmt);
出现在QApplication a(argc, argv);
之前?因为在它之后会出现问题。
我们再来查看一下已经下载到本地的Qt QSurfaceFormat的源代码。
// 已省略
void QSurfaceFormat::setDefaultFormat(const QSurfaceFormat &format)
{
#ifndef QT_NO_OPENGLif (qApp) {QOpenGLContext *globalContext = QOpenGLContext::globalShareContext();if (globalContext && globalContext->isValid()) {qWarning("Warning: Setting a new default format with a different version or profile ""after the global shared context is created may cause issues with context ""sharing.");}}
#endif*qt_default_surface_format() = format;
}
// 已省略
这样看似乎还是不够明显。我们再来查看一下已经下载到本地的Qt qguiapplication.h的源代码,如下所示:
// 已省略
#if defined(qApp)
#undef qApp
#endif
#define qApp (static_cast<QGuiApplication *>(QCoreApplication::instance()))
// 已省略
这样就解释了在QApplication a(argc, argv);
创建应用对象之前调用QSurfaceFormat::setDefaultFormat(default_fmt);
的原因了。