TL;DR

  • OpenXR은 XR 앱과 벤더 런타임 사이의 공통 API를 제공해 플랫폼 파편화를 줄인다.
  • Loader, Runtime, Session State Machine, Frame Loop, Action System을 이해해야 실제 디버깅과 성능 튜닝이 가능하다.
  • Meta, Valve, Microsoft, Google, Pico가 사실상 OpenXR 표준 생태계로 수렴했고 Apple만 독자 노선을 유지한다.

1. 개념

OpenXR은 VR·AR·MR 앱이 단일 API로 다양한 헤드셋 런타임을 호출하도록 만든 Khronos 표준이다. 앱은 Loader를 통해 활성 Runtime에 연결되고, 세션·입력·공간·프레임 제출을 일관된 방식으로 다룬다.

2. 배경

초기 XR 시장은 Oculus SDK, OpenVR, WMR, Daydream, Wave처럼 플랫폼별 API가 분리돼 있었다. 개발사는 기기별로 입력·렌더링·세션 로직을 반복 구현해야 했고, 이 비용이 생태계 확장에 큰 마찰이 됐다.

3. 이유

OpenXR을 제대로 이해해야 런타임별 차이, predicted display time, reference space, extension 지원 여부, OpenVR/Oculus SDK 마이그레이션 같은 실무 이슈를 정확히 처리할 수 있다.

4. 특징

  • Loader–API Layer–Runtime으로 분리된 표준 아키텍처
  • 세션 상태 머신, 프레임 루프, swapchain, composition layer 중심 설계
  • Action System과 interaction profile 기반 입력 추상화
  • KHR/EXT/vendor extension으로 기능 확장
  • Unity, Unreal, Godot, WebXR와 연결되는 광범위한 엔진 생태계

5. 상세 내용

OpenXR 크로스플랫폼 XR 표준 완전가이드

작성일: 2026-04-29 카테고리: XR / Graphics / Khronos / Game Dev 포함 내용: Khronos Group, OpenXR Loader, API Layers, Runtime, XrInstance, XrSession, XrSpace, XrAction, XrSwapchain, Reference Spaces (VIEW/LOCAL/STAGE/LOCAL_FLOOR/UNBOUNDED), Composition Layers, xrWaitFrame, xrBeginFrame, xrEndFrame, xrPollEvent, Session State Machine, Predicted Display Time, Late Latching, Foveated Rendering, Application SpaceWarp, Multiview, Action System, Interaction Profile, Suggested Bindings, Graphics Binding (Vulkan/D3D11/D3D12/OpenGL/OpenGL ES), Project Treble 비교, Monado, Meta Quest Horizon OS, SteamVR, WMR/HoloLens 2, Pico, Varjo, HTC Vive, Magic Leap 2, Lynx, Snap Spectacles, Apple visionOS 부재, OpenVR/Oculus SDK 마이그레이션, Unity OpenXR Plugin, XR Interaction Toolkit, Meta XR SDK, Unreal Meta XR Plugin, Godot 4 OpenXR, BabylonJS/Three.js WebXR, StereoKit, Bevy XR, Android XR (Google), CTS, OpenXR 1.1, Privacy Sandbox, Snapdragon Spaces


1. OpenXR이란?

핵심 개념

┌─────────────────────────────────────────────────────────────────┐
│                  OpenXR 한 줄 정의                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "Khronos가 관리하는 royalty-free 개방 표준 — VR/AR/MR(통칭 XR)  │
│   하드웨어와 소프트웨어를 분리하는 크로스플랫폼 C API"            │
│                                                                  │
│  ┌──────────────────────────────────────────────────────┐       │
│  │  App                                                  │       │
│  │   │  단일 OpenXR API                                  │       │
│  │   ▼                                                   │       │
│  │  Loader (Khronos 제공, 앱에 번들)                     │       │
│  │   ├── API Layers (validation/dump/...)                │       │
│  │   └── Runtime (Meta/SteamVR/Monado/MSFT/Pico/...)    │       │
│  │                                                        │       │
│  │  Write once, run on any conformant runtime            │       │
│  └──────────────────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────────────────┘

XR / VR / AR / MR 관계

약어 풀 이름 의미
VR Virtual Reality 물리 세계 차단, 디지털 세계 몰입
AR Augmented Reality 물리 세계 위에 디지털 오버레이
MR Mixed Reality 물리/디지털 실시간 상호작용, 공간 앵커
XR eXtended Reality VR/AR/MR 전체를 포괄하는 우산 용어

왜 “OpenXR”이지 “OpenVR”이 아닌가?

  1. OpenVR은 이미 Valve의 등록상표: 2016년부터 SteamVR용으로 Valve가 사용 중
  2. 범위 차이: OpenXR은 VR뿐 아니라 AR, MR, 미래의 어떤 형태도 포괄
  3. 컨소시엄 중립성: 다양한 회원사(Oculus VR, Microsoft MR, Magic Leap AR)의 이해를 모두 아우름

Khronos Group의 어원

Χρόνος (Chrónos) — 그리스 신화의 “측정 가능한 선형 시간”의 신. 그리스인들은 시간을 χρόνος(연대기적 시간)와 καιρός(결정적 순간)로 구분했다. Khronos Group은 전자를 택했는데, 이는 “실시간(real-time) 컴퓨팅과 그래픽 처리”를 다루는 조직 성격에 정확히 부합한다 — 시간이 곧 프레임, 렌더링, 지연(latency)의 언어이기 때문이다.

2000년 설립. 비영리 개방 컨소시엄. 주요 표준: OpenGL(이관 1992), OpenGL ES(2003), WebGL(2011), OpenCL(2009), Vulkan(2016), SPIR-V(2015), glTF(2015), OpenXR(2019).


2. 등장 배경 (Pre-OpenXR Fragmentation)

2.1 2013~2017 파편화 시대

┌─────────────────────────────────────────────────────────────────┐
│              "왜 또 다른 표준이 필요했는가"                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  앱이 동시 지원해야 했던 SDK들:                                   │
│                                                                  │
│  Oculus PC SDK (LibOVR)        — Rift, Rift S용                  │
│  Oculus Mobile SDK (VrApi)     — Gear VR, Quest용                │
│  OVRPlugin (Unity)             — 위 둘을 Unity에서 통합          │
│  OpenVR / SteamVR (Valve)      — Vive, Index, WMR (SteamVR 종속)│
│  Windows Mixed Reality API     — HoloLens, WMR HMD              │
│  Google VR SDK (Daydream)      — Android Daydream (2019 종료)    │
│  Vive Wave SDK (HTC)           — Vive Focus 시리즈               │
│  PSVR SDK (Sony)               — PSVR 콘솔 전용                  │
│  OSVR (Razer)                  — 2015 시도, 실패                 │
│                                                                  │
│  → 개발사는 N배 구현 비용                                        │
│  → 하드웨어 벤더가 자사 런타임에 개발자 가두기 경쟁              │
│  → 콘텐츠 부족 → 사용자 부족 → 개발자 외면 악순환                │
└─────────────────────────────────────────────────────────────────┘

2.2 OpenVR의 한계

Valve의 OpenVR은 다수 HMD를 지원했지만 SteamVR 런타임이 항상 중간에 존재해야 했다. “Open”이라는 이름과 달리 Valve가 런타임 계층을 통제했다.

2.3 OSVR 실패의 교훈

