WWDC Quick Look 💓 By SwiftGGTeam
Optimize for variable refresh rate displays

Optimize for variable refresh rate displays

元の動画を見る

ハイライト

macOS Monterey は Adaptive-Sync モニターのサポートを導入、メタル ゲームは合格可能presentAfterMinimumDurationアダプティブフレームレートを有効にします。 iPad ProのProMotionディスプレイで動作しますCADisplayLinktargetTimestampフレームレートが変化してもアニメーションを滑らかに保ちます。

主要内容

ほとんどの場合、ゲームは 120Hz モニター上で安定した 120fps で実行されます。ただし、シーンの複雑さが急増し、特定のフレームのレンダリングに 9 ミリ秒かかることがありました。固定リフレッシュ レート モニターでは、このフレームは次の 16.6 ミリ秒のリフレッシュ サイクルまで遅延し、ユーザーは明らかな遅延を経験します。 Adaptive-Sync モニタでは、このフレーム遅延はわずか 1ms で、ほとんど感知できません。

それが可変リフレッシュレートモニターの価値です。しかし、これを有効に活用するには、従来のフレーム レート制御の考え方を変える必要があります。

詳細

Adaptive-Sync モニターが macOS で動作する仕組み

01:27

固定リフレッシュ レート モニター (60Hz または 120Hz) は 16.6ms または 8.3ms ごとにリフレッシュされます。 GPU が新しいフレームの準備ができていない場合は、前のフレームが繰り返し表示されます。

Adaptive-Sync モニターの動作は異なります。各フレームが画面上に留まる時間は一定の間隔であり、固定値ではありません。たとえば、40 ~ 120 Hz の Adaptive-Sync モニターは、フレームごとに 8 ミリ秒から 25 ミリ秒の間画面上に留まります。

これはつまり:

  • フレームが 9 ミリ秒で完了した場合、9 ミリ秒の直後に表示されるため、16.6 ミリ秒まで待つ必要はありません
  • スタッタリングは 8 ミリ秒から 1 ミリ秒に短縮され、ユーザーはほとんど知覚できなくなりました。

02:44

キーポイント:

  • Adaptive-Sync モニターの更新間隔は動的です
  • フレームが完了した直後に表示されるため、次の固定リフレッシュ ポイントを待つ必要はありません。
  • 最大滞留時間を超えると、表示を更新する必要があり、一時的に使用できなくなります。

Adaptive-Sync 最佳实践

04:18

固定リフレッシュ レート モニターで、GPU の作業がリフレッシュ間隔を超えて継続する場合、Apple はフレーム レートを次の係数で下げることを推奨します (例: 60fps から 30fps)。

Adaptive-Sync モニターでは、このアドバイスは変わります。アプリケーションが確実に達成できる最高の均一フレーム レートでフレームをレンダリングする必要があります。

// 检测 Adaptive-Sync 是否可用
let screen = window.screen
let isAdaptiveSync = screen.minimumRefreshInterval != screen.maximumRefreshInterval
let isFullScreen = window.styleMask.contains(.fullScreen)

if isAdaptiveSync && isFullScreen {
    // Adaptive-Sync フレーム レート制御を有効にする
}

05:51

キーポイント:

  • minimumRefreshIntervalそしてmaximumRefreshIntervalNSScreenの新しいプロパティです
  • 两个值不相等表示 Adaptive-Sync 模式
  • ウィンドウが全画面表示かどうかも確認する必要があります
  • 只在全屏模式下才能使用 Adaptive-Sync 调度

presentAfterMinimumDuration を使用してフレーム レートを制御する

06:51

最も簡単な方法は使用することですpresentAfterMinimumDuration固定フレームレート:

let desiredInterval: CFTimeInterval = 1.0 / 78.0 // 目標 78Hz

// レンダリングループ内
drawable.present(afterMinimumDuration: desiredInterval)

07:11

キーポイント:

  • presentAfterMinimumDuration指定された間隔後にフレームがレンダリングされるようにする
  • ユーザーが調整可能な FPS スライダーの実装に適しています
  • フレームレートは安定していますが、最適ではない可能性があります

より高度なアプローチは、フレーム レートを動的に計算することです。

var averageGPUTime: CFTimeInterval = 1.0 / 120.0 // 初始乐观估计

// 第一帧:用乐观估计
let firstDrawable = getDrawable()
let commandBuffer = encodeWork()
commandBuffer.addCompletedHandler { _ in
    let gpuTime = CACurrentMediaTime() - frameStartTime
    // 滚动平均
    averageGPUTime = 0.9 * averageGPUTime + 0.1 * gpuTime
}
commandBuffer.commit()
firstDrawable.present(afterMinimumDuration: averageGPUTime)

08:44

キーポイント:

  • 使用CommandBuffer完成したハンドラーは GPU 時間を測定します
  • ローリング平均によりフレーム レートの変化が滑らかになります
  • 同じコード セットにより、パフォーマンスが異なる Mac 上のフレーム レートを自動的に調整します。
  • 弱い Mac では 48 Hz、強い Mac では 78 Hz まで動作する場合があります。

10:22

