Apple GPU 上的剪辑控制

IT最资讯 1年前 (2022) 导航君
33 0 0

使用开源 OpenGL 驱动程序在 Apple M1 GPU 上渲染 Neverball
经过一年的开发,Apple GPU 的开源“Asahi”驱动程序正在运行真正的游戏。还有更多工作要做,但Neverball已经可以玩了(而且很有趣!)。

Neverball 使用传统的“固定功能”OpenGL。旧的 OpenGL 1 应用程序不提供像 OpenGL 2 这样的可编程着色器,而是配置一组固定的图形效果,如雾和 alpha 测试。现代 GPU 不在硬件中实现这些功能。相反,驱动程序合成实现所需图形的着色器。这种翻译很复杂,但我们在 Mesa 中作为开源驱动程序“免费”获得它。如果我们实现现代着色器管道,Mesa 将为我们透明地处理固定函数 OpenGL。这是开源驱动程序的胜利,也是Asahi Linux上 GPU 加速的胜利。

为了实现现代 OpenGL 功能,我们依赖于对 Apple 的 Metal 驱动程序的行为进行逆向工程,因为我们没有硬件文档。尽管 Metal 使用与 OpenGL 相同的着色器管道,但它并不支持硬件支持的所有 OpenGL 功能,这让我们陷入了困境。过去,我依靠有根据的猜测来弥补差距,但还有另一种解决方案……而且很笨拙。

出于动机,请考虑OpenGL 中使用的剪辑空间。在地球上的所有其他 API 中,3D 世界中点的 Z 分量(深度)范围从 0 到 1,其中 0 表示“近”,1 表示“远”。然而,在 OpenGL 中,Z 的范围是从负 1到 1。由于 Metal 使用 0/1 剪辑空间,因此在 Metal 上实现 OpenGL 需要通过在顶点着色器中插入额外指令来转换 Z 坐标来模拟 -1/1 剪辑空间。尽管这种模拟增加了开销,但它适用于ANGLE在 Metal 上的 OpenGL ES 开源实现。

与 ANGLE 一样,Apple 的 OpenGL 驱动程序在内部转换为 Metal。因为 Metal 使用 0 到 1 的剪辑空间,所以它应该需要这个仿真代码。奇怪的是,当我们反汇编使用 OpenGL 实现编译的着色器时,我们看不到任何这样的仿真。这意味着除了 Metal 的首选 0/1 之外,Apple 的 GPU 还必须支持 -1/1 剪辑空间。问题是弄清楚如何使用这个其他剪辑空间。

我们预计这些剪辑空间之间会有一些切换。这种位的逻辑位置是视口数据包,但 Metal 和 OpenGL-on-Metal 发出的视口数据包之间没有明显区别。通常,我们会通过切换 Metal 中的剪辑空间并比较内存转储来识别位。但是,根据 Apple 的文档,无法更改 Metal 中的剪辑空间。

这显然是矛盾的。Metal 无法使用 -1/1 剪辑空间,但 Apple 的 OpenGL-on-Metal 转换器使用 -1/1 剪辑空间。是什么赋予了?

这里有个小秘密:有两个称为“Metal”的图形 API。你知道的 Metal 是 Apple 为 App Store 开发人员记录的有限 API,缺乏 OpenGL 和 Vulkan 支持的有用功能的 API。

还有 Apple 自己使用的 Metal,一个内部 API 添加了 Apple 不希望您使用的功能。虽然 ANGLE 在已记录的 Metal 上实现了 OpenGL ES,但 Apple 可以在秘密 Metal 上实现 OpenGL。

Apple 没有为这个更丰富的 Metal API 发布文档或标题,但如果我们幸运的话,我们可以在幕后一瞥。构成内部 Metal API 的未记录类和方法在生产 Metal 二进制文件中仍然可用。要使用它们,我们只需要缺少的标题。幸运的是,Objective-C 符号包含足够的信息来重建头文件,允许我们使用从 OpenGL 继承的“额外”功能来试验未记录的方法。