Razer + Sensics가 2015년 시작한 OSVR(Open Source Virtual Reality)은 “VR의 Android”를 표방했지만 콘텐츠 부재, 하드웨어 정밀도 부족, 소프트웨어 미성숙으로 실패. Razer는 OSVR을 포기하고 Khronos OpenXR 창립 회원으로 합류했다 — 중립적 대형 컨소시엄 없이 오픈 표준 성공이 어렵다는 교훈.


3. 역사적 기원 (Historical Origins)

연도 사건
2016 초 Khronos 회원 면대면 회의에서 XR API 파편화 해결 탐색 그룹 형성
2016.03~04 Oculus Rift CV1 / HTC Vive 출시 — 두 PC VR 플랫폼이 완전히 다른 SDK
2017.02.27 (GDC 2017) Khronos가 OpenXR Working Group 공식 설립 발표. AMD, Intel, ARM, NVIDIA, Epic Games, Unity, Oculus/Facebook, Microsoft, Sony, Qualcomm, HTC 등
2019.03.18 OpenXR 0.90 Provisional Specification 공개
2019.07.29 (SIGGRAPH 2019) OpenXR 1.0 공식 비준 + 공개 릴리스. Monado(Linux), WMR, Oculus Rift 즉시 구현체 제공
2020.06 Valve, SteamVR OpenXR Developer Preview 공개. CTS 95% 통과
2020.11 Microsoft, OpenXR 1.0 Conformant 인증 (HoloLens 2 + WMR)
2021.02 Valve SteamVR, OpenXR 1.0 Conformant 등재
2021.07.23 Meta “All In on OpenXR” 공식 선언. Oculus 독자 API 단계적 종료 발표
2022.08.31 Oculus Mobile SDK(VrApi) + Oculus PC API 완전 지원 종료(Unsupported)
2022 PICO (ByteDance), OpenXR 1.0 Conformant Runtime 제공
2024.04.15 OpenXR 1.1 릴리스 — Local Floor, Palm Pose, Quad Views 등 5개 Extension을 Core로 승격. 연간 정기 릴리스 사이클 선언
2024.02 Apple Vision Pro 출시 — OpenXR 미지원. RealityKit + CompositorServices 독자 스택
2024.07 Varjo XR-4 시리즈 OpenXR 1.0 Conformant
2024.12.12 Google이 Android XR 발표 — OpenXR 1.1 기반. Samsung Galaxy XR (Project Moohan) 첫 출시 기기
2024.11 Pico 4 Ultra OpenXR 1.1 Conformant
2025~ OpenXR이 사실상 업계 표준화. Apple만 미참여

SIGGRAPH 2019 발언:

  • Brent Insko (Intel, OpenXR WG 의장): “Now is the time for software developers to start putting OpenXR to work.”
  • Don Box (Microsoft Technical Fellow): “The mobile era was defined by closed ecosystems. With mixed reality, the next wave must be open.

4. 용어 사전 (Terminology Dictionary)

용어 풀 이름 의미
OpenXR “Open eXtended Reality”. XR 우산 표준.
Khronos Group 그리스어 Chrónos(시간)에서 유래. 2000년 설립 비영리 개방 컨소시엄
CTS Conformance Test Suite 런타임이 사양을 올바르게 구현했는지 검증하는 자동/수동 테스트
Loader OpenXR Loader 앱과 함께 배포되는 벤더 중립 라이브러리. Active Runtime 탐색·로드
Runtime OpenXR Runtime 벤더(Meta/Valve/MSFT 등) 제공 구현. 트래킹, 컴포지터, 렌즈 왜곡 보정 등
API Layer 앱과 런타임 사이 선택적 미들웨어. validation, debug, profiling
XrInstance 런타임 최상위 핸들. xrCreateInstance로 생성
XrSession 그래픽스 바인딩된 렌더링 세션. 상태 머신 동반
XrSpace 3D 좌표계. Reference Space 또는 Action Space
XrAction / XrActionSet 의미적 입력 단위. 하드웨어 추상화
XrSwapchain 런타임 관리 텍스처 배열
Reference Space VIEW / LOCAL / STAGE / LOCAL_FLOOR / UNBOUNDED 좌표계 종류
Composition Layer Projection / Quad / Cylinder / Equirect / Cube xrEndFrame에 제출하는 렌더링 단위
Interaction Profile 컨트롤러 표준 경로 (/interaction_profiles/oculus/touch_controller 등)
Suggested Binding 앱이 제안하는 Action↔하드웨어 매핑. 런타임이 사용자 리바인딩 가능
Graphics Binding xrCreateSession 시 GPU 디바이스 선언. Vulkan/D3D11/D3D12/GL/GLES
predictedDisplayTime xrWaitFrame이 반환하는 미래 프레임 표시 시각. 헤드 포즈 예측에 사용
Late Latching GPU 큐 직전에 최신 포즈로 재투영 (motion-to-photon ↓)
ATW / ASW Asynchronous Time Warp / SpaceWarp 컴포지터가 누락 프레임을 보간
Foveated Rendering FFR / ETFR 시야 중심부만 고해상도 렌더링
Multiview 두 눈 동시 렌더링 (single pass stereo)
Monado Collabora 주도 오픈소스 OpenXR 런타임. Android XR/Pico/CloudXR/Snapdragon Spaces 기반
WebXR W3C JavaScript API. 브라우저가 내부적으로 OpenXR을 호출 (Chromium)

5. 3계층 아키텍처

5.1 전체 구조

┌──────────────────────────────────────────────────┐
│            Application (앱 / 게임 엔진)            │
└──────────┬───────────────────────────────────────┘
           │ OpenXR C API
           ▼
┌──────────────────────────────────────────────────┐
│       OpenXR Loader (Khronos)                    │
│  - 앱과 함께 배포 (또는 시스템 설치)                │
│  - Trampoline functions: 앱 → 첫 레이어 라우팅      │
│  - Per-instance dispatch table                   │
└──────────┬───────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────┐
│       API Layer Chain (선택적, 0개 이상)          │
│  XR_APILAYER_LUNARG_core_validation              │
│  XR_APILAYER_LUNARG_api_dump                     │
│  XR_APILAYER_LUNARG_best_practices               │
└──────────┬───────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────┐
│       Runtime Terminator (마지막 레이어)           │
└──────────┬───────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────┐
│   XR Runtime (벤더별)                             │
│   Meta Quest / SteamVR / Monado / WMR /          │
│   Pico / Varjo / Magic Leap / Android XR / ...   │
└──────────┬───────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────┐
│        Hardware Driver / OS / HMD                │
└──────────────────────────────────────────────────┘

5.2 Active Runtime 선택 메커니즘

Windows

HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1
  ActiveRuntime = "C:\...\oculus_openxr_64.json"
HKLM\...\AvailableRuntimes
  oculus_openxr.json = 0
  steamxr.json       = 0

Linux (XDG)

$XDG_CONFIG_HOME/openxr/1/active_runtime.json
/etc/xdg/openxr/1/active_runtime.json    ← 배포판 기본

Android

<!-- AndroidManifest.xml: 런타임 패키지 발견을 위한 Intent Category -->
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD"/>

ContentProvider 조회: org.khronos.openxr.runtime_broker(사용자 설치) → org.khronos.openxr.system_runtime_broker(시스템). 폴백: /vendor/etc/openxr/1/active_runtime.json.

환경변수 오버라이드 (전 플랫폼)

export XR_RUNTIME_JSON=/path/to/runtime/manifest.json

5.3 Runtime Manifest 예시

