2 OpenXR Setup

With your project created and your application building and running, we can start to use OpenXR. The goal of this chapter is to create an XrInstance and an XrSession, and setup the OpenXR event loop. This OpenXR code is needed to setup the core functionality of an OpenXR application to have that application interact with the OpenXR runtime and your graphics API correctly.

2.1 Creating an Instance

We will continue to use the OpenXRTutorial class in Chapter2/main.cpp that we created in Chapter 1.4.

Here, we will add the following highlighted text to the OpenXRTutorial class:

class OpenXRTutorial {
public:
        OpenXRTutorial(GraphicsAPI_Type apiType)
                : m_apiType(apiType)  {
        }
        ~OpenXRTutorial() = default;

        void Run() {
                CreateInstance();
                CreateDebugMessenger();

                GetInstanceProperties();
                GetSystemID();

                DestroyDebugMessenger();
                DestroyInstance();
        }

private:
        void CreateInstance() {
        }
        void DestroyInstance() {
        }
        void CreateDebugMessenger() {
        }
        void DestroyDebugMessenger() {
        }
        void GetInstanceProperties() {
        }
        void GetSystemID() {
        }
        void PollSystemEvents() {
        }

private:
        XrInstance m_xrInstance = {};
        std::vector<const char *> m_activeAPILayers = {};
        std::vector<const char *> m_activeInstanceExtensions = {};
        std::vector<std::string> m_apiLayers = {};
        std::vector<std::string> m_instanceExtensions = {};

        XrDebugUtilsMessengerEXT m_debugUtilsMessenger = {};

        XrFormFactor m_formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
        XrSystemId m_systemID = {};
        XrSystemProperties m_systemProperties = {XR_TYPE_SYSTEM_PROPERTIES};

        GraphicsAPI_Type m_apiType = UNKNOWN;

        bool m_applicationRunning = true;
        bool m_sessionRunning = false;
};

First, we updated OpenXRTutorial::Run() to call the new methods CreateInstance(), GetInstanceProperties(), GetSystemID() and DestroyInstance() in that order. Finally, we added those methods and the following members to the class within their separate private sections.

2.1.1 The OpenXR Instance

The XrInstance is the foundational object that we need to create first. The XrInstance encompasses the application setup state, OpenXR API version and any layers and extensions. So inside the CreateInstance() method, we will first add the code for the XrApplicationInfo.

XrApplicationInfo AI;
strncpy(AI.applicationName, "OpenXR Tutorial Chapter 2", XR_MAX_APPLICATION_NAME_SIZE);
AI.applicationVersion = 1;
strncpy(AI.engineName, "OpenXR Engine", XR_MAX_ENGINE_NAME_SIZE);
AI.engineVersion = 1;
AI.apiVersion = XR_CURRENT_API_VERSION;

This structure allows you to specify both the name and the version for your application and engine. These members are solely for your use as the application developer. The main member here is the XrApplicationInfo ::apiVersion. Here we use the XR_CURRENT_API_VERSION macro to specify the OpenXR version that we want to run. Also, note here the use of strncpy() to set the name strings. If you look at XrApplicationInfo ::applicationName and XrApplicationInfo ::engineName members, they are of type char[], so you must copy your string into that buffer. Also, be aware of the allowable length. XrApplicationInfo will be used later when we will fill out XrInstanceCreateInfo.

Note the slight difference in the approach the OpenXR API takes compared to the Vulkan API. In OpenXR, name strings are explicitly copied into structures like XrApplicationInfo, which contain fixed-size string buffers, whereas in Vulkan, structures such as VkApplicationInfo take pointers to C strings of arbitrary size.

Similarly to Vulkan, OpenXR allows applications to extend functionality past what is provided by the core specification. The added functionality could be hardware/vendor specific. Most vital of course is which Graphics API to use with OpenXR. OpenXR supports D3D11, D3D12, Vulkan, OpenGL and OpenGL ES. Due to the extensible nature of the specification, it allows newer Graphics APIs and hardware functionality to be added with ease. Following on from the previous code in the CreateInstance() method, add the following:

m_instanceExtensions.push_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME);
// Ensure m_apiType is already defined when we call this line.
m_instanceExtensions.push_back(GetGraphicsAPIInstanceExtensionString(m_apiType));

Here, we store in a vector of strings the extension names that we would like to use. XR_EXT_DEBUG_UTILS_EXTENSION_NAME is a macro of a string defined in openxr.h. The XR_EXT_debug_utils is an extension that checks the validity of calls made to OpenXR and can use a callback function to handle any raised errors. We will explore this extension more in Chapter 2.1. Depending on which XR_USE_GRAPHICS_API_... macro you defined, this code will add the relevant extension.

Not all API layers and extensions are available to use, so we must check which ones are available when OpenXR is initialized. We will use xrEnumerateApiLayerProperties and xrEnumerateInstanceExtensionProperties to check which ones the runtime can provide. Let’s do this now by adding the following code to the CreateInstance() method:

// Get all the API Layers from the OpenXR runtime.
uint32_t apiLayerCount = 0;
std::vector<XrApiLayerProperties> apiLayerProperties;
OPENXR_CHECK(xrEnumerateApiLayerProperties(0, &apiLayerCount, nullptr), "Failed to enumerate ApiLayerProperties.");
apiLayerProperties.resize(apiLayerCount, {XR_TYPE_API_LAYER_PROPERTIES});
OPENXR_CHECK(xrEnumerateApiLayerProperties(apiLayerCount, &apiLayerCount, apiLayerProperties.data()), "Failed to enumerate ApiLayerProperties.");

// Check the requested API layers against the ones from the OpenXR. If found add it to the Active API Layers.
for (auto &requestLayer : m_apiLayers) {
    for (auto &layerProperty : apiLayerProperties) {
        // strcmp returns 0 if the strings match.
        if (strcmp(requestLayer.c_str(), layerProperty.layerName) != 0) {
            continue;
        } else {
            m_activeAPILayers.push_back(requestLayer.c_str());
            break;
        }
    }
}

// Get all the Instance Extensions from the OpenXR instance.
uint32_t extensionCount = 0;
std::vector<XrExtensionProperties> extensionProperties;
OPENXR_CHECK(xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr), "Failed to enumerate InstanceExtensionProperties.");
extensionProperties.resize(extensionCount, {XR_TYPE_EXTENSION_PROPERTIES});
OPENXR_CHECK(xrEnumerateInstanceExtensionProperties(nullptr, extensionCount, &extensionCount, extensionProperties.data()), "Failed to enumerate InstanceExtensionProperties.");

// Check the requested Instance Extensions against the ones from the OpenXR runtime.
// If an extension is found add it to Active Instance Extensions.
// Log error if the Instance Extension is not found.
for (auto &requestedInstanceExtension : m_instanceExtensions) {
    bool found = false;
    for (auto &extensionProperty : extensionProperties) {
        // strcmp returns 0 if the strings match.
        if (strcmp(requestedInstanceExtension.c_str(), extensionProperty.extensionName) != 0) {
            continue;
        } else {
            m_activeInstanceExtensions.push_back(requestedInstanceExtension.c_str());
            found = true;
            break;
        }
    }
    if (!found) {
        XR_TUT_LOG_ERROR("Failed to find OpenXR instance extension: " << requestedInstanceExtension);
    }
}

These functions are called twice. The first time is to get the count of the API layers or extensions and the second is to fill out the array of structures - this is called the “two-call idiom”. Before the second call, we need to set XrApiLayerProperties ::type or XrExtensionProperties ::type to the correct value, so that the second call can correctly fill out the data. After we have enumerated the API layer and extension, we use a nested loop to check to see whether an API layers or extensions is available and add it to the m_activeAPILayers and/or m_activeInstanceExtensions respectively.

In OpenXR, we provide an explicit input capacity to both xrEnumerateApiLayerProperties and xrEnumerateInstanceExtensionProperties, which provides an additional layer of memory-safety. xrEnumerateInstanceExtensionProperties also allows you to query instance extensions by API layer name. In this tutorial, we just query the non-layer extensions that are implicitly loaded by the runtime.