与英特尔 Mac 中的桌面 GPU 相比,Apple 自己的 GPU 实现了与 Metal 的纤薄、现代的功能集映射。大多数“额外”功能都是模拟的。有趣的是,模拟发生在他们的 Metal 驱动程序而不是他们的 OpenGL 前端,但这并不奇怪,因为它允许他们的用于 Intel 和 AMD GPU 的 Metal 驱动程序本机实现该功能。虽然这些信息对“macOS 诠释学”很有吸引力,但它对我们的 Apple GPU 之谜无济于事。

对我们有帮助的是名为 的包罗万象的神秘方法setOpenGLModeEnabled,显然启用了“OpenGL 模式”。

Apple GPU 上的剪辑控制

命名为 like 的神秘方法只求被调用。

渲染管道描述符有这样一个方法。该描述符包含可以更改每次绘制的状态。在某些图形 API 中,例如带有 OpenGL 的 OpenGL 和带有的ARB_clip_controlVulkan VK_EXT_depth_clip_control,应用程序可以在每次绘制时更改剪辑空间。理想情况下,剪辑空间状态将是该描述符的一部分。

我们可以通过扩充我们的 Metal 测试台来测试这个乐观的猜测[MTLRenderPipelineDescriptorInternal setOpenGLModeEnabled: YES]。

调用这个隐藏方法感觉很奇怪。当代码编译并运行得很好时,这很奇怪。

然后我们可以比较 OpenGL 模式和普通 Metal 模式之间的轨迹。看起来,启用 OpenGL 模式会切换过多的随机未知位。即使其中一个是我们想要的,但“真正的”Metal 缺少适当的[setClipSpace: MTLMinusOneToOne]方法,而不是重新配置一堆松散相关的 API 行为,这有点令人不满意。

唉,对于“OpenGL 模式”中的所有随机变化,似乎都没有影响剪裁行为。

希望还没有消失。还有另一种setOpenGLModeEnabled方法,这次是在渲染通道描述符中。这个描述符的状态只能在渲染通道之间改变,而不是可以改变每次绘制的管道状态。在两次绘图之间更改该状态将需要昂贵的刷新到主内存,类似于在其他地方看到的部分渲染 Apple GPU。尽管如此,还是值得一试。

将我们的测试台更改为 call [MTLRenderPassDescriptorInternal setOpenGLModeEnabled: YES],我们发现另一个随机位集合发生了变化。它们中的大多数都在硬件数据包中,而且似乎也没有一个可以控制剪辑空间。

有一点确实很突出。这不是硬件位。

除了用户空间驱动程序为硬件准备的数据包外,用户空间还向内核传递了一大块渲染传递状态,描述了从图块大小到深度/模板缓冲区的所有内容。这样的设计是不寻常的。通常,GPU 内核驱动程序只关心内存管理和调度,而忽略 3D 图形。相比之下,Apple 在内核中处理此状态,然后将状态转发给 GPU 的固件以配置实际硬件。

比较跟踪,渲染过程“OpenGL 模式”在这个内核处理的块中设置了一个未知位。如果我们在 OpenGL 驱动程序中设置相同的位,我们会发现剪辑空间变为 -1/1。胜利,对吧?

几乎。因为这个位是渲染传递状态,我们不能用它来改变两次绘制之间的剪辑空间。这对于基线 OpenGL 和 Vulkan 来说是可以的,但它会阻止我们有效地实现ARB_clip_control和VK_EXT_depth_clip_control扩展。至少有三种(低效)实现。

第一个是忽略硬件支持并通过在使用“错误”剪辑空间时将额外指令插入顶点着色器来模拟其中一个剪辑空间。除了额外的开销之外,这还需要针对不同剪辑空间的着色器变体。

着色器变体很糟糕。

在 Vulkan、Metal 和 D3D12 等新的 API 中,编译着色器所需的一切都预先知道是整体管道的一部分。这意味着管道在创建时进行编译,而不是在使用时进行编译,并且永远不会重新编译。相比之下,像 OpenGL 和 D3D11 这样的旧 API 允许使用具有不同 API 状态的相同着色器,需要一些驱动程序动态重新编译着色器。编译着色器很慢,因此着色器变体可能会导致应用程序的帧速率出现不可预知的下降,这对于桌面游戏玩家来说就像是卡顿一样。如果我们在 OpenGL 驱动程序中使用这种方法,切换剪辑模式可能会由于重新编译着色器而导致卡顿。在糟糕的情况下,这种卡顿甚至可能在模式切换后很长时间内发生。

