Creating Instances and Sessions
XR devices have varying differences between instances and sessions. On a PC with multiple connected headsets, apps use an OpenXR instance to communicate with the runtime. A session is tied to a specific VR system, which must be picked before starting the session.
To create a session, you must follow this order of operations:
- Create
XrInstance
. - Use
XrInstance
to get XrSystemId
. - Create a session using
XrSystemId
.
Running OpenXR on a device requires setting up the Graphics API and receiving platform information. Various platform and graphics API extensions require defining these
flags before including
openxr.h
.
For Meta Quest headsets, use these flags:
- Graphics API Header Control:
XR_USE_GRAPHICS_API_OPENGL_ES
or XR_USE_GRAPHICS_API_VULKAN
- Window System Header Control:
XR_USE_PLATFORM_ANDROID
For Rift / Rift S and Meta Quest headsets that use Link, use these flags:
- Graphics API Header Control:
XR_USE_GRAPHICS_API_D3D11
, XR_USE_GRAPHICS_API_D3D12
, XR_USE_GRAPHICS_API_OPENGL
or XR_USE_GRAPHICS_API_VULKAN
- Window System Header Control:
XR_USE_PLATFORM_WIN32
The
hello_xr sample app wraps the platform-specific instance creation parameters in a struct that implements an abstract class, named
IPlatformPlugin
. This is not required for OpenXR apps, but an implementation-specific detail of hello_xr to support multiple platforms.
This instance creation helper struct for Android platforms is defined as:
struct AndroidPlatformPlugin : public IPlatformPlugin {
AndroidPlatformPlugin(const std::shared_ptr<Options>& /*unused*/, const std::shared_ptr<PlatformData>& data) {
instanceCreateInfoAndroid = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
instanceCreateInfoAndroid.applicationVM = data->applicationVM;
instanceCreateInfoAndroid.applicationActivity = data->applicationActivity;
}
std::vector<std::string> GetInstanceExtensions() const override { return {XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME}; }
XrBaseInStructure* GetInstanceCreateExtension() const override { return (XrBaseInStructure*)&instanceCreateInfoAndroid; }
XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid;
};
The XrBaseInStructure
struct pointer helps to return an arbitrary OpenXR struct. Because structs always start with type
, the actual type can be safely inferred later.
The instanceCreateInfoAndroid
member variable stores Android-specific information for calling xrCreateInstance
and is returned by GetInstanceCreateExtension
.
The GetInstanceExtensions
method returns a vector of OpenXR instance-level extension names required by the specified platform (implementation-specific).
Note: Initialize the loader with platform-specific parameters before loading. These parameters are specified in the KHR loader extensions, XR_KHR_loader_init
and XR_KHR_loader_init_android
. Apps must first get the function pointer for xrInitializeLoaderKHR
through xrGetInstanceProcAddress
with a null instance pointer, and then call xrInitializeLoaderKHR
with the XrLoaderInitInfoAndroidKHR
struct defined in XR_KHR_loader_init_android
.
API Layers and Extensions
API layers and extensions both add functionality to OpenXR. The key difference is that API layers cannot add new OpenXR functions or change the API’s shape. API layers provide additional functionality by intercepting OpenXR functions from the layer above and then performing different operations than would otherwise be performed without the layer. Extensions can add new functions and modify existing ones. API layers and extensions are enabled when creating an instance.
The hello_xr sample app enumerates available instance extension properties by calling xrEnumerateInstanceExtensionProperties
for a given API layer:
// Write out extension properties for a given layer.
const auto logExtensions = [](const char* layerName, int indent = 0) {
uint32_t instanceExtensionCount;
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, 0, &instanceExtensionCount, nullptr));
std::vector<XrExtensionProperties> extensions(instanceExtensionCount, {XR_TYPE_EXTENSION_PROPERTIES});
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, (uint32_t)extensions.size(), &instanceExtensionCount, extensions.data()));
...
};
The app loops through and outputs these for debugging purposes:
const std::string indentStr(indent, ' ');
Log::Write(Log::Level::Verbose, Fmt("%sAvailable Extensions: (%d)", indentStr.c_str(), instanceExtensionCount));
for (const XrExtensionProperties& extension : extensions) {
Log::Write(Log::Level::Verbose, Fmt("%s Name=%s SpecVersion=%d", indentStr.c_str(), extension.extensionName, extension.extensionVersion));
}
OpenXR provides the xrEnumerateApiLayerProperties
function and the XrApiLayerProperties
struct to determine available API layers and their properties.
uint32_t layerCount;
CHECK_XRCMD(xrEnumerateApiLayerProperties(0, &layerCount, nullptr));
std::vector<XrApiLayerProperties> layers(layerCount, {XR_TYPE_API_LAYER_PROPERTIES});
CHECK_XRCMD(xrEnumerateApiLayerProperties((uint32_t)layers.size(), &layerCount, layers.data()));
The hello_xr app ensures the instance is not initialized before proceeding.
CHECK(m_instance == XR_NULL_HANDLE);
Initially, m_instance
is defined as:
XrInstance m_instance{XR_NULL_HANDLE};
It receives available extensions based on the platform and graphics plugins, creating a union of platform and graphics-related extensions in a vector called extensions
(implementation-specific). To create an instance, use a XrInstanceCreateInfo
struct that stores the following:
- enabled API Layer count
- enabled API Layer names
- enabled extension count
- enabled extension names
Pass this information and the instance handle to the xrCreateInstance
function. This call creates and enables an XrInstance
and then initializes global API layers and extensions.
XrInstanceCreateInfo createInfo{XR_TYPE_INSTANCE_CREATE_INFO};
createInfo.next = m_platformPlugin->GetInstanceCreateExtension();
createInfo.enabledExtensionCount = (uint32_t)extensions.size();
createInfo.enabledExtensionNames = extensions.data();
strcpy(createInfo.applicationInfo.applicationName, "HelloXR");
createInfo.applicationInfo.apiVersion = XR_API_VERSION_1_0;
CHECK_XRCMD(xrCreateInstance(&createInfo, &m_instance));
This occurs after retrieving the form factor. Achieve this by having a valid instance and an initially null system ID, checked with:
CHECK(m_instance != XR_NULL_HANDLE);
CHECK(m_systemId == XR_NULL_SYSTEM_ID);
The app calls xrGetSystem
to retrieve a systemId
.
XrSystemGetInfo systemInfo{XR_TYPE_SYSTEM_GET_INFO};
systemInfo.formFactor = m_formFactor;
CHECK_XRCMD(xrGetSystem(m_instance, &systemInfo, &m_systemId));
The graphics plugin initializes the device, as seen in the
graphicsplugin_opengles.cpp
file of the hello-xr app, in the
InitializeDevice
function.
An XrSession
is a graphics interaction with a particular XrSystem
using a particular graphics binding.
Create sessions by calling the xrCreateSession
function. It requires a valid XrSystemId
from xrGetSystem
and a valid XrSessionCreateInfo
struct, returning a handle to the session.
XrSessionCreateInfo createInfo{XR_TYPE_SESSION_CREATE_INFO};
createInfo.next = m_graphicsPlugin->GetGraphicsBinding();
createInfo.systemId = m_systemId;
CHECK_XRCMD(xrCreateSession(m_instance, &createInfo, &m_session));
The GetGraphicsBinding
function is specific to the implementation. It returns a pointer to an XrBaseInStructure
struct that receives the graphics binding header for session creation. For example, for a Vulkan-based XrSession
, the app chains a pointer to an XrGraphicsBindingVulkan2KHR
with the XrSessionCreateInfo
parameter of xrCreateSession
.