ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
可変リフレッシュレートディスプレイ向けの最適化
ダイナミックディスプレイタイミングをサポートしているすべてのAppleプラットフォーム上でスムーズな画面更新を実現する方法を紹介します。macOS上のAdaptive Syncディスプレイでフルスクリーンのゲームの更新頻度を調整するためのテクニックを説明して、低電力モードなどのシステム状態がProMotionディスプレイで使用できるフレームレートに与える影響について明らかにします。また、ディスプレイリンクAPIを使用したカスタム描画のためのベストプラクティスも紹介します。
リソース
関連ビデオ
WWDC22
WWDC19
-
ダウンロード
私は CPU Software Engineeringチームの Kyle Sannerです 同僚のAlex Liと一緒に 可変リフレッシュ ディスプレイにおいてAppで 最適なフレームペーシングを 取得する方法について お話します macOSに登場するいくつかの 新しいディスプレイテクノロジー Adaptive-Sync および 全ての条件下でiPadProに カスタム描画をスムーズに実行 する方法に焦点をあてます まず Appleプラットフォーム で現在サポートされている ディスプレイの種類について 簡単に説明します
MacのAdaptive-Sync ディスプレイとこのディスプレイ のフルスクリーンAppやゲームで スムーズなフレームレートを 実現するのに使えるmacOS Montereyの新ツールを紹介します
次に iPad ProのProMotionに ついて詳しく説明し Appが異なるフレームレートで 正しいフレームペースを維持 するための CADisplyLinkの ベストプラクティスを いくつか見ていきます
Appleデバイス対応のディスプレイ の種類を確認しましょう
ディスプレイは固定の リフレッシュレートで動作します つまり 電源がオンになるたびに 一定の速度で更新されます iPadのProMotion ディスプレイや macOSの Adaptive-Sync ディスプレイは例外です MacのAdaptive-Sync ディスプレイの新機能を紹介します
Adaptive-Sync表示とは何かを 機能を含め説明します その前に 固定レートの表示 の仕組みを説明します 60Hzディスプレイに配信される フレームを示す図です 各フレームはディスプレイに 表示され ディスプレイが 更新するまでの16ms間 留まります Macで描かれた新しいフレームが フレームバッファに 用意されていれば その新しい フレームが表示されます それ以外の場合は 前の フレームが再び表示されます
120Hzのディスプレイを見ると リフレッシュレートが 2倍になり 表示される 間隔が半分になりましたが 同じように動作し 速くなっています
一方 このAdaptive-Sync ディスプレイを見てください 静的期間の代わりに 各フレームには画面に表示できる 時間枠があります この画面は付属のディスプレイに よって異なります このディスプレイは40~120Hz で動作するため8msと25msの間で フレームを画面に 表示することができます
最大時間が経過すると システムはパネルを更新する必要 があり ディスプレイは短時間 新しい更新に対応できなく なりますのでご注意ください では Adaptive-Sync ディスプレイでは ゲームやAppにどのような メリットがあるのでしょうか? ほとんどがディスプレイの 最大リフレッシュレートで 実行されるAppでは Adaptive-Sync表示は無料ご提供 まず このシナリオを ご覧ください Appは8ms以下で新フレームを 生成することができるため 120Hzでかなり安定して 動作しています しかし シーンの複雑さが 瞬間的に増加するため 完成したフレームは 前のフレームが最初に表示されてから 9ms後にフレームバッファに 到達します 固定フレームレート表示では 意図した8msではなく 16msで前のフレームが 表示されます その結果 Appに違和感が 生じます
Adaptive-Syncディスプレイ では フレームが完了直後に ディスプレイに表示され 発生ペナルティは1msのみです この小さなヒッチは ユーザーには認識されません ディスプレイの最大フレームレートに 達しないワークロードでは Appがドローアブルを 表示する方法に 少し変更を加えれば スムーズで均一なフレームを提供できます このシナリオを考えてみましょう 複雑なシーンを実行しているゲームは 約90Hzで更新を生成できます ただし 断続的な効果で 複雑さが大幅に増加しますが 一貫性がなく 66Hzまでの 突然のスパイクが発生します AppのGPUの動作を監視 することで 複雑さの急増に 対応し シーンの複雑さが 一貫して低くなるまで 意図的にフレームの表示を 遅くすることができます 続いてAdaptive-Syncの ベストプラクティスの説明です 固定レートのディスプレイでは AppのGPU動作が常に ディスプレイのガラス上の間隔 を超えている場合 ディスプレイの最速のリフレッシュ レートの次の要素に達するように レンダリングを遅くすることを 推奨してきました
通常これは この例のように 1秒あたりのターゲットフレーム数を 60から30に下げることを意味します
ただし Adaptive-Sync ディスプレイに表示する場合は そのガイダンスを変更しています 代わりに Appが均等に 表示できる最高のレートで フレームを表示するように してください フレームを均等に 表示することに加えて ディスプレイがサポートする 最小レート以下でフレームを表示すると 新しいフレームでディスプレイ が使用できなくなりAppで ジャダーが発生する可能性が あるので注意してください ただし サポートされている 範囲内にある限り Appに最適なレートを 自由に選択できます 今年Macに登場する新しい ディスプレイサポートの概要を 理解したところで ゲーム でAdaptive Syncを有効にする 方法について説明しましょう まず サポートされている Macが必要です Apple Silicon GPUを搭載 したMacではどれでも機能し 最新のIntelベースのMacの 多くもサポートしています 次に サポートされたAdaptive-Sync ディスプレイが必要で Adaptive-Syncモードを 有効にする必要があります これはディスプレイシステム 環境設定で利用可能な新しい 可変リフレッシュレートを選 択することで実行できます Appはfull-screen mode で実行する必要があります AppでAdaptive-Syncスケ ジューリングを試みるか否かの 検出のために呼び出だせる APIを見てみましょう まず 実行しているディスプレイが Adaptive-Syncスケジューリングに 対応しているかを 判断する必要があります そのため 今年はNSScreen minimumRefreshInterval maximumRefreshIntervalに新 しいプロパティがあります これらの値は このディスプレイに 表示されるフレームの 有効な画面上の時間の範囲を 示します 固定フレームディスプレイでは これらの値は同じになるため 単純な異なる比較により この画面が Adaptive-Syncモードで あるかどうかがわかります 次に ウィンドウが フルスクリーンであるかを見ます ウィンドウのstyleMask を見るとわかります
AppのAdaptive-Sync 日程計画の利用にはチェック を組み合わせる必要があることを 忘れないでください いいでしょう これでAdaptive-Sync ディスプレイと それを検出するためにmacOSが 提供する新しいAPIを理解できたので 既存のメタルプレゼンテーション手法の 均等表示方法を Adaptive-Sync ディスプレイで見てみましょう
PresentAfterMinimumDurationや PresentAtTimeなどの フレームスペーシングが組み込まれた MetalDrawable APIを Adaptive-Syncディスプレイで 効果的に使用することができます または 現在の呼び出しと 独自のカスタムタイマーを使用して 独自のソリューションを ロールバックもできます いくつかの異なる実装の 機能の仕方を見てみましょう
簡単な例から始めましょう ここでは Drawableを取得し GPU作業をセットアップして 画面に表示します フレームレートを設定するた めに利用できるDrawableの 背圧に依存しています 固定レートのディスプレイでは GPUの動作がディスプレイの リフレッシュレートに 一致するという保証がないため これは最善の策ではないこと がわかっています
Adaptive-Syncの画面で 撮影した楽器のキャプチャ画面を 見ても 一貫していれば 正しく機能しているようです 問題はここが定期的に障害に 遭遇していることです これはユーザーにわかる スタッターに変換されます 一定の均等なレート提示で それを修正してみましょう この手法はゲームプレーヤーに ユーザーが調整可能な FPSスライダーを実装する場合にも 使用できます ここでは 必要な周波数を 78Hzに設定しました 単純な現在の呼び出の代わりに このDrawableに現在の afterMinimumDurationを使用し 前述の間隔を指定します ここで要求レートでスムーズに フレームを見れます 前の例ほど迅速に 提示していませんが ユーザーがスタッターに遭遇する 可能性ははるかに低く Appが使用するCPUやGPUの時間は より少なくなります さて ここからが少し面白い ところです 単一の固定レートを設定せずに 等ペースのフレームを 生成するアプローチを試して みましょう そのためには 各フレームの 生成に必要なGPU作業の 移動平均を計算し その時間を 現在のDrawable呼び出しに 送り込む方法があります 最初のフレームで平均GPU時間を 開始値で読み込みます ここでは 楽観的に考えて ディスプレイが対応できる 最速のレートを目標にします これは平均値の出発点に 過ぎないのでどんな合理的な 推測をしても大丈夫です 次に CommandBuffer完了 ハンドラをアタッチして GPUが このフレームのレンダリング に費やした時間を測定し その時間を移動平均に組み込み ましょう まず GPUが作業完了までに 要した時間を取得できます 次にその新しい時間を移動平均に 組み込み 次のフレームを 提示する際に使用します そして これが結果です ご覧のように 前例同様の レートで表示していますが この限界は以前に生成した フレームによって決定され Mac GPUの範囲全体で均一な フレームレートを生成します ここでは 追加コードを 変更せずにやや弱いMacで 同じプログラムが48Hzで 円滑に動作しているのがわかります
さて これでAppをAdaptive-Sync ディスプレイに最適化するための 新しいツールや テクニックを手に入れました macOSのAdaptive-Syncディスプレイ についての詳細は Developerサイトの新しいMetal サンプルプロジェクトをご覧ください Metalでパフォーマンス テクノロジーを実現するための 詳細については 過去のWWDC での講演をご覧ください ここからはAlexに交代して iPad Proでのフレームペーシング について詳しく 説明していきます ありがとう Kyle 次は ProMotionについて 話しましょう 2017年以降 すべてのiPad Proには 最大120Hzのリフレッシュレートを 実現するProMotion ディスプレイが搭載されています ただし 今年のiPadOS 15で iPadに搭載された低電力モードを オンにした場合など 状況によっては120Hzが 利用できない場合があります 適切なフレームペーシングにより 表示特性 ユーザー 設定 システム状態に関係なく Appはモーションコンテンツを 正確かつスムーズに 表示できます ProMotionと固定レートの 表示の違い および一部の フレームレートが使用できない 状況について見ていきます 次に 表示リンクとは何か およびAppがそれを使用して カスタム描画を実行する 方法について説明します ディスプレイリンクのベスト プラクティスを紹介します 早速 ご紹介しましょう Kyoeが簡単に紹介したように 60Hz固定のディスプレイは 16msごとに更新される 一定のリズムを持っています フレームレートが60倍の コンテンツのスムーズな表示を サポートします たとえば 60Hz 30Hz 20Hz などです ただし コンテンツがディスプレイの リフレッシュレートよりも い場合 例えば30Hzでも ディスプレイ自体を 同じリズムでリフレッシュする 必要があるため 1フレームごとに前のフレームを 繰り返し 電力を消費します 一方 ProMotionは最大120Hzの リフレッシュレートで 優れた応答性を提供します また 画面上コンテンツに適応し 消費電力を削減します それがどのように機能するか 見てみましょう 120Hzの最大走査周波数では 8ms毎に表示が更新されます
120は60の倍数 ProMotionは既存の 全フレーム値をサポートします 20Hzだけでなく Appの中間フレームレートも 提供します さらに ProMotionは リフレッシュレートを動的に 調整可能で スムーズな 60Hzコンテンツでは 固定の120Hzディスプレイ繰り返しなしで 16msごとにリフレッシュできます これは24Hzまでずっと 当てはまります
現在 このフレームレートは 常時利用可とは限りません 最大フレームレートを60Hzに 制限するアクセシビリティ設定で フレームレートの制限を オンにできます また デバイスが高温になると システムが120Hzの利用制限を かけることがあります iPadOS 15では 低電力モードでも 60Hzの上限を適用します では このシナリオは Appにどう影響しますか? 幸い ほとんどのAppは 変更なく動作します ただし Appがフレームごとの カスタム描画を実行する場合は このフレームレートの変更に 注意する必要があり その方法を説明します カスタム描画を駆動する 推奨ツールは 表示リンクで 基本的に画面のリフレッシュレートと 同期するタイマーです これは Appドライブを サポートします 2つの表示リンクがあります 1つはmacOSでCoreVideoが 提供するCVDisplayLink もう1つは他のプラットフォームの CoreAnimationが提供する CADisplayLinkと macOSのCatalystで それぞれ特性や動作が若干異なります 今日はCADisplayLinkについて のみ説明しますが 大まかに言えば これらの概念は両方に当てはまります CADisplayLinkは vsyncごとに 起動しコールバックを呼び出します これで アプリケーションが 動作を8msで完了します
NSTimerなどの通常のタイマーでは ディスプレイと完全に同期することは まずありません 位相ずれ またはドリフトする可能性が あるためAppの作業完了に 時間不足で コマ落ちになることがあります CADisplayLinkがいかに安定した タイミングを提供するかを確認でき さらにいくつかの利点を ご紹介します ディスプレイのリフレッシュレートよりも 遅いレートで実行できるため AppはpreferredFramesPerSecond を介してヒントを提供し 利用可能な最も近い フレームレートを選択します 前述したように フレームレートの可用性が変わると CADisplayLinkは内部で自動的に レートを調整します もちろん カスタム描画が これらの変更を認識できるように Appに必要なタイミング情報も 提供します カスタムアニメーションや カスタムレンダリングループの 作成法には触れませんが カスタム描画を表示時期と 同期し 落とし穴回避のベスト プラクティスを4つ紹介します
まず ハードコーディング するのではなく 実行時に ディスプレイのリフレッシュレートの 照会が重要です 次に 通常 CADisplayLink自体の フレームレートを使用する 必要があります 次に targetTimestampを使用して 描画を準備すると障害を減らせます 最後に 不測の事態に備える ために タイムデルタを動的に 計算することは いつでも得策です それらを一つずつ 見ていきましょう 最大ディスプレイリフレッシュレートは UIScreenを介して照会でき 低電力モードがオンに なっている場合でさえ ProMotionディスプレイで 常に120Hzを返します 一方 CADisplayLinkは 実際には期間プロパティを介して フレーム間の最短間隔を提供し 現在のデバイス状態に基づいて 動的に更新します ただし ほとんどの場合 ディスプレイリンクは ディスプレイの最大リフレッシュ レートよりも遅くなる可能性があるため フレーム情報はCADisplayLinkから 直接使う必要があります また フレームレートの可用性は ハードウェアに依存し 実際のフレームレートは システム状態変化に応じ ディスプレイリンク自体によって 動的に変わる場合があります 例をみてみましょう 40Hzのディスプレイリンクを リクエストするとします ProMotionディスプレイで 40Hzがサポートされています ただし 60Hzディスプレイや ProMotionが60Hzに制限されると ディスプレイリンクは 自動的に30Hzに調整されます 各ウェイクアップが可能な Vsyncで行われ 各フレームに均等な時間を与える 良いリズムが確保されます 仮にフレームレートを意識しない 40HzのNSTimerを使うと ウェイクアップがvsync間隔の ちょうど真ん中になってしまい 当然そこではフレームを 提示できないので カスタム描画に不具合が 発生してしまいます では コード上では どうなっているのでしょうか? さて これが通常表示リンクを 設定する方法です まず ターゲットと セレクタを準備し 呼び出される コールバックを指定します 次に preferredFramesPerSecondで 40Hzの優先フレームレートを指示します そして 表示リンクを 現在のランループに追加し そこからコールバックが 呼び出されます したがって コールバックでは targetTimestampから タイムスタンプを差し引くことで ディスプレイリンクの ウェイクアップの 予想間隔を取得できます 表示リンク自体が異なる周波数で 実行されている可能性があり この間隔は必ずしも 1対40であるとは限りません 次に このタイムスタンプについて 説明しましょう CADisplayLinkには 主に2つの タイムスタンプがあります Timestampは コールバックが 呼び出される予定の時刻を示し targetTimestampは 次のフレームが CoreAnimationによって 合成される時刻を示します ここでは なぜtargetTimestampを使って 図面を作成する必要があるのか 例を挙げて説明します 0から1まで正規化した 時間領域でのアニメーションです 最高のフレームレートを目指し 今120Hzであると仮定 CADisplayLinkが起動し タイムスタンプを使用して フレーム表示を準備すると ここで直接サンプリングされ 次のvsyncで表示されてから こに表示されます
同じ処理を続けると 120Hzのフレームごとに アニメーションの進行が 0.05ずつ増加する良いリズムあります さて 熱状態が変化し 120Hzが使えなくなったとします これで 表示リンクが再び起動し Appは進行状況0.4で アニメーションを準備し 次のvsyncでここに表示されます
同様のパターンが続きます ここでの推移は 何かが誤っています 進行状況は0.05増加しますが 1つは8msを超え もう1つは16msを超えています 進捗状況を時間軸でプロットすると 移行時に不都合が生じ それがユーザーの目に見える形で 反映されることになりますが これは 望ましくありません
ではtargetTimestampを 試してみましょう CADisplayLinkはここで 起動します 進行状況はtargetTimestampで サンプリングされ 0.15になります 同じパターンが続き ここでも良いリズムが見られます このフレームレート移行ポイントで 表示リンクが起動し targetTimestampでサンプリングし 0.50を取得します 同様に続きます 同じように進捗対時間の グラフをプロットしてみると 直線になっているので フレームレートが変わっても スムーズにコンテンツを 提供できることがわかります 描画作成には タイムスタンプではなく targetTimestampを使用します コード上では タイムスタンプの使用をすべて targetTimestampに 置き換えるだけで十分です 最後に 時間デルタの動的な 計算法を説明しましょう targetTimestampと timestampの差は ディスプレイリンクコールバック間の 予想時間はわかりますが 実際の時間は 保証されません 優先度の高いスレッドがCPUで スケジュールされているか ランループが他でビジー状態の 可能性があります 極端な例 コールバックが 完全に飛ぶ可能性があり この状況で最高の ユーザーエクスペリエンスを得るために カスタム描画で正しい時期を 維持するのが特に重要です CADisplayLinkコールバックの 呼び出しでAppは 次のフレームに必要な更新や レンダリングを準備します 通常 コールバックは スケジュールされた起動時間に 呼び出されますが 違うこともあります ここで次のコールバックが あると予想しています ただし 表示リンクは vsync 間隔の数ミリ秒まで 実行される機会がありません したがって 完全な8msを取得 できない場合があります この場合 CACurrentMediaTime に照会し targetTimestampと 比較して 使用可能な時間を 把握できます
このフレームで作業に時間が かかりすぎているとします 次のコールバックは ランループの再解放までありません 遅延のため 次のコールバックは スキップされるので このコールバックで カスタム描画の準備をする際 カスタムの描画状態が更新される前の タイムスタンプを追跡する場合 使用する時間の差分は 8msではなく16msであることに 注意してください したがって アプリケーションが カスタム描画の状態を進めるために タイムデルタを使用している場合 カルバックがスキップされるたびに カスタム描画が 1フレーム遅くなります 代わりに 以前のtargetTimestamp を追跡することで 状態を正しく進めることができます また カスタム描画の作業量が多い場合は targetTimestampを調べ 期限に 間に合うように作業量を削減 できる可能性もあります
ベストプラクティスでは リフレッシュレートの 推測なし実行する時には 必ず照会を実行してください カスタム描画は サポートされている フレームレートや異なるレートにも 柔軟に対応できる必要があります targetTimestampを使用して 問題のないフレームレート移行を確保し 表示リンクのコールバックの欠落など 予期しない状況に注意してください では まとめましょう 前半では macOSのAdaptive-Sync ディスプレイで実行する際 Appのフレームペースを 最適化する方法を説明しました 後半では iPad Proの ProMotionディスプレイ上で Appがカスタム描画を推進し スムーズなフレームペースを 維持するためのベストプラクティス について説明しました ディスプレイ技術が進化し続ける中 本セッションが 洞察力だけでなく ますます 動的になるディスプレイの タイミングをサポートする ツールやベストプラクティスを 提供できていることを 願っています ご参加ありがとうございます 残りのWWDC2021をお楽しみください [ミュージック]
-
-
5:51 - Is Adaptive-Sync scheduling enabled
// Detecting an Adaptive-Sync display - (BOOL) isAdaptiveSyncSupported:(NSScreen *)screen { NSTimeInterval minInterval = screen.minimumRefreshInterval; NSTimeInterval maxInterval = screen.maximumRefreshInterval; return minInterval != maxInterval; } // Detecting full-screen - (BOOL) isWindowFullscreen:(NSWindow *)window { return ([window styleMask] &= NSFullScreenWindowMask) == NSFullScreenWindowMask; } // Tying it all together - (BOOL) isAdaptiveSyncSchedulingEnabled:(NSScreen *)window { NSScreen* windowScreen = [window screen]; return [self isWindowFullscreen:window] && [self isAdaptiveSyncSupported:windowScreen]; }
-
6:49 - Leverage Drawable present calls
// Drawable present APIs with frame-pacing [commandBuffer presentDrawable:drawable afterMinimumDuration:interval]; [commandBuffer presentDrawable:drawable atTime:t]; // Drawable present API without frame-pacing [commandBuffer presentDrawable:drawable];
-
7:11 - A simple example
id<CAMetalDrawable> currentDrawable = [metalLayer nextDrawable]; // Your encoder and command buffers here [commandBuffer presentDrawable:currentDrawable];
-
7:55 - Adaptive-Sync in your app 1
id<CAMetalDrawable> currentDrawable = [metalLayer nextDrawable]; NSTimeInterval userFramerateCap = 78.0; NSTimeInterval userInterval = 1.0 / userFramerateCap; // Your encoders and command buffers are still here [commandBuffer presentDrawable:currentDrawable afterMinimumDuration:userInterval];
-
8:43 - Adaptive-Sync in your app 2
id<CAMetalDrawable> currentDrawable = [metalLayer nextDrawable]; // Your encoders and command buffers are still available! NSTimeInterval averageGPUTime = screen.minimumRefreshInterval; [commandBuffer presentDrawable:currentDrawable afterMinimumDuration:averageGPUTime]; [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { const NSTimeInterval GPUTime = buffer.GPUEndTime - buffer.GPUStartTime; // Use an exponential moving average const double alpha = .25; averageGPUTime = (GPUTime * alpha) + (averageGPUTime * (1.0 - alpha)); }];
-
15:36 - Query the display refresh rate at runtime
// Maximum frame rate from UIKit NSInteger maxRate = [[UIScreen mainScreen] maximumFramesPerSecond]; // Current maximum frame rate from CoreAnimation NSInteger currentMaxRate = round(1 / link.duration);
-
17:06 - Use the actual frame rate of the CADisplayLink
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)]; [link setPreferredFramesPerSecond:40]; [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - (void)displayLinkCallback:(CADisplayLink *)link { CFTimeInterval interval = link.targetTimestamp - link.timestamp; //... }
-
21:47 - Dynamically compute the time delta 1
- (void)displayLinkCallback:(CADisplayLink *)link { progress += link.targetTimestamp - link.timestamp; [self renderAnimationWithProgress:progress]; }
-
21:57 - Dynamically compute the time delta 2
- (void)displayLinkCallback:(CADisplayLink *)link { progress += link.targetTimestamp - previousTargetTimestamp; previousTargetTimestamp = link.targetTimestamp; [self renderAnimationWithProgress:progress]; }
-
22:08 - Dynamically compute the time delta 3
- (void)displayLinkCallback:(CADisplayLink *)link { progress += link.targetTimestamp - previousTargetTimestamp; previousTargetTimestamp = link.targetTimestamp; [self renderAnimationWithProgress:progress withDeadline:link.targetTimestamp]; }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。