将QOpenGLWidget子类转换为使用Metal而不是OpenGL的子类是否可行?

Is it feasible to convert a QOpenGLWidget subclass into one that uses Metal instead of OpenGL?

本文关键字:子类 OpenGL 是否 转换 QOpenGLWidget Metal      更新时间:2023-10-16

背景:我有一个Qt/C++应用程序,它目前运行在(并部署在(MacOS/X、Windows和Linux上。 在应用程序的一个窗口中,有几十个音频表的视图需要频繁更新(即以 20Hz 或更快的速度(,因此我使用 QOpenGLWidget 和一些简单的 OpenGL 例程实现了该视图(请参阅下面的示例代码(。

这一切都很好,但是Apple最近弃用了OpenGL,并希望所有开发人员将他们的应用程序转换为Apple自己的"Metal"API;这意味着最终任何使用OpenGL的程序都将停止在MacOS/X上运行。

如果必须的话,我不介意在我的代码中做一点#ifdef魔术来支持MacOS/X的单独API,但是目前尚不清楚编码到Metal是否实际上可以在Qt中完成。 如果Metal-inside-Qt是可能的,那么正确的使用方法是什么? 如果没有,我是否应该等待具有更好Metal支持(例如Qt 5.12?(的未来Qt版本,而不是浪费时间尝试在当前的Qt版本(5.11.2(中使Metal工作?

// OpenGL meters view implementation (simplified for readability)
class GLMetersCanvas : public QOpenGLWidget
{  
public:
GLMetersCanvas( [...] );
virtual void initializeGL()
{  
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glDisable(GL_COLOR_MATERIAL);
glDisable(GL_LIGHTING);
glClearColor(0, 0, 0, 0);
}
virtual void resizeGL(int w, int h)
{  
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, w, 0, h, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
virtual void paintGL()
{  
const float meterWidth = [...];
const float top        = [...];
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
float x = 0.0f;
for (int i=0; i<numMeters; i++)
{
const float y = _meterHeight[i];
glColor3f(_meterColorRed[i], _meterColorGreen[i], _meterColorBlue[i]);
glVertex2f(x,            top);
glVertex2f(x+meterWidth, top);
glVertex2f(x+meterWidth, y);
glVertex2f(x,            y);
x += meterWidth;
}
glEnd();
}
};

是的,可以做你想做的事。这可能不是一个直接的过渡,因为您发布的代码使用了OpenGL非常旧的弃用功能。此外,您最好只使用CoreGraphics进行您正在做的简单绘图。(看起来正在绘制许多纯色的四边形。这在CoreGraphics中非常简单且相当高效。金属对于这项工作来说似乎有点矫枉过正。也就是说,这里有一些想法。

Metal本质上是一个 Objective-C API,所以你需要将 Metal 代码包装在某种包装器中。有很多方法可以编写这样的包装器。你可以创建一个Objective-C类来绘制你的图,并从你的C++/Qt类中调用它。 (你需要把你的Qt类放到一个.mm文件中,这样编译器就会把它当作Objective-C++来调用Objective-C代码。或者你可以让你的Qt类成为一个抽象类,它有一个实现指针,指向执行实际工作的类。在Windows和Linux上,它可以指向执行OpenGL绘图的对象。在macOS上,它会指向使用Metal进行绘图的Objective-C++类。

这个混合OpenGL和Metal的例子可能有助于理解两者的相似之处以及它们的不同之处。与在 OpenGL 中那样设置状态并进行绘制调用的上下文不同,在 Metal 中,您可以使用绘图命令创建命令缓冲区,然后提交它们进行绘制。与更现代的OpenGL编程一样,您拥有顶点数组并将顶点和片段着色器应用于每个几何体,在Metal中,您还将提交顶点并使用片段和顶点着色器进行绘制。

不过,老实说,这听起来像是很多工作。(但当然可以做到。如果你在CoreGraphics中这样做,它看起来像这样:

virtual void paintCG()
{  
const float meterWidth = [...];
const float top        = [...];
CGRect backgroundRect = CGRectMake(...);
CGContextClearRect(ctx, backgroundRect);
float x = 0.0f;
for (int i=0; i<numMeters; i++)
{
const float y = _meterHeight[i];
CGContextSetRGBFillColor(ctx, _meterColorRed[i], _meterColorGreen[i], _meterColorBlue[i]);
CGRect meterRect = CGRectMake(x, y, meterWidth, _meterHeight[i]);
CGContextFillRect(ctx, meterRect);
x += meterWidth;
}
glEnd();
}

它只需要你有一个CGContextRef,我相信你可以从你画进的任何窗口得到它。如果窗口是NSWindow,则可以调用:

NSGraphicsContext* nsContext = [window graphicsContext];
CGContextRef ctx = nsContext.CGContext;

在这种情况下,这似乎比使用 Metal 更容易编写和维护。