View in English

  • 打开菜单 关闭菜单
  • Apple Developer
搜索
关闭搜索
  • Apple Developer
  • 新闻
  • 探索
  • 设计
  • 开发
  • 分发
  • 支持
  • 账户
在“”范围内搜索。

快捷链接

5 快捷链接

视频

打开菜单 关闭菜单
  • 专题
  • 相关主题
  • 所有视频
  • 关于

更多视频

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 探索 Metal 4 游戏

    了解如何借助 Metal 4 的最新改进来优化游戏引擎。我们将介绍如何统一命令编码来充分降低 CPU 开销,如何扩大图形资源管理来支持海量场景并最大限度地提高内存预算,以及如何快速载入大型管道状态库。 为了充分从这个讲座中获益,请先观看“探索 Metal 4”。

    章节

    • 0:00 - 简介
    • 1:33 - 提升编码效率
    • 8:42 - 扩大资源管理
    • 17:24 - 快速载入管线
    • 31:25 - 后续步骤

    资源

    • Human Interface Guidelines: Designing for games
    • Metal binary archives
    • Reading and writing to sparse textures
    • Resource synchronization
    • Synchronizing resource accesses between multiple passes with a fence
    • Synchronizing resource accesses with earlier passes with a consumer-based queue barrier
    • Synchronizing resource accesses with subsequent passes with a producer-based queue barrier
    • Synchronizing resource accesses within a single pass with an intrapass barrier
    • Understanding the Metal 4 core API
    • Using a render pipeline to render primitives
    • Using the Metal 4 compilation API
      • 高清视频
      • 标清视频

    相关视频

    WWDC25

    • 实现 Metal 4 机器学习与图形应用程序的完美融合
    • 探索 Metal 4
    • 深入探索 Metal 4 游戏
    • 用于打造沉浸式 App 的 Metal 渲染的新功能
    • 让游戏更上一层楼

    Tech Talks

    • 针对 Apple 芯片优化游戏的 CPU 作业调度
  • 搜索此视频…

    大家好 我叫 Jason 我叫 Yang 我们是 GPU 驱动程序工程师 在这个视频中 我们将介绍如何 使用 Metal 4 加速你的游戏引擎 这是我们四部分系列中的第二部分 这个系列介绍 Metal API 的 下个主要版本 在开始探索 Metal 4 游戏之前 请先观看“探索 Metal 4” 以全面了解 Metal 4 提供的功能 观看完这个讲座后 再观看“深入探索 Metal 4 游戏” 了解令人惊叹的全新 Metal FX 和 Metal Raytracing API 然后观看“Metal 4 机器学习” 这个有关集成 ML 的专门讲座 让我们开始探索 Metal 4 吧

    Metal 4 专为现代游戏引擎打造 像育碧出品的“刺客信条:影” 这样的游戏 绘制了极其细致的人物和风景 让玩家沉浸在奇幻世界中 它们可以流式传输数千兆字节的 精细几何图形和纹理 使用数千个着色器进行渲染 以充分利用 Apple 芯片提供的 全部计算性能 未来的游戏 对于性能的要求会更加严苛 并且需要一个 能够胜任这一任务的图形 API 这就是 Metal 4

    在 Metal 4 游戏中 Metal 4 带来了一些重大改进 其中包括高效的命令编码 还有可扩展的资源管理 来提升热路径的执行速度 以及更快的管道加载 让玩家无需等待加载屏幕 即可进入游戏 在这个讲座中 我和我的同事将 深入探讨如何充分利用这些功能 在游戏的每一帧中 绘制调用、内核调度、位块传输 以及光线追踪任务 都位于编码热路径上 Metal 4 编码旨在 通过高效率和并发处理 应对这一挑战

    Metal 4 将最常见的计算任务 统一到两种编码器类 这样你就能利用每个编码器 完成更多任务 通过命令分配器 你可以显式管理 编码时产生的内存分配 通过命令缓冲区 你能跨多个线程对任务进行编码

    “探索 Metal 4”讲座介绍了 两种编码器类 即渲染类和计算类 如何处理所有命令编码 为了高效使用它们 你需要同步 计算操作之间的数据依赖项 并使用附件映射机制 来重新映射片段着色器输出 所有计算操作、内核调度、 位块传输和加速结构构建版本 现在都可以在单个计算编码器中编码 无需任何额外的同步 这些命令将并发运行 这样 没有依赖项的工作负载 就可以更好地利用 GPU 资源 如果通道中的某些命令 由于数据依赖项而需要串行运行 你可以使用通道屏障来表达这一点 这种屏障可确保 GPU 会等待 这个编码器上 所有先前的位块传输完成后 再开始后续的计算调度 这个示例说明了如何同步 从位块传输到调度的访问 首先 copyFromBuffer 位块传输会 更新 buffer1 因此要编码通道屏障 然后你可以对使用 buffer1 中数据的调度进行编码 这就是统一计算编码 在一个编码器中完成所有计算任务 并使用屏障来表达数据依赖项 Metal 4 还更新了渲染编码 通过颜色附件映射 你现在可以控制 渲染管道的颜色输出和 渲染编码器附件之间的对应关系 你无需将管道绑定到 固定的渲染目标布局 而是改为提供颜色附件映射 这样在设置新管道时 你就可以更改映射 而无需切换编码器 假设你有一个 Metal 管道 其中包含一个 绘制到三个附件的片段函数 在没有颜色附件映射的情况下 你会创建一个包含三个颜色附件的 渲染编码器 这个片段函数返回三种颜色输出 编码器将这些输出定向到 分块内存中的相应附件 对于下一个绘制调用 你可能需要一个 写入不同输出集的管道 由于附件不同 你需要创建一个新的渲染编码器 来匹配颜色输出 借助颜色附件映射 你无需第二个编码器 这个渲染编码器已包含了 两个管道需要的所有颜色附件 然后 颜色附加映射将着色输出 转化为特定附件 要实现颜色附件映射 首先应设置一个支持颜色附件映射的 渲染通道描述符 然后创建编码器将使用的 附件的完整超集 要配置编码器将绘制到哪个附件 应创建一个颜色附件映射 然后设置它的重新映射条目 为每个条目设置一个逻辑索引 来确定着色器输出 再设置一个物理索引 来确定附件索引 在编码之前构建这些映射对象 并在每帧中重复使用它们 设置渲染管线时 还要绑定颜色附件映射 如果管道绘制到其他附件 你可以切换到其他颜色附件映射 颜色附件映射可以显著减少 游戏中的渲染编码器数量 这将减少编码开销 并通过减少渲染通道的数量 来提高 GPU 效率

    Metal 4 还能让你 更有力地控制内存分配

    命令分配器允许你 重复使用命令缓冲区内存 并避免在编码时进行动态分配 随着你编码命令 分配器内存会不断增加 关联的 GPU 命令运行完成后 应重置分配器 重置操作会释放这些内存 以便在后续的命令编码中重新利用 你可以使用多个分配器 以避免在完成 GPU 任务期间 阻塞命令编码 当你在新的命令分配器上 进行编码时 它将为编码的命令分配内存 这部分命令内存用于存储 在 GPU 上运行的任务 因此应等到提交的任务完成后再重置 GPU 任务完成后 重置命令分配器 这会立即将它的内存 标记为可重复使用 要在 GPU 任务运行时继续编码 应使用第二个命令分配器 这样就可以避免 在完成 GPU 任务的过程中阻塞编码 GPU 任务完成后务必重置命令分配器 分配器内存将增加以支持编码 直到被重置 如果你不打算在某个命令分配器上 进行更多编码任务 你可以释放它以减少内存占用 命令分配器不是线程安全的 因此需要为不同线程 使用不同的分配器 当你并行处理场景编码时 这一点非常重要 Metal 4 命令缓冲区让你可以 将编码划分到多个线程中 在单线程编码中 你会按顺序将一系列命令编码到 一个或多个命令缓冲区 利用 Apple 芯片强大的多核 CPU 在多个线程上启动多个命令缓冲区 每个都有不同的命令分配器 你可以利用 Metal 4 计算编码器 增强的灵活性 均衡分配位块传输、调度 和加速结构的编码任务 完成对命令缓冲区的编码后 你可以通过单个提交调用 将它们全部提交 Metal 4 还允许你将多个渲染编码器 作为单个 GPU 渲染通道提交 假设你有一个 耗时很长才能完成编码的渲染通道 默认情况下 如果你将编码划分到 单独的渲染编码器中 GPU 会它们 作为单独的渲染通道运行 每次传递都会产生 存储和加载中间结果的开销

    Metal 4 提供了“暂停/恢复”选项 来合并多个渲染编码器 只需在一个命令缓冲区中 暂停渲染编码器 然后在另一个命令缓冲中 恢复它即可 完成对命令缓冲区的编码后 通过单个提交调用按顺序提交它们 在一次提交中提交渲染编码器 会指示 Metal 合并渲染通道 要实现这个功能 请创建带暂停选项的首个编码器 Metal 会将这个编码器 与之后的编码器合并 为每个编码器使用不同的命令缓冲区 中间编码器具有恢复和暂停选项 创建仅包含恢复选项的最终编码器 一起提交全部三个编码的命令缓冲区 这样就可以了 你的渲染通道现在已经合并了 使用 Metal 4 可减少编码器数量、 重复使用命令内存 并跨多个线程进行编码 从而提高编码效率 要进一步了解 Metal 4 命令编码 请参阅 Apple 开发者网站上的这篇文章 介绍完高效编码后 我们来深入了解高效的资源管理 Metal 4 提供了 一些激动人心的新功能 来帮助你大规模管理资源 通过参数表和驻留资源集 你可以将资源绑定扩展到数千个资源 Metal 4 让你自主管理 可绘制对象资源 并全面掌控依赖项 队列屏障提供了一种 大规模表达资源依赖项的方法 纹理视图池和放置稀疏堆 帮助你管理大量资源所需的内存 增加着色器的复杂性通常意味着 就资源的数量而言 适合采用无绑定模型 使用单个参数缓冲区 你的着色器可以访问数千个资源 包括缓冲区、纹理、 采样器、管道状态等 但索引绑定点仍用于绑定根级别资源

    使用参数表按索引绑定资源 在编码时 设置下一次绘制或调度时 应使用哪个参数 这些资源可用作 着色器的索引函数参数 在绘制和调度时 Metal 会收集参数 这意味着在绘制调用之间将新资源 设置为绑定索引是安全的 可以在多个编码器阶段 设置单个参数表

    在编码之前创建参数表 可让你将资源绑定移出关键路径 你可以将一个参数表 附加到多个编码器 将参数表与参数缓冲区结合使用 以根据你的资源绑定需求灵活调整 在着色器中访问这些资源的下一步是 使它们对 GPU 可见 每当需要在 GPU 上获取某个资源时 将它添加到驻留资源集中 这包括管道、缓冲区、 纹理和可绘制对象 使用驻留资源集 你可以将许多资源组合在一起 然后一次性使它们全部可见 要么将它们附加到 正在提交的命令缓冲区 要么直接附加到命令队列 如果驻留资源集 随着时间的推移没有太大变化 最好将它附加到命令队列 但如果驻留资源集经常更改 那么应将它附加到 相应的命令缓冲区 为 GPU 准备大量资源 可能需要一些时间 你可以让 Metal 提前 将资源集的资源设为常驻资源 建议使用更少的驻留资源集 但每个中包含更多资源 这样 Metal 就可以批量处理资源 从而提高性能 要进一步了解驻留资源集 请参阅 Apple 开发者网站上的这篇文章 以及去年的“将高端游戏 移植到 Apple 平台”讲座 使用 Metal 4 你对资源驻留的控制 也适用于游戏的可绘制表面 要将游戏的渲染内容发送到显示屏 你需要渲染到 CAMetalLayer 中的可绘制表面 每个 Metal 层都维护一个 动态驻留资源集 将它添加到命令队列中 让图层中的所有纹理成为常驻资源 你只需添加一次驻留资源集 CAMetalLayer 将根据需要更新它 在 Metal 4 中 你还需要将渲染 与可绘制对象同步 在每一帧中 获取下一个可绘制对象后 在命令队列上编码一个等待操作 再对可绘制对象进行渲染 然后在提交渲染任务后 将信号编码到队列中的可绘制对象

    调用 present 以在渲染完成后 将帧内容发送到显示屏 为了减少跟踪开销 Metal 4 让你自主掌控资源的同步 在讲座的前面部分 我介绍了 如何在编码器中使用通道屏障 另一方面 队列屏障表达了 同一队列上编码器之间的 数据依赖项

    屏障根据 Metal 阶段进行过滤 编码器中的每个命令都与 一个或多个执行阶段相关联 例如 渲染编码器中的绘制调用 会生成顶点和片段着色阶段 Apple 芯片 GPU 将 所有顶点着色任务一起批处理 然后再进行所有片段着色任务 Metal 4 的计算命令对应于调度、 位块传输和加速结构阶段 选择适当的阶段 以避免过度同步非常重要 在这个示例中 一个计算通道 在内核调度阶段执行大气模拟 它将结果写入内存中的纹理 渲染通道将绘制场景 片段着色需要获得模拟结果 以便与光照混合 但是顶点着色任务 应该能与计算任务同时进行 为了同步对模拟结果的访问 需要从队列的调度阶段 到渲染编码器的片段阶段 编码一道屏障 为了实现这个示例 首先在计算编码器上对调度进行编码 然后在渲染命令编码器上 在队列阶段调度之后 和片段阶段之前添加一个屏障 添加屏障之后 你可以对绘制调用进行编码 Metal 可确保任何片段阶段任务 包括当前渲染编码器 和未来编码器中的这类任务 在先前编码器中的 所有调度阶段任务完成之后再运行

    为了帮助你找到添加屏障的最佳位置 Metal 调试器将显示它们的位置 以及它们会应用于 哪些编码器和哪些阶段 这样你就能尽可能提高并发的效率 并同时维护数据依赖项

    要进一步了解 如何使用 Metal 屏障来同步资源 你可以在 Apple 开发者网站上 阅读这些完整文章

    借助纹理和缓冲区流式传输 你可以管理数千个资源的 内存占用 Metal 4 允许你 高效地流式传输缓冲区和纹理 你可以通过放置稀疏技术 创建轻量级纹理视图并管理你的 内存资源占用 现代游戏每帧可以创建 数百个纹理视图 和缓冲区视图 TextureViewPools 允许你预先分配 包含所有纹理视图 所需的内存 这样 你就可以在池中的任何索引处 创建轻量级纹理视图 这不会导致任何动态分配 因此你可以在编码时创建它们 使用纹理视图的资源 ID 将视图绑定到参数缓冲区 或参数表 下面是具体的实现方法 在编码时间之前创建纹理视图池 在这个示例中 创建的纹理视图池 提供为 500 个纹理视图分配的内存 在编码时 在纹理视图池中的所需索引处 设置纹理视图 使用返回的 MTLResourceID 将纹理视图绑定到参数表 有时 你需要绑定的资源 可能会占用大量内存 稀疏资源非常适合那些可能无法 同时放入内存的高保真资源 它们将资源创建与内存支持分离 使用放置稀疏技术 你可以控制 资源映射到堆页面的方式 在更新资源的内存映射时 Metal 4 命令队列上的 API 允许你将这些更新 与其他 GPU 任务同步 放置堆中的内存 以一系列分块的形式组织 你可以控制将这些分块 分配给稀疏缓冲区和纹理的方式 为稀疏资源提供内存 方法是通过将字节范围或像素区域 对应到稀疏分块

    在创建放置堆时 考虑资源需要的稀疏页大小 较大的页面大小 在进行对应和取消对应期间 具有一定的性能优势 但会占用更多的内存 来进行填充和对齐 放置堆将支持所有稀疏页大小 最高可达到你指定的最大值 这个示例选择 最大 64 KB 的页面大小 创建放置堆后 就可以创建稀疏资源了 放置稀疏缓冲区和纹理的创建会在 Metal 设备中完成 与非稀疏资源类似 对于缓冲区 应将请求的缓冲区大小 调整为稀疏块的整数倍 设备会提供查询以执行这个转化 在通过指定长度调用新缓冲区时 或在纹理描述符上 设置资源放置的稀疏页大小 这个属性会通知 Metal 设备 放置堆将提供内存支持 首次创建放置稀疏缓冲区时 它没有任何内存支持 你需要通过更新映射操作 将分块分配给缓冲区范围 要将分块从放置堆分配到缓冲区 首先应指定更新映射操作 提供起始偏移量和长度 以及要分配给这个缓冲区范围的 堆中的分块偏移量 然后将映射操作 提交到 Metal 4 命令队列中

    要进一步了解如何使用稀疏资源 请查看 Apple 开发者网站上的这篇文章 现代游戏面临的另一个挑战 是管理大型管道状态库 有请 Yang 为大家介绍这部分内容 谢谢你 Jason 现代游戏需要创建数千个管道 来创建复杂和动态的视觉效果 快速加载许多管道 对于消除着色器编译卡顿 和缩短游戏加载时间至关重要 要在 Metal 4 中快速载入管道 你可以重复使用渲染管线编译 也可以利用新的并行性级别 在设备端编译管道 要更进一步 通过提前编译管道 你可以将管道加载时间 缩短到接近于零 首先 我将为大家展示 如何通过灵活渲染管道状态 重复使用渲染管线编译 假设你正在创建一个城市建设者游戏 玩家可以在地图上的各处放置房屋 在玩家决定了在哪里放置房屋后 游戏应以全息图样式渲染房屋 因此它需要一个 具有加性混合状态的管道 玩家放置房子 房子开始建造 为了以透明方式渲染房屋 以展示建造进度 需要使用另一个 具有透明混合状态的管道 最后 当房子完成建造时 它将使用具有不透明混合状态的 第三个管道渲染房屋 你可以在创建时提供完整的管道配置 使用完整状态编译这三个管道 首先是顶点函数和片段函数 以及不透明、透明 和全息图样式房屋的 颜色附件配置 这里的颜色附件配置 是描述符的一部分 它会影响将片段着色器输出 写入颜色附件 这包括附件的像素格式、 写掩码和混合状态 创建一个渲染管道描述符 这个描述符引用了顶点函数、 片段函数和不透明配置 使用这个描述符 创建不透明管道 它包含了顶点二进制文件、 片段二进制主体和片段输出部分 通过交换描述符中的颜色附件配置 你能以类似方式 创建透明管道和全息图管道 这三个管道中的 大多数二进制文件都是相同的 只有片段输出部分不同 在 CPU 时间线视图中 你会先编译完全不透明管道 然后是透明管道 最后是全息图管道 CPU 花费大量时间 重新编译几乎相同的管道 但片段输出部分除外 使用 Metal 4 你现在可以通过 先创建一个非专用管道 重复使用大部分的管道编译 然后使用不同颜色附件配置 获得所需的最终专用管道 这可以为你 节省大量渲染管线编译时间 要实现节省 先创建一个非专用管道 使用相同的描述符 但不是提供实际的颜色附件配置 而是将每个字段设置为非专用 为此 只需遍历所有颜色附件描述符 并将 pixelFormat、writeMask 和 blendingState 设置为 为相应的非专用值 非专用管道包含一个顶点二进制文件 片段二进制主体和默认片段输出部分 默认片段输出在某些情况下有效 但大多数时候 你需要通过对管道 进行专用化处理来替换它 要创建专用管道 先创建非专用管道 和新的渲染管道描述符 这一次 将描述符中的颜色附件配置 设置为所需的实际值 专用管道包含相应的片段输出 用于替换默认片段输出 这个新的片段输出可以迅速生成 因此 你无需再次完成 完整的着色器编译过程 回到对透明管道 进行专用化处理的示例 首先 在描述符中设置 之前的非专用属性 启用混合状态并设置混合子状态 这里的代码会将管道设置为 执行预乘 alpha 混合 然后结合使用新描述符与非专用管道 实例化专用管道 你的游戏可能正在创建 数千个有状态渲染管道 为了最大限度地缩短加载时间 应创建所有非专用渲染管线 并在之后根据需要 对它们进行专用化处理 这样操作之后 你可能会注意到 有少量的 GPU 性能开销 许多开销可能来自共享片段主体中 不必要的任务 例如 如果片段着色器 写入四个颜色通道 而颜色附件只有一个通道 编译器就无法再优化未使用的通道 从片段二进制主体 跳转到片段输出部分 也会导致少量开销 这部分开销通常很小 但在某些片段着色器中可能会很大 确定那些重要的着色器 并在后台以完整状态进行编译 这样你的玩家就能享受 较短的加载时间和出色的帧速率 你可以使用 Instruments 的 Metal System Trace 对开销最高的专用片段着色器 进行排名 我们总结一下如何最有效地 将灵活渲染管道状态 整合到你的游戏中 首先编译每个非专用渲染管道 然后根据需要对它们进行专用化处理 如果性能明显下降 使用 Instrument 的 Metal System Trace 来剖析游戏 并确定重要的管道 对于这些重要的管道 在后台编译一个主要版本 并在准备就绪时用它来替换专用版本 要进一步了解灵活渲染管道状态 请查看 Apple 开发者网站上的这篇文章

    在通过灵活渲染管道状态 重复使用渲染管线编译之后 通过并行处理设备端编译 来进一步缩短管道加载时间 某些游戏在游戏过程中 可能会用单线程来载入管道 这是用于构建 游戏将使用的管道的单一编译线程 这是一个渲染线程 它会运行重复的帧渲染任务 例如编码 如果所需的管道没有及时准备就绪 游戏可能会卡顿 你可以通过添加另一个编译线程 来加快管道加载速度 管道编译会更快地完成 但是如果不谨慎设置线程优先级 游戏仍可能错过显示的区间 将后台编译线程的优先级设置为 低于渲染线程的优先级值后 卡顿就消失了 玩家现在可以享受 更流畅的游戏体验 下面介绍如何将多线程编译 添加到你的游戏中 使用 Metal 4 编译 API 在 Metal 4 中 编译器可以扩展到更广的范围 你可使用 Grand Central Dispatch 或创建自己的线程池 具体取决于哪种方式 更适合你的游戏架构 无论选择哪种方式 请记住设置适当的优先级 Metal 将遵从编译任务的优先级 Grand Central Dispatch 是发布 多线程编译的最简单方法 如果希望编译继承调用线程的优先级 你可以将调度组 与编译器提供的异步方法搭配使用 异步方法是使用 completionHandler 的方法 Metal 会自动并发执行这些方法 如果你想自定编译的优先级 你可以创建一个并发调度队列 为它指定自定服务质量类 或 QoS 类 对于管道预热和流式处理 建议将这个类设置为默认 要将编译任务提交到调度队列 你可以在代码块中调用异步方法 然后通过 dispatch_async 将它发送到队列 异步方法是 不使用 completionHandler 的方法

    你也可以创建自己的线程池 如果这种方式更适合你的游戏架构 在 Metal 设备中将 maximumConcurrentCompilationTaskCount 属性 用作线程池的线程数 将默认线程数设置为 2 因为这是不支持这个属性的 操作系统中的最大并发数 此外 为我们的编译线程设置适当的 服务质量类或 QoS 类也很重要 以避免游戏中 其他重要线程的资源匮乏 将 QoS 类设置为默认 以便进行管道预热和流式处理 这样就可以了 你现在可以开始 将编译任务发送到线程池 要进一步了解并行处理管道编译 和确定管道编译优先级的最佳方式 请查看 Apple 开发者网站上的这些文章 设备端多线程编译 可以大大缩短编译时间 要进一步将它缩短为接近零 则应在开发时预编译管道 要提前编译管道 游戏通常使用端到端工作流程 工作流程首先通过 运行带代码插桩的游戏 来收集游戏中使用的管道配置 收集结果会提供给 GPU 工具链 以构建 GPU 二进制文件 最后 在运行时 游戏会查找预编译的 GPU 二进制文件以快速构建管道 Metal 4 帮助你更轻松地 联机收集管道配置 并在发布游戏中 查找预编译的二进制文件 在 Metal 4 中 收集管道配置的 最简单方法是 序列化管道脚本 管道脚本只是一个 JSON 格式文件 它包含你在设备上创建的 管道描述符的文本表示 使用 Metal 4 中的 管道数据集序列化器 你可以轻松序列化管道脚本 将这个对象绑定到编译器后 它会自动记录所创建管道的描述符 接下来 你可以将这些描述符 序列化为管道脚本 要创建管道数据集序列化器 应从描述符开始 将配置设置为 CaptureDescriptors 这会通知序列化器仅跟踪管道描述符 并减少内存占用 使用序列化器描述符 创建管道数据集序列化器 然后将序列化器 附加到用于创建编译器的 编译器描述符 创建编译器后 你可以像往常一样 使用它创建管道 序列化器会自动记录 你使用的管道描述符 完成采集后 调用 serializeAsPipelinesScriptWithError 将记录的管道序列化为管道脚本 返回值为 NSData 你可以使用自己偏好的方法 将它发送回你的开发系统 这个示例是将它写入磁盘上的文件 将文件的后缀设置为 mtl4-json 这是 GPU 工具链所需的后缀 收集管道配置后 下一步是构建二进制文件 将管道配置脚本和 Metal IR 库 提供给 metal-tt 它将输出打包在 Metal 存档中的 GPU 二进制文件 在将收集的管道脚本 提供给 metal-tt 之前 打开脚本并修改 Metal IR 库的路径 以匹配你开发系统上的路径 要了解有关管道配置脚本格式的 更多信息 使用这个命令打开手册页面 接下来 只需在屏幕上执行 metal-tt 命令以构建 iOS 存档 你已经预编译了二进制文件 你的游戏需要在运行时查找它们 Metal 4 让你更轻松地 从存档中的 GPU 二进制文件 创建管道 只需使用进行设备端编译 所用的描述符 来检索管道状态 例如 通过提供存档的 URL 来创建 MTL4Archive 对象 然后 使用管道描述符 直接从存档对象查询管道状态

    对存档的查找 可能因多种原因而丢失 例如 由于没有匹配的管道、 系统不兼容 或 GPU 架构不兼容 你需要在 Metal 4 中 自行处理这类丢失 这里的示例只是回退到设备端编译 这样游戏之后仍具有 必需的管道状态

    这里再次显示了 示例游戏的 CPU 时间线 包含了多线程设备端编译 通过采用提前编译 管道加载时间缩短为几乎为零 要进一步了解如何提前编译 请查看 Apple 开发者网站上的这些文章 总结一下 Metal 4 提供了多种出色方式 可以比以往更快地载入管道状态 你可以使用管道专用化 重新使用编译结果 你可以使用多线程编译 进一步加快编译速度 为了将管道加载时间缩至最短 采用提前编译以及 简化的采集和查找工作流程 我们很高兴能向大家展示 使用 Metal 4 API 打造新一代高性能游戏的多种方法 下载新版 Xcode 开始优化你的游戏的 编码、资源管理和管道加载 我们提供了示例项目 和详细的文章来支持你的旅程 要继续探索 Metal 4 的功能 请观看这个系列中的其他讲座 感谢你的观看

    • 0:01 - Synchronize access to a buffer within an encoder

      // Synchronize access to a buffer within an encoder
      
      id<MTL4ComputeCommandEncoder> encoder = [commandBuffer computeCommandEncoder];
      
      [encoder copyFromBuffer:src sourceOffset:0 toBuffer:buffer1 destinationOffset:0 size:64];
      
      [encoder barrierAfterEncoderStages:MTLStageBlit 
                     beforeEncoderStages:MTLStageDispatch
                       visibilityOptions:MTL4VisibilityOptionDevice];
      
      [encoder setComputePipelineState:pso];
      
      [argTable setAddress:buffer1.gpuAddress atIndex:0];
      [encoder setArgumentTable:argTable];
      [encoder dispatchThreads:threadsPerGrid threadsPerThreadgroup:threadsPerThreadgroup];
      
      [encoder endEncoding];code snippet.
    • 4:29 - Configure superset of color attachments

      // Configure superset of color attachments
      
      MTL4RenderPassDescriptor *desc = [MTLRenderPassDescriptor renderPassDescriptor];
      
      desc.supportColorAttachmentMapping = YES;
      
      desc.colorAttachments[0].texture = colortex0;
      desc.colorAttachments[1].texture = colortex1;
      desc.colorAttachments[2].texture = colortex2;
      desc.colorAttachments[3].texture = colortex3;
      desc.colorAttachments[4].texture = colortex4;
    • 4:38 - Set color attachment map entries

      // Set color attachment map entries
      
      MTLLogicalToPhysicalColorAttachmentMap* myAttachmentRemap = [MTLLogicalToPhysicalColorAttachmentMap new];
      
      [myAttachmentRemap setPhysicalIndex:0 forLogicalIndex:0];
      [myAttachmentRemap setPhysicalIndex:3 forLogicalIndex:1];
      [myAttachmentRemap setPhysicalIndex:4 forLogicalIndex:2];
    • 4:57 - Set a color attachment map per pipeline

      // Set a color attachment map per pipeline
      
      [renderEncoder setRenderPipelineState:myPipeline];
      [renderEncoder setColorAttachmentMap:myAttachmentRemap];
      // Draw with myPipeline
      
      [renderEncoder setRenderPipelineState:myPipeline2];
      [renderEncoder setColorAttachmentMap:myAttachmentRemap2];
      // Draw with myPipeline2
    • 8:03 - Encode a single render pass with 3 render encoders

      // Encode a single render pass with 3 render encoders with suspend/resume options
      
      
      id<MTL4RenderCommandEncoder> enc0 = [cmdbuf0 renderCommandEncoderWithDescriptor:desc options:MTL4RenderEncoderOptionSuspending];
      
      id<MTL4RenderCommandEncoder> enc1 = [cmdbuf1 renderCommandEncoderWithDescriptor:desc options:MTL4RenderEncoderOptionResuming | MTL4RenderEncoderOptionSuspending];
      
      id<MTL4RenderCommandEncoder> enc2 = [cmdbuf2 renderCommandEncoderWithDescriptor:desc options:MTL4RenderEncoderOptionResuming];
      
      
      id<MTL4CommandBuffer> cmdbufs[] = { cmdbuf0, cmdbuf1, cmdbuf2 };
      [commandQueue commit:cmdbufs count:3]
    • 11:48 - Synchronize drawable contents

      // Synchronize drawable contents
      
      id<MTLDrawable> drawable = [metalLayer nextDrawable];
      [queue waitForDrawable:drawable];
      
      // ... encode render commands to commandBuffer ...
      [queue commit:&commandBuffer count:1];
      
      [queue signalDrawable:drawable];
      
      [drawable present];
    • 13:25 - Encode a queue barrier to synchronize data

      // Encode a queue barrier to synchronize data
      
      id<MTL4ComputeCommandEncoder> compute = [commandBuffer computeCommandEncoder];
      
      [compute dispatchThreadgroups:threadGrid threadsPerThreadgroup:threadsPerThreadgroup];
      
      [compute endEncoding];
      
      
      id<MTL4RenderCommandEncoder> render = [commandBuffer renderCommandEncoderWithDescriptor:des];
      
      [render barrierAfterQueueStages:MTLStageDispatch
                         beforeStages:MTLStageFragment
                    visibilityOptions:MTL4VisibilityOptionDevice];
      
      [renderCommandEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                               vertexStart:vertexStart
                               vertexCount:vertexCount];
      
      [render endEncoding];
    • 14:57 - Create a texture view pool

      // Create a texture view pool
      
      MTLResourceViewPoolDescriptor *desc = [[MTLResourceViewPoolDescriptor alloc] init]; 
      desc.resourceCount = 500;
       
      id <MTLTextureViewPool> myTextureViewPool =  
          [myDevice newTextureViewPoolWithDescriptor:myTextureViewPoolDescriptor 
                                               error:nullptr];
    • 15:07 - Set a texture view

      // Set a texture view
      
      MTLResourceID myTextureView = [myTextureViewPool setTextureView:myTexture  
                                                           descriptor:myTextureViewDescriptor  
                                                              atIndex:5];
      
      [myArgumentTable setTexture:myTextureView 
                          atIndex:0];
    • 16:01 - Choose appropriate sparse page size

      MTLHeapDescriptor *desc = [MTLHeapDescriptor new];    
      desc.type = MTLHeapTypePlacement;
      desc.storageMode = MTLStorageModePrivate;
      desc.maxCompatiblePlacementSparsePageSize = MTLSparsePageSize64;
      desc.size = alignedHeapSize;
      
      id<MTLHeap> heap = [device newHeapWithDescriptor:desc];
    • 17:05 - Update buffer mappings

      // Update buffer mappings
      
      MTL4UpdateSparseBufferMappingOperation bufferOperation;
      
      bufferOperation.mode = MTLSparseTextureMappingModeMap;  
      bufferOperation.bufferRange.location = bufferOffsetInTiles;
      bufferOperation.bufferRange.length = length;
      bufferOperation.heapOffset = heapOffsetInTiles;
      
      [cmdQueue updateBufferMappings:myBuf heap:myHeap operations:&bufferOperation count:1];
    • 20:41 - Set unspecialized configuration

      // In MTL4RenderPipelineColorAttachmentDescriptor
      // Set unspecialized configuration
      
      pipelineDescriptor.colorAttachments[i].pixelFormat   = MTLPixelFormatUnspecialized;
      pipelineDescriptor.colorAttachments[i].writeMask     = MTLColorWriteMaskUnspecialized;
      pipelineDescriptor.colorAttachments[i].blendingState = MTL4BlendStateUnspecialized;
    • 21:40 - Create a specialized transparent pipeline

      // Create a specialized transparent pipeline
      
      // Set the previously unspecialized properties
      pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
      pipelineDescriptor.colorAttachments[0].writeMask =
          MTLColorWriteMaskRed | MTLColorWriteMaskGreen | MTLColorWriteMaskBlue;
      pipelineDescriptor.colorAttachments[0].blendingState = MTL4BlendStateEnabled;
      
      pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
      pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = 
          MTLBlendFactorOneMinusSourceAlpha;
      pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
      
      id<MTLRenderPipelineState> transparentPipeline = 
          [compiler newRenderPipelineStateBySpecializationWithDescriptor:pipelineDescriptor
                                                                pipeline:unspecializedPipeline
                                                                   error:&error];
      
      // Similarly, create the specialized opaque and hologram pipelines
    • 26:22 - Determine thread count

      // Determine thread count
      NSInteger numThreads = 2;
      if (@available(macOS 13.3, iOS 19, visionOS 3, tvOS 19, *))
      {
          numThreads = [device maximumConcurrentCompilationTaskCount];
      }
    • 26:30 - Set a proper QoS class for your compilation threads

      // Create thread pool
      for (NSInteger i = 0; i < numThreads; ++i)
      {
          // Creating a thread with a QoS class DEFAULT
          pthread_attr_set_qos_class_np(&attr, QOS_CLASS_DEFAULT, 0) ;
          pthread_create(&threadIds[i], &attr, entryPoint, NULL);
          pthread_attr_destroy(&attr);
      }
    • 28:24 - Harvest pipeline configuration scripts

      // Harvest pipeline configuration scripts with the pipeline data set serializer
      
      // Create a pipeline data set serializer that only captures descriptors
      MTL4PipelineDataSetSerializerDescriptor *desc = [MTL4PipelineDataSetSerializerDescriptor new];
      desc.configuration = MTL4PipelineDataSetSerializerConfigurationCaptureDescriptors;
      id<MTL4PipelineDataSetSerializer> serializer =
          [device newPipelineDataSetSerializerWithDescriptor:desc];
      
      // Set the pipeline data set serializer when creating the compiler
      MTL4CompilerDescriptor *compilerDesc = [MTL4CompilerDescriptor new];
      [compilerDesc setPipelineDataSetSerializer:serializer];
      id<MTL4Compiler> compiler = [device newCompilerWithDescriptor:compilerDesc error:nil];
      
      // Create pipelines using the compiler as usual
      
      // Serialize the descriptors as a pipeline script
      NSData *data = [serializer serializeAsPipelinesScriptWithError:&err];
      
      // Write the pipeline script data to disk
      NSString *path = [NSString pathWithComponents:@[folder, @"pipelines.mtl4-json"]];
      BOOL success = [data writeToFile:path options:NSDataWritingAtomic error:&err];
    • 30:28 - Query pipeline state from MTLArchive

      // Query pipeline state from MTLArchive
      
      id<MTL4Archive> archive = [device newArchiveWithURL:archiveURL error:&error];
      
      id<MTLRenderPipelineState> pipeline = 
          [archive newRenderPipelineStateWithDescriptor:descriptor error:&error];
      
      if (pipeline == nil)
      {
          // handle lookup miss
      		pipeline = [compiler newRenderPipelineStateWithDescriptor:descriptor 
                                                compilerTaskOptions:nil 
      }
    • 0:00 - 简介
    • 这是关于 Metal 4 的四部分系列中的第二部分,Metal 4 是 Apple 推出的新一代图形 API,专为现代游戏引擎而设计。Metal 4 增强了命令编码、资源管理和管道载入。Metal 4 能够满足当前和未来游戏的需求,这些游戏可流式传输数千兆字节的精细几何图形和纹理,并使用数千个着色器进行渲染,以充分利用 Apple 芯片提供的全部计算性能。 此外,请观看该系列的其他部分,进一步了解 MetalFX、光线追踪和机器学习集成。

    • 1:33 - 提升编码效率
    • Metal 4 旨在通过优化命令编码来提高 GPU 的效率。它引入了两个主要的编码器类,即渲染类和计算类,现在可以处理最常见的游戏操作。使用 Metal 4 可减少编码器数量、重复使用命令内存并跨多个线程进行编码,从而提高编码效率。

    • 8:42 - 扩大资源管理
    • Metal 4 提供了一些激动人心的新功能,来帮助你大规模管理资源。通过参数表和驻留资源集,你可以将资源绑定扩展到数千个资源。Metal 4 让你自主管理可绘制对象资源并全面掌控依赖项。队列屏障提供了一种大规模表达资源依赖项的方法。纹理视图池和放置稀疏堆可帮助你管理大量资源所需的内存。

    • 17:24 - 快速载入管线
    • 现代游戏需要构建数千个管道来创建复杂和动态的视觉效果。快速载入许多管道对于消除着色器编译卡顿和缩短游戏载入时间至关重要。要在 Metal 4 中快速载入管道,你可以重复使用渲染管道编译、利用新的并行性级别在设备端编译管道,并提前编译管道,以便将管道载入时间缩短到接近于零。

    • 31:25 - 后续步骤
    • Metal 4 API 旨在帮助你打造新一代高性能游戏。你可以查看开发者网站上的文档,试用示例项目,并下载新的 Xcode 开始你的体验。

Developer Footer

  • 视频
  • WWDC25
  • 探索 Metal 4 游戏
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载 (英文)
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    获取 Apple Developer App。
    版权所有 © 2025 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则