This is a subtle difference here from the two-call idiom in the Vulkan API.

Note that m_activeAPILayers and m_activeInstanceExtensions are of type std::vector<const char *>, which is helpful when filling XrInstanceCreateInfo. We can do just that since we have assembled all of the necessary information. Add the following to the CreateInstance() method.

XrInstanceCreateInfo instanceCI{XR_TYPE_INSTANCE_CREATE_INFO};
instanceCI.createFlags = 0;
instanceCI.applicationInfo = AI;
instanceCI.enabledApiLayerCount = static_cast<uint32_t>(m_activeAPILayers.size());
instanceCI.enabledApiLayerNames = m_activeAPILayers.data();
instanceCI.enabledExtensionCount = static_cast<uint32_t>(m_activeInstanceExtensions.size());
instanceCI.enabledExtensionNames = m_activeInstanceExtensions.data();
OPENXR_CHECK(xrCreateInstance(&instanceCI, &m_xrInstance), "Failed to create Instance.");

This section is fairly simple: we have used the previously collected data and assigned it to the members in the XrInstanceCreateInfo structure. Then, we called xrCreateInstance where we took pointers to the XrInstanceCreateInfo and XrInstance objects. When the function is called, if successful, it returns XR_SUCCESS and XrInstance will be non-null (i.e. not equal to XR_NULL_HANDLE).

At the end of the app, we should destroy the XrInstance with xrDestroyInstance. Add the following to the DestroyInstance() method:

OPENXR_CHECK(xrDestroyInstance(m_xrInstance), "Failed to destroy Instance.");

While we do have an XrInstance, let’s check its properties. Add the following code to the GetInstanceProperties() method:

XrInstanceProperties instanceProperties{XR_TYPE_INSTANCE_PROPERTIES};
OPENXR_CHECK(xrGetInstanceProperties(m_xrInstance, &instanceProperties), "Failed to get InstanceProperties.");

XR_TUT_LOG("OpenXR Runtime: " << instanceProperties.runtimeName << " - "
                            << XR_VERSION_MAJOR(instanceProperties.runtimeVersion) << "."
                            << XR_VERSION_MINOR(instanceProperties.runtimeVersion) << "."
                            << XR_VERSION_PATCH(instanceProperties.runtimeVersion));

Here, we have initialized the XrInstanceProperties with the correct XrStructureType and passed it along with the XrInstance to xrGetInstanceProperties. This will fill the rest of that structure. Next, we logged out the runtime’s name, and using the XR_VERSION_... macros, we parsed and logged the runtime version.

2.1.2 XR_EXT_debug_utils

XR_EXT_debug_utils is an instance extension for OpenXR, which allows the application to get more information on errors, warnings and messages raised by the runtime. You can specify which message severities and types are checked. If a debug message is raised, it is passed to the callback function, which can optionally use the user data pointer provided in the XrDebugUtilsMessengerCreateInfoEXT structure.

The message severities are:
  • Verbose: Output all messages.

  • Info: Output at least information messages helpful in debugging.

  • Warning: Output at least messages that could suggest an application bug and that need reviewing.

  • Error: Output messages from errors that may cause undefined behavior and/or crashes.

The message types are:
  • General: General information.

  • Validation: indicates possibly invalid usage of OpenXR.

  • Performance: indicates possible non-optimal usage of OpenXR.

  • Conformance: indicates a non-conformant OpenXR result from the runtime.

See also Debug Message Categorization in the OpenXR Specification.

Copy the following code into CreateDebugMessenger() and DestroyDebugMessenger() respectively:

// Check that "XR_EXT_debug_utils" is in the active Instance Extensions before creating an XrDebugUtilsMessengerEXT.
if (IsStringInVector(m_activeInstanceExtensions, XR_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
    m_debugUtilsMessenger = CreateOpenXRDebugUtilsMessenger(m_xrInstance);  // From OpenXRDebugUtils.h.
}
// Check that "XR_EXT_debug_utils" is in the active Instance Extensions before destroying the XrDebugUtilsMessengerEXT.
if (m_debugUtilsMessenger != XR_NULL_HANDLE) {
    DestroyOpenXRDebugUtilsMessenger(m_xrInstance, m_debugUtilsMessenger);  // From OpenXRDebugUtils.h.
}

In the above code, we first check that XR_EXT_DEBUG_UTILS_EXTENSION_NAME or XR_EXT_debug_utils is in activeInstanceExtensions, which we used to create the XrInstance. Next, we call the CreateOpenXRDebugUtilsMessenger() function. At the end of the application, we call DestroyOpenXRDebugUtilsMessenger() to release the resource.

Another feature of OpenXR is the API Layers, which may also assist you in debugging. You can read more about them in Chapter 6.3.

2.1.3 Obtaining the System Id

The next object we want to get is the XrSystemId. According to System in the OpenXR spec, OpenXR separates the concept of physical systems of XR devices from the logical objects that applications interact with directly. A system represents a collection of related devices in the runtime, often made up of several individual hardware components working together to enable XR experiences. So, an XrSystemId could represent a VR headset and a pair of controllers, or perhaps a mobile device with video pass-through for AR. So we need to decide what type of XrFormFactor we want to use, as some runtimes support multiple form factors. Here, we are selecting XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY. OpenXR currently offers two options for the XrFormFactor:

typedef enum XrFormFactor {
    XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY = 1,
    XR_FORM_FACTOR_HANDHELD_DISPLAY = 2,
    XR_FORM_FACTOR_MAX_ENUM = 0x7FFFFFFF
} XrFormFactor;

The above code is an excerpt from openxr/openxr.h

Add the following code to the GetSystemID() method:

// Get the XrSystemId from the instance and the supplied XrFormFactor.
XrSystemGetInfo systemGI{XR_TYPE_SYSTEM_GET_INFO};
systemGI.formFactor = m_formFactor;
OPENXR_CHECK(xrGetSystem(m_xrInstance, &systemGI, &m_systemID), "Failed to get SystemID.");

// Get the System's properties for some general information about the hardware and the vendor.
OPENXR_CHECK(xrGetSystemProperties(m_xrInstance, m_systemID, &m_systemProperties), "Failed to get SystemProperties.");

Here, we have filled out the XrSystemGetInfo structure with the desired XrFormFactor and passed it as a pointer along with the XrInstance and a pointer to the XrSystemId to the xrGetSystem function. After the function is called, if successful, XrSystemId will be non-null.

With the above code, we have also got the system’s properties. We partially filled out a XrSystemProperties structure and passed it as a pointer along with the XrInstance and the XrSystemId to the xrGetSystemProperties function. This function will fill out the rest of the XrSystemProperties structure; detailing the vendor’s ID, the system’s name and the system’s graphics and tracking properties.

typedef struct XrSystemGraphicsProperties {
    uint32_t    maxSwapchainImageHeight;
    uint32_t    maxSwapchainImageWidth;
    uint32_t    maxLayerCount;
} XrSystemGraphicsProperties;

typedef struct XrSystemTrackingProperties {
    XrBool32    orientationTracking;
    XrBool32    positionTracking;
} XrSystemTrackingProperties;

typedef struct XrSystemProperties {
    XrStructureType               type;
    void* XR_MAY_ALIAS            next;
    XrSystemId                    systemId;
    uint32_t                      vendorId;
    char                          systemName[XR_MAX_SYSTEM_NAME_SIZE];
    XrSystemGraphicsProperties    graphicsProperties;
    XrSystemTrackingProperties    trackingProperties;
} XrSystemProperties;

The above code is an excerpt from openxr/openxr.h

You can now run the application to check that you have a valid XrInstance and XrSystemId.

2.2 Creating a Session

The next major component of OpenXR that needs to be created is an XrSession. An XrSession encapsulates the state of the application from the perspective of OpenXR. When an XrSession is created, it starts in the XrSessionState XR_SESSION_STATE_IDLE. It is up to the runtime to provide any updates to the XrSessionState and for the application to query them and react to them. We will explore this in Chapter 2.3.

For now, we are just going to create an XrSession. At this point, you’ll need to select which Graphics API you wish to use. Only one Graphics API can be used with an XrSession. This tutorial demonstrates how to use D3D11, D3D12, OpenGL, OpenGL ES and Vulkan in conjunction with OpenXR to render graphics to the provided views. Ultimately, you will most likely be bringing your own rendering solution to this tutorial, therefore the code examples provided for the Graphics APIs are placeholders for your own code base; demonstrating in this sub-chapter what objects are needed from your Graphics API to create an XrSession. At compile time the Graphics API is selected and the macro XR_TUTORIAL_GRAPHICS_API is set accordingly. At runtime, in CheckGraphicsAPI_TypeIsValidForPlatform, we double check that the Graphics API is supported in the current platform. This tutorial uses polymorphic classes; GraphicsAPI_... derives from the base GraphicsAPI class. We construct an appropriate derived class through the use of std::unique_ptr<>.

Update the constructor of the OpenXRTutorial class, the OpenXRTutorial::Run() method, and add the definitions of the new methods and data members to their separate private sections. All the new code is highlighted below.

class OpenXRTutorial {
public:
        OpenXRTutorial(GraphicsAPI_Type apiType)
                : m_apiType(apiType) {
                if (!CheckGraphicsAPI_TypeIsValidForPlatform(m_apiType)) {
                        std::cout << "ERROR: The provided Graphics API is not valid for this platform." << std::endl;
                        DEBUG_BREAK;
                }
        }
        ~OpenXRTutorial() = default;

        void Run() {
                CreateInstance();
                CreateDebugMessenger();

                GetInstanceProperties();
                GetSystemID();

                CreateSession();
                DestroySession();

                DestroyDebugMessenger();
                DestroyInstance();
        }

private:
        void CreateInstance()
        {
                // [...]
        }
        void DestroyInstance()
        {
                // [...]
        }
        void GetInstanceProperties()
        {
                // [...]
        }
        void GetSystemID()
        {
                // [...]
        }
        void CreateDebugMessenger()
        {
                // [...]
        }
        void DestroyDebugMessenger()
        {
                // [...]
        }
        void CreateSession()
        {
        }
        void DestroySession()
        {
        }
        void PollSystemEvents()
        {
        }


private:
        XrInstance m_xrInstance = {};
        std::vector<const char *> m_activeAPILayers = {};
        std::vector<const char *> m_activeInstanceExtensions = {};
        std::vector<std::string> m_apiLayers = {};
        std::vector<std::string> m_instanceExtensions = {};

        XrDebugUtilsMessengerEXT m_debugUtilsMessenger = {};

        XrFormFactor m_formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
        XrSystemId m_systemID = {};
        XrSystemProperties m_systemProperties = {XR_TYPE_SYSTEM_PROPERTIES};

        GraphicsAPI_Type m_apiType = UNKNOWN;
        std::unique_ptr<GraphicsAPI> m_graphicsAPI = nullptr;

        XrSession m_session = XR_NULL_HANDLE;

        bool m_applicationRunning = true;
        bool m_sessionRunning = false;
};

2.2.1 XrSession

Copy the following code into the CreateSession() method.

XrSessionCreateInfo sessionCI{XR_TYPE_SESSION_CREATE_INFO};
m_graphicsAPI = std::make_unique<GraphicsAPI_Vulkan>(m_xrInstance, m_systemID);

When creating the Vulkan Instance, developers should be aware of any Vulkan layers that they may have externally activated.

sessionCI.next = m_graphicsAPI->GetGraphicsBinding();
sessionCI.createFlags = 0;
sessionCI.systemId = m_systemID;

OPENXR_CHECK(xrCreateSession(m_xrInstance, &sessionCI, &m_session), "Failed to create Session.");

For the DestroySession() method, add the following code:

OPENXR_CHECK(xrDestroySession(m_session), "Failed to destroy Session.");

Above is the code for creating and destroying an XrSession. xrDestroySession will destroy the XrSession when we are finished and shutting down the application. xrCreateSession takes the XrInstance, XrSessionCreateInfo and a XrSession return. If the function call is successful, xrCreateSession will return XR_SUCCESS and m_session will be non-null.

In the XrSessionCreateInfo structure we specify the system id we got from xrGetSystem, we do not set any flags (createFlags), and we define which Graphics API we wish to use. The Graphics API is defined via the XrSessionCreateInfo ::next void pointer. Following the Vulkan style of extensibility, structures for creating objects can be extended to enable extra functionality. In our case, the extension is required and thus XrSessionCreateInfo ::next can not be a nullptr. That pointer must point to ‘exactly one graphics API binding structure (a structure whose name begins with “XrGraphicsBinding”)’ (XrSessionCreateInfo(3) Manual Page). We get a pointer to the correct Graphics Binding structure by calling GraphicsAPI::GetGraphicsBinding();.

2.3 Polling the Event Loop

OpenXR uses an event-based system to describe changes within the XR system. It’s the application’s responsibility to poll these events and react to them. The polling of events is done by the function xrPollEvent. The application should call this function on every frame of its lifetime. Within a single XR frame, the application should continuously call xrPollEvent until the internal event queue is ‘drained’; multiple events can occur in an XR frame and the application needs to handle and respond to each accordingly.

Firstly, we will update the class. In the OpenXRTutorial::Run() method add the highlighted code below. Also, add the highlighted code for the new methods and data members in their separate private sections.

class OpenXRTutorial {
public:
        OpenXRTutorial(GraphicsAPI_Type apiType)
                : m_apiType(apiType) {
                if(!CheckGraphicsAPI_TypeIsValidForPlatform(m_apiType)) {
                         std::cout << "ERROR: The provided Graphics API is not valid for this platform." << std::endl;
                        DEBUG_BREAK;
                }
        }
        ~OpenXRTutorial() = default;

        void Run() {
                CreateInstance();
                CreateDebugMessenger();

                GetInstanceProperties();
                GetSystemID();

                CreateSession();

                while (m_applicationRunning) {
                        PollSystemEvents();
                        PollEvents();
                        if (m_sessionRunning) {
                                // Draw Frame.
                        }
                }

                DestroySession();

                DestroyDebugMessenger();
                DestroyInstance();
}

private:
        void CreateInstance()
        {
                // [...]
        }
        void DestroyInstance()
        {
                // [...]
        }
        void GetInstanceProperties()
        {
                // [...]
        }
        void GetSystemID()
        {
                // [...]
        }
        void CreateDebugMessenger()
        {
                // [...]
        }
        void DestroyDebugMessenger()
        {
                // [...]
        }
        void CreateSession()
        {
                // [...]
        }
        void DestroySession()
        {
                // [...]
        }
        void PollEvents()
        {
        }
        void PollSystemEvents()
        {
        }

private:
        XrInstance m_xrInstance = {};
        std::vector<const char *> m_activeAPILayers = {};
        std::vector<const char *> m_activeInstanceExtensions = {};
        std::vector<std::string> m_apiLayers = {};
        std::vector<std::string> m_instanceExtensions = {};

        XrDebugUtilsMessengerEXT m_debugUtilsMessenger = {};

        XrFormFactor m_formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
        XrSystemId m_systemID = {};
        XrSystemProperties m_systemProperties = {XR_TYPE_SYSTEM_PROPERTIES};

        GraphicsAPI_Type m_apiType = UNKNOWN;
        std::unique_ptr<GraphicsAPI> m_graphicsAPI = nullptr;

        XrSession m_session = XR_NULL_HANDLE;
        XrSessionState m_sessionState = XR_SESSION_STATE_UNKNOWN;
        bool m_applicationRunning = true;
        bool m_sessionRunning = false;
};

2.3.1 xrPollEvent

Copy the following code into the PollEvents() method:

// Poll OpenXR for a new event.
XrEventDataBuffer eventData{XR_TYPE_EVENT_DATA_BUFFER};
auto XrPollEvents = [&]() -> bool {
    eventData = {XR_TYPE_EVENT_DATA_BUFFER};
    return xrPollEvent(m_xrInstance, &eventData) == XR_SUCCESS;
};

while (XrPollEvents()) {
    switch (eventData.type) {
    // Log the number of lost events from the runtime.
    case XR_TYPE_EVENT_DATA_EVENTS_LOST: {
        XrEventDataEventsLost *eventsLost = reinterpret_cast<XrEventDataEventsLost *>(&eventData);
        XR_TUT_LOG("OPENXR: Events Lost: " << eventsLost->lostEventCount);
        break;
    }
    // Log that an instance loss is pending and shutdown the application.
    case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
        XrEventDataInstanceLossPending *instanceLossPending = reinterpret_cast<XrEventDataInstanceLossPending *>(&eventData);
        XR_TUT_LOG("OPENXR: Instance Loss Pending at: " << instanceLossPending->lossTime);
        m_sessionRunning = false;
        m_applicationRunning = false;
        break;
    }
    // Log that the interaction profile has changed.
    case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: {
        XrEventDataInteractionProfileChanged *interactionProfileChanged = reinterpret_cast<XrEventDataInteractionProfileChanged *>(&eventData);
        XR_TUT_LOG("OPENXR: Interaction Profile changed for Session: " << interactionProfileChanged->session);
        if (interactionProfileChanged->session != m_session) {
            XR_TUT_LOG("XrEventDataInteractionProfileChanged for unknown Session");
            break;
        }
        break;
    }
    // Log that there's a reference space change pending.
    case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: {
        XrEventDataReferenceSpaceChangePending *referenceSpaceChangePending = reinterpret_cast<XrEventDataReferenceSpaceChangePending *>(&eventData);
        XR_TUT_LOG("OPENXR: Reference Space Change pending for Session: " << referenceSpaceChangePending->session);
        if (referenceSpaceChangePending->session != m_session) {
           XR_TUT_LOG("XrEventDataReferenceSpaceChangePending for unknown Session");
            break;
        }
        break;
    }
    // Session State changes:
    case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
        XrEventDataSessionStateChanged *sessionStateChanged = reinterpret_cast<XrEventDataSessionStateChanged *>(&eventData);
        if (sessionStateChanged->session != m_session) {
            XR_TUT_LOG("XrEventDataSessionStateChanged for unknown Session");
            break;
        }

        if (sessionStateChanged->state == XR_SESSION_STATE_READY) {
            // SessionState is ready. Begin the XrSession using the XrViewConfigurationType.
            XrSessionBeginInfo sessionBeginInfo{XR_TYPE_SESSION_BEGIN_INFO};
            sessionBeginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
            OPENXR_CHECK(xrBeginSession(m_session, &sessionBeginInfo), "Failed to begin Session.");
            m_sessionRunning = true;
        }
        if (sessionStateChanged->state == XR_SESSION_STATE_STOPPING) {
            // SessionState is stopping. End the XrSession.
            OPENXR_CHECK(xrEndSession(m_session), "Failed to end Session.");
            m_sessionRunning = false;
        }
        if (sessionStateChanged->state == XR_SESSION_STATE_EXITING) {
            // SessionState is exiting. Exit the application.
            m_sessionRunning = false;
            m_applicationRunning = false;
        }
        if (sessionStateChanged->state == XR_SESSION_STATE_LOSS_PENDING) {
            // SessionState is loss pending. Exit the application.
            // It's possible to try a reestablish an XrInstance and XrSession, but we will simply exit here.
            m_sessionRunning = false;
            m_applicationRunning = false;
        }
        // Store state for reference across the application.
        m_sessionState = sessionStateChanged->state;
        break;
    }
    default: {
        break;
    }
    }
}

