Introduction
Overview
Development Environment
Setting up OpenXR
Loading OpenXR Procedures
Creating an XrInstance
To use OpenXR, we must first initialize it by creating an OpenXR instance. The instance is the connection between your application and the OpenXR runtime, and when creating it we must pass the runtime details about our application.
First, we'll fill in an XrApplicationInfo
struct with information about our application.
This data will be used by the driver to present useful information, such as in the runtime's overlay.
XrApplicationInfo application_info = {
.apiVersion = XR_CURRENT_API_VERSION,
.applicationName = "OpenXR Tutorial",
.applicationVersion = 1,
.engineName = "OpenXR Tutorial Engine",
.engineVersion = 1,
};
We're using designated initialization, which allows us to conveniently intitialise the
applicationName
and engineName
fields, as well as automatically zero initializing any fields
we did not specify explictly.
Most information in OpenXR is passed through structs rather than function parameters. Creating
an instance requires us to fill out one more struct: XrInstanceCreateInfo
. This tells the driver
which API layers and Extensions that we wish to use. For now, we'll be leaving those fields blank
and just passing in the application info.
XrInstanceCreateInfo instance_info = {
.type = XR_TYPE_INSTANCE_CREATE_INFO,
.applicationInfo = application_info,
};
This struct has a field type
which is common among all structs passed directly to OpenXR procedures.
Now that we have our structs filled out, we can call xrCreateInstance
to create the instance. Once
we've called that, we'll check that the result is equal to XR_SUCCESS
before continuing, and exit
if it is not.
XrInstance instance;
XrResult result = xrCreateInstance(&instance_info, &instance);
if (result != XR_SUCCESS) {
printf("Failed to Create Instance\n");
return -1;
}
If your program exited with an error code above, you may have also seen something printed in your terminal such as:
Error [GENERAL | xrCreateInstance | OpenXR-Loader] : LoaderInstance::CreateInstance chained CreateInstance call failed
Error [GENERAL | xrCreateInstance | OpenXR-Loader] : xrCreateInstance failed
Even without a debug messenger enabled, the OpenXR Loader will print some minimal information such as this. On desktop, the likeliest explanation for this error is simply that no VR HMD is plugged into your computer. Runtimes such as SteamVR will refuse to even initialise OpenXR if no HMD is detected, so make sure yours is plugged in.
Loading Extensions
Creating a Debug Messenger
When writing OpenXR application code, it's inevitable that we'll make the occasional mistake. Since OpenXR is rigorously specified, correct usage of OpenXR procedures can, to an extent, be programmatically verified.
Doing this by default would, however, impose too large a performance penalty on applications.
So, during development, we can optionally enable debug utils to help us write correct code.
This is done with the XR_EXT_debug_utils
extension. To enable the extension, we must modify
our call to xrCreateInstance
to include the name of the extension. Since this is, for now,
our only extension, we'll put the extension count behind a preprocessor check, to only enable
it during debug builds.
#ifndef NDEBUG
const char *enabled_extensions[] = {XR_EXT_DEBUG_UTILS_EXTENSION_NAME};
uint32_t extension_count = 1;
#else
const char **enabled_extensions = NULL;
uint32_t extension_count = 0;
#endif
XrInstanceCreateInfo instance_info = {
.type = XR_TYPE_INSTANCE_CREATE_INFO,
.applicationInfo = application_info,
.enabledExtensionCount = extension_count,
.enabledExtensionNames = enabled_extensions,
};
With the extension enabled, we can now create a Debug Utils Messenger, which will be referenced by
a XrDebugUtilsMessengerEXT
handle. To create one, we must provide the OpenXR runtime with a callback,
which will be invoked whenever a debug message is produced. Inside this callback, we can choose to
do whatever we wish. Here, we will print the information with printf
, another option would be writing
messages to a logfile.
The function signature of the callback is declared in the OpenXR headers as PFN_xrDebugUtilsMessengerCallbackEXT
.
We can copy that signature to define our function. In C++, extern "C"
should be prepended to the declaration
to ensure it has C linkage.
XrBool32 openxr_debug_callback(
XrDebugUtilsMessageSeverityFlagsEXT messageSeverity,
XrDebugUtilsMessageTypeFlagsEXT messageTypes,
const XrDebugUtilsMessengerCallbackDataEXT* callbackData,
void* userData) {
printf("XR DEBUG MESSAGE: %s\n", callbackData->message;
return XR_FALSE;
}
The return value of this callback is an XrBool32
indicating whether the calling layer should abort the call of
the function that produced the debug callback. This can be useful to abort execution if, for example, messageSeverity
contains the XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
bit. Here, we simply always continue execution.