ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Metal 3で、リソースのロードを高速化
Metal 3の高速なリソースストリーミングで、アセットを迅速にロードする方法をご覧ください。SSDストレージの速度とApple Siliconのユニファイドメモリアーキテクチャのスループットを活用するために、Appで非同期の "set-it-and-forget-it" ワークフローを使用する方法を紹介します。また、GPU のレンダリングと計算の作業と並行して実行する別のキューを作成し、それと同期させる方法についても検討します。最後に、オーディオなどのアセットを優先度の高いキューに指定して、データを低レイテンシーでロードする方法について説明します。
リソース
関連ビデオ
WWDC22
-
ダウンロード
♪ 心地よいヒップホップ ♪ ♪ こんにちはJaideep Joshiです AppleのGPU Softwareのエンジニアです このセッションでは ゲームやAppのリソースロードを簡素化 最適化するMetal 3の新機能をご紹介します まず 高速リソース ロード機能が Appのアセット ローディングパイプラインに どう適合するかをご紹介します Apple製品の新しいストレージ技術を活用した 重要な機能が実装されています 高速リソースロードは Appが遭遇する可能性のある 興味深いシナリオを解決する 高度な機能を備えています これらの機能をAppで効果的に 利用するために知っておくべき ベストプラクティスの 推奨事項がいくつかあります Appに高速リソースロード機能を追加する際 Metal System Traceや GPUデバッガなどのツールが 遭遇する可能性のある問題の 分析と修正に役立ちます 最後に 高速リソースロードを 実際に行う例をご紹介します では Metal 3の高速リソース ロードの効果をご紹介します
Metal 3の高速リソースロードでは Appleプラットフォームに 搭載されている Appleシリコン ユニファイドメモリ アーキテクチャと高速SSDストレージを 活用することでゲームやAppの低レイテンシ化 および高スループットでの アセットロードが可能です ゲームのアセットをオンタイムで 準備するためにデータをストリーミングし ロード時間を短縮するための 最適な方法を学びます ロード時間を短縮するための重要なポイントは 可能な限り粒度を下げ 必要な物だけをロードすることです Metal 3の高スループットと低レイテンシにより Appはテクスチャ オーディオ ジオメトリデータなど より高品質な アセットをストリーミングできます 次に ゲームにおけるアセットの ロードの例について説明します ゲームは通常最初の起動時や 新しいレベルの開始時に ローディング画面を表示し ゲームのアセットをメモリにロードします プレイヤーがレベルを進むにつれ ゲームはシーン用のアセットを さらにロードします その欠点は ゲームがストレージシステムに 何度もリクエストを出し 前もってアセットをロードする間 プレイヤーは長時間待たされることです さらにこれらのアセットは メモリを多く消費します このエクスペリエンスを改善する 方法はいくつかあります ゲームは プレイヤーがオブジェクトに 近づくにつれ動的にストリーミングして このエクスペリエンスを改善できます この方法では ゲームは最初に 必要な物だけをロードし プレイヤーがレベルを進むにつれて 他のリソースを徐々にストリーミングします 例えば この黒板は最初は低解像度で 読み込まれますがプレイヤーが歩き 近づくと高解像度で読み込まれます この方法でローディング時間での 待ち時間を短縮できます しかし 高解像度のアイテムを 読み込むのに時間が掛かり過ぎ プレイヤーが近づいても 低解像度のまま表示される可能性があります その対処法として各アセットを小分けにして ストリーミングする方法があります 例えば ゲームではMipレベル全体ではなく シーン内で見える領域の スパーステクスチャのみを ロードすることができます これでAppがストリームする データ量を大幅に削減できます この方法で ロード要求は小さくなりますが 数は大きくなります しかし 最新のストレージハードウェアは 複数のロード要求を一度に 実行でき 問題ありません つまり ゲームプレイを犠牲にせずに シーンの解像度やスケールを 大きくすることができます 低ロードのリクエストを大量に出すと同時に ロードのリクエストに 優先順位をつけ 優先度の高い リクエストを時間内に 終了させることもできます ここまで ゲームのビジュアル 再現度を上げつつ ロード時間を 短縮する方法を紹介しましたが Metal 3の高速リソース ローディングがどのように 役立つかをご紹介します 高速リソースローディングはストレージから リソースをロードする非同期APIです 既存のロードAPIとは異なりロードを発行する スレッドは ロードの終了を 待つ必要がありません ロードオペレーションは同時に実行され 高速なストレージのスループットを より有効活用できます ロードオペレーションをバッチ処理することで リソースロードのオーバーヘッドを さらに最小化できます 最後に Metal 3では ロードオペレーションに 優先順位をつけ 低レイテンシを実現可能です では リソースを ロードする手順からアセットロード パイプラインの構築に役立つ 主な機能を紹介します リソースの読み込みは3つのステップで行います ファイルを開き 必要なロードコマンドの発行し そのレンダリング作業を同期させます ここでは ファイルを開く方法から説明します 既存のファイルを開くにはMetalデバイスの インスタンスでファイルハンドルを作成します 例えば このコードではMetalデバイスの インスタンスを使用して 新しいmakeIOHandleメソッドにファイルパスの URLを指定してファイルハンドルを作成します ファイルハンドルができたら それを使ってロードコマンドを発行できます ここではAppの典型的なシナリオとして ロードオペレーションを行い GPU作業を エンコードする場合を説明します 既存のロードAPIでは Appはレンダリング作業の エンコード前に ロード作業の 終了を待つ必要があります Metal 3ではAppは ロードコマンドを非同期で実行できます まず Metal IOコマンドキューを作成します 次にそのキューを使い IOコマンドバッファを作成し それらのバッファにロード コマンドをエンコードします しかし コマンドバッファはコマンドキュー上で 非同期に実行されるためAppはロード作業の 終了を待つ必要がありません 実際にIOコマンドバッファ内の すべてのコマンドが実行されるだけではなく IOコマンドバッファ自体も同時に実行され 順序に関係なく完了します この同時実行モデルはスループットを最大化し より高速なストレージ ハードウェアを有効活用できます コマンドバッファには3種類の IOコマンドをエンコード可能です テクスチャストリーミングのために MetalテクスチャをロードするloadTexture シーンまたはジオメトリデータを MetalバッファにロードするloadBuffer CPUアクセス可能なメモリに ロードするloadBytesです IOコマンドキューから IOコマンドバッファを作成します キューを作成するにはIOコマンドキュー 記述子を最初に作成し 設定します キューはデフォルトで同時実行されますが コマンドバッファを連続して 順番に実行するよう設定することも可能です 次にキュー記述子を Metalデバイスインスタンスの makeIOCommandQueueメソッドに渡します コマンドキューのmakeCommandBuffer メソッドを呼び出し IOコマンド バッファを作成します 次にそのコマンドバッファを使用して テクスチャやバッファを ロードするロードコマンドをエンコードします Metalのバリデーションレイヤーは実行時に エンコードエラーを検出します ロードコマンドが先に作成したfileHandle インスタンスを使用します コマンドバッファに ロードコマンドを追加し終わったら コマンドバッファのコミットメソッドを 呼び出して実行のためにキューに投入します 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コマンドを使用し ロードとレンダリングを同期させる 次は 役に立つかもしれない 高度な機能について説明します 典型的なシナリオとして ゲームのマップ全体をメモリに収められず マップを地域に分割する例を挙げます プレイヤーがマップを進むにつれ 一部のマップのプリロードが始まります プレイヤーの向きからゲームは プリロードに最適な地域が北西 西 南西の3地域だと判断します しかし プレイヤーが西に移動し 南下を始めると 北西部をプリロードする利点はなくなります 今後のロードのレイテンシを 短縮するため Metal 3は ロードのキャンセルを行えます では 実際のやり方を見てみましょう プレイヤーが中央地域にいるとき 3つの地域のIOコマンドバッファを エンコードしコミットします 次に プレイヤーが西の領域から 南に向かう際tryCancelメソッドを使い 北西地域のロードをキャンセルします キャンセルはコマンドバッファの要素なため コマンドバッファの 実行中にキャンセルできます もし 後の時点で地域が完全に 読み込まれたかを知りたければ コマンドバッファの状態を確認します Metal 3ではIO 作業の優先順位を付けられます プレイヤーがテレポートして 新しい場所に移動し 大量のグラフィックアセットを 読み込むゲームを考えてみましょう 同時に ゲームではテレポートの 効果音を鳴らす必要があります 高速なリソースロードにより オーディオを含む Appの すべてのアセットをロードできます オーディオをロードするには先ほどの loadBytesコマンドを使用して Appに 割り当てメモリにロードできます この例では テクスチャと オーディオのIOコマンドバッファは 単一のIOコマンドキューで 同時に実行されています この図は ストレージ層でのリクエストを 簡略化したものです ストレージシステムはオーディオと テクスチャの両方のロード 要求を並行して行えます オーディオの遅延を防ぐために ストリーミングシステムが テクスチャのリクエストよりも オーディオのリクエストを 優先できることが重要です オーディオのリクエストを優先するには IOコマンドキューを別に作り 優先順位を高に設定します ストレージシステムが 優先度の高いIOリクエストの レイテンシを下げ 他のリクエストよりも優先されます オーディオアセット用に優先度の高い キューを別に作成することでオーディオの ロードリクエストの実行時間が短くなり 並列で行われる テクスチャの ロードリクエスト実行時間は長くなります 優先度の高いキューの作成方法ですが コマンドキュー記述子の優先順位プロパティを 高に設定するだけです 優先順位を標準または低に設定し通常通り 新しいIOコマンドを 記述子から作成することも可能です キューを作成した後に 優先順位を変更することは できませんのでご注意ください Appに高速リソースロードを追加する際に 気を付けるべきベストプラクティスを いくつかご紹介します まず アセットの圧縮を検討しましょう ビルトインまたは カスタム圧縮を使用することで Appのディスク使用領域を減らすことができます 圧縮を使用すると実行時のパフォーマンスと 引き換えに ディスク使用領域を削減できます さらに スパーステクスチャを使用する場合は スパースページサイズを調整することで ストレージスループットを改善できます それぞれについて圧縮から先に 詳しく見ていきましょう Metals 3のAPIを使って オフラインでアセットファイルを圧縮できます まず 圧縮コンテキストを作成し チャンクサイズと圧縮方法を設定します 次に アセットファイルのパーツを コンテキストに渡すことで すべてのファイルの圧縮を 一度に行うことができます 圧縮コンテキストは すべてのデータをチャンクし お使いのコーデックで圧縮し パックファイルに格納します この例では コンテキストは64Kチャンクで データを圧縮しますが 圧縮したいデータのサイズと 種類に応じて 適切なチャンク サイズを選択できます Metal 3における圧縮APIの 使用方法を説明します まず 圧縮ファイルを作成するための パス 圧縮方法チャンクサイズを指定して 圧縮コンテキストを作成します 次に ファイルデータを取得し コンテキストに追加します ここでは ファイルデータは NSDataオブジェクトの中にあります 複数回コールしてデータを追加すれば 異なるファイルのデータの追加が可能です データの追加が終わったら FlushAndDestroyCompression Context関数を呼び出し 圧縮ファイルをファイナライズ保存します ファイルハンドルを作成することで 圧縮ファイルを開いたりアクセスできます このファイルハンドルは ロードコマンドの発行時に使用します 圧縮ファイルに対して Metal 3は インライン解凍を行い オフセットを解凍に必要な チャンクのリストに変換して リソースにロードします Metalデバイスのインスタンスで ファイルハンドルを作成します 例えば このコードではMetalデバイス インスタンスを使って 先に説明したmakeIOHandleメソッドに 圧縮ファイルのパスを提供し ファイルハンドルを作成しています 圧縮ファイルの場合 追加の パラメータは圧縮方法です これは圧縮ファイル 作成時に使用した圧縮方法と同じです では 対応する 圧縮形式とそれぞれの 特徴について説明します 圧縮方法の選び方をより良く理解しましょう LZ4は 解答速度が重要で ディスク容量に余裕がある場合に使用します コーデックの速度と圧縮率のバランスが 重要な場合はZLib LZBitmap LZFSEを使用しましょう バランス型コーデックの中でも ZLibはApple以外のデバイスで良く機能します LZBitmapはエンコードとデコードが高速で LZFSEは高い圧縮率を持っています 最高の圧縮率が必要な場合 LZMAコーデックを検討しますが Appがアセットのデコードに 要する時間は長くなります また 独自の圧縮形式を使用することも可能です カスタム圧縮コーデックが有効な データもあるかもしれません その場合圧縮コンテキストを 自身の圧縮方式とオフセットに置き換え 実行時に解凍を独自に行います 圧縮を使用してディスク使用量を 減らす方法を見ましたので スパースページサイズの 最適化について見てみましょう Metalの以前のバージョンでは 16Kの粒度でスパーステクスチャに タイルをロードしていました Metal 3では 2つの新しいスパース タイルサイズを指定できます 64と256Kです これらの新しいサイズでより大きな粒度で テクスチャをストリーミングでき ストレージハードウェアを有効活用できます より大きなタイルサイズを 使用するとストリーミングする データ量も増大するため お使いのAppとそのスパーステクスチャに 最適なサイズを確かめる必要があります 次に Metal Developer Toolsを使用して Appの高速リソース読み込みの分析とデバッグを 行う方法を見ていきましょう Xcode 14は高速リソースロードを フルサポートしています Metal System Traceによる ランタイムプロファイリングや MetalデバッガによるAPIインスペクション 高度な依存性解析も可能です
まず ランタイム プロファイリングから始めましょう Xcode 14では Instrumentsは Metal System Traceテンプレートを 使用して 高速リソースロードを プロファイリングできます Instrumentsは MetalAppで 実現するための強力な解析および 解析およびプロファイリングツールです Metal System Traceテンプレートは 読み込み操作のエンコードと 実行のタイミングを You will be able to understand how they correlate アクティビティとどのように相関しているかを 相関しているかをon both the CPU and the GPU. InstrumentsでMetal Appを プロファイリングする 方法については 以前のセッション 「GPUカウンタによる MetalのAppやゲームの最適化」と 「Apple GPU向けのハイエンドゲームの 最適化」をご覧ください ではデバッグの話に入ります Xcode 14のMetalデバッガを使うと 新しい高速リソース読み込みAPIを 使ったゲームの解析ができるようになります フレームキャプチャを取ればすべての 高速リソース読み込みAPIコールを確認できます 作成されたIOコマンドバッファから 発行された読み込み操作まで確認できます 新しいDependencyビューアを使って 高速リソース読み込みの 依存関係を視覚的に確認できます DependencyビューアはIOコマンドバッファと Metalパス間のリソースの 依存関係を詳細に表示できます ここから 新しい シンクロナイゼーションエッジや グラフフィルタリングなど 新しいDependencyビューアの すべての機能を使用して リソース読み込みの依存関係を 深く掘り下げ 最適化できます Xcode 14のDependency ビューアについては 今年の 「Metal 3でバインドレスにする」の セッションをご覧ください では実際に高速リソース 読み込みを見てみましょう これは 新しい高速リソース 読み込みAPIでタイルサイズ16Kの スパーステクスチャを 使用し テクスチャデータを ストリーミングするテストシーンです このビデオはM1 Proチップを搭載した MacBook Proのものです ストリーミングシステムは GPUのスパーステクスチャアクセス カウンタにクエリして サンプリングしたが ロードされていないタイルと ロードしたがAppが使用していない タイルの2つを識別します Appはこの情報を用いて必要なタイルの ロードリストと 不必要なタイルのエビクション リストをエンコードします これにより 作業用セットにはAppが主に 使用するタイルのみが含まれるようになります プレイヤーがシーンの別の場所に移動する場合 Appは全く新しい高解像度テクスチャの セットをストリーミングする必要があります ストリーミングシステムが十分に高速であれば プレイヤーはストリーミングの 発生に気づきません シーンを一時停止すると より明確に画像の違いを観察できます 左側はpread APIを使用してシングルスレッドで スパースタイルを読み込んでいます 右側は高速リソース読み込みAPIを使い スパースタイルを読み込んでいます プレイヤーがシーンに入った時は ほとんどのテクスチャは 完全に読み込まれていません 読み込みが完了すると最終的な 高解像度版のテクスチャを見ることができます シーンの冒頭に戻ってスロー再生すると 高速リソース読み込みによる 改善がより分かりやすくなります このレンダリングではAppがまだ 読み込んでいないタイルを赤で マークして違いを強調しています 最初 シーンには ほとんどのタイルが 読み込まれていないことが分かります しかし プレイヤーがシーンに入ると 高速リソース読み込みにより 高解像度版タイルの読み込みが改善され シングルスレッドのpread版に比べ 遅延を最小限に抑えられます Metal 3の高速リソース 読み込みにより パワフルで 効率的なアセットストリーミングを構築し 最新ストレージ技術を Appに活用させることができます 高画質な画像を含むアセットを ジャストインタイムでストリーミングすることで ロード時間の短縮が可能になります Metalの共有イベントを使用して GPUがシーンをレンダリングする間 非同期でアセットを読み込めます すぐに必要なアセットには より優先度の高い コマンドキューを作成することで 待ち時間を最小化できます そして ロードコマンドを早めに送信して ストレージシステムを最大限活用しましょう 不要なコマンドはいつでもキャンセルできます 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) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。