ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
MetalでGPUレンダリングを最適化
最新のMetal機能とベストプラクティスを使用してGPUレンダリングを最適化する方法を学びましょう。応答性の高いオーサリングワークフローと最速のレンダリング速度を維持するための関数特殊化と並列シェーダーコンパイルの使用方法を紹介し、最適なパフォーマンスを実現するためのコンピュートシェーダーのチューニングする方法を理解しましょう
関連する章
- 0:00 - Intro
- 2:00 - Maximize shader performance
- 7:45 - Asynchronous compilation
- 10:10 - Fast runtime compilation
- 12:46 - Tune compiler options
- 16:10 - Wrap-Up
リソース
関連ビデオ
Tech Talks
WWDC22
WWDC21
-
ダウンロード
♪ ♪
こんにちは 私Gauri Jogは AppleのMetal Ecosystemチームに 所属しています Metalを使ったGPUレンダリングの 最適化についてお話しします
最近のデジタルコンテンツ制作 アプリケーションやゲームエンジンでは コンテンツ制作者が3Dアセットの マテリアルをインタラクティブに作成 変更できるようになっています このような複雑で動的なマテリアルを 実行時に処理するための 一般的な手法がいくつかあります マテリアルを個々の シェーダにコンパイルする アプリケーションはじめUber Shaderや シェーダ仮想マシンなどの データ駆動型ソリューションを 使用するものもあります これらのマテリアル中心のワークフローには 主に2つの性能目標があります マテリアルのオーサリングは 高速なイテレーションと 最高のエクスペリエンスのために レスポンシブであるべきです レンダリングパフォーマンスは リアルタイムのインタラクティブ性と 効率的な最終フレームレンダリングのために 可能な限り良好であるべきです
Blender 3Dのこのデモでは マテリアル編集はレスポンシブです ユーザーインターフェイスで マテリアルスライダーを変更すると シェーダの再コンパイルに よるスタッタなしに ビューポートに即座に結果が表示されます マテリアルが変更されると その結果のレンダリングパフォーマンスは 高速でインタラクティブになり コンテンツ制作者は 作業結果を効率的に確認することができます
アプリケーションで応答性と パフォーマンスの高いワークフローを 実現するには Metalの主要な機能を活用し Metalのベストプラクティスを実装します Metalは複雑なシェーダーの パフォーマンスを最大化し 非同期コンパイルを活用して アプリケーションの応答性を維持し ダイナミックリンクを使用して コンパイルを高速化し Metalの新しいコンパイラーオプションで コンピュートシェーダーを 調整する際に役立ちます シェーダーの最適化は パフォーマンスの鍵です Uber Shaderとは あらゆるマテリアルの レンダリングに使用できる 長くて複雑なシェーダーの一例です この種のシェーダーには可能な 組み合わせごとに分岐が多数存在します アーティストがマテリアルを作成するとき マテリアルのパラメータは Metalバッファに保存され マテリアルシェーダで使用されます このバッファはパラメータを変更すると 更新されますが 再コンパイルは必要ありません
このアプローチは優れた レスポンシブオーサリング体験を提供します しかし Uber Shaderは すべての可能なオプションを 考慮しなければならないため 最適ではありません
最も最適なシェーダーバリアントを作るには 関数定数を使った Metalの特化型を使うべきです Metalシェーダで関数定数を宣言し その値が変更されたときに 実行時に設定するだけです マテリアルバッファの内容は シェーダパイプラインステートで 単に定数になり 動的分岐はなくなります 特化されたマテリアルは 最も高いパフォーマンスをもたらします これはBlender 3Dの 2つの一般的なテストアセット WandererとTree Creatureの リアルタイム― パフォーマンスデータの比較です 1つ目はUber Shaderを使用した シーンのフレームレートに関する ベースラインのパフォーマンスです 2つ目は関数定数を使った 特化型シェーダのアプローチで はるかに高速に動作します 最速の特化型シェーダの バリエーションを作るには 関数定数を使用して未使用の機能を 無効にして分岐をなくします
Uber Shaderはバッファから マテリアルパラメータをクエリし 実行時に条件分岐を行い 機能を有効にしたり無効にしたりします 関数定数を使用するとマテリアル機能ごとに 定数を1つ宣言します これでフィーチャーの コードパスの動的分岐が 関数定数に置き換えられ 未使用のコードがすべてなくなります 以下は同じUber Shaderを 関数定数で表したものです Metal コンパイラはこれらを 定数ブーリアンとして折り畳み 未使用コードを削除することができます falseに解決される分岐式は最適化され 真の分岐だけが残されます 未使用の制御フローはすべて最適化されます
特化型シェーダはマテリアルデータを クエリする必要がなくなり よりシンプルな制御フローになりました メモリロードと分岐が削除され 実行時のパフォーマンスが速くなりました
関数の特化型は 定数の折りたたみにも役立ちます 変化しない材料パラメータは 定数に置き換えられます このマテリアル例ではMetalバッファからの 入力パラメータのコレクションを使います 色や重さや光沢の色をはじめ その他多くのものがパラメーターとなります
マテリアル作成時に これらの静的パラメータを関数定数に 置き換えることができます 関数定数はバッファの読み込みを必要とせず 最適なコードを生成します ホスト側では特殊な パイプライン状態を作成する際に 関数の定数値が提供されます MaterialParameter 構造体は マテリアルの定数である― すべてのパラメータを表すために 使用できます IsGlossyは光沢度を制御するブール値の マテリアル機能フラグの例です MaterialColorは 色を表現するベクターパラメータの例です
特殊なPipeline State Objectを 作成するには MetalFunctionConstantValues セットを繰り返し setConstantValueを使用して 値を挿入します
その後 通常通り Render Pipelineを作成するだけです 唯一の違いは フラグメント関数を作成するときに newFunctionWithNameバリアントを constantValuesで使用することです
最後にPipeline State Object を作成します 出来上がったシェーダはこのマテリアルの 最も最適な性能を持つバリアントです
常に XcodeのGPU Debugger Performanceセクションを使用して 関数定数を使用することの 影響を確認してください
元のUber ShaderはALU命令数が多く スピル量が多いことを示しています メモリ待ちの数も多いです
特化型されたアプローチはALUと スピルに対して即座に緩和を与えます これはデッドコードの排除と 定数の折りたたみによるものです また メモリ待ちの数も 大幅に少なくなっています
オリジナルのUber Shaderを ランタイムシェーダー実行コストで見ると GPUはメモリ待機に かなりの時間を費やしています
対照的に 特化型されたアプローチでは メモリ待機に費やす時間がはるかに減少し ほかの効率的な利点とともに より生産的な ALU利用が可能になります
GPU Debuggerのタイムラインビューでは Uber Shaderを使った― マテリアルパスのレンダリングに 58ミリ秒かかっています 特化型ではレンダリングに 12.5 ミリ秒しかかかりません これはかなり劇的な改善です
マテリアルの特化型には ランタイムシェーダーコンパイルが必要で 特化型されたマテリアルが 生成される際にブロックして待つと 多くの場合ヒッチが発生します Metalの非同期コンパイルAPIによって 汎用Uber Shaderを使用し バックグラウンドで 特化型されたバージョンを生成しながら インタラクティブかつレスポンシブに ユーザーエクスペリエンスを保てます
非同期パイプライン状態生成を選択するには 完了ハンドラを提供します これらの呼び出しは即座に返されるため インタラクティブかつレスポンシブに ユーザーエクスペリエンスを保てます 完了ハンドラは特化された パイプラインステートの準備ができたときに 呼び出され すぐに最適なシェーダに 切り替えることができます
これは非同期マテリアル ワークフローの図です デフォルトでは マテリアルが まだ特化型されていない場合 Uber Shaderを使用します 同時に Metalは バックグラウンドで 特化型シェーダをコンパイルします
これが完了するとUber Shaderを 高速にスペシャライズされた マテリアルに切り替えることができます
ランタイムのMetalシェーダーコンパイルは バランスの取れたレベルの並列性を 提供するように設計されています しかし 最新のコンテンツ作成の アプリでは マルチマテリアル編集ワークフローを 提供する必要があり その結果 多くのシェーダの 再コンパイルが発生します このようなヘビーな オーサリングを支援するために Metalに対して シェーダーコンパイルの並列性を 最大化するように依頼できます macOS13.3のMetalデバイスには hould-Maximize-Concurrent-Compilation という新しいプロパティがあります これをYesに設定すると MetalコンパイラはCPUコアを 最大限に活用します 同時コンパイルを最大化することは マルチマテリアルの オーサリングワークフローにとって 本当に素晴らしいことです コンパイラのジョブを増やすことで 特殊なマテリアルのバリエーションを より早く利用できるようになります これが実際にどのように 機能するか説明します マテリアルのパラメータが変更されると そのマテリアルの現在の 特化型されたバリエーションは無効になり オーサリングの流動性を保つために Uber Shaderの使用に切り替わります 新しい非同期ジョブがキューに入れられ それが完了すると 特化型されたマテリアルが 使用されるようになり パフォーマンスが大幅に向上します 最近のアプリの多くは 非常に複雑なマテリアルを使用しているため 特化したバリエーションが準備できるまでに かなりの時間を要することがあります Metalのダイナミックライブラリによって ユーティリティ関数をプリコンパイルし 全体的なマテリアルの コンパイル時間を短縮することができます これを行うには機能のグループを 別々のダイナミックライブラリに分割します 実行時のコンパイルを さらに速くするために ユーティリティライブラリをオフラインで プリコンパイルすることもできます そうすれば実行時にコンパイルするコードは かなり減少します
以前のUber Shaderを dylibsに分割するとしたら アプローチの1つは 共通の機能グループごとに分割することです この場合 1つは 数学ユーティリティライブラリ用で もう1つはライティング関数用です
関数のシンボルを リンク時に見えるようにするには 「default」の可視性を割り当てます 「hidden」という可視性を 割り当てることで 外部プログラムから シンボルを隠すこともできます
Metalデバイスが ダイナミックライブラリを サポートしているかを確認するための プロパティが2つあります レンダーパイプラインにはMetalデバイスの supportsRenderDynamicLibraries プロパティを使用する必要があります これは現在 Apple6以上のGPU ファミリを搭載したデバイスで利用可能です
コンピュートパイプラインについては supportsDynamicLibraries プロパティを照会してください これはApple6以上と Mac2 GPUファミリーの ほとんどで利用可能です
既存のMetalライブラリから ダイナミックライブラリを作成するには 単にnewDynamicLibraryを呼び出し Metalライブラリを渡します URLから作成するにはnewDynamic LibraryWithURLメソッドを呼び出し 格納されているダイナミックライブラリへの パスを指定します
メタルコンパイラツールチェーンで ダイナミックライブラリを オフラインでプリコンパイル することができます プリコンパイルされた ダイナミックライブラリを 実行時にロードする場合 コンパイルは完全に回避されます リンク段階でディライブを指定するには パイプライン記述子の preloadedLibrariesパラメータに Metal Dynamic Library Objectsの 配列を渡します また ほかのシェーダーライブラリを コンパイルする際に Metal Compile Optionsを介して このダイナミックライブラリの配列を 提供するオプションもあります ユーティリティコードの大部分を ダイナミックライブラリに移動することで ランタイムコンパイルが 大幅に短縮されます 最後に コンパイラオプションの チューニングは最終的なプロダクションー クオリティのレンダリングにおける パストレーシングのような コンピュートケースにとって非常に重要で 最終的なレンダリングから最大限の パフォーマンスを引き出す Metalの追加機能が1つあります Metalのコンパイラーオプションと 占有ヒントは ダイナミックリンクを使用する場合に特に これらのコンピュートカーネルの 性能のチューニングを可能にします
すべてのGPUワークロードには 分析と評価が必要な 性能的な最適点があります 希望するGPU占有率をターゲットとする Metal APIがあり これは現在ダイナミックライブラリでも 利用可能です これにより元のコードや アルゴリズムを変更することなく 既存のワークロードの性能を 解放することができます 性能特性はGPUアーキテクチャによって 異なる可能性があるため チューニングはデバイスごとに行う必要が あることは注目に値します
Metalコンピュートパイプライン 記述子プロパティでは Max-Total-Threads-Per- Threadgroup値を 指定することで 希望する占有レベルを表現できます この値が大きいほどコンパイラが より高い占有率を目指すことになります ダイナミック・ライブラリ用のこの新しい Metal-Compile-Optionsプロパティで パイプラインの状態オブジェクトを 希望の占有レベルに合わせることができます Max-Total-Threads-Per- Threadgroupは iOS 16.4 および macOS 13.3 の MetalCompileOptionsで利用可能です
これでMetalダイナミックライブラリを 最適なパフォーマンスに調整しながら パイプラインステートオブジェクトの 希望占有率に単純に合わせられます
このBlender Cyclesのシェーディングと 交差計算カーネルの パフォーマンスのグラフは Max-Total-Threads-Per- Threadgroupの変更の影響を示します これはパイプラインステートオブジェクトと dylibsのために変更した唯一の変数です この場合カーネルが最高のパフォーマンスを 発揮するスイートスポットが存在します ワークロードとデバイスは それぞれ固有であり Max-Total-Threads-Per- Threadgroupの最適値は カーネルの性質によって異なります 最適な値はGPUがサポートする スレッドグループあたりの スレッド数の最大値とは限りません あなたのカーネルで実験して 使いたい最適値を見つけて コードに組み込んでください 以下は Blender Cyclesの シェーディングカーネルです コンパイラの統計はカーネルが 非常に複雑であることを示しています 実際の実行時間に影響する パラメータがいくつかあります スピルの量や使用されるレジスタの数 メモリロードのようなほかの操作などです Max-Total-Threads-Per- Threadgroupをチューニングすることで ターゲット占有率を変更しパフォーマンスの スイートスポットを見つけることができます
スイートスポットを見つけたあと スピルは少し増えますが 全体的な占有率を上げることで カーネル性能は大幅に向上しています
Blender 3D 3.5内の Cyclesパストレーサーは 現在Metal用に最適化されており 今日取り上げたベストプラクティスを すべて使用しています
関数の特化型を使用して 大規模で複雑なシェーダの シェーダ性能を最大化すること 非同期コンパイルを使用して 最適化されたシェーダを バックグラウンドで生成しながら アプリケーションの応答性を維持すること 実行時のコンパイルを高速化するために ダイナミックリンクを有効にすること 最適な性能を得るために 新しいMetalコンパイラオプションで コンピュートカーネルを チューニングすることを忘れないでください Apple GPU向けに コンピュートワークロードを スケーリングする方法を学び Metalのコンパイルワークフローを さらに発見できる 以前のセッションもぜひご覧ください ご視聴ありがとうございました ♪ ♪
-
-
3:45 - Reduce Branch Performance Cost
// Reduce branch performance cost fragment FragOut frag_material_main(device Material &material [[buffer(0)]]) { if(material.is_glossy) { material_glossy(material); } if(material.has_shadows) { light_shadows(material); } if(material.has_reflections) { trace_reflections(material); } if(material.is_volumetric) { output_volume_parameters(material); } return output_material(); }
-
3:55 - Function constant declaration per material feature
constant bool IsGlossy [[function_constant(0)]]; constant bool HasShadows [[function_constant(1)]]; constant bool HasReflections [[function_constant(2)]]; constant bool IsVolumetric [[function_constant(3)]];
-
3:59 - Dynamic branch for the feature codepath is replaced with function constants
if(material.has_reflections) { trace_reflections(material); }
-
4:05 - Dynamic branch for the feature codepath is replaced with function constants
/* replaced with function constants*/ if(HasReflections) { trace_reflections(material); }
-
4:13 - Reduce branch performance cost with function constants
constant bool IsGlossy [[function_constant(0)]]; constant bool HasShadows [[function_constant(1)]]; constant bool HasReflections [[function_constant(2)]]; constant bool IsVolumetric [[function_constant(3)]]; // Reduce branch performance cost fragment FragOut frag_material_main(device Material &material [[buffer(0)]]) { if(IsGlossy) { material_glossy(material); } if(HasShadows) { light_shadows(material); } if(HasReflections) { trace_reflections(material); } if(IsVolumetric) { output_volume_parameters(material); } return output_material(); }
-
4:58 - Function constants for material parameters
// Function constants for material parameters constant float4 MaterialColor [[function_constant(0)]]; constant float4 MaterialWeight [[function_constant(1)]]; constant float4 SheenColor [[function_constant(2)]]; constant float4 SheenFactor [[function_constant(3)]]; struct Material { float4 blend_factor; }; void material_glossy(const constant Material& material) { float4 light, sheen; light = glossy_eval(MaterialColor, MaterialWeight); sheen = sheen_eval(SheenColor, SheenFactor); glossy_output_write(light, sheen, material.blend_factor); }
-
5:21 - MaterialParameter structure for constant parameters
struct MaterialParameter { NSString* name; MTLDataType type; void* value_ptr; }; MaterialParameter is_glossy{@"IsGlossy", MTLDataTypeBool, &material.is_glossy}; MaterialParameter mat_color{@"MaterialColor", MTLDataTypeFloat4, &material.color};
-
5:51 - Declare and populate MTLFunctionConstantValues
// Declare and populate MTLFunctionConstantValues MTLFunctionConstantValues* values = [MTLFunctionConstantValues new]; for(const MaterialParameter& parameter : shader_parameters) { [values setConstantValue: parameter.value_ptr type: parameter.type withName: parameter.name]; }
-
5:51 - Create pipeline render state object with function constant declarations
struct Material { bool is_glossy; float color[4]; }; struct MaterialParameter { NSString* name; MTLDataType type; void* value_ptr; }; // Declare material Material material = {true, {1.0f,0.0f,0.0f,1.0f}}; // Declare function constant paramters MaterialParameter is_glossy{@"IsGlossy", MTLDataTypeBool, &material.is_glossy}; MaterialParameter mat_color{@"MaterialColor", MTLDataTypeFloat4, &material.color}; MaterialParameter shader_parameters[2] = {is_glossy, mat_color}; // Declare and populate MTLFunctionConstantValues MTLFunctionConstantValues* values = [MTLFunctionConstantValues new]; for(const MaterialParameter& parameter : shader_parameters) { [values setConstantValue: parameter.value_ptr type: parameter.type withName: parameter.name]; } // Create MTLRenderPipelineDescriptor and create shader function from MTLLibrary MTLRenderPipelineDescriptor *dsc = [MTLRenderPipelineDescriptor new]; NSError* error = nil; dsc.fragmentFunction = [shader_library newFunctionWithName:@"frag_material_main" constantValues:values error:&error]; // Create pipeline render state object id<MTLRenderPipelineState> pso = [device newRenderPipelineStateWithDescriptor:dsc error:&error];
-
6:14 - Create MTLRenderPipelineDescriptor and create shader function from MTLLibrary
// Create MTLRenderPipelineDescriptor and create shader function from MTLLibrary MTLRenderPipelineDescriptor *dsc = [MTLRenderPipelineDescriptor new]; NSError* error = nil; dsc.fragmentFunction = [shader_library newFunctionWithName:@"frag_material_main" constantValues:values error:&error];
-
8:07 - Shader library creation
- (void)newLibraryWithSource:(NSString *)source options:(MTLCompileOptions *)options completionHandler:(MTLNewLibraryCompletionHandler)completionHandler;
-
8:09 - Render pipeline state creation
- (void)newRenderPipelineStateWithDescriptor:(MTLRenderPipelineDescriptor *)descriptor completionHandler:(MTLNewRenderPipelineStateCompletionHandler)completionHandler;
-
9:00 - Use as many threads as possible for concurrent compilation
@property (atomic) BOOL shouldMaximizeConcurrentCompilation;
-
10:58 - Assign symbol visibility to default or hidden
__attribute__((visibility(“default"))) void matrix_mul(); __attribute__((visibility(“hidden"))) void matrix_mul_internal();
-
11:19 - Verify device support
//For render pipelines @property (readonly) BOOL supportsRenderDynamicLibraries; //For compute pipelines @property(readonly) BOOL supportsDynamicLibraries;
-
11:46 - Compile dynamic libraries
//create a dynamic library from an existing Metal library - (id<MTLDynamicLibrary>) newDynamicLibrary:(id<MTLLibrary>) library error:(NSError **) error //create from the URL - (id<MTLDynamicLibrary>) newDynamicLibraryWithURL:(NSURL *) url error:(NSError **) error
-
12:18 - Dynamically link shaders
//Pipeline state MTLRenderPipelineDescriptor* dsc = [MTLRenderPipelineDescriptor new]; dsc.vertexPreloadedLibraries = @[dylib_Math, dylib_Shadows]; dsc.fragmentPreloadedLibraries = @[dylib_Math, dylib_Shadows]; //Compile options MTLCompileOptions* options = [MTLCompileOptions new]; options.libraries = @[dylib_Math, dylib_Shadows]; [device newLibraryWithSource:programString options:options error:&error];
-
13:45 - Specify desired max total threads per threadgroup
@interface MTLComputePipelineDescriptor : NSObject @property (readwrite, nonatomic) NSUInteger maxTotalThreadsPerThreadgroup;
-
14:12 - Match desired max total threads per threadgroup
@interface MTLCompileOptions : NSObject @property (readwrite, nonatomic) NSUInteger maxTotalThreadsPerThreadgroup;
-
14:25 - Tune Metal dynamic libraries
MTLCompileOptions* options = [MTLCompileOptions new]; options.libraryType = MTLLibraryTypeDynamic; options.installName = @"executable_path/dylib_Math.metallib"; if(@available(macOS 13.3, *)) { options.maxTotalThreadsPerThreadgroup = 768; } id<MTLLibrary> lib = [device newLibraryWithSource:programString options:options error:&error]; id<MTLDynamicLibrary> dynamicLib = [device newDynamicLibrary:lib error:&error];
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。