WWDC Quick Look 💓 By SwiftGGTeam
Optimize GPU renderers with Metal

Optimize GPU renderers with Metal

元の動画を見る

ハイライト

Metal は、関数定数を通じて uber シェーダーの動的ブランチを排除し、非同期コンパイルを使用してマテリアル編集の応答を維持し、ダイナミック リンクを使用してコンパイル時間を短縮し、占有ヒントを使用して計算コアを調整し、Blender 3D のマテリアル レンダリング時間を 58 ミリ秒から 12.5 ミリ秒に短縮します。

主な内容

Uber シェーダーのジレンマ

デジタル コンテンツ作成アプリケーションとゲーム エンジンでは、マテリアル システムには 2 つの相反する目標があります。

  1. 編集応答: スライダーをドラッグするとビューポートがすぐに更新され、遅延はありません。
  2. レンダリング パフォーマンス: リアルタイム インタラクションと最終フレーム レンダリングの両方が高速化されています。

Uber シェーダは、考えられるすべてのマテリアル プロパティを含む巨大なシェーダです。編集時にはマテリアル パラメータ バッファを更新するだけで済み、再コンパイルの必要がなく、応答が非常に高速です。ただし、すべてのブランチを処理する必要があるため、実行時のパフォーマンスは非常に低くなります。

関数定数はこの矛盾を解決します。コンパイル中にマテリアル パラメーターを定数に変換し、コンパイラーはデッド コードを削除し、それに応じて定数を折りたたんで最適な特殊バージョンを生成します。

02:05

関数定数を使用して材料を特殊化する

Uber シェーダーの問題コード:

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();
}

これらif実行時に決定されるため、マテリアル バッファーを毎回読み取る必要があり、その結果、多数の分岐が発生します。

代わりに関数定数を使用してください:

constant bool IsGlossy        [[function_constant(0)]];
constant bool HasShadows      [[function_constant(1)]];
constant bool HasReflections  [[function_constant(2)]];
constant bool IsVolumetric    [[function_constant(3)]];

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();
}

重要なポイント:

  • [[function_constant(N)]]コンパイル時定数を宣言する
  • Metal コンパイラは定数のブール値を折りたたみ、偽の値を持つ分岐は直接削除されます。
  • 機能の切り替えを決定するためにマテリアル バッファを読み取る必要はありません
  • 制御フローが大幅に簡素化され、メモリ読み取りと分岐が削除されました。

03:45

定数フォールディングの最適化

変化しない静的パラメータには関数定数も使用できます。

constant float4 MaterialColor  [[function_constant(0)]];
constant float4 MaterialWeight [[function_constant(1)]];
constant float4 SheenColor     [[function_constant(2)]];
constant float4 SheenFactor    [[function_constant(3)]];

void material_glossy(const constant Material& material) {
    float4 light = glossy_eval(MaterialColor, MaterialWeight);
    float4 sheen = sheen_eval(SheenColor, SheenFactor);
    glossy_output_write(light, sheen, material.blend_factor);
}

コンパイラは、バッファから読み取ることなく、これらの値を命令に直接インライン化します。

04:58

ホスト側で特殊なパイプラインを作成する

// 定义材质参数结构
struct MaterialParameter {
    NSString* name;
    MTLDataType type;
    void* value_ptr;
};

// 创建参数实例
Material material = {true, {1.0f, 0.0f, 0.0f, 1.0f}};
MaterialParameter is_glossy{@"IsGlossy", MTLDataTypeBool, &material.is_glossy};
MaterialParameter mat_color{@"MaterialColor", MTLDataTypeFloat4, &material.color};
MaterialParameter shader_parameters[2] = {is_glossy, mat_color};

// 填充 function constant values
MTLFunctionConstantValues* values = [MTLFunctionConstantValues new];
for (const MaterialParameter& parameter : shader_parameters) {
    [values setConstantValue:parameter.value_ptr
                        type:parameter.type
                    withName:parameter.name];
}