{
  "file_format_version": "1.0.0",
  "runtime": {
    "name": "Monado",
    "library_path": "/usr/lib/libopenxr_monado.so",
    "functions": {
      "xrCreateInstance": "monado_xrCreateInstance"
    },
    "instance_extensions": [
      {"name": "XR_KHR_vulkan_enable2", "extension_version": 2}
    ]
  }
}

5.4 API Layer Manifest

{
  "file_format_version": "1.0.0",
  "api_layer": {
    "name": "XR_APILAYER_LUNARG_core_validation",
    "library_path": "./XrApiLayer_core_validation.so",
    "api_version": "1.0",
    "implementation_version": "1",
    "enable_environment": "XR_ENABLE_CORE_VALIDATION",
    "disable_environment": "XR_DISABLE_CORE_VALIDATION"
  }
}

활성화:

export XR_ENABLE_API_LAYERS=XR_APILAYER_LUNARG_core_validation:XR_APILAYER_LUNARG_api_dump

6. 객체 모델 (Object Handles)

XrInstance                           ← 런타임 연결
└── XrSession                        ← 그래픽스 바인딩 세션
    ├── XrSwapchain[]                ← 그래픽스 API 텍스처 배열
    ├── XrSpace[]                    ← 좌표계
    │   ├── Reference Space (VIEW/LOCAL/STAGE/LOCAL_FLOOR/UNBOUNDED)
    │   └── Action Space (컨트롤러 포즈 등)
    ├── XrActionSet[]
    │   └── XrAction[] (boolean/float/vector2f/pose/vibration)
    └── XrSpatialAnchor[]            ← 확장

6.1 XrInstance 생성

XrApplicationInfo appInfo = {
    .applicationName    = "MyXRApp",
    .applicationVersion = 1,
    .engineName         = "MyEngine",
    .engineVersion      = 1,
    .apiVersion         = XR_CURRENT_API_VERSION
};
const char* extensions[] = {
    "XR_KHR_vulkan_enable2",
    "XR_EXT_hand_tracking",
};
XrInstanceCreateInfo ci = {
    .type                  = XR_TYPE_INSTANCE_CREATE_INFO,
    .applicationInfo       = appInfo,
    .enabledExtensionCount = 2,
    .enabledExtensionNames = extensions
};
XrInstance instance;
xrCreateInstance(&ci, &instance);

XrSystemGetInfo sysInfo = {
    .type       = XR_TYPE_SYSTEM_GET_INFO,
    .formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY
};
XrSystemId systemId;
xrGetSystem(instance, &sysInfo, &systemId);

6.2 XrSession 생성 (Graphics Binding 필수)

XrGraphicsBindingVulkan2KHR vkBinding = {
    .type             = XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR,
    .instance         = vkInstance,
    .physicalDevice   = vkPhysicalDevice,  // 반드시 런타임 권장 디바이스
    .device           = vkDevice,
    .queueFamilyIndex = graphicsQueueFamily,
    .queueIndex       = 0
};
XrSessionCreateInfo sessionCI = {
    .type     = XR_TYPE_SESSION_CREATE_INFO,
    .next     = &vkBinding,    // ★ next chain으로 그래픽스 바인딩
    .systemId = systemId
};
XrSession session;
xrCreateSession(instance, &sessionCI, &session);

런타임 권장 GPU 디바이스 획득:

PFN_xrGetVulkanGraphicsDevice2KHR xrGetVulkanGraphicsDevice2KHR;
xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDevice2KHR",
    (PFN_xrVoidFunction*)&xrGetVulkanGraphicsDevice2KHR);

XrVulkanGraphicsDeviceGetInfoKHR info = {
    .type           = XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR,
    .systemId       = systemId,
    .vulkanInstance = vkInstance
};
VkPhysicalDevice physDevice;
xrGetVulkanGraphicsDevice2KHR(instance, &info, &physDevice);
// 반드시 이 physical device로 VkDevice 생성

6.3 Graphics Binding 종류

API 바인딩 구조체 Extension
Vulkan XrGraphicsBindingVulkan2KHR XR_KHR_vulkan_enable2
OpenGL (Win32) XrGraphicsBindingOpenGLWin32KHR XR_KHR_opengl_enable
OpenGL ES (Android) XrGraphicsBindingOpenGLESAndroidKHR XR_KHR_opengl_es_enable
Direct3D 11 XrGraphicsBindingD3D11KHR XR_KHR_D3D11_enable
Direct3D 12 XrGraphicsBindingD3D12KHR XR_KHR_D3D12_enable
Metal XrGraphicsBindingMetalKHR (Apple 미참여로 Khronos 공식 미존재)

클립 공간 주의: Vulkan = +Y down, near=0, far=1; D3D11/D3D12 = +Y up, near=0, far=1.


7. Session State Machine (가장 중요)

                  xrCreateSession()
                        │
                        ▼
                  ┌──────────────┐
                  │   UNKNOWN    │
                  └──────┬───────┘
                         │ runtime ready
                         ▼
              ┌──→  ┌─────────┐  ←─ xrEndSession 완료
              │     │  IDLE   │     (재진입)
              │     └────┬────┘
              │          │ 런타임이 RUNTIME_READY 알림
              │          ▼
              │     ┌─────────┐
              │     │  READY  │ ★ xrBeginSession() 호출 시점
              │     └────┬────┘
              │          │ xrBeginSession()
              │          ▼
              │     ┌────────────────┐
              │     │ SYNCHRONIZED   │  shouldRender=false 가능
              │     └────┬───────────┘
              │          │ 가시화
              │          ▼
              │     ┌─────────┐
              │     │ VISIBLE │  렌더링은 하나 입력은 받지 않음
              │     └────┬────┘
              │          │ 포커스 획득
              │          ▼
              │     ┌─────────┐
              │     │ FOCUSED │  ★ 정상 게임 루프 (입력 + 렌더)
              │     └────┬────┘
              │          │ 런타임이 종료 요청
              │          ▼
              │     ┌──────────┐
              └─────│ STOPPING │ ★ 즉시 xrEndSession() 호출
                    └──┬───────┘
                       │ 손실 / 재시작
                       ▼
                  ┌────────────────┐    ┌─────────┐
                  │ LOSS_PENDING   │ →  │ EXITING │ → 종료
                  └────────────────┘    └─────────┘

7.1 상태별 의미

상태 앱이 해야 할 일
IDLE CPU/GPU 최소화. 이벤트 폴링 계속
READY 즉시 xrBeginSession() 호출
SYNCHRONIZED 프레임 루프 실행. shouldRender=false면 렌더 생략 가능
VISIBLE 렌더링 + 레이어 제출. 입력 처리 X
FOCUSED 정상 게임 루프: 렌더 + xrSyncActions + 입력
STOPPING 즉시 xrEndSession() 호출. 프레임 루프 중단
LOSS_PENDING xrDestroySession() 후 선택적 재생성
EXITING 프로세스 종료 또는 비-XR UI 전환

7.2 이벤트 폴링 패턴

