OpenXR API編

C++ で OpenXR アプリを作るメモ。
`Quest` 向けに `GLES3` + `NDK` を主に。

https://registry.khronos.org/OpenXR/specs/1.0/man/html/

# OpenXRのパターン

## Swapchain を作る
* entry_point(main, android_main)
* Graphics 初期化(OpenGL コンテキストや D3Dデバイス初期化)
* XrInstance: インスタンス作成
* XrSystemID 取得
* XrSession 作成(Graphicsに依存, XrInstanceに対象のGraphics拡張を入れる必要あり)
* XrSwapchain 初期化(Graphicsに依存, XrInstanceに対象のGraphics拡張を入れる必要あり)

:::note
- XrInstance: Graphics拡張を指定する
- XrSystemID: どんなGraphicsに対応しているかの情報を含む
- XrSession, XrSwapchain: Graphics に依存して生成する

OpenGL, OpenGLES, D3D11, D3D112, Vulkan 等のどれを使うかを最初に決定させるのがシンプル。
`XrSystemID` の情報を見て複数の Graphics をスイッチすることもできるが自作ではそこまでしなくてよいだろう。
:::

## Mainloop
* Mainloop
  * Platform の EventHandling(Android, glfw...etc)
  * OpenXR の EventHandling(session の active を判定)
  * if XrSession active
    * xrWaitFrame
    * xrBeginFrame
    * EachView(left, right...)
      * xrWaitSwapchainImage
      * xrAcquireSwapchainImage
      * render(OpenGLなど)
      * xrReleaseSwapchainImage
    * xrEndFrame

# 初期化

```c++
  XrInstance m_instance;
  XrSession m_session;
  XrSystemId m_systemId;
```

## XrResult

すべて？の OpenXR 関数は XrResult で成功したかどうか返す。

```c
typedef enum XrResult {
    XR_SUCCESS = 0,
    // 省略
} XrResult;

// error 文字列
  if (XR_FAILED(ret)) {
    char errbuf[XR_MAX_RESULT_STRING_SIZE];
    xrResultToString(m_instance, ret, errbuf);
    // logger
  }
```

## xrInitializeLoaderKHR

```c++
        // Initialize the loader for this platform
        PFN_xrInitializeLoaderKHR initializeLoader = nullptr;
        if (XR_SUCCEEDED(
                xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)(&initializeLoader)))) {
            XrLoaderInitInfoAndroidKHR loaderInitInfoAndroid;
            memset(&loaderInitInfoAndroid, 0, sizeof(loaderInitInfoAndroid));
            loaderInitInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
            loaderInitInfoAndroid.next = NULL;
            loaderInitInfoAndroid.applicationVM = app->activity->vm;
            loaderInitInfoAndroid.applicationContext = app->activity->clazz;
            initializeLoader((const XrLoaderInitInfoBaseHeaderKHR*)&loaderInitInfoAndroid);
        }
```

## xrCreateInstance

```c++
struct android_app *app;

  XrInstanceCreateInfoAndroidKHR ciAndroid = {
      .type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR,
      .applicationVM = app->activity->vm,
      .applicationActivity = app->activity->clazz,
  };
  std::array<const char *, 2> extensions = {
      "XR_KHR_android_create_instance",
      "XR_KHR_opengl_es_enable",
  };
  XrInstanceCreateInfo ci = {
      .type = XR_TYPE_INSTANCE_CREATE_INFO,
      .next = &ciAndroid,
      .applicationInfo =
          {
              .apiVersion = XR_CURRENT_API_VERSION,
          },
      .enabledExtensionCount = (uint32_t)extensions.size(),
      .enabledExtensionNames = extensions.data(),
  };
  strncpy(ci.applicationInfo.applicationName, "OXR_GLES_APP",
          XR_MAX_ENGINE_NAME_SIZE - 1);
  auto xrResult = xrCreateInstance(&ci, &m_instance);
```

## xrGetSystem

```c++
  XrSystemGetInfo sysInfo = {
      .type = XR_TYPE_SYSTEM_GET_INFO,
      .formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY,
  };
  auto xrResult = xrGetSystem(m_instance, &sysInfo, &m_systemId);
```

## Graphics初期化

## xrCreateSession

# View
## Swapchain

以下のような構成になっている。

```
leftView
  swapchain
    [swapchainImage, swapchainImage, ...]
rightView
  swapchain
    [swapchainImage, swapchainImage, ...]
```
## xrWaitSwapchainImage
## xrAcquireSwapchainImage
## render

XrSwapchainImage に対して XrView で描画する。

### XrView

#### projection matrix

https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrFovf.html

```c++
typedef struct XrFovf {
    float    angleLeft; // 👈 負
    float    angleRight;
    float    angleUp;
    float    angleDown; // 👈 負
} XrFovf;
```

https://github.com/jglrxavpok/Carrot/blob/55332acf03730d133a813bd2fc572c54ff955037/engine/engine/utils/conversions.h#L39

#### view matrix

https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html

## xrReleaseSwapchainImage

# Frame
## Event
## xrWaitFrame, xrBeginFrame
## xrLocateViews, xrLocateSpace
## xrEndFrame

# Space
- https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html

原点を定義する概念。

## XR_REFERENCE_SPACE_TYPE_VIEW
HMD の View を原点とする。HUD など画面に貼る付ける要素に使う

## XR_REFERENCE_SPACE_TYPE_LOCAL
seated-scale 向け。初期位置を原点とする。
recentering できる。`XrEventDataReferenceSpaceChangePending` event

## XR_REFERENCE_SPACE_TYPE_STAGE
standing-scale, room-scale 向け。
地面に四角形を配置し、その中心が原点。
ユーザーが事前に設定したもの。
設定が無いときは参照できない。

# 拡張
拡張の一覧や対応状況がまとめられている

https://github.khronos.org/OpenXR-Inventory/extension_support.html

## XR_EXT_hand_tracking

https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_hand_tracking

https://developer.oculus.com/documentation/native/android/mobile-hand-tracking/

# 参考

openxr を使ったアプリが整理されていて、大変参考になります。
マルチプラットフォームの OpenXR-SDK の hello_xr よりも quest 向けで読み易いのでおすすめ。

- `OpenXRMobileSDK-0.38` `cmake` https://github.com/terryky/android_openxr_gles