// 创建管线描述符,使用带 function constants 的片段函数
MTLRenderPipelineDescriptor *dsc = [MTLRenderPipelineDescriptor new];
dsc.fragmentFunction = [shader_library newFunctionWithName:@"frag_material_main"
                                            constantValues:values
                                                     error:&error];

// 创建管线状态对象
id<MTLRenderPipelineState> pso = [device newRenderPipelineStateWithDescriptor:dsc
                                                                        error:&error];

重要なポイント:

  • newFunctionWithName:constantValues:error:特殊な機能を作成する
  • MTLFunctionConstantValues定数値を名前で設定する
  • 作成された PSO は、このマテリアル パラメータのセットにとって最適なバージョンです。

05:21

##詳細

GPUデバッガによる最適化効果の検証

Uber シェーダーの GPU デバッガー データは次を示します。

  • ALU命令数が多い
  • 大量のレジスター流出(流出)
  • メモリ待ち時間が長い

特殊なデータ:

  • ALU 命令と即時スピル削減 (デッド コードの削除と定数のフォールディング)
  • メモリ遅延が大幅に減少

タイムライン ビューでは、uber シェーダー レンダリング マテリアル パスには 58 ミリ秒かかりますが、特化したバージョンには 12.5 ミリ秒しかかかりません。

06:26

非同期コンパイルの応答性は維持されます

特殊化にはランタイム コンパイルが必要であり、ブロックと待機により遅延が発生します。Metal の非同期 PSO 作成 API は、この問題を解決します。

// 异步创建 shader library
[device newLibraryWithSource:source
                     options:options
           completionHandler:^(id<MTLLibrary> library, NSError* error) {
    // 库编译完成
}];

// 异步创建渲染管线状态
[device newRenderPipelineStateWithDescriptor:descriptor
                           completionHandler:^(id<MTLRenderPipelineState> pso, NSError* error) {
    // PSO 编译完成,切换到特化版本
}];

ワークフロー:

  1. デフォルトで uber シェーダー レンダリングを使用する
  2. 特殊なバージョンの非同期コンパイルを同時にトリガーします
  3. コンパイルが完了したら、専用バージョンに切り替えます
  4. マテリアル パラメータが変更されると、キャッシュされた特殊バージョンが無効になり、uber シェーダにフォールバックし、新しい非同期コンパイルがトリガーされます。

07:52

同時コンパイルを最大化する

macOS 13.3 新機能shouldMaximizeConcurrentCompilation財産:

// 启用最大并发编译
device.shouldMaximizeConcurrentCompilation = YES;

設定すると、Metal コンパイラはすべての CPU コアを完全に利用します。これは、複数のマテリアルを同時に編集するシーンで特に便利で、特化したバージョンをより速く準備できます。

09:16

動的リンクによりコンパイルが高速化されます

複雑なマテリアルの特殊なバージョンは、コンパイルに時間がかかることがあります。動的リンクにより、共通関数をライブラリにプリコンパイルできるため、実行時のコンパイルの量が削減されます。

シンボル表示制御:

// 对外可见的函数
__attribute__((visibility("default")))
void matrix_mul();

// 内部使用的函数
__attribute__((visibility("hidden")))
void matrix_mul_internal();

デバイスのサポートを確認してください:

// 渲染管线动态库
BOOL supportsRenderDylib = device.supportsRenderDynamicLibraries;

// 计算管线动态库
BOOL supportsComputeDylib = device.supportsDynamicLibraries;

Apple6 以降の GPU ファミリは、レンダリング パイプライン ダイナミック ライブラリをサポートします。Apple6 以降およびほとんどの Mac2 GPU ファミリは、計算パイプライン動的ライブラリをサポートしています。

動的ライブラリの作成:

// 从现有 Metal 库创建
id<MTLDynamicLibrary> dylib = [device newDynamicLibrary:library error:&error];

// 从文件 URL 创建
id<MTLDynamicLibrary> dylib = [device newDynamicLibraryWithURL:url error:&error];

パイプライン内のリンク:

MTLRenderPipelineDescriptor* dsc = [MTLRenderPipelineDescriptor new];
dsc.vertexPreloadedLibraries   = @[dylib_Math, dylib_Shadows];
dsc.fragmentPreloadedLibraries = @[dylib_Math, dylib_Shadows];

// 或在编译选项中指定
MTLCompileOptions* options = [MTLCompileOptions new];
options.libraries = @[dylib_Math, dylib_Shadows];
[device newLibraryWithSource:programString
                     options:options
                       error:&error];

重要なポイント:

  • 動的ライブラリはオフラインで事前コンパイルできるため、実行時のコンパイルを完全に回避できます。
  • 大規模なユーティリティ関数を個別の動的ライブラリに分割する
  • コンパイル時にメインシェーダをリンクするだけで済み、時間を大幅に短縮

10:58

計算カーネルの占有率の調整

すべての GPU ワークロードには、実験を通じて見つける必要があるパフォーマンスのスイート スポットがあります。Metal は、必要な GPU 使用率を指定するための API を提供します。

// 计算管线描述符
@interface MTLComputePipelineDescriptor : NSObject
@property (readwrite, nonatomic) NSUInteger maxTotalThreadsPerThreadgroup;
@end

// 编译选项(macOS 13.3 / iOS 16.4 新增)
@interface MTLCompileOptions : NSObject
@property (readwrite, nonatomic) NSUInteger maxTotalThreadsPerThreadgroup;
@end

動的な図書館占有マッチング:

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];

重要なポイント:

  • maxTotalThreadsPerThreadgroup値が大きいほど、コンパイラは高い占有率を優先します。
  • パイプライン記述子と動的ライブラリのコンパイル オプションに同じ値を設定する必要がある
  • 最適な値はワークロードやデバイスごとに異なり、実験が必要です
  • 最適な値は、必ずしも GPU がサポートする最大スレッド数であるとは限りません。

Blender Cycles シェーディングおよび交差計算カーネルのパフォーマンス カーブは、明確なパフォーマンス スイート スポットがあることを示しています。この時点で、流出量はわずかに増加しますが、全体的な占有率が増加するため、炉心パフォーマンスは大幅に向上します。

13:45

重要ポイント

  • 内容: 関数定数を使用して複雑なマテリアル シェーダを特殊化します。

  • 実行する価値がある理由: Blender 3D の実際の測定時間は 58 ミリ秒から 12.5 ミリ秒に短縮され、ALU 命令、スピル、メモリ待機が大幅に減少しました。

  • 開始方法: マテリアル プロパティ スイッチを次のように宣言します。[[function_constant(N)]]、PSO の作成時に渡されますMTLFunctionConstantValues値を渡す

  • 内容: 非同期マテリアル コンパイル ワークフローを実装します。

  • 実行する価値がある理由: マテリアルの編集時に遅延がなく、超シェーダーが保証され、バックグラウンド コンパイルの完了後に特殊なバージョンをシームレスに切り替えることができます。

  • 開始方法: 完了ハンドラーを備えた非同期 API を使用してライブラリと PSO を作成し、コンパイル完了後にレンダリングに使用されるパイプラインを置き換えます

  • 内容: 動的リンクを使用して共通関数ライブラリを分割する

  • 実行する価値がある理由: 数学や照明などの一般的な関数を動的ライブラリに事前コンパイルします。コンパイル時にメインシェーダをリンクするだけで済むため、時間を大幅に短縮できます。

  • 開始方法: 関数ごとにグループ化された動的ライブラリを作成し、セットアップしますvisibility("default")、パイプライン記述子内preloadedLibrariesで引用された

  • 内容: コンピューティング カーネルの maxTotalThreadsPerThreadgroup を調整します。

  • 実行する価値がある理由: コードやアルゴリズムを変更する必要はなく、パラメーターを 1 つ変更するだけでパフォーマンスのスイート スポットを見つけることができます。

  • 開始方法: カーネルにさまざまな設定を行いますmaxTotalThreadsPerThreadgroup値を指定するには、GPU デバッガーを使用して実行時間を測定し、最適な値を見つけます。

関連セッション

コメント

GitHub Issues · utterances