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.