3
2

More than 3 years have passed since last update.

【iOS】Metal Best Practicesの解説(8) フレームレート

Last updated at Posted at 2021-08-30

Metal Best Practicesは、iOS/MacOS/tvOSのAPIであるMetalを用いた設計のベストプラクティスガイドです。

本稿では、何回かに分けてこのガイドを読み解き、コード上での実験を交えて解説していきます。
読んでそのまま理解できそうなところは飛ばしますので、原文を読みながら原文のガイドとしてご利用下さい。
また、iOSの記事なので他のOS(MacOS, tvOS)についての記載は割愛します。

他の記事の一覧は、初回記事よりご覧下さい。

Frame Rate (iOS and tvOS)(フレームレート)

ベストプラクティス:一貫した安定したフレームレートでドローアブルを表示します。

アプリは一定したフレームレートを維持できるようにしましょう、という話。フレームレートが一定でない場合Jitterと呼ばれる現象が起きます。Jitterは遅延の変動のことで、これが起きると画面のモーションにカクつきが出るためユーザーに不快な印象を与えます。シューティングゲームでこれがあると操作しづらいでしょうね😅 

アプリが、60FPSを維持できるような処理速度であれば何も問題はありません。ただ、それが難しいときはフレームレートを下げる必要があります。

iOSのフレームレートはUIScreenのmaximumFramesPerSecondプロパティで参照でき、通常60FPSです。

MTKViewは、フレームレートの調整がしやすくなっていて、preferredFramesPerSecondプロパティを使うことで、指定したFPSにすることができます。

ドローアブルの表示時間の固定

preferredFramesPerSecondに30FPSなど低いフレームレートを指定して、アプリのレンダリングループを遅くしても、ディスプレイのリフレッシュレートは60FPSのままなので、タイミングによってはアプリの処理が60FPSの間隔(16.67ms)に間に合ってしまうことがります。これによりフレームレートが一定せずJitterが起きます。

そこで、presentDrawableのafterMinimumDuration引数を指定すると、ドローアブルの最小表示時間を指定できます。この引数に、1.0/preferredFramesPerSecondを指定すると、preferredFramesPerSecondのフレームレートにあわせたドロアーブルの表示間隔を保ってくれるので、アプリのレンダリングループとドローアブルの表示間隔が同期し、Jitterの問題を回避できます。

コードで検証してみる

何も指定しない場合と、フレームレートを固定した場合、ドロアーブルの表示間隔をフレームレートにあわせた場合をコードで比較してみます。

サンプルコードはこちらにあるコードを改変して作ります。

実行すると、パーティクルが上から下に流れてきます。

(実行イメージ)

20210829_frame_rate.gif

検証のためにパーティクルの数を10万に増やします。

TripleBufferingMetalView.swift
class Coordinator : NSObject, MTKViewDelegate {
    static let numberOfParticles = 100000

そのまま実行した場合

サンプルコードをそのまま実行してみます。

FPSは46〜48FPSの間で変動していました。パーティクルの流れは滑らかさに欠ける感じがあります。
(スクリーンキャプチャしてGIFアニメするとGIFのカクつきの方が大きかったので、キャプチャは省略します)

Metal System Traceを使って実行中の状態を取得してみます。
このツールの使い方はこちらの記事をご覧ください。

(実行中の状態)

image.png

このコードは、パーティクルを動かす処理をCPU側でやっているので、遅延の原因の殆どがCPUの処理によるものです。

ディスプレイのリフレッシュ間隔が49msになったり、16msになったりと一定していないようです。平均すると21ms(=46FPS)ぐらいです。

MTKViewDelegateのdrawメソッドは内部タイマーに基づいて呼び出されますが、呼び出しの間隔は21ms〜28msの間で変化していました。

60FPSに間に合っていても間に合わなくても、準備ができたらすぐに描画、といった感じでしょうか。

フレームレートを固定した場合

MTKViewのpreferredFramesPerSecondを使ってフレームレートを30FPSに固定してみます。

次のようにコードを変更します。

TripleBufferingMetalView.swift
func makeUIView(context: Context) -> MTKView {
    // 〜中略〜
    mtkView.drawableSize = mtkView.frame.size
    mtkView.colorPixelFormat = .bgra8Unorm_srgb
    mtkView.preferredFramesPerSecond = 30 // 👈これを追加
    return mtkView
}

実行するとFPSは30で一定のままになりました。パーティクルのカクつきはほとんどなくなったように見えます。

(実行中の状態)

image.png

drawメソッドはの呼び出しの間隔も33msで一定でしたが、たまに処理がディスプレイのリフレッシュ間隔(16.67ms)に間に合うことがあるようで、その場合は16ms間隔でリフレッシュされ、その後33msに戻ったりと一定のリフレッシュ間隔にはなっていません。

この結果からの推測ですが、preferredFramesPerSecondは、おそらくMTKViewDelegateのdrawメソッドの更新間隔を決定しているものと思われます。preferredFramesPerSecondで30FPSを指定すると、33msの間隔でdrawメソッドは呼ばれますが、ディスプレイのリフレッシュレートは60FSPのままなので、タイミングによっては、16msの間隔で画面が更新されるようです。

フレームレートを固定 + ドロアーブルの表示間隔を固定した場合

フレームレートの固定に加え、ドローアブルの表示間隔も固定してみます。

TripleBufferingMetalView.swift
func draw(in view: MTKView) {
        renderEncoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: Coordinator.numberOfParticles)        
        renderEncoder.endEncoding()        
//      👇引数afterMinimumDurationを追加
        commandBuffer.present(drawable, afterMinimumDuration: 1.0/Double(parent.mtkView.preferredFramesPerSecond))
        commandBuffer.addCompletedHandler {[weak self] _ in
            self?.semaphore.signal()
        }
        commandBuffer.commit()
    }
}

こちらも実行すると30FPSで一定になります。パーティクルのカクつきは全くなく、とても滑らかになったように見えます。(目視なので気のせいかもしれませんが。。)

(実行中の状態)

image.png

drawメソッドの呼び出しの間隔は33msで一定しており、ディスプレイのリフレッシュ間隔も33msで一定したまま続いているのがわかります。

結論

今回は、アプリが60FPSを守れない場合の対策、のような感じになりましたが、30FPSなどフレームレートを下げても滑らかな動きを実現するためには、preferredFramesPerSecondでフレームレートを固定し、presentDrawableのafterMinimumDurationでドローアブルの表示間隔を同期させる必要がありそうです。

最後に

iOSを使った3D処理やAR、ML、音声処理などの作品やサンプル、技術情報を発信しています。
作品ができたらTwitterで発信していきますのでフォローをお願いします🙏

Twitterは作品や記事のリンクを貼っています。
https://twitter.com/jugemjugemjugem

Qiitaは、iOS開発、とくにARや機械学習、グラフィックス処理、音声処理について発信しています。
https://qiita.com/TokyoYoshida

Noteでは、連載記事を書いています。
https://note.com/tokyoyoshida

Zennは機械学習が多めです。
https://zenn.dev/tokyoyoshida

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2