该选项是不可取的,因此第二种方法总是插入在运行时读取所需剪辑空间的仿真指令,为转换保留统一(推送常数)。这样,同一着色器可用于任一剪辑空间,消除着色器变体。但是,这比第一种方法具有更高的开销。如果应用程序在渲染过程中频繁更改剪辑空间,则此方法将是三种方法中最有效的。如果没有,这种方法会给每个应用程序增加持续的开销。知道哪种方法更好,需要驾驶员拥有一个神奇的水晶球。1

最后一个选项是使用硬件剪辑空间位并在剪辑空间更改时拆分渲染通道。在这里,着色器是最佳的,不需要变体。但是,如果应用程序频繁更改剪辑空间,则拆分渲染通道会浪费大量内存带宽。然而,这种方法得到了ARB_clip_control规范的一些支持:

某些 [OpenGL] 实现可能会在更改剪辑控制状态时引入刷新。因此,不建议频繁更改剪辑控制。

每种方法都有取舍。目前,最简单的“选择”就是把头埋在沙子里,然后完全放弃ARB_clip_control。在 OpenGL 4.5 之前,OpenGL 扩展是可选的。Apple 没有在他们的 OpenGL 堆栈中实现它。因为ARB_clip_control主要是为了移植 Direct3D 游戏,原生 OpenGL 游戏没有它也很开心。当然,Neverball 并不介意。目前,我们可以使用硬件位在 OpenGL 中无条件地使用 -1/1 剪辑空间,在 Vulkan 中无条件地使用 0/1。这不需要任何仿真或刷新,尽管它阻止我们宣传扩展。

这足以在 macOS 上运行 Neverball,使用我们在 Mesa 中的用户空间 OpenGL 驱动程序和 Apple 的专有内核驱动程序。有一个问题:Neverball 必须在 macOS 上使用已弃用的 X11 服务器。多年前,Apple 工程师2在 macOS (XQuartz) 上为 X11 提供了 Mesa 支持,使我们能够使用我们的 Mesa 驱动程序运行 X11 应用程序。但是,不支持 Apple 自己的 Cocoa 窗口系统,这意味着本机 macOS 应用程序无法与我们的驱动程序一起使用。在 macOS 上运行 Linux 较新的 Wayland 显示服务器也没有简单的方法。尽管如此,Neverball 并不直接使用 Cocoa。相反,它使用跨平台SDL2库来创建它的窗口,它在内部使用适用于操作系统的 Cocoa、X11 或 Wayland。有足够的汗水和泪水,我们可以构建一个 macOS/X11 版本的 SDL2 并将 Neverball 与它联系起来。

这个 Neverball/macOS/X11 端口令人沮丧,尤其是当游戏apt install在 Linux 上仅此而已时。这是Asahi Lina的工作,她一直在努力为 Apple 的 GPU 编写 Linux 内核驱动程序。当我们的工作收敛时,我的用户空间 Mesa 驱动程序将使用她的内核驱动程序在 Linux 上运行,以在 Asahi Linux 上实现用于 3D 加速的完整开源图形堆栈。

请调整您的期望:即使有硬件文档,优化的 Vulkan 驱动程序堆栈(具有足够的功能以将 OpenGL 4.6 与 Zink 分层)需要多年的全职工作。至少目前,没有人全职开发这个驱动程序3。逆向工程大大减慢了这个过程。我们不会很快玩 AAA 游戏。

也就是说,由于 Mesa 中大量的共享代码,一个基本的 OpenGL 驱动程序可以由一个人完成。我很乐观,我们将在今年年底之前在 Asahi Linux 中使用本机 OpenGL 2.1。这足以加速您的桌面环境和浏览器。玩旧游戏(如 Neverball)也足够了。即使没有花哨的功能,GPU 加速也意味着流畅的动画和更长的电池寿命。

鉴于此,Asahi Linux 的未来看起来一片光明。

版权声明:导航君 发表于 2022年8月23日 上午12:56。
转载请注明:Apple GPU 上的剪辑控制 | 第八网址导航

相关文章

暂无评论

暂无评论...