void PollEvents(XrInstance instance, XrSession session, bool *running, bool *quit) {
    XrEventDataBuffer event = {.type = XR_TYPE_EVENT_DATA_BUFFER};
    while (xrPollEvent(instance, &event) == XR_SUCCESS) {
        switch (event.type) {
        case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
            XrEventDataSessionStateChanged *e = (void*)&event;
            switch (e->state) {
            case XR_SESSION_STATE_READY: {
                XrSessionBeginInfo bi = {
                    .type = XR_TYPE_SESSION_BEGIN_INFO,
                    .primaryViewConfigurationType =
                        XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO
                };
                xrBeginSession(session, &bi);
                *running = true;
                break;
            }
            case XR_SESSION_STATE_STOPPING:
                xrEndSession(session); *running = false; break;
            case XR_SESSION_STATE_LOSS_PENDING:
            case XR_SESSION_STATE_EXITING:
                *quit = true; break;
            }
            break;
        }
        case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
            *quit = true; break;
        case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
            // 재센터 → 새 기준 적용 시점 이후 xrLocateSpace 결과 변화
            break;
        case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
            // 컨트롤러 교체. xrGetCurrentInteractionProfile() 재호출
            break;
        }
        event.type = XR_TYPE_EVENT_DATA_BUFFER;
    }
}

제약: xrBeginSession은 READY에서만, xrEndSession은 STOPPING에서만 호출 가능. 다른 상태에서 호출 시 에러.


8. Frame Loop

8.1 왜 Predicted Display Time이 핵심인가

머리 움직임 → 센서(~1ms) → CPU 게임루프(~3-5ms) → GPU 렌더(~8-10ms)
            → 컴포지터 합성(~2ms) → 디스플레이 스캔아웃(~1ms) → 포톤
                                                                  │
                                              motion-to-photon ←──┘

90Hz = 11.1ms 예산. 20ms 초과 시 멀미. 따라서 현재 헤드 포즈가 아닌 디스플레이 시점의 예측 포즈로 렌더해야 한다.

8.2 표준 프레임 루프

while (running) {
    // 1) 이벤트
    PollEvents(instance, session, &running, &quit);

    // 2) 프레임 동기화
    XrFrameState fs = {.type = XR_TYPE_FRAME_STATE};
    xrWaitFrame(session, NULL, &fs);
    // fs.predictedDisplayTime, fs.predictedDisplayPeriod, fs.shouldRender

    // 3) 프레임 시작
    xrBeginFrame(session, NULL);

    XrCompositionLayerBaseHeader *layers[2] = {0};
    uint32_t layerCount = 0;

    if (fs.shouldRender) {
        // 4) 뷰 포즈 — 반드시 predictedDisplayTime 사용
        XrViewLocateInfo vli = {
            .type                  = XR_TYPE_VIEW_LOCATE_INFO,
            .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
            .displayTime           = fs.predictedDisplayTime,  // ★
            .space                 = appSpace
        };
        XrView views[2];
        XrViewState vs = {.type = XR_TYPE_VIEW_STATE};
        uint32_t vc = 2;
        xrLocateViews(session, &vli, &vs, vc, &vc, views);

        // 5) Swapchain 획득 → 렌더 → 해제
        for (int eye = 0; eye < 2; eye++) {
            uint32_t imgIdx;
            xrAcquireSwapchainImage(swapchains[eye], NULL, &imgIdx);
            XrSwapchainImageWaitInfo wi = {
                .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,
                .timeout = XR_INFINITE_DURATION
            };
            xrWaitSwapchainImage(swapchains[eye], &wi);

            RenderEyeView(eye, imgIdx, views[eye], fs.predictedDisplayTime);

            xrReleaseSwapchainImage(swapchains[eye], NULL);
        }

        // 6) Composition Layer 구성
        XrCompositionLayerProjectionView projViews[2] = {
            {.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW,
             .pose = views[0].pose,  // ★ 렌더 시 사용한 포즈와 동일
             .fov  = views[0].fov,
             .subImage = {swapchains[0], {{0,0},{w,h}}, 0}},
            { /* right eye */ }
        };
        static XrCompositionLayerProjection projLayer = {
            .type      = XR_TYPE_COMPOSITION_LAYER_PROJECTION,
            .space     = appSpace,  // ★ world-locked space (LOCAL/STAGE)
            .viewCount = 2,
            .views     = projViews
        };
        layers[layerCount++] = (XrCompositionLayerBaseHeader*)&projLayer;
    }

    // 7) 프레임 제출
    XrFrameEndInfo fei = {
        .type                 = XR_TYPE_FRAME_END_INFO,
        .displayTime          = fs.predictedDisplayTime,  // ★ 동일 값
        .environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE,
        .layerCount           = layerCount,
        .layers               = layers
    };
    xrEndFrame(session, &fei);
}

핵심 규칙:

  1. xrWaitFrame이 반환한 predictedDisplayTimexrLocateViews, xrEndFrame에 동일하게 사용
  2. xrLocateViews로 받은 pose를 그대로 XrCompositionLayerProjectionView.pose에 전달
  3. XrCompositionLayerProjection.space는 반드시 world-locked (LOCAL/STAGE 등). VIEW를 사용하면 ATW가 망가짐

8.3 Late Latching / ATW / ASW

Late Latching (Meta): GPU 커맨드 버퍼 큐에 쌓인 후, GPU 실행 직전 최신 포즈로 원자적으로 latch. ~15ms 지연 감소. Phase Sync(앱 빠를 때 효과)와 상보적.

ATW (Asynchronous Time Warp): 컴포지터가 디스플레이 직전 최신 헤드 트래킹으로 이미지를 재투영(reproject) → 누락 프레임도 보간.

ASW (Application SpaceWarp, XR_FB_space_warp): 앱이 36 FPS로 렌더, 컴포지터가 motion vector + depth buffer로 중간 프레임 합성하여 72 FPS 표시. GPU 최대 70% 절감. Vulkan + Multiview 필수, Unity URP/HDRP 필요.

8.4 Composition Layer 종류

레이어 용도
Projection 표준 스테레오 3D 렌더링. 거의 모든 앱의 메인
Quad 3D 공간의 2D 사각형. UI/HUD/자막에 최적 (단안 렌더로 샤프)
Cylinder 원통 곡면 UI
Equirect 360° 등장방형 (스카이박스, 360 비디오)
Cube 큐브맵 (스카이박스). XR_KHR_composition_layer_cube

핵심 장점: 각 레이어가 독립 해상도 → 3D 씬 해상도 낮추고 UI Quad는 고해상도 유지 가능.


9. Reference Spaces

VIEW         원점: 헤드(좌우 눈 중점), 헤드와 함께 이동
             용도: 헬멧 HUD
             ★ Projection layer에 사용 금지 (ATW 파괴)

LOCAL        원점: 앱 시작 시 헤드 위치 (Y축 중력 기준 수직)
             재센터 시 수평만 리셋, Y 유지. 바닥 정보 없음
             용도: 착석형

STAGE        원점: 물리적 바닥 중앙 (Y=0이 바닥)
             사전 룸스케일 캘리브레이션 필요
             xrGetReferenceSpaceBoundsRect()로 플레이 영역 조회
             용도: 룸스케일

LOCAL_FLOOR  LOCAL + 추정 바닥 높이 (캘리브레이션 없이 사용 가능)
             OpenXR 1.1 코어 (이전: XR_EXT_local_floor)

UNBOUNDED    STAGE 범위 초과 (건물/야외). 원점 표류 가능
             AR/MR 세계 규모. XR_MSFT_unbounded / XR_ANDROID_unbounded 등

9.1 공간 조회

XrSpaceLocation loc = {.type = XR_TYPE_SPACE_LOCATION};
xrLocateSpace(leftHandSpace,                // 조회 대상
              localSpace,                   // 기준
              fs.predictedDisplayTime,
              &loc);

if ((loc.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) &&
    (loc.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)) {
    // loc.pose.position, loc.pose.orientation 사용
}

좌표계: Right-handed, +Y up, -Z forward. 쿼터니언은 (x, y, z, w) 순서.


