前言:Metal主要用于连接cpu与GPU,主要用于操作GPU,本文从官方文档的角度,解读所有metal的功能性特点,当然你当然可以深入去看Metal.by.Tutorials这本书,反正我是暂时没花钱买,你们看了也要记录一下,毕竟,书本记录的总是抽象的以及晦涩难懂并且无法交流的😄
概述
如果你想处理高性能视频处理,visionpro应用开发,科学研究应用程序,2d,3d应用程序,但是我以为即使不是2d 3d应用,metal同样能给你带来意想不到的启发与原理性探究甚至创造
许多 Apple 高级框架都充分利用了 Metal 的性能,包括RealityKit,SceneKit,SpriteKit和Core Image。这些高级框架会为您实现 GPU 编程细节。不过,您通常可以通过编写自己的自定义 Metal 和着色器代码来获得更好的性能
Metal 与其他框架协同工作,增强其功能,MetalFX 可在比原生渲染更短的时间内提升渲染效果,而MetalKit则简化了在屏幕上显示 Metal 内容的任务。Metal Performance Shaders)框架提供了一个大型优化的计算和渲染着色器库,可充分利用每个 GPU 的独特硬件。在 visionOS 中,借助Compositor Services框架,您可以创建完全沉浸式的立体内容
这里按照数字分类,每一个点都会做一些解释,按照官方文档的顺序做解答,因为metal的知识是不容易理解以及需要大量练习以及琐碎的
所有知识点均有demo!
Metal 学习要点梳理(基于 2025 WWDC Metal4 更新)
1. Metal 版本更新与学习目标
- Metal4 在 2025 WWDC 发布,增加了若干核心 API。
- 重点不在于罗列细节,而是从根本上理解并彻底掌握 Metal。
- 专注于理解“真正重要的核心概念与机制”。
2. Metal Shading Language(MSL)
- MSL 是基于 C++ 的专用着色器语言变体。
-
重点:
- 学习更多 MSL 规范和使用示例需要自己查询并动手实践。
- 熟悉并掌握 C++ 属性语法至关重要。
- Apple 对 C++ 进行了扩展,这包括:
- 特殊的
[[key_xxx_xxx]]属性语法。 -
kernel公共内核函数概念等。
- 特殊的
- 建议:深入研究 LLVM 编译器及其对 MSL 的语法扩展。
3. Metal 工作流程总结(核心)
- GPU 需要通过某种方式执行内核函数。
- Metal API 的作用是将内核函数传递给 GPU 执行。
-
GPU 和 CPU 之间共享内存,能够互访数据。
- 熟悉 Metal API 就是熟悉如何正确管理和使用这种共享内存。
这是 Metal 学习的关键点,理解清楚这里才能更好地掌握后续内容。
4. GPU 的选择机制(暂时忽略)
- macOS 支持多 GPU,同时处理任务。
- 相关内容后续深入研究。
5. GPU 加速机器学习
- Metal 对机器学习加速有所支持。
- pytorch对于metal没有支持,但是苹果自己给出了办法可以使用mac加速机器学习
- 这里的内容太高深,暂时忽略
6. metal绘制内容
- 绘制基本的桥接组件就是mtkview
- 基本的方式就是MTKView通过代理委托创建了绘制多个纹理,然后存储到GPU可以访问的区域,提供 渲染到屏幕的养料,这里说的太抽象了
- 基本的动作为,创建mtkview,在mtkview代理里面绘制,创建基于gpu的commandqueue,Buffer, encoder,然后提交Buffer,提交渲染纹理目标给Buffer,让Buffer与coreanimation链接起来,即可
7. metal绘制内容---使用渲染管道结合mtkview的渲染描述符绘制基元
有了上面的铺垫,你应该已经了解了一般的GPU渲染流程,那么如何真正使用渲染描述符让coreanimation和metal的mtkview真正链接起来渲染呢?
从第6点可知,我们的渲染是绝对需要一个渲染描述符对象去生成编码器的,你可以这样理解,这个所谓的描述符,实际上就是用来描述视图应该怎么去在GPU中渲染,从开始到结束,整个渲染过程的一种描述,它不单纯是所谓的把着色器代码转变成可编码元素的一种对象,因为渲染视图需要的不仅仅是着色器,还有很多其他配置,例如是否重绘等,metal通过api的方式简化归并了这一步骤
了解了流程,就需要开始真正绘制一个简单的视图,由于图元装配的最小基元就是三角形,可以试着先使用着色器代码绘制一个小小的三角形作为铺垫(使用metal4 最新API)
明确实现原理:三角形即三个点连接成的闭合形状,使用代码的方式表现这些,然后传入着色器,让其固定点连接成线,然后把这些绘制过程通过metal api传给GPU,GPU和coreanimation结合,就可以展示了
构建实现方式:这里你需要自行查阅归一化坐标,viewport,以及着色器参数等问题,viewport是像素级别的,metal不考虑手机上的逻辑尺寸,除此之外就是你需要利用小学三角函数知识构建三角形顶点的逻辑坐标或者归一化坐标,这里没有深入齐次坐标系,mvp矩阵,因此怎么简单怎么来,然后顶点的颜色信息,此外,demo中展示了三缓冲区概念,即用一个数组保存循环利用的三个Buffer,防止Buffer等待,例如在操作Buffer的时候下一帧来到,那么此时显然无法操作还没使用完的Buffer,当然,这不是重点(后续还会有类似的讲解)
有了确定的原理步骤,就要开始实现,需要给着色器函数固定的顶点,以及对应的填充颜色,那么需要定义一个着色器可以识别的顶点结构体,虽然swift和C可以互相兼容ABI,但是一般情况下,使用C定义这种类型表现才是正确的选择,无他,swift仍然不够成熟以及兼容,由于一步一步叙述实在是繁琐,具体可以移步实际的demo代码(具体需要联系本人),但是实际的根本原理大概已经阐述清楚了
我想有必要再次说明一下pso,GPU渲染管线,状态机,以及mtkview的视图渲染描述符之前到底是什么关系,到底是什么含义,到底各自做什么工作,pso就是链接着色器与GPU的中间执行对象,metal自行封装处理了其过程步骤,状态机是pso的子集,mtkview的视图渲染描述符看起来似乎也是和pso差不多的东西,但是实际上mtkview的视图渲染描述符是另一种视图如何渲染的描述,他不是真正执行着色器的中间状态,而是和coreanimation紧密连接,是描述视图如何在coreanimation下展示的一系列描述,我想metal这样设计的初衷也是希望可以严格区别这种含义
8. macos动态切换GPU
- 这部分不做具体讲解,仅做如下说明:mac可以扩展多个GPU设备,而如果想平滑切换每个GPU设备,metal给出的解决方案是提前渲染准备可绘制对象,其实就是提前准备多个和GPU关联的管道描述符(GPU可以识别的着色器对象包装),以及资源,以及顶点和缓冲区数据,至于切换的时候,其实就是切换到不同的device,然后提交绘制命令,这个时候由于资源已经准备好了,剩下的就是提交命令,编码,以及提交到当前的可绘制纹理对象中,具体还不清楚的可以单独提问。总之就是metal可以提供不同的GPU设备可以直接替换同一个mtkview的可渲染对象,具体优化就不得而知了
9. 自定义渲染过程设置(实现离屏渲染)
这部分就是离屏渲染的实际操作了,通常很多人会提出离屏渲染并把它作为卡顿的一种原理,例如你给单元格添加阴影,滚动的时候就会卡顿,许多人并不理解真正卡顿的原因以及如何真正解决,说白了,卡顿的真实原因在于滚动的时候需要从离屏渲染的纹理里面拿到阴影纹理并在主线程进行最终渲染纹理合成,这些由于同时滚动和合成都发生在主线程,自然会卡顿了,实际上如果能把真正需要显示的内容以及叠加阴影,全部由离屏渲染合成好,自然不会有问题,当然早前的异步渲染也能解决,但是他只是从cg框架的api角度,并没有真正的离屏渲染,对于复杂且需要高性能显示的视图来说并不是很好的手段,况且离屏渲染主要用于大型复杂渲染中的光照,阴影等复杂效果
关于这部分api的细节点含义是非常多的,很多时候并不是那么容易理解,因为你需要把这些计算机操作细节点完全搞清楚,甚至有的时候把自己想象成GPU或者CPU,比如渲染描述符中的颜色附件中的load和store的含义等,关于这些,我想把它的具体含义放到demo中更合适,因为这仍然是属于最基础的知识。
这里只描述真正重要的原理,执行的流程等,首先确认一个metal不可忽略的点,就是metal其实是会自动同步或者组合渲染的顺序,只要你告诉了他渲染之间是否存在依赖关系,这点比较晦涩难懂,可以直接看demo,举个你常见的现象来说,当你使用表视图创建带有阴影效果的单元格,通常来说滑动是卡顿的,一般来说就是触发了所谓的离屏渲染,导致渲染延迟,自然的不能及时得到渲染的纹理,也不能及时的渲染到屏幕上,这就会导致,你看到的东西会跳一下,流畅度降低,那是因为渲染器会自动同步阴影和你要绘制的阴影视图合成,因此,要优化其实也并不是没有可能,那就是和滑动效果独立开来,或者说区别于主线程同步,你可以另外开辟一个子线程去离屏渲染纹理,那就一定不会影响主线程滑动,只是可能阴影效果延迟了,当然了,还有很多其他办法,比如预渲染等,这都是后话了,我只是想表达,解决这样的问题并不难,并且熟悉metal之后你自己随心所欲定制自己的空间,甚至再造一个属于你的uikit
这里其实自定义渲染过程已经简单说明白了,总结一下就是你可以自己绘制一个自定义纹理,然后多种纹理组合,然后渲染到可绘制对象并结合ca显示在屏幕上,这里的demo是最简单的情况,却也是uikit中离屏渲染阴影的内部实现方式。
细节点补充:有必要补充一些重要的概念,metal给我的感觉始终是晦涩难懂的,很多东西,只有深入查资料才能真正搞明白,这里主要补充usage = [.shaderRead, .renderTarget],这两个枚举的主要含义就是告诉metal,metal告诉底层gpu,这个纹理是可以作为采样纹理提供给着色器,以及这个纹理可以用来作为画图,也就是渲染图像,这种枚举精细化了gpu的操作权限,也许是metal特有的性能优化细节点吧
demo的粗略讲解,防止过于抽象不具体,还是需要稍微讲解一下,demo的开始就是创建一个mtkview,实现代理方法,其中需要创建离屏渲染过程描述符,真正需要渲染的mtkview自带渲染描述符,所以不用创建,总结来说就是创建渲染队列,渲染队列管道,离屏渲染描述符,离屏渲染管道,队列编码,队列设置Buffer,给编码设置当前的管道,然后提交,然后就能看到效果啦,至于细节点,我想上面的写的应该够用了,完毕,开始下一章节。
10. 创建自定义 Metal 视图,从自定义layer开始
实现根据您的应用需求定制的 Metal 渲染轻量级视图。虽然 MetalKit提供了丰富的功能,让您可以快速上手编写 Metal 代码,但有时您仍希望更好地控制 Metal 内容的渲染方式,这就需要回到原始的自定义layer的方式,此方式是coreanimation定义的方式,大家肯定很熟悉
剩下的基本上就比较简单,创建所谓的displaylink不断刷新渲染这个layer的可绘制对象,过于简单了,忽略,不过demo还是有的
11. 使用深度测试计算图元可见性
- 这部分比较好玩,会仔细说明一下,类似的蒙版效果以及颜色叠加等问题都会讲解
总结
- 学习 Metal 时,先聚焦“Metal 如何驱动 GPU 工作”这个核心问题。
- MSL 语言是重点,尤其需要掌握 Apple 对 C++ 的扩展语法和 GPU 内核函数机制。
- 熟悉 Metal API 是用好 GPU 和 CPU 共享内存及调度的关键。
ii