In PollEvents() we use a lambda to call xrPollEvent. This call will fill in the XrEventDataBuffer structure that is accessible via the by-reference capture ([&]). We also check that the returned XrResult is XR_SUCCESS. Whilst xrPollEvent returns XR_SUCCESS, there are events for us to process, and therefore we use a while loop to continually check for new events. xrPollEvent will update the member variable type, which then determines how we respond to the event. Depending on the updated type, we use a reinterpret_cast<>() to get the actual data that xrPollEvent returned. In certain cases, we also check that the returned XrSession matches the one we created.

The description of the events comes from 2.22.1. Event Polling of the OpenXR specification.

Event Type

Description

XR_TYPE_EVENT_DATA_EVENTS_LOST

The event queue has overflowed and some events were lost.

XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING

The application is about to lose the instance.

XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED

The active input form factor for one or more top-level user paths has changed.

XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING

The runtime will begin operating with updated space bounds.

XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED

The application has changed its lifecycle state.

As described in the table above, most events are transparent in their intentions and how the application should react to them. For the XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING state, after the specified lossTime, the application may want to try re-creating the XrInstance in a loop until it is sucessful. XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED and XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING are used for updating how the user interacts with the application and whether a new space change has been detected respectively. XrSpace s are discussed in detail in Chapter 3.2.2