10. Action System

10.1 왜 직접 버튼 폴링이 아닌가

기존 SDK는 GetButtonState(OCULUS_A_BUTTON) 같은 하드웨어 직접 폴링이었다. 두 가지 문제:

  1. 하드웨어 종속: 컨트롤러 바뀌면 코드 수정
  2. 리바인딩 불가: 사용자가 버튼 배치 변경 못 함

OpenXR Action System은 앱이 의미적 액션(grab, teleport)을 정의하고, 런타임이 하드웨어 매핑을 관리한다.

10.2 전체 흐름

[앱 시작 시 1회]
  xrCreateActionSet() → "gameplay"
  xrCreateAction()    → "grab" / "teleport" / "menu" / "palm-pose"
  xrSuggestInteractionProfileBindings() per profile
    /interaction_profiles/oculus/touch_controller
    /interaction_profiles/valve/index_controller
    /interaction_profiles/khr/simple_controller   ← fallback
  xrAttachSessionActionSets()

[매 프레임]
  xrSyncActions()
  xrGetActionStateBoolean / Float / Vector2f / Pose

10.3 핵심 구현

// ActionSet
XrActionSetCreateInfo asci = {
    .type = XR_TYPE_ACTION_SET_CREATE_INFO,
    .priority = 0
};
strcpy(asci.actionSetName,           "gameplay");
strcpy(asci.localizedActionSetName,  "Gameplay");
XrActionSet gameplay;
xrCreateActionSet(instance, &asci, &gameplay);

// Subaction paths (좌/우 손 구분)
XrPath handPaths[2];
xrStringToPath(instance, "/user/hand/left",  &handPaths[0]);
xrStringToPath(instance, "/user/hand/right", &handPaths[1]);

// Action: float "grab"
XrActionCreateInfo aci = {
    .type                = XR_TYPE_ACTION_CREATE_INFO,
    .actionType          = XR_ACTION_TYPE_FLOAT_INPUT,
    .countSubactionPaths = 2,
    .subactionPaths      = handPaths
};
strcpy(aci.actionName,          "grab-object");
strcpy(aci.localizedActionName, "Grab Object");
XrAction grabAction;
xrCreateAction(gameplay, &aci, &grabAction);

// Suggested Bindings
XrPath profile;
xrStringToPath(instance,
    "/interaction_profiles/khr/simple_controller", &profile);
XrPath grabL, grabR;
xrStringToPath(instance, "/user/hand/left/input/select/click",  &grabL);
xrStringToPath(instance, "/user/hand/right/input/select/click", &grabR);
XrActionSuggestedBinding bindings[] = {
    {grabAction, grabL}, {grabAction, grabR},
};
XrInteractionProfileSuggestedBinding sb = {
    .type                   = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING,
    .interactionProfile     = profile,
    .countSuggestedBindings = 2,
    .suggestedBindings      = bindings
};
xrSuggestInteractionProfileBindings(instance, &sb);

// Attach (세션당 1회)
XrSessionActionSetsAttachInfo ai = {
    .type            = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO,
    .countActionSets = 1,
    .actionSets      = &gameplay
};
xrAttachSessionActionSets(session, &ai);

// 매 프레임
XrActiveActionSet active = {.actionSet = gameplay, .subactionPath = XR_NULL_PATH};
XrActionsSyncInfo si = {
    .type                  = XR_TYPE_ACTIONS_SYNC_INFO,
    .countActiveActionSets = 1,
    .activeActionSets      = &active
};
xrSyncActions(session, &si);

XrActionStateGetInfo gi = {
    .type         = XR_TYPE_ACTION_STATE_GET_INFO,
    .action       = grabAction,
    .subactionPath = handPaths[0]
};
XrActionStateFloat st = {.type = XR_TYPE_ACTION_STATE_FLOAT};
xrGetActionStateFloat(session, &gi, &st);
if (st.isActive && st.currentState > 0.5f) {
    // 왼손 잡기
}

10.4 Interaction Profile 경로 구조

/interaction_profiles/<vendor>/<device>
  /user/hand/<left|right>/input/<component>/<type>
                        /output/haptic

/interaction_profiles/khr/simple_controller   (fallback 표준)
/interaction_profiles/oculus/touch_controller (Quest)
/interaction_profiles/valve/index_controller  (Index)
/interaction_profiles/microsoft/motion_controller (WMR)
/interaction_profiles/htc/vive_controller     (Vive)
/interaction_profiles/ext/eye_gaze_interaction (시선)

OpenXR 1.1에서 13개 추가 인터랙션 프로파일이 코어로 편입.


11. Extension 메커니즘

11.1 네이밍 컨벤션

접두사 의미
XR_KHR_* Khronos 비준 (코어와 동일 라이선스)
XR_EXT_* 멀티벤더 합의
XR_FB_* / XR_META_* Meta (Facebook)
XR_MSFT_* Microsoft
XR_VALVE_* Valve
XR_HTC_* HTC
XR_BD_* ByteDance (Pico)
XR_ML_* Magic Leap
XR_VARJO_* Varjo
XR_NV_* NVIDIA
XR_ULTRALEAP_* Ultraleap
XR_ANDROID_* Android XR

11.2 Extension 활성화 패턴

// 1) 사용 가능 확장 열거 (two-call idiom)
uint32_t extCount;
xrEnumerateInstanceExtensionProperties(NULL, 0, &extCount, NULL);
XrExtensionProperties *props = malloc(extCount * sizeof(*props));
for (uint32_t i = 0; i < extCount; i++)
    props[i].type = XR_TYPE_EXTENSION_PROPERTIES;
xrEnumerateInstanceExtensionProperties(NULL, extCount, &extCount, props);

// 2) 지원 여부 확인
bool hasHandTracking = false;
for (uint32_t i = 0; i < extCount; i++)
    if (strcmp(props[i].extensionName, XR_EXT_HAND_TRACKING_EXTENSION_NAME) == 0)
        hasHandTracking = true;

// 3) xrCreateInstance에서 활성화
const char* exts[] = {"XR_KHR_vulkan_enable2", "XR_EXT_hand_tracking"};
ci.enabledExtensionCount = 2;
ci.enabledExtensionNames = exts;

11.3 주요 Extension

Extension 용도
XR_KHR_vulkan_enable2 Vulkan 그래픽스 바인딩
XR_KHR_D3D12_enable D3D12 바인딩
XR_EXT_hand_tracking 26 관절 손 추적
XR_EXT_eye_gaze_interaction 시선 추적 (인터랙션 용도)
XR_FB_passthrough Meta Quest 패스스루 카메라
XR_FB_spatial_entity Meta 공간 앵커
XR_FB_body_tracking Meta 전신 추적
XR_FB_face_tracking2 Meta 안면 추적
XR_FB_foveation / XR_FB_foveation_configuration FFR
XR_FB_space_warp Application SpaceWarp
XR_MSFT_spatial_anchor Microsoft 공간 앵커
XR_MSFT_scene_understanding HoloLens 환경 메시
XR_MSFT_unbounded_reference_space 무제한 공간
XR_KHR_composition_layer_depth Depth 레이어 (재투영 향상)
XR_EXT_performance_settings 성능 레벨 힌트
XR_HTC_facial_tracking HTC 표정 추적

11.4 Extension 함수 동적 로드

PFN_xrCreateHandTrackerEXT xrCreateHandTrackerEXT = NULL;
xrGetInstanceProcAddr(instance, "xrCreateHandTrackerEXT",
    (PFN_xrVoidFunction*)&xrCreateHandTrackerEXT);

