Developing with OpenXR and Monado

After installing the OpenXR SDK and Monado with the Getting Started Guide it is time to develop an OpenXR application.

The OpenXR SDK

On Archlinux, the OpenXR loader installs these files

Other distributions may use different paths like /usr/lib/x86_64-linux-gnu/....

The OpenXR headers

To start developing for OpenXR only those components are necessary. Similar to how Vulkan applications are developed by including Khronos’ Vulkan headers and linking to Khronos’ Vulkan loader, OpenXR applications include Khronos’ OpenXR headers and link to Khronos’ OpenXR loader. Just like compiling a Vulkan application does not require any Vulkan driver to be installed, compiling an OpenXR application does not require any OpenXR runtime to be installed.

Khronos has not released a reference implementation for OpenXR, so for actually running an OpenXR application some vendor’s OpenXR runtime (for example Monado) has to be installed.

Setting up the project

CMake

Here is a minimal cmake example to compile an example executable that can include OpenXR headers and links to the OpenXR loader.

cmake_minimum_required(VERSION 3.0.0)
project(Example)

add_executable(example main.c)

find_package(OpenXR REQUIRED)
if(OpenXR_FOUND)
  target_include_directories(example PRIVATE OpenXR::Headers)
  target_link_libraries(example PRIVATE OpenXR::openxr_loader)
else()
  MESSAGE(FATAL_ERROR "Please verify your OpenXR SDK installation")
endif()

As an alternative you can use OpenXR’s pkg-config file with CMake’s FindPkgConfig module.

cmake_minimum_required(VERSION 3.0.0)
project(Example)

add_executable(example main.c)

INCLUDE(FindPkgConfig)
PKG_SEARCH_MODULE(OpenXR REQUIRED openxr)
if(OpenXR_FOUND)
  target_link_libraries(example PRIVATE ${OpenXR_LIBRARIES})
  target_include_directories(example PRIVATE ${OpenXR_HEADERS})
else()
  MESSAGE(FATAL_ERROR "Please verify your OpenXR SDK installation")
endif()

If you prefer using your own FindOpenXR.cmake file, the xrtraits utility has an example.

meson

Meson uses pkg-config by default for its dependency() mechanism.

project('Example', ['c'])

openxr_dep = dependency('openxr')
executable('example', ['main.c'], dependencies: [openxr_dep])

Application

With this project setup you can start including the <openxr/openxr.h> header and start calling OpenXR functions.

As a starting point, here is a minimal C example for starting an OpenGL application on Linux with the xlib graphics binding.

// openxr_platform.h does not include all its dependencies, we have to include some headers before it
#include <string.h>
#include <X11/Xlib.h>
#include <GL/glx.h>

// before including openxr_platform.h we have to define which platform specific parts we want enabled
#define XR_USE_PLATFORM_XLIB
#define XR_USE_GRAPHICS_API_OPENGL
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>

int main()
{
  char *extensions[] = { XR_KHR_OPENGL_ENABLE_EXTENSION_NAME };
  int extension_count = sizeof(extensions) / sizeof(extensions[0]);

  XrInstanceCreateInfo instanceCreateInfo = {
    .type = XR_TYPE_INSTANCE_CREATE_INFO,
    .next = NULL,
    .createFlags = 0,
    .enabledExtensionCount = extension_count,
    .enabledExtensionNames = (const char * const *) extensions,
    .enabledApiLayerCount = 0,
    .applicationInfo =  {
      .applicationVersion = 1,
      .engineVersion = 0,
      .apiVersion = XR_CURRENT_API_VERSION,
    },
  };

  strncpy(instanceCreateInfo.applicationInfo.applicationName, "Example Application", XR_MAX_APPLICATION_NAME_SIZE);
  strncpy(instanceCreateInfo.applicationInfo.engineName, "Example Engine", XR_MAX_APPLICATION_NAME_SIZE);

  XrInstance instance;
  xrCreateInstance(&instanceCreateInfo, &instance);
  return 0;
}

Explaining the entire basic OpenXR API is no short task. Instead, the commented OpenXR-Simple-Example tries to be as straightforward as possible in dealing with the OpenXR API.

You can also look at the list of open source examples and applications or a recording of the OpenXR Master Class at Laval Virtual.

Your most important reference of course will be the OpenXR specification: https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html

Helpful Drivers

qwerty

The qwerty driver is named after the qwerty keyboard layout. It enables basic control of a virtual HMD and controllers with keyboard and mouse.

The qwerty driver is enabled by using the environment variable QWERTY_ENABLE=1 for monado-service. The qwerty driver can only be used in combination with Monado’s debug gui, therefore the environment variable OXR_DEBUG_GUI=1 has to be set too: QWERTY_ENABLE=1 OXR_DEBUG_GUI=1 monado-service.

By default the qwerty driver disables all connected hardware devices. The additional environment variable QWERTY_COMBINE=1 will only fill the HMD, left controller and right controller slots, if no hardware device is occupying that slot.

In the Monado debug gui, find and expand Help in the Qwerty System #1 box for basic usage information.

remote

Monado’s remote driver is intended for passing the conformance test suite without hardware, as well as debugging and testing.

Configuration

The driver is enabled by adding a “remote” section to ~/.config/monado/config_v0.json. An example config file, containing configuration for the remote driver as well as the Valve Index camera calibration:

{
	"active": "remote",
	"remote": {
		"version": 0,
		"port": 4242
	}
}

(The “version” and “port” fields are actually optional.)

The special field "active": "remote" is used to tell monado which configuration entry to use for this run. To switch to the Valve Index calibration config, it would be changed to "active": "tracking".

The active field can also be dynamically overriden with an environment variable: P_OVERRIDE_ACTIVE_CONFIG=tracking.

Usage

Start monado-service and an OpenXR client application as usual, or just an OpenXR client application in case of a monado build with service disabled.

Then, start monado-gui and select “Remote”, then “Connect”. A “Remote control” panel should open, allowing to adjust individual values for HMD and controller poses and velocities as well as “finger curl” values for the simple finger curl value based hand tracking model. Note that the values can be adjusted with drag & drop on the bars.

Debugging

gdb

By default Monado is compiled with two parts. Firstly, monado-service runs in its own process and drives the compositor and the device drivers. Secondly, a monado client library is loaded into the OpenXR application. The client library communicates with monado-service over shared memory and a socket, which can be hard to debug.

When debugging from the application into the monado compositor is desired, consider compiling monado with the service disabled.

Renderdoc

Renderdoc typically looks for an application displaying a buffer in a window to know when to capture frames. If your OpenXR application only submits to the OpenXR runtime without displaying a window on the desktop, consider using the In-application API to add frame markers.

When running an OpenXR client in renderdoc on monado with the service enabled you may encounter an error message like ERROR [vk_alloc_and_bind_image_memory] client_vk_swapchain - Got too little memory 18956312 vs 18874376. This is likely caused by renderdoc meddling with allocations.

Running monado-service with the environment variable ENABLE_VULKAN_RENDERDOC_CAPTURE=1 makes sure that the Vulkan textures created by the monado service and imported on the client side are going through the same renderdoc layer.