2.3.2 XrSessionState

The final event type, XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED, in the above code and table, is what we will focus on for the rest of this chapter. There are currently nine valid states:

typedef enum XrSessionState {
    XR_SESSION_STATE_UNKNOWN = 0,
    XR_SESSION_STATE_IDLE = 1,
    XR_SESSION_STATE_READY = 2,
    XR_SESSION_STATE_SYNCHRONIZED = 3,
    XR_SESSION_STATE_VISIBLE = 4,
    XR_SESSION_STATE_FOCUSED = 5,
    XR_SESSION_STATE_STOPPING = 6,
    XR_SESSION_STATE_LOSS_PENDING = 7,
    XR_SESSION_STATE_EXITING = 8,
    XR_SESSION_STATE_MAX_ENUM = 0x7FFFFFFF
} XrSessionState;

The above code is an excerpt from openxr/openxr.h

Below is a table describing the states of XrSessionState:

(*) Applications may wish to re-create objects like XrSystemId and XrSession if hardware changes are detected.

Developers should also be aware of the lifecycle of an XrSession. Certain XrSessionState s can only lead to others under specific conditions. Below is a diagram showing the lifecycle of an XrSession within an OpenXR application.

OpenXR Session Life-Cycle

OpenXR Session Life-Cycle

2.3.3 xrBeginSession and xrEndSession