XrHandTrackerCreateInfoEXT htci = {
    .type         = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT,
    .hand         = XR_HAND_LEFT_EXT,
    .handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT
};
XrHandTrackerEXT handTracker;
xrCreateHandTrackerEXT(session, &htci, &handTracker);

12. OpenXR 런타임 생태계

12.1 Conformant Runtime 목록 (2026.04 기준)

벤더 기기 / 런타임 OS OpenXR 버전
Meta Quest 1/2/3/3S/Pro (Horizon OS) Android 1.1
Meta Oculus PC (Rift S, Quest Link) Windows 1.0+
Valve SteamVR Windows / Linux 1.0
Microsoft HoloLens 2 + WMR Windows 1.0
ByteDance (Pico) Pico 4 / 4 Ultra / Neo 3 Android 1.1 (Ultra)
Google Galaxy XR / Android XR Android XR 1.1
Varjo XR-3 / XR-4 / Aero Windows (PC tethered) 1.0
HTC VIVE Focus 3 / XR Elite Android / Windows 1.0
Magic Leap Magic Leap 2 Android (ML OS) 1.0
Collabora Monado Linux / Android 1.0
NVIDIA CloudXR Linux 1.1
Sony ELF-SR1/SR2 (Spatial Reality) Windows 1.0
Canon MREAL Windows 1.0
Lynx R-1 / R-2 Lynx OS (Android 포크)
Snap Spectacles Snap OS WebXR만 지원

12.2 Apple의 부재

Apple Vision Pro (2024.02):
├── visionOS (iPadOS 파생)
├── RealityKit (Swift 씬 그래프)
├── CompositorServices (Metal 직접)
├── ARKit (공간 인식)
└── 컨트롤러 없음 (눈+손+음성)

OpenXR 미참여:
- Khronos Working Group 멤버 아님
- 컨트롤러 미지원 → OpenXR Interaction Profile 모델과 불일치
- 수직통합 차별화 전략 (OpenGL→Metal과 동일)

WebXR은 Safari에서 지원 (OpenXR 미경유, 자체 변환)
Unity/Unreal은 별도 PolySpatial 등 컴패티빌리티 레이어로 지원

12.3 Monado — 오픈소스 OpenXR 런타임의 부상

Collabora 주도 오픈소스 구현체. 2024년 이후 산업 채택 폭증:

  • Google Android XR (2024.12) — Monado 기반
  • Pico (Neo3, Pico 4) — Monado 활용
  • Qualcomm Snapdragon Spaces — Monado 기반
  • NVIDIA CloudXR (2025.06 Conformant) — Monado 기반 Linux 런타임
  • Hololight Stream, PortalVR

“Mesa state-tracker 철학 계승: API 구현과 하드웨어 추상화를 명확히 분리”

오픈소스 OpenXR 구현체로서 신규 런타임 구축의 사실상 표준 플랫폼이 됨.


13. 게임 엔진 통합

13.1 Unity

