なおしのこれまで、これから

学んだこと・感じたこと・やりたいこと

【Vive Pro Eye】アイトラッキングのデータを取得する場合はコールバックを使った方が良い

始めに

前回アイトラッキングの情報をアバターに反映させました。

shitakami.hatenablog.com


そのときに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_FrameworkEnable Eye Data Callback にチェックを入れる必要があります。

f:id:vxd-naoshi-19961205-maro:20220222010255p:plain



簡単なサンプルでの計測

SRanipal_Eye_AIP.GetEyeData_v2RegisterEyeDataCallback_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 も使っています。

f:id:vxd-naoshi-19961205-maro:20220222004954p:plain



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 で取得している箇所が見えなくなりました。

f:id:vxd-naoshi-19961205-maro:20220222005221p:plain



比較結果

SRanipal_Eye_AIP.GetEyeData_v2 の Analyzer を見て頂けたらわかると思いますが、アイトラッキングの情報を取得する処理だけで、PlayerLoop の6割以上を占めています。

対して、コールバックを使用した方は PlayerLoop 自体が0.250msとかなり小さくなりました。 もし、アイトラッキングの情報を取得する場合であれば、少しめんどくさいですが、コールバックで情報を取得したほうが良いでしょう。



ひとりごと

アイトラッキングではコールバックによる取得ができるのに、フェイストラッキングではコールバックがないのが悔やまれる。(アイトラッキングに比べたら小さいが 1ms ぐらい取得にかかる)



最後に