iPad Pro の ProMotion ディスプレイは最大 120Hz をサポートしますが、次の状況では使用できない場合があります。

  • ユーザーが低電力モードをオンにしました (iPadOS 15 の新機能)
  • ユーザーはアクセシビリティで制限フレーム レートを設定します (60 Hz に制限)
  • デバイスが過熱し、システムは 120Hz に制限されています

CADisplayLinkは、カスタム描画を実行するための推奨ツールです。 vsync ごとに起動しますが、これは通常よりも優れていますNSTimerより正確に。

// CADisplayLink のセットアップ
displayLink = CADisplayLink(target: self, selector: #selector(draw))
displayLink.preferredFramesPerSecond = 40
displayLink.add(to: .current, forMode: .common)

// 在回调中
@objc func draw() {
    let interval = displayLink.targetTimestamp - displayLink.timestamp
    // 間隔は実際に利用可能なレンダリング時間です
}

15:37

キーポイント:

  • preferredFramesPerSecond単なる提案です。実際のフレーム レートはシステムによって決定されます。
  • 60Hz モニターで 40Hz を要求すると、システムは自動的に 30Hz に調整します。
  • durationプロパティは現在の実際のフレーム間隔を反映します
  • targetTimestamp 而不是 timestamp 准备动画

タイムスタンプの代わりに targetTimestamp を使用する理由

17:46

フレームレートが 120Hz から 60Hz に低下した場合、timestampアニメーションの進行状況をサンプリングすると、ジャンプが発生します。

// タイムスタンプを使用: フレーム レートが切り替わるとアニメーションの進行状況がジャンプします。
// 帧1 (120Hz): timestamp=0, progress=0
// 帧2 (120Hz): timestamp=8ms, progress=0.05
// フレーム 3 (60Hz): タイムスタンプ = 16 ミリ秒、進行状況 = 0.10 <- ここにジャンプしてください。

targetTimestamp 则平滑:

// targetTimestamp を使用: フレーム レートを切り替えるときにアニメーションを滑らかにします
// 帧1 (120Hz): targetTimestamp=8ms, progress=0.05
// 帧2 (120Hz): targetTimestamp=16ms, progress=0.10
// フレーム 3 (60Hz): targetTimestamp=32ms、progress=0.20 <- スムーズな移行

19:26

キーポイント:

  • timestampコールバックが呼び出される時刻です
  • targetTimestamp次のフレームが合成される時間です
  • 使用targetTimestampサンプリング アニメーション、フレーム レートが変化してもジャンプはありません
  • コードの変更は非常に簡単です。timestampと置き換えますtargetTimestamp

不在のコールバックの処理

20:16

優先度の高いスレッドまたはビジーな実行ループにより、CADisplayLinkコールバックが遅延またはスキップされます。

@objc func draw() {
    let currentTime = CACurrentMediaTime()
    let timeRemaining = displayLink.targetTimestamp - currentTime

    if timeRemaining < minimumWorkTime {
        // 時間が足りません。作業負荷を減らすか、このフレームをスキップしてください
        return
    }

    // 前のフレームの targetTimestamp を使用して正しい時間増分を計算します。
    let delta = displayLink.targetTimestamp - previousTargetTimestamp
    previousTargetTimestamp = displayLink.targetTimestamp

    // デルタを使用してアニメーションの状態を更新する
    updateAnimation(delta: delta)
}

21:48

キーポイント:

  • 使用CACurrentMediaTime()残り時間を確認する
  • 予約するpreviousTargetTimestamp正しい時間増分を計算する
  • コールバックがスキップされると、デルタは自動的にフレーム間隔の 2 倍になります
  • 动画不会减速或加速

重要ポイント

  1. メタル ゲーム用の Adaptive-Sync サポートを追加。検出minimumRefreshInterval != maximumRefreshIntervalまた、ウィンドウが全画面の場合は、ローリング平均 GPU 時間を使用してフレーム レートを動的に制御します。入口API:NSScreen.minimumRefreshInterval + MTLDrawable.present(afterMinimumDuration:)

  2. CADisplayLink のすべてのタイムスタンプを targetTimestamp に置き換えます。これは、フレーム レートを切り替えるときにアニメーションのジャンプを回避する唯一の正しい方法です。入口API:CADisplayLink.targetTimestamp

  3. カスタム アニメーション用のフレーム レート低下ロジックを追加。いつtimeRemaining < minimumWorkTimeワークロードを軽減するかフレームをスキップして、フレームが失われないようにします。入口API:CACurrentMediaTime() + displayLink.targetTimestamp

  4. iPad アプリで ProMotion が利用できるかどうかを検出します。使用CADisplayLink.duration実際のフレーム間隔を取得します。使用しません。UIScreen.maximumFramesPerSecond(常に 120 が返されます)。入口API:CADisplayLink.duration

  5. ユーザーが調整可能な FPS キャップをゲームに追加します。使用presentAfterMinimumDurationフレーム レートを固定し、GPU の電力消費と発熱を削減します。入口API:MTLDrawable.present(afterMinimumDuration:)

関連セッション

コメント

GitHub Issues · utterances