[Unity App C#]
    ↓
[XR Interaction Toolkit (XRI)] ← com.unity.xr.interaction.toolkit
    ↓
[Unity OpenXR Plugin] ← com.unity.xr.openxr (공식 권장)
   또는
[Meta OpenXR] ← com.unity.xr.meta-openxr (Meta 확장)
    ↓
[XR Plugin Management] ← com.unity.xr.management
    ↓
[Runtime: Meta / SteamVR / Pico / ...]
패키지 용도
com.unity.xr.openxr OpenXR 표준 경로 (모든 신규 권장)
com.unity.xr.meta-openxr Meta 확장 (Passthrough, Body Tracking, Spatial Anchors)
com.unity.xr.interaction.toolkit v3+ 입력/인터랙션 추상화
com.unity.xr.hands 손 추적
com.unity.xr.oculus Deprecated (OVRPlugin 기반 레거시)

visionOS는 별도: Unity PolySpatial — RealityKit 호출, OpenXR 미경유.

13.2 Unreal Engine

단계 플러그인
레거시 OculusVR Plugin (OVRPlugin 자체 구현, deprecated)
현재 권장 Epic OpenXR Plugin (UE 4.27+, UE5 기본) + Meta XR Plugin
추가 OpenXRMsftHandInteraction, OpenXRHandTracking (HoloLens)

13.3 Godot 4

  • OpenXR 코어 내장 (Godot 4.0, 2023). 별도 플러그인 불필요
  • godot_openxr_vendors 플러그인 — Meta/Pico/HTC 전용 확장 래퍼
  • Meta가 2024년 Godot 기여자 직접 자금 지원 시작 → 4.3에서 Quest 최적화 다수 추가
  • WebXR 별도 지원

13.4 기타

프레임워크 특징
StereoKit Microsoft 후원 C# XR. OpenXR 위 고수준 추상화. 코드 중심 빠른 프로토타입
BabylonJS / Three.js W3C WebXR API (브라우저). Chromium이 내부적으로 OpenXR 호출
Bevy XR Rust. 커뮤니티 bevy_mod_openxr (공식 미내장)

14. WebXR vs OpenXR

항목 WebXR OpenXR
표준 기관 W3C Khronos
언어 JavaScript C/C++
환경 브라우저 Native / 게임 엔진
배포 URL (설치 X) APK / 설치형
성능 브라우저 오버헤드 Native
기능 범위 서브셋 풀 사양

Chromium의 WebXR → OpenXR 변환: device/vr/openxr/ 모듈이 WebXR 세션 요청을 OpenXR API로 변환. Chrome/Edge가 Windows에서 WebXR 지원하는 방식이 이것.

visionOS Safari: Apple 자체 XR API → WebXR (OpenXR 미경유).


15. 대안 비교 (Alternatives)

15.1 OpenXR vs OpenVR (Valve)

항목 OpenVR OpenXR
표준 주체 Valve 단독 Khronos 컨소시엄
대상 VR 전용 VR + AR + MR
런타임 SteamVR 단일 종속 로더 + 다중 런타임
현황 유지보수 모드 활성, 연간 릴리스
Unity 공식 지원 종료 XR Plugin

OpenComposite / xrizer: OpenVR 호출을 OpenXR로 포워딩 → SteamVR 없이 OpenXR 런타임 직접 실행.

15.2 OpenXR vs Oculus PC/Mobile SDK

항목 Oculus SDK OpenXR + Meta Extensions
포터빌리티 Quest만 Quest + Pico + SteamVR + …
신규 기능 종료 Meta Extensions로
Unity 패키지 com.unity.xr.oculus (legacy) com.unity.xr.openxr + com.unity.xr.meta-openxr
에코시스템 통합 OpenXR 코어 + Meta XR Core SDK

실무 패턴: “OpenXR for portable XR core, vendor SDK for ecosystem” — 이식 가능한 코어는 OpenXR, Meta Avatars/Voice/Presence Platform 같은 에코시스템은 Meta XR SDK.

15.3 OpenXR vs ARKit / ARCore

항목 ARKit / ARCore OpenXR
폼팩터 폰 (단일 카메라) HMD (스테레오 6DoF)
추적 디바이스 카메라 세계 고정 Reference Space
AR vs VR Phone AR 풀 VR + AR + MR

15.4 OpenXR vs 그래픽스 API

핵심: OpenXR은 그래픽스 API가 아니다. XR 런타임 API. 렌더링은 Vulkan/D3D/GL과 페어링하여 사용.

┌─────────────────────────────────────────────┐
│              Application                     │
├──────────────────┬──────────────────────────┤
│   OpenXR API     │   Graphics API           │
│ (세션/추적/입력)  │ (Vulkan/D3D12/...)       │
├──────────────────┴──────────────────────────┤
│         Graphics Binding Extension           │
│  XrGraphicsBindingVulkanKHR / D3D12KHR ...   │
└─────────────────────────────────────────────┘

16. 상황별 최적 선택

시나리오 권장
PC + Quest + Pico 크로스플랫폼 OpenXR (단일 코드베이스)
Quest 전용 + 에코시스템 (아바타/소셜) OpenXR + Meta XR SDK
iOS / visionOS ARKit + RealityKit (OpenXR 미지원)
Web 배포 XR WebXR
Android 폰 AR ARCore
Linux 오픈소스 Monado + OpenXR
C# 빠른 MR 프로토타입 StereoKit
HoloLens 2 OpenXR (MSFT extensions)
Android XR (Google, 2026~) OpenXR

17. 성능 최적화 베스트 프랙티스

17.1 프레임 예산

Hz 예산 M2P 목표
72 13.9ms < 20ms
90 11.1ms < 20ms
120 8.3ms < 20ms

17.2 Foveated Rendering

종류 Extension 설명
FFR (Fixed) XR_FB_foveation, XR_VARJO_foveated_rendering 화면 중앙 고해상도 / 주변부 저해상도
ETFR (Eye-Tracked) OpenXR 1.1 코어 (Quad Views, 이전 XR_VARJO_quad_views) 시선 따라 fovea 이동. Microsoft Flight Simulator 2024 등

씬 전환 시점에 FFR 레벨 변경 (씬 중간 변경은 노티스어블). 고대비 텍스트는 프레임 중앙 배치.

// Unity
XRDisplaySubsystem displaySubsystem = ...;
displaySubsystem.SetFoveatedRenderingLevel(FoveatedRenderingLevel.Medium);

17.3 Multiview (Single Pass Stereo)

[2-pass (비효율)]                  [Multiview (권장)]
DrawCall × N → Left Eye           DrawCall × N → 두 눈 동시
DrawCall × N → Right Eye          gl_ViewIndex / SV_ViewID로 구분
RenderPass 2개, CPU 2배           RenderPass 1개, CPU 절반

Vulkan: arraySize = 2 스왑체인 + VkRenderPassMultiviewCreateInfo viewMask = 0b11.

17.4 렌더 파이프라인

항목 권장
렌더링 Forward (Deferred는 G-Buffer가 모바일 타일 GPU 대역폭 폭증)
MSAA 4x 베이스라인 (VR aliasing이 멀미 악화)
Swapchain Format xrEnumerateSwapchainFormats 첫 번째 (런타임 선호)
UI Quad Layer 분리 (샤프니스 + 씬 해상도 분리)

HoloLens 2 예외: Quad Layer 추가 시 post-processing 페널티 → Projection만 사용 권장.

17.5 Application SpaceWarp (XR_FB_space_warp)

앱 36 FPS 렌더 → 컴포지터가 motion vector + depth로 중간 프레임 합성 → 72 FPS 표시
GPU 최대 70% 절감

요구사항: Vulkan + Multiview, Unity URP/HDRP, motion vector + depth buffer 추가 제출.


18. 함정 / 안티패턴

18.1 Predicted Display Time 오용

// WRONG
XrTime now = getCurrentTimeNs();
xrLocateViews(..., now, ...);  // 세계가 "수영"

// CORRECT
xrLocateViews(..., fs.predictedDisplayTime, ...);
xrEndFrame(.../* fei.displayTime = */ fs.predictedDisplayTime);

18.2 xrWaitFrame / xrBeginFrame 짝 무결성

// WRONG: xrBeginFrame 누락 → 다음 xrWaitFrame 무한 데드락
for (;;) {
    xrWaitFrame(...);
    // skip xrBeginFrame ← 절대 금지
    render(); xrEndFrame(...);
}

18.3 xrPollEvent 스킵

이벤트 폴링 누락 → STOPPING/LOSS_PENDING 미처리 → 앱 정지/크래시.

18.4 Action Set 미동기화

// 매 프레임 xrSyncActions() 호출 없으면 xrGetActionState* 항상 false
xrSyncActions(session, &si);  // ★ 필수
xrGetActionStateBoolean(session, &gi, &state);

18.5 Swapchain Acquire-Wait-Release 순서

xrAcquireSwapchainImage(...);  // 인덱스 획득
xrWaitSwapchainImage(...);     // 컴포지터 읽기 완료 대기
// ★ 이 사이에서만 렌더링
xrReleaseSwapchainImage(...);

// PITFALL 1: Wait 없이 렌더 → 컴포지터 읽는 중 이미지 덮어쓰기
// PITFALL 2: Release 없이 다시 Acquire → XR_ERROR_CALL_ORDER_INVALID

18.6 미지원 Extension 요청

// xrCreateInstance에서 미지원 extension 요청 시 INSTANCE_CREATE 실패
// → 반드시 xrEnumerateInstanceExtensionProperties로 사전 검사

18.7 좌표계 / 쿼터니언 컨벤션

OpenXR  Right-handed, +Y up, -Z forward, quat (x,y,z,w)
Unity   Left-handed,  +Y up, +Z forward, quat (x,y,z,w)
Unreal  Left-handed,  +Z up, +X forward

// Unity → OpenXR 변환 (Unity Plugin 내부)
openxrQuat.x = -unityQuat.x;
openxrQuat.y = -unityQuat.y;
openxrQuat.z =  unityQuat.z;
openxrQuat.w =  unityQuat.w;

18.8 Composition Layer Projection.space에 VIEW 사용 금지

VIEW(헤드 잠금) 공간을 projection layer space로 쓰면 ATW가 망가져 “swimming” 발생. 반드시 LOCAL/STAGE 같은 world-locked.

18.9 SESSION_LOSS_PENDING 미처리

case XR_SESSION_STATE_LOSS_PENDING:
    xrDestroySession(session);
    // 적절 주기로 xrGetSystem 폴링 → 시스템 재발견 시 새 세션
    break;

18.10 Reference Space Recenter 미처리

case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
    // 재센터 → 기존 holography 위치가 갑자기 이동한 것처럼 보임
    // changeTime 이후 새 기준 적용. 필요 시 앵커 재설정
    break;

18.11 멀티벤더 Extension 혼용

동일 기능이 XR_FB_*, XR_META_*, XR_VARJO_*로 중복 → KHR/Core 우선, 폴백으로 벤더 버전.

OpenXR 1.1에서 코어 편입:

  • XR_EXT_local_floorLOCAL_FLOOR
  • XR_VARJO_quad_views → Stereo Foveated
  • XR_EXT_palm_pose → Grip Surface
  • XR_KHR_locate_spacesxrLocateSpaces

19. 마이그레이션 패턴

19.1 OpenVR → OpenXR

단계 작업
1 SteamVR Plugin 제거, OpenXR SDK 추가
2 VR_Init()xrCreateInstance() + xrCreateSession()
3 WaitGetPoses()xrWaitFrame() + xrLocateViews()
4 Submit(L,R)xrEndFrame() + Projection layer
5 OpenVR Input → OpenXR Action System
빠른 이행 OpenComposite / xrizer (앱 무수정 OpenXR 런타임 실행)

19.2 Oculus SDK → OpenXR (Unity)

// 구: Oculus XR Plugin
// OVRPlugin.GetNodeState() / OVRInput.Get()

// 신: Unity OpenXR Plugin
using UnityEngine.XR;
InputDevice head = InputDevices.GetDeviceAtXRNode(XRNode.Head);
head.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 pos);