As the application runs, if the XrSessionState changes to XR_SESSION_STATE_READY, the application can call xrBeginSession to begin the session and synchronize the application with the runtime’s frame hook.

if (sessionStateChanged->state == XR_SESSION_STATE_READY) {
    // SessionState is ready. Begin the XrSession using the XrViewConfigurationType.
    XrSessionBeginInfo sessionBeginInfo{XR_TYPE_SESSION_BEGIN_INFO};
    sessionBeginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
    OPENXR_CHECK(xrBeginSession(m_session, &sessionBeginInfo), "Failed to begin Session.");
    m_sessionRunning = true;
}

The above code was added in Chapter 2.3.1. We’ve assigned to XrSessionBeginInfo ::primaryViewConfigurationType the value of XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO. This specifies the view configuration of the form factor’s primary display - For Head Mounted Displays, it is two views (one per eye). For the time being, we have defaulted to XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO. In Chapter 3.1.1, we enumerate all the system’s view configurations to pick a suitable one.

If the XrSessionState changes to XR_SESSION_STATE_STOPPING, the application should call xrEndSession. This means that the runtime has stopped the session either from the user’s input or from some other reason, our application should respond by ending the session and freeing any resources. Below is a small excerpt from the code added in Chapter 2.3.1.

if (sessionStateChanged->state == XR_SESSION_STATE_STOPPING) {
    // SessionState is stopping. End the XrSession.
    OPENXR_CHECK(xrEndSession(m_session), "Failed to end Session.");
    m_sessionRunning = false;
}

2.4 Summary

We have now created an XR application that can begin and stop an XrSession. In the next chapter we will add graphics to our application!

Below is a download link to a zip archive containing this chapter’s C++ and CMake code for all platform and graphics APIs combinations.

Chapter2.zip

Version: v1.0.5