始めに
そのときにAnalyzerで処理を調べたところ、アイトラッキングの情報取得が重いことが分かりました。なので、取得処理をコールバックに変更してどれだけ軽くなるのかを調べてまとめようと思います。
アイトラッキングの情報の取得方法について
SRanipal_Eye_AIP.GetEyeData_v2
[DllImport("SRanipal")] public static extern Error GetEyeData_v2(ref EyeData_v2 data);
このメソッドは引数に EyeData_v2
の参照を渡して、そこにトラッキング情報を保持させて結果を受け取れるメソッドです。
このメソッドを直接呼び出すことはほとんどなく、主に SRanipal_Eye_v2.Update()
で呼び出されます。
private static bool UpdateData() { if (Time.frameCount == LastUpdateFrame) return LastUpdateResult == Error.WORK; else LastUpdateFrame = Time.frameCount; LastUpdateResult = SRanipal_Eye_API.GetEyeData_v2(ref EyeData_); return LastUpdateResult == Error.WORK; }
また、この UpdateData()
はトラッキング情報の一部(目の開閉や瞳の位置など)を取得するときに呼び出されるようになっています。(ただし、同フレーム内で1度だけ呼ばれる)
public static bool GetEyeOpenness(EyeIndex eye, out float openness) { UpdateData(); return GetEyeOpenness(eye, out openness, EyeData_); }
RegisterEyeDataCallback_v2
[DllImport("SRanipal")] public static extern int RegisterEyeDataCallback_v2(IntPtr callback);
このメソッドは引数にコールバック関数のポインタを渡して、そのコールバック関数を経由してトラッキング情報を取得します。
また、このメソッドを直接呼ぶのではなくラッピングされたメソッドを経由して呼び出されます。
public static int WrapperRegisterEyeDataCallback(System.IntPtr callback) { return SRanipal_Eye_API.RegisterEyeDataCallback_v2(callback); }
サンプルでのこのメソッドの使用方法は次のようになっています。処理としては、コールバック関数を登録出来る場合は登録を行い、出来ない場合は登録を解除するとなっています。
注意すべき点として、コールバック関数は static 関数でなくてはいけません。(それ以外はUnityが落ちたりします)
アイトラッキングの情報を使用する場合は static 変数の eyeData
を使って情報を取得します。
private static EyeData_v2 eyeData private void Update() { if (SRanipal_Eye_Framework.Status != SRanipal_Eye_Framework.FrameworkStatus.WORKING && SRanipal_Eye_Framework.Status != SRanipal_Eye_Framework.FrameworkStatus.NOT_SUPPORT) return; if (SRanipal_Eye_Framework.Instance.EnableEyeDataCallback == true && eye_callback_registered == false) { SRanipal_Eye_v2.WrapperRegisterEyeDataCallback(Marshal.GetFunctionPointerForDelegate((SRanipal_Eye_v2.CallbackBasic)EyeCallback)); eye_callback_registered = true; } else if (SRanipal_Eye_Framework.Instance.EnableEyeDataCallback == false && eye_callback_registered == true) { SRanipal_Eye_v2.WrapperUnRegisterEyeDataCallback(Marshal.GetFunctionPointerForDelegate((SRanipal_Eye_v2.CallbackBasic)EyeCallback)); eye_callback_registered = false; } } private static void EyeCallback(ref EyeData_v2 eye_data) { eyeData = eye_data; }
また、注意としてコールバックを使用する場合は SRanipal_Eye_Framework
の Enable Eye Data Callback
にチェックを入れる必要があります。
簡単なサンプルでの計測
SRanipal_Eye_AIP.GetEyeData_v2
と RegisterEyeDataCallback_v2
でそれぞれトラッキングの情報を取得して、負荷を計測してみます。
SRanipal_Eye_AIP.GetEyeData_v2
サンプルプログラムは次のようになります。
using UnityEngine; using ViveSR.anipal.Eye; public class ViveProEyeTrackingInput_GetByMethod : MonoBehaviour { private EyeData_v2 _eyeData; private void Update() { var openness = GetEyeOpenness(EyeIndex.LEFT); Debug.Log(openness); } private float GetEyeOpenness(EyeIndex eyeIndex) { SRanipal_Eye_v2.GetEyeOpenness(eyeIndex, out var openness); return openness; } }
計測結果は次のようになります。
Update
処理自体で約5ms、SRanipal_Eye_AIP.GetEyeData_v2
だけで 4.67ms も使っています。
RegisterEyeDataCallback_v2
サンプルプログラムです。
using System.Runtime.InteropServices; using UnityEngine; using ViveSR.anipal.Eye; public class ViveProEyeTrackingInput : MonoBehaviour { private static EyeData_v2 _eyeData; private bool eye_callback_registered = false; private void Update() { if (SRanipal_Eye_Framework.Status != SRanipal_Eye_Framework.FrameworkStatus.WORKING && SRanipal_Eye_Framework.Status != SRanipal_Eye_Framework.FrameworkStatus.NOT_SUPPORT) return; if (SRanipal_Eye_Framework.Instance.EnableEyeDataCallback && !eye_callback_registered) { SRanipal_Eye_v2.WrapperRegisterEyeDataCallback( Marshal.GetFunctionPointerForDelegate((SRanipal_Eye_v2.CallbackBasic) EyeCallback)); eye_callback_registered = true; } else if (!SRanipal_Eye_Framework.Instance.EnableEyeDataCallback && eye_callback_registered) { SRanipal_Eye_v2.WrapperUnRegisterEyeDataCallback( Marshal.GetFunctionPointerForDelegate((SRanipal_Eye_v2.CallbackBasic) EyeCallback)); eye_callback_registered = false; } var openness = GetEyeOpenness(EyeIndex.LEFT); Debug.Log(openness); } private float GetEyeOpenness(EyeIndex eyeIndex) { SRanipal_Eye_v2.GetEyeOpenness(eyeIndex, out var openness, _eyeData); return openness; } private static void EyeCallback(ref EyeData_v2 eye_data) { _eyeData = eye_data; } }
計測結果は次のようになります。
Update
の処理が 0.250ms とかなり小さくなりました。取得処理自体はコールバックになったので、Analyzer で取得している箇所が見えなくなりました。
比較結果
SRanipal_Eye_AIP.GetEyeData_v2
の Analyzer を見て頂けたらわかると思いますが、アイトラッキングの情報を取得する処理だけで、PlayerLoop の6割以上を占めています。
対して、コールバックを使用した方は PlayerLoop 自体が0.250msとかなり小さくなりました。 もし、アイトラッキングの情報を取得する場合であれば、少しめんどくさいですが、コールバックで情報を取得したほうが良いでしょう。
ひとりごと
アイトラッキングではコールバックによる取得ができるのに、フェイストラッキングではコールバックがないのが悔やまれる。(アイトラッキングに比べたら小さいが 1ms ぐらい取得にかかる)