// XRI 사용:
XRRayInteractor / XRDirectInteractor / XRSocketInteractor

19.3 Vendor → KHR/Core 프로모션

VENDOR (XR_FB_, XR_MSFT_, XR_NV_)
  ↓ 충분한 채택
EXT (XR_EXT_) — 멀티벤더 합의
  ↓ Khronos 비준
KHR / Core

벤더 extension 사용 시 KHR 버전 존재 여부 먼저 확인 후 폴백으로.


20. 검증 & 디버깅

20.1 API Layers

export XR_ENABLE_API_LAYERS=XR_APILAYER_LUNARG_core_validation:XR_APILAYER_LUNARG_api_dump
Layer 목적
XR_APILAYER_LUNARG_core_validation Valid Usage 위반 탐지, 디버그 콜백
XR_APILAYER_LUNARG_api_dump API 호출 + 파라미터 덤프
XR_APILAYER_LUNARG_best_practices 성능 경고 (비효율 swapchain format 등)

20.2 디버그 콜백

XrDebugUtilsMessengerCreateInfoEXT dci = {
    .type             = XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
    .messageSeverities= XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
                       | XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT,
    .messageTypes     = XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
                       | XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
    .userCallback     = myDebugCallback
};
xrCreateDebugUtilsMessengerEXT(instance, &dci, &messenger);

20.3 로더 로깅

export XR_LOADER_DEBUG=all

20.4 GPU 캡처 도구

도구 지원
RenderDoc OpenXR + Vulkan 캡처 (외부 메모리 공유 일부 제한)
PIX for Windows D3D12 OpenXR 캡처
Meta ODT (Oculus Debug Tool) Quest Link 시 퍼포먼스 오버레이, ASW 상태
SteamVR Performance Tool SteamVR 환경

21. 빅테크 전략

기업 입장 핵심
Meta 완전 채택 (2022 독자 API 폐기) OpenXR + XR_FB_* 200+ 확장. Ron Bessems(Meta) WG 의장
Microsoft 초기 선도 (2020 Conformant) HoloLens 2 + WMR 모두 OpenXR. Mesh 협업 통합
Google Android XR (2024.12) Monado 기반. Samsung Galaxy XR(Project Moohan) 첫 출시
Valve OpenVR → OpenXR 전환 중 SteamVR 기본은 OpenVR, 신규는 OpenXR 권장
HTC 채택 Wave SDK 병행이나 OpenXR 권장
Pico/ByteDance 채택 (1.1, 2024) Monado 기반, 북미 진출
Sony PSVR2 부분 (PC 모드만) PS5 콘솔은 독자 API. PC Steam 경유 OpenXR
Apple 미참여 RealityKit/visionOS 독자
Qualcomm Snapdragon Spaces 지원 Monado 기반 AR 개발
NVIDIA CloudXR Linux Conformant (2025) Monado 기반 스트리밍

21.1 Meta — “All In on OpenXR” (2021)

2021.07.23  v31부터 신규 기능 OpenXR Extension 전용
2021.08.31  Oculus Mobile/PC API → "Compatibility Support"
2022.08.31  ★ Oculus Mobile SDK + Oculus PC API 완전 종료 (Unsupported)

자기가 지배하는 플랫폼에서도 표준 API 강제 → 업계 표준화 사실상 완성. Meta가 OpenXR Working Group 창설(2016) 참여, 의장 직책 보유.

21.2 Google Android XR (2024.12)

2024.12.12  뉴욕 발표
첫 기기:    Samsung Project Moohan (Galaxy XR)
SoC:        Qualcomm Snapdragon XR2+ Gen 2
AI:         Google Gemini 멀티모달 내장
OpenXR:     ★ 1.1 완전 지원 (Khronos Conformant 2024.12.08)
도구:       Android Studio, Jetpack Compose, Unity, OpenXR
런타임:     Monado 기반
파트너:     Samsung, Sony, XREAL (Lynx는 계약 파기)

Daydream(2019 종료) 이후 재도전 — 이번엔 OpenXR 표준 + Samsung·Qualcomm 협력으로 단편화 회피 시도.

21.3 OpenXR 1.1 (2024.04.15) 변경

코어 승격 출처
LOCAL_FLOOR reference space XR_EXT_local_floor
Stereo with Foveated Rendering XR_VARJO_quad_views
Grip Surface (Palm Pose) XR_EXT_palm_pose
xrLocateSpaces 배치 함수 XR_KHR_locate_spaces
XrUuid 타입 XR_EXT_uuid

13개 인터랙션 프로파일 추가 (thumb_resting_surfaces, stylus, trigger_curl, trigger_slide 등). 연간 정기 릴리스 사이클 선언.


22. 엔터프라이즈 / 방위산업

22.1 OpenXR이 엔터프라이즈에 중요한 이유

문제 독자 SDK OpenXR
벤더 락인 종속 코드 유지하며 기기 교체
개발 비용 플랫폼별 재작성 단일 코드베이스
조달 경쟁 단일 공급사 다중 벤더 가격 경쟁
유지보수 SDK 버전 종속 표준 기반 장기 유지

22.2 방위·훈련 사례

  • 미 국방부(DoD): Mass Virtual의 미 공군 Virtual Hangar → DoD 전반 확산. 의회도서관 2025 보고: “XR 훈련 환경이 국방부 전 영역에 보편화”
  • Varjo XR-4 Secure Edition: 핀란드(NATO) 제조, 라디오 모듈 제거, 오프라인 운용. I/ITSEC 2024 주요 전시
  • HTC Vive: VIVE Focus + OpenXR 기반 군사·방위 훈련 솔루션 (IT2EC 2024)

22.3 산업 훈련

기업 적용
Boeing 항공기 조립·유지보수 훈련. “first-time quality 90% 향상”
Volkswagen 제조 공정 훈련 (선도 기업 사례)
Walmart 직원 온보딩, 위기 대응 훈련
Delta Air Lines 승무원 훈련
Southern Company 전력 인프라 안전 훈련

2024 트렌드: 단순 몰입에서 정량 훈련 효과 측정(ROI)으로 전환 — 학습 완료율, 실수율 감소, 비용 절감 수치 요구.


23. 경험적 조언 (실무 엔지니어 관점)

  1. Extension 체크는 Feature Flag처럼: 폴백 경로 필수
  2. predictedDisplayTime은 신성불가침: 무시하면 반드시 문제 발생
  3. Swapchain 포맷은 항상 런타임 선호 우선: 첫 번째 반환값
  4. 멀티벤더 크로스플랫폼은 extension 교집합: Meta/Pico/SteamVR 공통 지원분 파악, 벤더 특화는 폴백
  5. OpenXR Core만으론 에코시스템 커버 불가: “OpenXR for portability, vendor SDK for ecosystem”
  6. visionOS는 별개 스택: Unity/Unreal 브리지로도 Shared Space 불가
  7. Validation Layer는 개발 중 항상 켜라: 한 런타임에서 OK인 코드가 다른 런타임에서 크래시하는 경우 다수

24. 참고 자료

표준 / 공식 문서

보도 자료

튜토리얼 / 가이드

성능 / 최적화

게임 엔진

마이그레이션 / 전환

런타임 / 에코시스템

Apple 부재 / 비교

학술 / 분석

위키