大多数浏览器和
Developer App 均支持流媒体播放。
-
利用 Metal 3 更快地加载资源
了解如何在 Metal 3 中利用快速的资源流传输,为加载素材加速。我们将介绍如何在您的 App 中借助异步的“即设即忘”工作流程,充分利用 SSD 存储的速度和 Apple 芯片的统一内存架构所带来的数据吞吐能力。我们还将探索如何创建与您的 GPU 渲染和计算工作并行且同步的单独队列。最后,我们还会分享如何指定音频等素材和高优先级队列,来帮助您以更低延迟加载数据。
资源
相关视频
WWDC22
-
下载
♪ 柔和乐器演奏的嘻哈音乐 ♪ ♪ 嗨 我是 Jaideep Joshi 我是 Apple 的 一名 GPU 软件工程师 在今天的讲座中 我将介绍 Metal 3 中的一项新功能 它将为您的游戏和 App 简化和优化资源加载 我将首先向您展示 快速资源加载功能可以如何 适配于您 App 的资产加载管线 它的几项核心功能 可以在 Apple 产品上 充分利用新存储技术 快速资源加载的一些高阶功能 可以解决您的 App 可能遇到的有趣场景 还有一些您应当了解的 最佳实践的建议 它们会帮助您 在 App 中有效地使用这些功能 当您向 App 添加快速资源加载时 Metal System Trace 和 GPU 调试器等工具 可以帮助分析性能和 解决您可能遇到的问题 最后 我将介绍一个 展示快速资源加载实际运用的案例 以下这些都是 Metal 3 的快速资源 加载功能可以为您做到的
借助 Metal 3 的 快速资源加载功能 您的游戏和 App 将以低延迟和高通量 加载资产 这些是借助 Apple 芯片统一内存架构 以及 Apple 平台配备的 快速 SSD 存储来实现的 您将了解到 用于流式传输和 和减少加载时间的 以确保您的 游戏资产按时准备就绪的 最好的方法 减少加载时间的一个关键在于 以尽可能小的粒度 只加载您所需要的资源 Metal 3 中的高通量 和低延迟 让您的 App 可以 流式传输更高质量的资产 包括纹理 音频和几何数据 现在我将向您介绍一个游戏中 资产加载的案例 在游戏第一次启动 或在新关卡开始时 通常会显示加载画面 这样就可以将游戏的 资产加载到内存当中 当玩家在关卡中移动时 游戏会为场景加载更多资产 缺点是玩家需要花费很长时间 等待游戏向存储系统发起 多个请求 来进行资产的预先加载 而且 这些资产 还可能会占用大量内存 有几种方法可以改善这种体验 游戏可以通过 在玩家离对象越来越近时 对其进行动态流式传输 来改善这种状况 这样一来 游戏只需要 加载它一开始需要的内容 并在玩家在关卡中移动时 逐渐流式传输其他资源 比如说 游戏最初加载这块黑板时 使用了较低分辨率 但当玩家走向它时 游戏就加载了更高分辨率的版本 这种方法减少了玩家在加载画面中 等待的时间 但是 玩家仍然有可能 看到分辨率较低的物体 即使是在近距离场景中 因为加载高分辨率版本 所需要的时间太长 解决这个问题的一种方法是 将每项资产以较小份额 进行流式传输 比如说 您的游戏可以只加载 场景的可见区域 用稀疏的纹理流式传输图块 而不是所有 mip 等级 这大大减少了您的 App 需要进行流式传输的数据量 使用这种方法 加载请求变得更小 但数量更多 但没关系 因为现代存储硬件 可以同时运行多个加载请求 这意味着您可以在 不损失可玩性的前提下 提高场景的分辨率和规模 在发出大量小载入请求的同时 您还可以设定载入请求的优先级 确保高优先级的请求及时完成 刚才我已经向您介绍了 提高游戏视觉保真度 同时减少加载时间的方法 现在我将向您展示 Metal 3 的快速资源加载功能 如何帮助您做到这一点 快速资源加载是一种 从存储中加载资源的异步 API 与现有的加载 API 不同 发起加载的线程 不需要等待加载完成 加载操作通过并行执行 来更有效地利用加速存储的通量 您可以批量进行加载操作 进一步将资源加载的 额外开销降到最低限度 最后 使用 Metal 3 您可以优先处理 用于降低延迟的加载操作 现在我将向您展示 可以帮助您构建 资产加载管道的核心功能 首先从加载资源的步骤开始 加载资源分为三个步骤 打开一个文件 发出必要的加载命令 然后将加载命令与渲染工作同步 下面是具体方法 首先打开一个文件 可以通过使用 Metal 设备 实例创建文件句柄 来打开现有文件 例如 这段代码 使用 Metal 设备实例 通过调用它的 新 makeIOHandle 方法 传入文件路径 URL 来创建文件柄 一旦有了文件句柄 您就可以使用它来发出加载命令 在这个 App 的典型场景中 它在执行加载操作 和编码 GPU 的工作 使用现有的加载 API App 必须等待加载工作完成 才能对渲染工作进行编码 Metal 3 让您的 App 可以异步执行加载命令 首先创建一个 Metal IO 命令队列 然后使用该队列 创建 IO 命令缓冲 并将加载命令编码到缓冲 不过 由于命令缓冲在命令队列上 异步执行 您的 App 无需等待 加载操作完成 实际上 不仅 IO 命令缓冲内 所有命令并发执行 IO 命令缓冲本身也并发执行 并乱序完成 这种并列执行模型通过 最大化通量来 更好地利用加速存储硬件 您可以将三种类型的 IO 命令编码到命令缓冲 loadTexture 加载到 Metal 纹理上 用于纹理的流式传输 loadBuffer 加载到 Metal 缓冲上 用于流式传输场景和几何数据 还有 loadBytes 加载到 CPU 可访问的内存 您可以从 IO 命令队列上 创建 IO 命令缓冲 要创建队列 首先制作和配置 一个 IO 命令队列描述符 默认情况下 队列是并行的 但您也可以将它们设置为 按顺序 完全不打乱地 运行命令缓冲 然后 请传递队列描述符 到 Metal 设备实例的 makeIOCommandQueue 方法 请调用命令队列的 makeCommandBuffer 方法 来创建 IO 命令缓冲 然后使用该命令缓冲对 加载纹理和缓存的 加载命令进行编码 Metal 的验证层将在运行时 捕获编码错误 加载命令使用之前创建的 fileHandle 实例 在您将加载命令添加到命令缓冲后 请通过调用命令 缓冲的 commit 方法 将它提交到队列中进行执行 介绍完如何创建 IO 命令队列 命令缓冲 发出加载命令 并将它们提交到队列中之后 我想向您展示 如何将加载工作 与其他 GPU 工作同步 App 通常会在完成渲染资源的 加载之后 启动此渲染工作 但是使用快速资源加载的 App 需要一种方法来将 IO 命令队列 与渲染命令队列同步 您可以通过 Metal 共享事件 让这些队列同步 Metal 共享事件让您可以同步 IO 队列中的命令缓冲区 与渲染队列中的命令缓冲区 您可以通过编码 waitEvent 命令 告知命令缓冲区需等待共享事件 同样地 您也可以通过 编码 signalEvent 命令 告知命令缓冲区 发出共享事件信号 Metal 确保所有命令缓冲内的 IO 命令 在它发出共享事件信号 之前都已经完成 要使各个命令缓冲同步 您首先需要 一个 Metal 共享事件 您可以调用 waitForEvent 方法 告知命令缓冲区需等待共享事件 同样 您也可以通过调用 signalEvent 方法 告知命令缓冲需发出 共享事件信号 您可以将类似的逻辑添加到 相应的 GPU 命令缓冲区 使它等待 IO 命令缓冲区 发出相同共享事件的信号 来回顾一下 这些是在 Metal App 加载资源的核心功能和 API 创建 Metal 文件句柄 来打开文件 通过创建 IO 命令队列 和 IO 命令缓冲 来发出加载命令 接下来 将加载命令 编码到命令缓冲区 在队列中执行 最后 使用 Metal 共享事件的 wait 和 signalEvent 命令 来同步加载与渲染 现在 我将介绍一些您可能会 觉得有帮助的高阶功能 一个典型的例子就是 游戏无法将整个地图放进内存 因此它将地图细分为区域 随着玩家在地图上前进 游戏开始预加载地图的部分区域 根据玩家的方向 游戏判定 最适合预加载的区域是西北区 西区和西南区 但是 一旦玩家移动到西区 并开始转向南方 那么预加载西北区就不再有用 为了减少未来加载的延迟 Metal 3 允许您 尝试取消加载操作 让我们来看看如何在实践中进行操作 当玩家处在中心区域时 为三个区域编码 并提交 IO 命令缓冲 之后 当玩家到达西区 并开始往南移动时 使用 tryCancel 方法 取消西北区的加载 取消的粒度是命令缓冲区 因此您可以在 执行过程中取消命令缓冲区 如果之后您想知道 该区域是否已完全加载 您可以检查命令缓冲区的状态 Metal 3 还可以让您 将 IO 工作优先处理 设想一下 当玩家被传送到 新的场景区域时 您的游戏开始流式传输 大量的图形资产 同时 游戏需要播放传送音效 快速资源加载功能 允许您加载 App 的所有资产 包括音频数据 要加载音频 您可以使用 前面谈到的 loadBytes 命令 来加载到 App 分配的内存 在这个例子中 纹理和音频 IO 命令缓冲 在单个 IO 命令队列上并列执行 这张简化图标展示了 存储层的请求 存储系统能够同时执行 音频和纹理加载请求 为了避免音频延迟 至关重要的一点是 流式传输系统能够将 音频请求优先于 纹理请求进行处理 要将音频请求优先 您可以创建 一个单独的 IO 命令队列 并将其优先级设置为高 存储系统将 确保高优先级的 IO 请求 具有较低的延迟并优先于其他请求 在为音频资产创建单独的 高优先级队列后 音频加载请求的 执行时间变短了 而并行的纹理加载请求的 执行时间变长了 创建高优先级队列的方法是这样的 您只需要将命令队列 描述符的优先级属性 设置为高 您还可以将优先级设置为正常或低 然后像往常一样从描述符中 创建一个新的 IO 命令队列 但需要记住 在创建队列之后 您就不能再更改 它的优先级了 当您向 App 添加 快速资源加载功能时 有一些最佳实践需要牢记 首先 请考虑对资产进行压缩 您可以通过内置或自定义压缩 来降低 App 的磁盘占用 压缩让您可以用运行时性能 换取更小的磁盘占用空间 此外 在使用稀疏纹理时 您可以通过调整 稀疏页大小来提高存储通量 接下来我将更详细地介绍它们 首先是压缩 您可以使用 Metals 3 的 API 离线压缩您的资产文件 首先 创建一个压缩上下文并使用 chunk size 和 compression 方法对它进行配置 然后将部分资产文件传递给上下文 生成所有文件的单一压缩版本 压缩上下文将所有数据分块 使用您选择的 编解码器对其进行压缩 并将其存储到包文件中 在这个例子中 上下文将数据 压缩为 64KB 大小的分块 但您也可以根据需要压缩的数据 自身的大小和类型 来选择合适的分块大小 下面为您介绍 如何在 Metal 3 中使用压缩 API 首先 提供创建压缩文件的路径 压缩方法和分块大小 来创建压缩上下文 接下来 获取文件数据 并将其附加到上下文上 在这里 文件数据 处于一个 NSData 对象中 您可以通过多次调用附加数据 附加来自不同文件的数据 添加完数据后 您就可以调用刷新 和销毁压缩上下文函数 来完成并保存压缩文件 您可以通过创建文件句柄 打开并访问压缩文件 文件句柄在发出加载命令时使用 对于压缩文件 Metal 3 执行内联解压 通过将偏移量转换为需要解压的 分块列表 并将它们 加载到您的资源中 您可以使用 Metal 设备实例 创建文件句柄 比如说 这段代码 使用 Metal 设备实例 创建文件句柄的方式是 通过提供压缩文件路径 到我之前介绍过的 makeIOHandle 方法中 压缩文件的一个附加参数 是压缩方法 它与您在创建压缩文件时使用的 压缩方法一致 现在 我将介绍 支持的各种压缩方法 以及每一种的特点 来帮助您了解 如何在它们之间进行选择 当解压速度非常关键 并且您的 App 可以承受较大的磁盘 占用空间时 您可以使用 LZ4 如果平衡编解码器速度和压缩比 对您来说很重要 那就请选择 ZLib、LZBitmap 或 LZFSE 在注重平衡的编解码器中 ZLib 更适用于 非 Apple 设备 LZBitmap 编码 和解码速度很快 而 LZFSE 具有很高的压缩比 如果您需要最佳压缩比 请考虑选用 LZMA 编解码器 前提是您的 App 能承受解码资产所需的额外时间 您也可以使用您自己的压缩方案 在一些情况中 您的数据可能会从 自定义压缩编解码器中受益 如果是这样 您可以使用自己的压缩器 和平移偏移量替换压缩上下文 并在运行时自行解压 现在您已经知道该如何使用压缩 来减少磁盘占用了 让我们来看看如何调整稀疏页大小 先前版本的 Metal 支持加载 稀疏纹理粒度 在 16KB 大小的图块 使用 Metal 3 您可以 指定两种新的稀疏图块大小: 64KB 和 256KB 这些新尺寸可以让您 以更大的粒度进行纹理的流式传输 更充分地利用存储硬件并使它饱和 请注意 您需要权衡 流式传输更大尺寸的图块 和流式传输的数据量 所以您必须使用 App 及其稀疏纹理进行测试 找到最合适的尺寸 接下来 让我们来看看如何借助 一组 Metal 开发者工具 为您 App 的快速加载 资源功能进行性能分析和调试 Xcode 14 包含了对 快速资源加载功能的全面支持 从使用 Metal System Trace 的运行时性能分析 到使用 Metal 调试器的 API 检查 和高级依赖分析
首先是运行时性能分析 在 Xcode 14 中 Instruments 可以 使用 Metal System Trace 模板 对快速资源加载功能进行性能分析 Instruments 是一个强大的 性能分析工具 可以帮助您 在 Metal App 中 达成最佳性能 Metal System Trace 模板 允许您在 加载操作被编码和执行时进行检查 您将理解它们是如何与 您的 App 在 CPU 和 GPU 上运行的操作 相互关联的 如需了解如何使用 Instruments 对您的 Metal App 进行性能分析 请查看之前的讲座 “使用 GPU 计数器 优化 Metal App 和游戏” 以及“针对 Apple GPU 优化高端游戏” 现在 让我们来谈谈调试 使用 Xcode 14 中的 Metal 调试器 您现在可以分析游戏中新的 快速资源加载 API 的使用情况 进行帧捕获后 您就可以检查 所有快速资源加载 API 的调用 从创建的 IO 命令缓冲 到已发出的加载操作 您现在可以使用新的依赖关系查看器 来对快速资源加载依赖关系 进行可视化检查 依赖关系查看器提供了 IO 命令缓冲和 Metal 传递之间资源依赖关系的 详细描述 从这里出发 您可以使用新的 依赖关系查看器中的各种功能 比如新的同步边缘和图形筛选 来深入研究并优化资源加载依赖关系 如需了解 Xcode 14 中 新的依赖关系查看器的更多信息 请查看今年的 “利用 Metal 3 实现无绑定” 讲座 现在让我们看看 快速资源加载的实际效果 这个测试场景使用了 新的快速资源加载 API 以 16KB 的图块大小 进行纹理数据的流式传输 该视频来自配备 M1 Pro 芯片的 MacBook Pro 流式传输系统查询 GPU 的稀疏纹理访问计数器 来确认两项内容 已采样但未加载的图块 以及 App 未使用的已加载图块 App 使用此信息对所需图块的 加载列表以及 不需要图块的回收列表进行编码 这样一来 工作集中只包含 App 最有可能使用的图块 如果玩家决定前往场景的另一部分 App 就需要对一套全新的 高分辨率纹理进行流式传输 如果流式传输系统足够快 玩家就不会注意到传输正在发生 如果我暂停这个场景 您就可以更清楚地观察画面差异 左侧画面是在单个线程上 使用 pread API 加载稀疏图块 右侧画面是 使用快速资源加载 API 加载稀疏图块 当玩家进入场景时 大部分纹理尚未完全加载 加载完成后 就可以看到最终的 高分辨率版本纹理 如果我回到场景的开头并进行慢放 您将更容易注意到 快速资源加载功能带来的改善 为了突出差异 渲染中将用 红色标出 App 尚未加载的图块 一开始 场景显示 App 尚未加载大多数图块 然而 在玩家进入场景时 与单线程 pread 版本相比 快速资源加载功能 改善了高分辨率图块的加载 并最大限度减少了延迟 Metal 3 的快速资源 加载功能可以 帮助您构建强大而高效的资产 流式传输系统 让您的 App 充分利用最新的存储技术 您可以使用它来及时流式传输 包括更高质量的图像 在内的资产以减少加载时间 您也使用 Metal 的共享事件 在 GPU 渲染场景时 异步加载资产 对于您 App 急需的资产 可以通过创建具有更高 优先级的命令队列 将延迟降到最低 请记住 一定要提前发送加载命令 让存储系统保持繁忙 您可以随时取消不需要的命令 Metal 3 中的快速资源 加载功能引入了一些新方法 来发挥现代存储硬件的力量 实现高通量资产加载 我迫不及待想看看 您是如何使用这些功能 来提高您 App 的 视觉质量和响应能力的 感谢您的观看 ♪
-
-
5:19 - Create a handle to an existing file
// Create an Metal File IO Handle // Create handle to an existing file var fileIOHandle: MTLIOFileHandle! do { try fileHandle = device.makeIOHandle(url: filePath) } catch { print(error) }
-
6:49 - Create a Metal IO command queue
// Create a Metal IO command queue let commandQueueDescriptor = MTLIOCommandQueueDescriptor() commandQueueDescriptor.type = MTLIOCommandQueueType.concurrent // or serial var ioCommandQueue: MTLIOCommandQueue! do { try ioCommandQueue = device.makeIOCommandQueue(descriptor: commandQueueDescriptor) } catch { print(error) }
-
7:17 - Create and submit a Metal IO command buffer
// Create Metal IO Command Buffer let ioCommandBuffer = ioCommandQueue.makeCommandBuffer() // Encode load commands // Encode load texture and load buffer commands ioCommandBuffer.load(texture, slice: 0, level: 0, size: size, sourceBytesPerRow:bytesPerRow, sourceBytesPerImage: bytesPerImage, destinationOrigin: destOrigin, sourceHandle: fileHandle, sourceHandleOffset: 0) ioCommandBuffer.load(buffer, offset: 0, size: size, sourceHandle: fileHandle, sourceHandleOffset: 0) // Commit command buffer for execution ioCommandBuffer.commit()
-
8:51 - Synchronize loading and rendering with Metal shared events
var sharedEvent: MTLSharedEvent! sharedEvent = device.makeSharedEvent() // Create Metal IO command buffer let ioCommandBuffer = ioCommandQueue.makeCommandBuffer() ioCommandBuffer.waitForEvent(sharedEvent, value: waitVal) // Encode load commands ioCommandBuffer.signalEvent(sharedEvent, value: signalVal) ioCommandBuffer.commit() // Graphics work waits for the IO command buffer to signal
-
10:29 - TryCancel Metal IO command buffer
// Player in the center region // Encode loads for the North-West region ioCommandBufferNW.commit() // Encode loads for the West region ioCommandBufferW.commit() // Encode loads for the South-West region ioCommandBufferSW.commit() // Player in the western region and heading south // tryCancel NW command buffer ioCommandBufferNW.tryCancel() // .. // .. func regionNWCancelled() -> Bool { return ioCommandBufferNW.status == MTLIOStatus.cancelled }
-
12:28 - Create a High Priority Metal IO command queue
// Create a Metal IO command queue let commandQueueDescriptor = MTLIOCommandQueueDescriptor() commandQueueDescriptor.type = MTLIOCommandQueueType.concurrent // or serial // Set Metal IO command queue Priority commandQueueDescriptor.priority = MTLIOPriority.high // or normal or low var ioCommandQueue: MTLIOCommandQueue! do { try ioCommandQueue = device.makeIOCommandQueue(descriptor: commandQueueDescriptor) } catch { print(error) }
-
14:04 - Create a compressed file
// Create a compressed file // Create compression context let chunkSize = 64 * 1024 let compressionMethod = MTLIOCompressionMethod.zlib let compressionContext = MTLIOCreateCompressionContext(compressedFilePath, compressionMethod, chunkSize) // Append uncompressed file data to the compression context // Get uncompressed file data MTLIOCompressionContextAppendData(compressionContext, filedata.bytes, filedata.length) // Write the compressed file MTLIOFlushAndDestroyCompressionContext(compressionContext)
-
15:05 - Create a handle to a compressed file
// Create an Metal File IO Handle // Create handle to a compressed file var compressedFileIOHandle : MTLIOFileHandle! do { try compressedFileHandle = device.makeIOHandle(url: compressedFilePath, compressionMethod: MTLIOCompressionMethod.zlib) } catch { print(error) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。