Copyright (c) 2017-2024, The Khronos Group Inc. All Rights reserved.

Introduction

This document contains the necessary information for understanding how to develop for, and interact with, the OpenXR loader. Intended use of this document is as a detailed design document and a tool for learning general OpenXR loader behavior.

In the event of any discrepancies between this document and the OpenXR specification, that document is authoritative.

OpenXR™ and the OpenXR logo are trademarks owned by The Khronos Group Inc. and are registered as a trademark in China, the European Union, Japan, and the United Kingdom. OpenGL and OpenGL ES are registered trademarks of Hewlett Packard Enterprise, all used under license by Khronos. All other product names, trademarks, and/or company names are used solely for identification and belong to their respective owners.

1. Terminology

The key words must, may, can, cannot, should, required, recommend, and optional in this document are to be interpreted as described in RFC 2119.

2. Overview

OpenXR is a layered architecture, made up of the following elements:

The general concepts in this document are applicable to the loaders available for Windows and Linux based systems.

First, let’s look at the OpenXR environment as a whole. The OpenXR application is at the start of the execution chain, and interfaces directly with the OpenXR loader. The loader, in turn, detects, loads, and interacts with any number of OpenXR runtimes and API layers. Each OpenXR runtime controls a complete VR/XR/MR system that an application can choose to interact with. The loader may inject any number of optional API layers between the application and the runtime to augment behavior. As a result, any OpenXR command may involve executing code in a number of different modules, including the loader, API layers, and runtimes.

high level loader
Figure 1. High Level View of the OpenXR Loader

2.1. Who Should Read This Document

While this document is primarily targeted at developers of OpenXR runtimes or API layers, and those wishing to contribute to the OpenXR loader; the information contained in it may be useful to anyone wanting a better understanding of OpenXR.

The OpenXR API Specification should be used as the primary means of understanding the OpenXR API and all care has been made to not conflict with any element of that document. In any case where this document and the OpenXR API Specification differ, behavior as defined by the OpenXR specification must be considered the correct standard.

2.2. OpenXR Loader

The loader is critical to detecting, exposing, and possibly loading any available OpenXR runtime or API layer on the system. Once setup, the loader is also responsible with managing the proper dispatching of OpenXR commands to each of these components.

This document is intended to provide an overview of the necessary interfaces between the loader and:

In addition, this document also covers various internal design elements of the OpenXR loader.

2.2.1. Goals of the Loader

The loader was designed with the following goals in mind.

  1. It must support one or more OpenXR-capable runtimes on a user’s computer system.

  2. It must support OpenXR API layers (optional modules that can be enabled by an application, developer, or standard system settings).

  3. It must strive to reduce its overall memory and performance impact to an OpenXR application.

2.3. OpenXR API Layers

API layers are optional components that augment the OpenXR system. They may intercept, evaluate, modify, and insert existing OpenXR commands on their way from the application down to the runtime. API layers are implemented as libraries that are enabled in a variety of ways (including by application request). All API layers are enabled in an OpenXR system during the xrCreateInstance call. Each API layer may choose to hook (intercept) any OpenXR command which in turn may be ignored or augmented. An API layer does not need to intercept all OpenXR commands but only intercept those commands it desires.

Some examples of features that API layers may expose include:

  • Validating API usage

  • Adding the ability to perform API tracing and debugging

  • Intercept and filter information between the application and the runtime

Because API layers are optional, you may choose to enable API layers for debugging your application, but then disable any API layer usage when you release your product.

For more information on how the OpenXR interacts with API layers, refer to the "API Layer Interaction" section of this document.

2.4. OpenXR Runtimes

OpenXR works with multiple runtimes each supporting one or more devices. Each OpenXR runtime controls a complete VR/AR/MR system. Multiple runtimes may be installed on a system, with one being active at any given time. The loader discovers the active runtime on the system and loads it. When an XrInstance is created during xrCreateInstance, the active runtime is associated with the instance.

For more information on how the OpenXR interacts with runtimes, refer to the "Runtime Interaction" section of this document.

2.5. OpenXR Objects

OpenXR uses an object model to control the scope of a particular action or operation. The object to be acted on is usually the first parameter of an OpenXR call. Under the covers, this object is a unique handle to either a class or structure. The contents of each class/structure vary, but each has a means of looking up a dispatch table which is used to direct the functional flow through any enabled API layers and into the appropriate runtime.

Note

Some OpenXR commands do not take an object, such as xrEnumerateApiLayerProperties and xrEnumerateInstanceExtensionProperties. These commands typically only provide information for other commands which do take an object.

2.6. OpenXR Call Chains

When an (optional) API layer is loaded, the loader links together a call chain that includes each function selected by the layer. At xrCreateInstance time the loader initializes all enabled API layers and creates call chains for each OpenXR command, with each entry of the resulting dispatch table pointing to the first element of that chain. The loader builds an individual dispatch table for the XrInstance that is created.

When an application calls an OpenXR command, this typically will first hit a trampoline function in the loader. These trampoline functions are small, simple functions that jump to the appropriate dispatch table entry for the object they are given. Some functions also require an additional loader function called a terminator, which is called after all enabled API layers to process data before proceeding to the appropriate runtime.

call chain example
Figure 2. Example Call Chain

The loader only allows a single outstanding XrInstance and uses the generated dispatch table for that XrInstance for all OpenXR API commands.

3. Application Interaction

An application requests a specific version of the OpenXR API when creating an instance by writing to the "apiVersion" member of the XrApplicationInfo structure which, in turn, is passed into the XrInstanceCreateInfo structure. If either the loader or the active runtime do not support the requested version, they may return an error and fail instance creation. Refer to the OpenXR API Specification documentation on xrCreateInstance for more information about this.

3.1. Interfacing with OpenXR Functions

There are two ways an application can choose interface with OpenXR functions through the loader:

  1. Directly linking to the core OpenXR commands exposed and exported by the loader.

  2. Creating an application-managed dispatch table of OpenXR commands by querying the loader for function pointers via xrGetInstanceProcAddr. This method supports core OpenXR commands, commands of (enabled) OpenXR extensions supported by the runtime, and any commands exposed by enabled API layers.

3.1.1. OpenXR Direct Exports

The loader library on Windows and Linux will directly export all core OpenXR commands for the OpenXR version it supports. When an application links directly to the loader library in this way, the OpenXR calls are simple trampoline functions that jump to the appropriate dispatch table entry for the object provided.

The specific list, in alphabetical order, of OpenXR commands directly exported by the loader for API version 1.0 are:

  • xrAcquireSwapchainImage

  • xrApplyHapticFeedback

  • xrAttachSessionActionSets

  • xrBeginFrame

  • xrBeginSession

  • xrCreateAction

  • xrCreateActionSet

  • xrCreateActionSpace

  • xrCreateInstance

  • xrCreateReferenceSpace

  • xrCreateSession

  • xrCreateSwapchain

  • xrDestroyAction

  • xrDestroyActionSet

  • xrDestroyInstance

  • xrDestroySession

  • xrDestroySpace

  • xrDestroySwapchain

  • xrEndFrame

  • xrEndSession

  • xrEnumerateApiLayerProperties

  • xrEnumerateBoundSourcesForAction

  • xrEnumerateEnvironmentBlendModes

  • xrEnumerateInstanceExtensionProperties

  • xrEnumerateReferenceSpaces

  • xrEnumerateSwapchainFormats

  • xrEnumerateSwapchainImages

  • xrEnumerateViewConfigurations

  • xrEnumerateViewConfigurationViews

  • xrGetActionStateBoolean

  • xrGetActionStateFloat

  • xrGetActionStatePose

  • xrGetActionStateVector2f

  • xrGetCurrentInteractionProfile

  • xrGetInputSourceLocalizedName

  • xrGetInstanceProcAddr

  • xrGetInstanceProperties

  • xrGetReferenceSpaceBoundsRect

  • xrGetSystem

  • xrGetSystemProperties

  • xrGetViewConfigurationProperties

  • xrLocateSpace

  • xrLocateViews

  • xrPathToString

  • xrPollEvent

  • xrReleaseSwapchainImage

  • xrRequestExitSession

  • xrResultToString

  • xrStopHapticFeedback

  • xrStringToPath

  • xrStructureTypeToString

  • xrSuggestInteractionProfileBindings

  • xrSyncActions

  • xrWaitFrame

  • xrWaitSwapchainImage

When an OpenXR application chooses to use one of the exports directly exposed from the OpenXR loader, the call chain will look like one of the following (if the user has enabled two API layers):

standard call chains
Figure 3. Standard Call Chains

The "Special Instance" call chain is used in several places where the loader has to perform some work before and after any API layers, but prior to calling the enabled runtime. xrCreateInstance, xrDestroyInstance, and xrGetInstanceProcAddr are three of the main commands that fall into this group. Most commands do not require a terminator and will use the second call chain.

3.1.2. OpenXR Indirect Linking

With loader indirect linking an application dynamically generates its own dispatch table of OpenXR commands. This method allows an application to fail gracefully if the loader cannot be found, or supports an older version of the API than the application. To do this, the application uses the appropriate platform specific dynamic symbol lookup (such as dlsym()) on the loader library to discover the address of the xrGetInstanceProcAddr command. Once discovered, this command can be used to query the addresses of all other OpenXR commands (such as xrCreateInstance, xrEnumerateInstanceExtensionProperties and xrEnumerateApiLayerProperties). Then, the application would use its table of function pointers to make the call into the OpenXR API.

One benefit when an application generates its own dispatch table is the removal of the loader trampoline call from most commands in a call chain, which could potentially increase performance. In that case, most commands would use the following call chain:

app dispatch table call chain
Figure 4. Call Chain For Application Dispatch

3.1.3. OpenXR Loader Library Name

The OpenXR loader’s dynamic library name is dependent on the user’s underlying operating system. The following table shows the loader library names for common OSs:

Operating System Loader Library Name

Windows

openxr-loader.lib/.dll

Linux

libopenxr_loader.so.<major>

<major> and <minor> in the above table refer to the major and minor API versions of OpenXR.

3.2. Application API Layer Usage

Applications desiring OpenXR functionality beyond what the core API offers may use various API layers or extensions. An API layer cannot introduce new OpenXR core API commands, but may introduce new extension-specific OpenXR commands that can be queried through the extension interface.

A common use of API layers is for validation which can be enabled by loading the API layer during application development, but not loading the API layer for application release. The overhead cost of validation is completely eliminated when the layer is not loaded.

An application can discover which API layers are available to it with xrEnumerateApiLayerProperties. This will report all API layers that have been discovered by the loader. The loader looks in various locations to find API layers on the system. For more information see the API Layer discovery section below.

An API layer or layers may be enabled by an application by passing a list of names in the enabledApiLayerNames field of the XrInstanceCreateInfo at xrCreateInstance time. During the xrCreateInstance, the loader constructs a call chain that includes the application specified (enabled) API layers.

Order is important in the enabledApiLayerNames array; array element 0 is the topmost (closest to the application) API layer inserted in the chain and the last array element is closest to the runtime. See the Overall API Layer Ordering section for more information on API layer ordering.

Important

Some API layers interact with other API layers in order to perform their tasks. Consult the appropriate API layer documentation when enabling an API layer to ensure that you are using it properly.

3.2.1. Implicit vs Explicit API Layers

Explicit API layers are API layers which are enabled by an application (e.g. with the xrCreateInstance function) or by setting a loader environment variable.

Implicit API layers are API layers which are enabled simply because they exist. An implicit layer found by the loader will be loaded by default, unless specifically disabled.

Some implicit API layer examples include:

  • A performance monitoring API layer which is enabled while using an external tool.

  • An application environment API layer (e.g. Steam or a game console) that enables additional features/tools for applications.

  • A tracing API layer designed to record API commands for future playback.

Because implicit API layers are enabled automatically, they have an additional requirement over explicit API layers in that they must specify a "disable environment variable" in the API layer’s JSON manifest file, with key name disable_environment. This environment variable when present will disable the implicit API layer. Implicit API layers are not otherwise visible to or controllable by applications.

Optionally, an implicit API layers may specify an "enable environment variable", in which case the loader will load the implicit API layer only when the enable environment variable is defined in the user’s execution environment. If both enable and disable environment variables are present in the environment, the layer will be disabled.

Both the enable and disable environment variables are specified in the API layer’s JSON manifest file, which is created by the API layer developer.

Discovery of system-installed implicit and explicit API layers is described later in the API Layer Discovery Section. For now, simply know that what distinguishes an API layer as implicit or explicit is dependent on the operating system, as shown in the table below.

Table 1. OS Implicit API Layer Detection Method
Operating System Implicit API Layer Identification Method

Windows

Implicit API Layers are located in the Windows Registry under the "ApiLayers\Implicit" key.

Linux

Implicit API Layers are located in the "api_layers/implicit.d" folder under any of the common API layer paths.

3.2.2. Forcing API Layer Folders (Desktop Only)

Desktop developers may also override the way the loader searches for API layers. If the developer/user would like to find API layers in a non-standard location, they may define the "XR_API_LAYER_PATH" environmental variable. For more information on using "XR_API_LAYER_PATH", see the Overriding the Default API Layer Paths section under "API Layer Interaction".

3.2.3. Forced Loading of API Layers (Desktop Only)

Desktop developers may want to enable API layers that are not enabled by the given application they are using. On Linux and Windows, the environment variable "XR_ENABLE_API_LAYERS" can be used to enable additional API layers which are not specified (enabled) by the application at xrCreateInstance. "XR_ENABLE_API_LAYERS" is a colon (Linux)/semi-colon (Windows) separated list of API layer names to enable. Order is relevant with the first API layer in the list being the top-most API layer (closest to the application) and the last API layer in the list being the bottom-most API layer (closest to the driver). See the Overall API Layer Ordering section for more information.

Application specified API layers and user specified API layers (via environment variables) are aggregated and duplicates removed by the loader when enabling API layers. API layers specified via environment variable are top-most (closest to the application) while API layers specified by the application are bottom-most. If an API layer name appears multiple times in the list of API layers to enable, all occurrences after the first are ignored.

To use XR_ENABLE_API_LAYERS, simply define the environment variable with a properly delimited list of API layer names. This is not the file name, but the name the API layers use when normally being enabled inside of xrCreateInstance. This usually takes the form of:

  • An OpenXR API layer prefix string: "XR_APILAYER_"

  • The name of the vendor developing the API layer: e.g. "LUNARG_"

  • A short descriptive name of the API layer: e.g. "test"

This would produce the name of: XR_APILAYER_LUNARG_test

Example 1. Setting XR_ENABLE_API_LAYERS

Windows

set XR_ENABLE_API_LAYERS=XR_APILAYER_LUNARG_test1;XR_APILAYER_LUNARG_test2

Linux

export XR_ENABLE_API_LAYERS=XR_APILAYER_LUNARG_test1:XR_APILAYER_LUNARG_test2

3.2.4. Overall API Layer Ordering

The overall ordering of all API layers by the loader based on the above looks as follows:

loader layer order calls
Figure 5. Loader API Layer Ordering

As shown in the above image, any implicit API layers will first intercept OpenXR commands, followed by any explicit API layers enabled by environmental variables, finally being intercepted by any explicit API layers directly enabled by the application. Whether or not the API layers continue into a loader terminator call, or directly into a runtime depends on the call chain.

Ordering may also be important internal to the list of explicit API layers. Some API layers may be dependent on other behavior being implemented before or after the loader calls it.

3.3. Application Usage of Extensions

Extensions are optional functionality provided by the loader, an API layer, or a runtime. Extensions can modify the behavior of the OpenXR API and need to be specified and registered with Khronos. These extensions can be created by a runtime vendor to expose new runtime and/or XR hardware functionality, or by an API layer writer to expose some internal feature, or by the loader to add additional loader functionality. Information about various extensions can be found in the OpenXR API Specification, and openxr.h header file.

To use extension functionality, the application should call xrEnumerateInstanceExtensionProperties to determine if the extension is available. Then it must enable the extension in xrCreateInstance.

If an application fails to determine whether an extension is available, and calls xrCreateInstance with that extension name in the list, the xrCreateInstance call will fail if it’s not supported.

When calling xrEnumerateInstanceExtensionProperties with NULL passed in for "layerName", the loader discovers and aggregates all extensions from implicit API layers (if allowable by OS), runtimes, and itself before reporting them to the application. If "layerName" is not NULL, and "layerName" is equal to a discovered API layer module name (which may belong to either an implicit or explicit API layer) then only extensions from that API layer are enumerated.

Duplicate extensions (e.g. an implicit API layer and runtime might report support for the same extension) are eliminated by the loader. For duplicates, the API layer version is reported and the runtime version is culled according to the loader ordering - an extension name is removed from the extension request list after loading the layer.

If an application fails to enable the extension using the "enabledExtensionNames" field in the XrInstanceCreateInfo structure passed into xrCreateInstance and then attempts to use that extension, it may result in undefined behavior. For example, querying an extension command using xrGetInstanceProcAddr might appear to succeed without having the extension enabled, only to then crash when calling that command.

4. API Layer Interaction

This section details how the loader interacts with API layers.

4.1. API Layer Call Chains and Distributed Dispatch

Remember, an API layer does not need to intercept all commands, instead, it can choose to intercept only a subset. Normally, when an API layer intercepts a given OpenXR command, it will call down the call chain as needed. The loader and all enabled API layers that participate in a call chain cooperate to ensure the correct sequencing of calls from one entity to the next. This group effort for call chain sequencing is hereinafter referred to as distributed dispatch.

If an API layer does not wish to intercept a command, it must forward the request made to its xrGetInstanceProcAddr implementation (provided through getInstanceProcAddr) down to the next xrGetInstanceProcAddr implementation in the call chain (provided to the API layer through nextGetInstanceProcAddr).

In distributed dispatch each API layer is responsible for properly calling the next entity in the call chain. This means that a dispatch mechanism is required for all OpenXR commands that an API layer intercepts.

For example, if the enabled API layers intercepted only certain functions, the call chain would look as follows:

optional layer function impl
Figure 6. Optional Function Example
Note

In the above example, notice that "API Layer A" fails to pass along the information for "xrFunction6". This may not be an error since some commands expect to only work with one specific API layer (xrEnumerateInstanceExtensionProperties when "layerName" is set). However, a badly designed API layer could do something similar for other commands.

The loader is responsible for dispatching all core and instance extension OpenXR functions to the first entity in the call chain.

4.2. API Layer Discovery

As mentioned in the Implicit vs Explicit API Layers section, API layers can be categorized into two categories:

  • Implicit API layers

  • Explicit API layers

The main difference between the two is that implicit API layers are automatically enabled, unless overridden, and explicit API layers must be manually enabled.

On any system, the loader looks in specific areas for information on the API layers that it can load. The process of finding the available API layers on a system is known as "API Layer Discovery". During "API Layer Discovery", the loader determines what API layers are available and then grabs some basic information about each API layer: the name, the version, and any extensions it supports. This information is then provided back to the application through the xrEnumerateApiLayerProperties command.

This section specifies the minimal conventions and rules an API layer must follow, especially with regards to interacting with the loader and other API layers.

On Windows and Linux systems, JSON formatted manifest files are used to store API layer information. In order to find OpenXR API layers, the OpenXR loader will read the JSON files to identify the names and attributes of API layers and their extensions.

4.2.1. Desktop API Layer Discovery

On Desktop systems (such as Windows and Linux) JSON formatted manifest files are used to store OpenXR component information. The OpenXR loader will search specific areas of the computer for the corresponding JSON manifest files that are used to identify the names and attributes of OpenXR API layers. The use of manifest files allows the loader to avoid actively loading the shared library files until the application request causes them to be activated.

The following sections define the standard locations the OpenXR loader searches when looking for JSON manifest files.

Windows Manifest Registry Usage

On Windows, the OpenXR loader will scan the Windows registry for details on JSON manifest files in the following locations:

Note

The following table uses 2 acronyms:

  • <HKLM> is used to indicate the top key of "HKEY_LOCAL_MACHINE"

  • <HKCU> is used to indicate the top key of "HKEY_CURRENT_USER"

This is done purely to simplify the reading of the table

Table 2. Typical Windows Manifest File Registry Keys
Windows OS Target Binary Target Base Registry Key (see note above) Common Uses

32-bit

32-bit

 <HKLM>\SOFTWARE\Khronos\OpenXR

Manifest files typically installed by some application for the entire system and all users on that system are found under this base key.

 <HKCU>\SOFTWARE\Khronos\OpenXR

Manifest files typically installed by some application for just the current user are found under this base key.

64-bit

64-bit

 <HKLM>\SOFTWARE\Khronos\OpenXR

Manifest files for 64-bit OpenXR components are typically installed by some application for the entire system and all users on that system and recorded under this base key.

 <HKCU>\SOFTWARE\Khronos\OpenXR

Manifest files for 64-bit OpenXR components are typically installed by some application for just the current user and recorded under this base key.

64-bit

32-bit

 <HKLM>\WOW6432Node\SOFTWARE\Khronos\OpenXR

Manifest files for 32-bit OpenXR components are typically installed by some application for the entire system and all users on that system and recorded under this base key.

 <HKCU>\WOW6432Node\SOFTWARE\Khronos\OpenXR

Manifest files for 32-bit OpenXR components are typically installed by some application for just the current user and recorded under this base key.

When the OpenXR loader scans each of the appropriate registry keys based on the Binary Target size, the loader will search under specific sub-keys for the correct JSON manifest files based on the type of content stored in the manifest file.

Additionally, the major version of the OpenXR API is used to separate up each group of elements so that they do not conflict with one another in the future.

Finally, each API layer type has their own sub-key under which their list of available layers resides:

Table 3. Sub-Key Suffix for Manifest Search
JSON Manifest File Type Sub-key Suffix Added to Each Valid Key

Implicit API Layer

ApiLayers\Implicit

Explicit API Layer

ApiLayers\Explicit

The following example shows possible keys searched for OpenXR 1.x explicit API layers for a 64-bit loader on a 64-bit system.

Example 2. OpenXR 1.x Explicit Layer Windows' Registry Locations
HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Explicit
HKEY_CURRENT_USER\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Explicit

When searching for implicit API layers, the loader appends ApiLayers\Implicit to each of the keys found.

Similar to the above example, this example shows the possible keys searched for OpenXR 1.x implicit API layers for the same system as above.

Example 3. OpenXR 1.x Implicit Layer Windows' Registry Locations
HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Implicit
HKEY_CURRENT_USER\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Implicit

Each registry value under the above keys must be defined in the following way:

  • The name of the value must be the full path name to a valid manifest JSON file (including the ".json" suffix)

  • The data for value must be a DWORD

    • In order for the loader to attempt to load the binary associated with this, the value must be 0.

    • To disable this manifest file so that the loader does not attempt to use it, set the value to a non-zero value.

For each value in these keys which has DWORD data set to 0, the loader opens the manifest file specified by the name of the value. The OpenXR loader will then obtain information about the library, including the name and pathname of the shared library (".dll") file which defines the actual API layer binary.

For example, let us assume the registry contains the following data:

[HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Explicit\]

"C:\vendor a\layer_a.json"=dword:00000000
"C:\windows\system32\layer_b.json"=dword:00000001
"C:\windows\system32\layer_c.json"=dword:00000000

In this case, the loader will step through each entry, and check the value. If the value is 0, then the loader will attempt to load the API layer manifest file. In this case, the loader will open the first and last listings, but not the middle one. This is because the value of 1 for layer_b.json disables the layer from being loaded.

Linux Manifest Search Paths

On Linux, the OpenXR loader will scan for JSON manifest files using the following base folders:

Table 4. Typical Linux Manifest File Search Directories
Environment Variable Common Locations Common Uses

XDG_CONFIG_DIRS

/etc/xdg

Defines common paths for config files

SYSCONFDIR

/usr/local/etc

The directory for installing read-only data files that pertain to a single machine. In this case, it is commonly used to store locally built API layers.

EXTRASYSCONFDIR

/etc

Location of API layers installed from non-Linux-distribution-provided packages.

XDG_DATA_DIRS

/usr/local/share, /usr/share/

Location of API layers installed from Linux-distribution-provided packages.

XDG_DATA_HOME

$HOME/.local/share

Used to define manually enabled API layers per user. $HOME is the current home directory of the application’s user id; this path will be ignored for suid programs

Note

The "/usr/local/*" directories can be configured to be other directories at build time.

When the OpenXR loader scans the directories defined by each of the above environmental variables, it pulls out each individual path (tokenizing the value based on the colon (:) separator), and then appends additional sub-path information on the end of each based on the type of manifest file being searched for. The following table shows the sub-path information added onto the end of each path based on the JSON manifest file type:

Table 5. Sub-Folder Suffix for Manifest Search
JSON Manifest File Type Sub-Folder Suffix Added to Each Path

Implicit Layer

openxr/<major_version>/api_layers/implicit.d

Explicit Layer

openxr/<major_version>/api_layers/explicit.d

Where <major_version> is the integer number for the OpenXR API’s major version the API layers are associated with.

The following example shows possible search paths for OpenXR 1.x explicit API layers (depending on the environmental variables defined on a user’s system).

Example 4. OpenXR 1.x Linux Explicit Layer Search Paths
/usr/local/etc/openxr/1/api_layers/explicit.d
/usr/local/share/openxr/1/api_layers/explicit.d
/etc/openxr/1/api_layers/explicit.d
/usr/share/openxr/1/api_layers/explicit.d
$HOME/.local/share/openxr/1/api_layers/explicit.d

When searching for OpenXR 1.x implicit API layers, the loader appends openxr/1/api_layers/implicit.d to each of the paths found.

Similar to the above example, the following shows the search paths for OpenXR 1.x implicit API layers for the same system as above.

Example 5. OpenXR 1.x Linux Implicit Layer Search Paths
/usr/local/etc/openxr/1/api_layers/implicit.d
/usr/local/share/openxr/1/api_layers/implicit.d
/etc/openxr/1/api_layers/implicit.d
/usr/share/openxr/1/api_layers/implicit.d
$HOME/.local/share/openxr/1/api_layers/implicit.d
Overriding the Default API Layer Paths

There may be times that a developer wishes to force the loader to use their own explicit API layers (or specific explicit API layers). In order to support this, the desktop loader can be forced to look in specific paths for explicit API layers with the XR_API_LAYER_PATH environment variable. Simply set it to a properly delimited list of paths that you want the loader to search for explicit API layer JSON Manfiest files. While relative paths may work, it is preferable to use absolute paths when defining this environment variable to reduce issues.

Important

If the "XR_API_LAYER_PATH" environmental variable is defined, then the desktop loader will not look in the standard locations to find explicit API layers, instead looking only at the paths defined in that environment variable. Implicit API layers will always be discovered using the standard paths.

Example 6. Setting XR_API_LAYER_PATH Override

Windows

set XR_API_LAYER_PATH=<my_api_layer_path_1>;<my_api_layer_path_2>;<my_api_layer_path_3>

Linux

export XR_API_LAYER_PATH=<my_api_layer_path_1>:<my_api_layer_path_2>:<my_api_layer_path_3>

4.2.2. Android API Layer Discovery

On Android the loader finds API layers by reading all Manifest files with the extension .json. The following directories inside APK assets are used for explicit and implicit layers:

openxr/__major_ver__/api_layers/implicit.d
openxr/__major_ver__/api_layers/explicit.d

The loader accesses these using the Android AssetManager classes.

The loader also looks for system installed API layers in a similar process as is used for active runtime discovery. The following system directories are searched for Manifest files in the following priority order for implicit and explicit API layers:

Implicit API Layers
  1. /product/etc/openxr/major_ver/api_layers/implicit.d

  2. /odm/etc/openxr/major_ver/api_layers/implicit.d

  3. /oem/etc/openxr/major_ver/api_layers/implicit.d

  4. /system/etc/openxr/major_ver/api_layers/implicit.d

  5. /vendor/etc/openxr/major_ver/api_layers/implicit.d

Explicit API Layers
  1. /product/etc/openxr/major_ver/api_layers/explicit.d

  2. /oem/etc/openxr/major_ver/api_layers/explicit.d

  3. /vendor/etc/openxr/major_ver/api_layers/explicit.d

  4. /system/etc/openxr/major_ver/api_layers/explicit.d

  5. /odm/etc/openxr/major_ver/api_layers/explicit.d

4.2.3. API Layer Manifest File Format

On Windows, Linux and Android, the loader uses manifest files to discover API layers. The loader doesn’t load the API layer libraries (e.g. DLL or .so files) for each of the enabled API layers except during xrCreateInstance when it sets up the call chain. This is to reduce the likelihood of loading a malicious API layer into memory. Instead, details are read from the manifest file, which are then provided for applications to determine what API layers should actually be loaded.

The following section discusses the details of the API layer manifest JSON file format. The JSON file itself does not have any requirements for naming. The only requirement is that the filename extension is ".json".

Example 7. API Layer Manifest Examples

Simple Explicit API Layer Manifest File

{
   "file_format_version" : "1.0.0",
   "api_layer": {
       "name": "XR_APILAYER_LUNARG_test",
       "library_path": "xrTestLayer.dll",
       "api_version" : "1.0",
       "implementation_version" : "2",
       "description" : "LunarG test API layer"
   }
}

More Complex Implicit API Layer Manifest File

{
   "file_format_version" : "1.0.0",
   "api_layer": {
       "name": "XR_APILAYER_LUNARG_test",
       "library_path": "xrTestLayer.dll",
       "api_version" : "1.0",
       "implementation_version" : "2",
       "description" : "LunarG test API layer",
       "functions": {
           "xrNegotiateLoaderApiLayerInterface":
               "TestLayer_xrNegotiateLoaderApiLayerInterface"
       },
       "instance_extensions": [
           {
               "name": "XR_EXT_instance_extension_example",
               "extension_version": "1"
           }
       ],
       "enable_environment": "ENABLE_XR_API_LAYER_TEST_1",
       "disable_environment": "DISABLE_XR_API_LAYER_TEST_1"
   }
}
Table 6. API Layer Manifest JSON Fields
JSON Node API Layer Type Description and Notes Introspection Query

"file_format_version"

Required for Implicit / Explicit

Manifest format major.minor.patch version number. Currently only a value of 1.0.0 is supported.

N/A

"api_layer"

Required for Implicit / Explicit

The identifier used to group a single API layer’s information together.

xrEnumerateApiLayerProperties

"name"

Required for Implicit / Explicit

The string used to uniquely identify this API layer to applications.

xrEnumerateApiLayerProperties

"library_path"

Required for Implicit / Explicit

The "library_path" specifies either a filename, a relative pathname, or a full pathname to the API layer’s shared library file. If "library_path" specifies a relative pathname, it is relative to the path of the JSON manifest file (e.g. for cases when an application provides an API layer that is in the same folder hierarchy as the rest of the application files). If "library_path" specifies a filename, the library must live in the system’s shared object search path. There are no rules about the name of the API layer shared library files other than it should end with the appropriate suffix (".DLL" on Windows, and ".so" on Linux).

N/A

"api_version"

Required for Implicit / Explicit

The major.minor (but not patch) version number of the OpenXR API that the shared library file for the library was built against. For example: 1.0.

xrEnumerateApiLayerProperties

"implementation_version"

Required for Implicit / Explicit

The version of the API layer implemented. If the API layer itself has any major changes, this number should change so the loader and/or application can identify it properly.

xrEnumerateApiLayerProperties

"description"

Required for Implicit / Explicit

A high-level description of the API layer and its intended use.

xrEnumerateApiLayerProperties

"functions"

Optional for Implicit / Explicit

This section can be used to identify a different function name for the loader to use in place of standard API layer interface functions. The "functions" node is required if the API layer is using an alternative name for xrNegotiateLoaderApiLayerInterface.

xrGetInstanceProcAddr (except for xrNegotiateLoaderApiLayerInterface which must be queried using the OS/platform-specific GetProcAddress).

"instance_extensions"

Optional for Implicit / Explicit

Contains the list of instance extension names supported by this API layer. One "instance_extensions" node with an array of one or more elements is required if any instance extensions are supported by a API layer, otherwise the node is optional. Each element of the array must have the nodes "name" and "extension_version" which correspond to XrExtensionProperties "extensionName" and "extensionVersion" respectively.

xrEnumerateInstanceExtensionProperties

"enable_environment"

Optional for Implicit

Indicates an environment variable used to enable the implicit API layer. If provided in the JSON file, this environment variable (which should vary with each "version" of the API layer) must be set in the environment or else the implicit API layer is not loaded. This is for application environments (e.g. Steam) which want to enable an API layer(s) only for applications that they launch, and allows for applications run outside of that environment to not get that implicit API layer(s).

N/A

"disable_environment"

Required for Implicit

Indicates an environment variable used to disable the implicit API layer. Required to allow users or applications to disable implicit layers that are not desired or that cause problems for the application. The user/application can set this environment variable (before calling OpenXR functions) to skip loading this API layer. This environment variable should vary with each "version" of the API layer. If both the "enable_environment" and "disable_environment" variables are set, the implicit API layer is disabled.

N/A

Note

If the same API layer shared library supports multiple, incompatible versions of text manifest file format versions, it must have separate JSON files for each (all of which may point to the same shared library).

API Layer Manifest File Version History

The current highest supported API layer manifest file format supported is 1.0.0. Information about each version is detailed in the following sub-sections:

API Layer Manifest File Version 1.0.0

The initial version of the API layer manifest file specified the basic format and fields of an API layer JSON file. The fields of the 1.0.0 file format include:

  • "file_format_version"

  • "api_layer"

  • "name"

  • "library_path"

  • "api_version"

  • "implementation_version"

  • "description"

  • "functions"

  • "instance_extensions"

  • "enable_environment"

  • "disable_environment"

4.3. Loader/API Layer Interface Negotiation

Now that an API layer has been discovered, an application can choose to load it (or it is loaded by default if it is an implicit API layer). When the loader attempts to load the API layer, the first thing it does is attempt to negotiate the version of the loader to API layer interface. In order to negotiate the loader/API layer interface version, the API layer must implement the xrNegotiateLoaderApiLayerInterface function (or a renamed version of this function identified in the manifest file). See the declaration of xrNegotiateLoaderApiLayerInterface in openxr_loader_negotiation.h.

4.3.1. API Layer Interface Versions

The current API layer interface is at version 1. The following sections detail the differences between the various versions.

API Layer Interface Version 1
  • Defined manifest file version 1.0.0.

  • Introduced the concept of negotiation.

    • Requires API layers to export xrNegotiateLoaderApiLayerInterface function.

4.4. API Layer Intercept Requirements

  • API Layers intercept an OpenXR command by defining a C/C++ function with signature identical to the OpenXR API for that command.

  • The following commands are required to be implemented by any API layer:

    • xrGetInstanceProcAddr

    • xrCreateApiLayerInstance

  • The following commands must not be implemented by any API layer:

    • xrCreateInstance

  • For any OpenXR command an API layer intercepts which has a non-void return value, an appropriate value must be returned by the API layer intercept command.

  • Most commands an API layer intercepts must call down the chain to the corresponding OpenXR command in the next entity.

    • The common behavior for an API layer is to intercept a call, perform some behavior, then pass it down to the next entity.

      • If a layer does not call down to the next entity for a given command, undefined behavior may occur. This is because the command will not be received by API layers and runtimes further down the call chain.

      • One command that cannot call down the chain is:

        • xrNegotiateLoaderApiLayerInterfaceVersion

      • Some commands that may choose to not call down the chain are:

        • xrGetInstanceProcAddr

  • API layer intercept commands may insert extra calls to OpenXR commands in addition to those that are intercepted

    • If an API layer inserts new calls, that API layer must pass along all new commands to the next entity.

4.5. API Layer Conventions and Rules

An API layer, when inserted into an otherwise compliant OpenXR implementation, must still result in a compliant OpenXR implementation. The intention is for API layers to have a well-defined baseline behavior. Therefore, it must follow some conventions and rules defined below:

  • An API layer may be in a call chain with any number of API layers before or after it.

  • It must not make invalid calls to, or rely on undefined behaviors of, its lower API layers.

  • If it changes the behavior of a function, it must ensure the API layers called prior to itself do not make invalid calls because of the changed behavior.

    • For example, if an API layer chooses to intercept an object creation function, and then wraps the objects created by lower API layers, it must make sure the lower API layers never see the wrapped objects.

      • This means it must protect the lower API layers directly from itself or indirectly from its upper API layers.

  • xrEnumerateApiLayerProperties must return only its own API layer properties.

  • xrEnumerateInstanceExtensionProperties must obey the "layerName" parameter:

    • If "layerName" is the name of this API layer, it must return the contents of the instance extensions it supports.

    • If "layerName" is NULL and:

      • It is an explicit API layer, it must not fill in any data.

      • It is an implicit API layer, it must add it’s own instance extension contents to the list of extensions.

  • For any OpenXR command the API layer intercepts, xrGetInstanceProcAddr must return a pointer to a local entry point.

    • Otherwise it returns the value obtained by calling down the instance call chain.

4.6. API Layer Create Instance Process

After interface negotiation and any directed xrEnumerateInstanceExtensionProperties calls, the next time an API layer is invoked is during the loader’s xrCreateInstance call. The API layer is only involved if it is in the enabled API layer list (this includes implicit, environment variable enabled, and application enabled API layers). An API layer needs additional information during xrCreateInstance calls, so each API layer must implement the xrCreateApiLayerInstance function, which is a special API layer function (declared in openxr_loader_negotiation.h).

During the xrCreateInstance call, the following happens:

  1. The call enters the loader’s trampoline function xrCreateInstance

  2. The loader will generate an instance of the XrApiLayerCreateInfo structure

  3. The loader will go through each API layer in reverse order (i.e. starting with the layer closest to the runtime and ending with the API layer closest to the application):

    1. Record the API layer’s name, xrGetInstanceProcAddr address, and the xrCreateApiLayerInstance address.

    2. Build a XrApiLayerNextInfo structure for the API layer recording the name and command addresses.

      1. If this is the first API layer (the one closest to the runtime) we want it to enter the loader again when we’re done. So, the loader sets the following:

        1. XrApiLayerNextInfo.nextGetInstanceProcAddr = loaderXrTermGetInstanceProcAddr

        2. XrApiLayerNextInfo.nextCreateLayerInstance = loaderXrTermCreateLayerInstance

        3. XrApiLayerNextInfo.next = NULL

      2. Otherwise, the loader sets the information to the previous API layer’s information:

        1. XrApiLayerNextInfo.nextGetInstanceProcAddr = Previous XrApiLayerNextInfo.loaderXrTermGetInstanceProcAddr

        2. XrApiLayerNextInfo.nextCreateLayerInstance = Previous XrApiLayerNextInfo.loaderXrTermCreateLayerInstance

        3. XrApiLayerNextInfo.next = address to previous XrApiLayerNextInfo

  4. The loader will then update the XrApiLayerCreateInfo.nextInfo to point to the last created XrApiLayerNextInfo since this is the first API layer in the call-chain.

  5. The loader calls the first API layer’s xrCreateApiLayerInstance command passing in the pointer to the created XrApiLayerCreateInfo

  6. The API layer receives the information in its xrCreateApiLayerInstance command.

  7. The API layer copies the XrApiLayerCreateInfo structure into it’s own structure.

  8. The API layer then updates it’s version of the XrApiLayerCreateInfo structure setting nextInfo to point to the XrApiLayerNextInfo for the next API layer (i.e. XrApiLayerCreateInfo→nextInfo = XrApiLayerCreateInfo→nextInfo→next;).

  9. The API layer may validate that it is getting the correct next information by checking that the layerName matches.

  10. The API layer then uses the information out of its XrApiLayerNextInfo to call down the call-chain to the next xrCreateApiLayerInstance, using a pointer to its XrApiLayerCreateInfo structure instead of the one that was passed in during its xrCreateApiLayerInstance command.

    1. If the call passes, this API layer may choose to setup its own dispatch table to the next API layer’s commands using the returned XrInstance, the next API layer’s xrGetInstanceProcAddr and the GeneratedXrPopulateDispatchTable utility command provided in the generated xr_generated_dispatch_table.h header.

  11. Finally, the API layer should return the result passed in from the next API layer.

5. Runtime Interaction

This section discusses the various requirements for the loader and an OpenXR runtime to properly interact.

5.1. Runtime Discovery

OpenXR may have a choice of multiple runtimes on a user’s system. The selection of the active runtime is handled external to the loader. The loader is responsible for discovering only the active OpenXR runtime on the system, and loading it properly. The standard process in which the loader discovers the active runtime on a system is platform dependent.

5.1.1. Active Runtime Information

The means of identifying the active runtime used by an OpenXR application vary based on the underlying operating system and is detailed in the sub-sections below. This information is also important if the active runtime needs to be changed by an external entity (on those platforms that support updating).

Linux Active Runtime Location

On Linux, the active runtime information is contained inside a JSON-formatted file located in a standard XDG configuration directory or the system’s standard global configuration directory (typically /etc), under the relative path:

openxr/<major_api_version>

<major_api_version> should be replaced with the integer value for the corresponding OpenXR API version.

Two JSON file names are checked in each directory. First, the loader looks for active_runtime.<arch>.json, where <arch> is an architecture and ABI specifier from the Architecture/ABI Identifiers table. If no such file is found, active_runtime.json is then checked. The JSON file contains the necessary information on how OpenXR components can load the appropriate runtime library.

For example, a globally-configured OpenXR 1.x active runtime file on an x86_64 PC might be at:

/etc/xdg/openxr/1/active_runtime.x86_64.json

When multiple such files exist, XDG_CONFIG_HOME is preferred, followed by the elements of XDG_CONFIG_DIRS in order, followed by the system’s global configuration directory. Both filenames are tested in a given directory before moving on. This allows the user’s preference to easily override a global default.

NOTE

active_runtime filenames may be a symbolic link to a file with a runtime-specific name, for ease of runtime switching and enumeration by the user or system tools. See Linux Installed Runtimes Enumeration for more information.

Windows Active Runtime Location

The Windows OpenXR active runtime information is located in the Windows Registry (not to be confused with the OpenXR registry) under the key:

HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\<major_api_version>

Where <major_api_version> should be replaced with the integer value for the corresponding OpenXR API version.

Important

If using a 32-bit application on a 64-bit Windows install, "WOW6432Node" is added before "SOFTWARE" in the preceding path as follows:

HKEY_LOCAL_MACHINE\WOW6432Node\SOFTWARE\Khronos\OpenXR\<major_api_version>

This means that the complete registry path to the OpenXR 1.x active runtime registry value is:

HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1

Under that registry key, the value "ActiveRuntime" is defined as a string value and should be defined to full directory path of the runtime’s JSON file.

An example "ActiveRuntime" value might look something like the following:

C:\windows\system32\my_system_runtime.json
Android Active Runtime Location

The Android OpenXR active runtime data, equivalent to the contents of a runtime manifest JSON file, is determined by a ContentProvider. Vendors producing Android-based devices where only a single runtime would make sense may consider implementing the "System" content provider directly. Others can use a working-group-provided installable "RuntimeBroker" implementation of the content provider and associated preference UI, and will only need to add metadata to their manifest file as described in Android Runtime Metadata for Installable Broker. The RuntimeBroker package’s responsibility is to externalize runtime finding logic and the runtime state user preferences.

An OpenXR runtime selection ContentProvider responds to one of two authorities:

  • org.khronos.openxr.runtime_broker - For use only by the working-group-provided package with the user preferences activity. This should not be installed or modified by vendors: it will be distributed through typical end-user channels for application packages and is intended for user-controlled installable (e.g. plug-in) devices. Read access is guarded by a permission of "normal" protection level named org.khronos.openxr.permission.OPENXR.

  • org.khronos.openxr.system_runtime_broker - This may be provided by vendors in system-installed packages. Read access is guarded by a permission of "normal" protection level named org.khronos.openxr.permission.OPENXR_SYSTEM.

The installable RuntimeBroker content provider defines the permission group org.khronos.openxr.permission-group.OPENXR, containing the above two permissions. The runtime brokers may use their respective permissions to guard read access to the ContentProvider. Write permission should not be allowed, or should be limited to explicit choices by the device user. The defined URIs for the ContentProvider authorities are as follows:

  • /openxr/major_ver/abi/abi/runtimes/active - This URI represents a "table" containing at most one item, the currently active runtime. The policy of which runtime is chosen to be active (if more than one is installed) is left to the content provider. No sort order is required to be honored by the content provider. Available columns include:

    • package_name - Name of the package providing the runtime

    • native_lib_dir - ABI-specific directory containing the runtime’s shared library

    • so_filename - The filename to load as the runtime: Combines with native_lib_dir to provide the absolute path of the shared object.

    • has_functions - Boolean, if true, it indicates the loader should query the functions/ URI to identify the names of entry points it should query.

  • /openxr/major_ver/abi/abi/runtimes/package/functions - This URI is for package-specific function name remapping. Since this is an optional field in the corresponding JSON manifests for OpenXR, it is optional here as well. If the active runtime contains "true" in its "has_functions" column, then this table must exist and be queryable. No sort order is required to be honored by the content provider. package is the package name containing the active runtime. Available columns include:

    • function_name - Corresponds to the "key" in the JSON manifest field.

    • symbol_name - Corresponds to the "value" in the JSON manifest field.

Within the loader, the results from either content provider are used to construct an object corresponding to an equivalent JSON runtime manifest file as used on other platforms.

If no runtime is found this way, as a fall-back, a similar process as on Linux takes place, searching for active_runtime.<arch>.json and active_runtime.json files in the following locations in priority order:

  1. /product/etc/openxr/major_ver

  2. /odm/etc/openxr/major_ver

  3. /oem/etc/openxr/major_ver

  4. /vendor/etc/openxr/major_ver

  5. /system/etc/openxr/major_ver

<major_api_version> should be replaced with the integer value for the corresponding OpenXR API version, and <arch> is an architecture and ABI specifier from the Architecture/ABI Identifiers table. Both filenames are checked in each directory before moving on to the next.

Android IPC and Runtime Access

As Android is a permission-limited environment, additional constraints are placed on runtimes and applications. In modern versions of Android, applications cannot query arbitrary packages for arbitrary services that are not pre-declared in the manifest. To avoid needing to update application manifests for each OpenXR runtime’s requirements, the intent service name org.khronos.openxr.OpenXRRuntimeService (same as used by installable runtime discovery) is reserved for use by OpenXR runtimes and their components. Similarly, the intent service name org.khronos.openxr.OpenXRApiLayerService is reserved for use by OpenXR API layers and their components. To target an API level higher than 29, applications must include provisions in their manifest to allow them to query for services with these intent names. In turn, runtimes and their components that need to locate their originating package should query first for packages providing a service for the relevant intent, then traverse the list of resolutions to find their own package. There is no set API provided by an intent service of this name: it exists solely as a marker of an OpenXR runtime and as a key for retrieving OpenXR runtime component packages without needing to perform arbitrary package queries. This does pose the risk that an application can view all OpenXR runtimes installed, rather than only the active one. However, the number of runtimes per device is likely to be very small, and this opens the smallest weakness possible to achieve the required functionality.

Note

Applications will require the following <queries> statements in their manifest for the loader to locate and load the runtime correctly. (If building an application using the loader AAR provided by the working group, beginning with version 1.0.28, these items are included in the AAR manifest and will be merged into your application manifest automatically.)

<uses-permission android:name="org.khronos.openxr.permission.OPENXR_SYSTEM" />

<queries>
  <provider android:authorities="org.khronos.openxr.runtime_broker;org.khronos.openxr.system_runtime_broker" />
  <intent>
    <action android:name="org.khronos.openxr.OpenXRRuntimeService" />
  </intent>
  <intent>
    <action android:name="org.khronos.openxr.OpenXRApiLayerService" />
  </intent>
</queries>

The permission is needed to contact a system broker. The provider query is to be able to contact system and installable brokers. The intent query is for runtimes to look up their own package, which is required when targeting API levels higher than 29. It is also recommended to include the following, which could be needed for haptic feedback:

<uses-permission android:name="android.permission.VIBRATE" />

Earlier versions of the installable broker also wanted the following permission, but this is no longer required. It is harmless to leave it in place, but the broker no longer requires it.

<uses-permission android:name="org.khronos.openxr.permission.OPENXR" />
Architecture/ABI Identifiers

On platforms such as Linux and Android where the active runtime manifest may be found by filename, the following table of known architectures and ABIs is used.

Architecture/ABI Identifier Android NDK ABI Name Debian Port Name Description

x32

x32

64-bit x86 instructions, using an ILP32 model (32-bit pointers)

x86_64

x86_64

x86_64

64-bit x86

i686

x86

i386

32-bit x86

aarch64

arm64-v8a

aarch64

64-bit ARM architecture, little endian

armv7a-vfp

armeabi-v7a (See note.)

armhf

32-bit ARMv7-A architecture, little endian, with hardware floating point and VFP PCS ABI

armv5te

armel

32-bit ARMv5TE architecture or compatible, little endian

mips64

mips64el

64-bit MIPS architecture, little endian

mips

mipsel

32-bit MIPS architecture, little endian

ppc64

ppc64

64-bit PowerPC architecture, big endian

ppc64el

ppc64el

64-bit POWER8/POWER9 architecture, little endian (OpenPOWER ELF ABI v2)

s390x

s390x

64-bit S390/z-Series architecture, big endian

hppa

hppa

32-bit HP PA-RISC architecture, big endian

alpha

alpha

64-bit Alpha architecture

ia64

ia64

64-bit IA-64 architecture

m68k

m68k

32-bit Motorola 68000-based architecture, big endian

riscv64

riscv64

64-bit RISC-V architecture, little endian

sparc64

sparc64

64-bit SPARC architecture

Note: The preceding table defines armv7a-vfp as the architecture/ABI identifier for 32-bit ARM in Android. While the Android ABI called armeabi-v7a does not use VFP PCS as the system ABI, a definition in openxr_platform_defines.h enables the VFP PCS calling convention for all OpenXR API calls and function pointers.

5.1.2. Runtime Enumeration

When multiple runtimes are installed on a system, the OpenXR loader will find the currently active runtime using the mechanism described before.

At that point, the loader will act as if this runtime is currently the only installed one.

To be able to allow additional tooling to discover other runtimes that are installed, but currently are not the active runtime on the system, each runtime should register the path to their manifest in a platform-dependant way in a list of "available runtimes".

Runtime manifest files may optionally contain a "name" field. Tools that allow users to configure what runtime is currently active should display this user-friendly name if present when referring to the runtime in question.

Windows Installed Runtimes Enumeration

On Windows, the available runtimes' information is located in the Windows Registry.

It can be found under the key:

HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\<major_api_version>\AvailableRuntimes

Each installed runtime should add a DWORD value under that key. The `DWORD’s name is the full path to the runtime’s manifest

Similarly to how Implicit API layers can control their activation state in the registry, as described in Windows Manifest Registry Usage:

  • A value of 0 indicates that the runtime can be discovered by tools that want to enumerate all installed runtimes.

  • A non-zero value may be used if the runtime wants to disable itself from enumeration (for example, if hardware configuration, or first time setup hasn’t been run by the user yet).

For instance, the OpenXR 1.x runtime used as an example in section Windows Active Runtime Location would add a DWORD value in:

HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1\AvailableRuntimes

With the name:

C:\Windows\system32\my_system_runtime.json

And set it to 0.

All considerations for 32 bit platforms mentioned in section Windows Active Runtime Location still apply.

Tools that want to change the currently ActiveRuntime should not modify the list of AvailableRuntimes, but rather copy the path to the correct location.

Linux Installed Runtimes Enumeration

As previously defined, the OpenXR configuration on Linux is either stored in a standard XDG configuration directory, or the system-wide configuration directory.

Aside from the currently active runtime, installed runtimes that make themselves available for enumeration should install their runtime manifest into one of these directories, using any name suitable.

Rules about precedence in different directories for runtime enumeration follow the same rules as in section Linux Active Runtime Location for selecting the folder containing the active runtime manifest.

Tools that want to change the current active runtime should only update the active_runtime.<arch>.json and/or active_runtime.json symlink to the active runtime, and not move, copy, or delete any other files.

5.1.3. Runtime Manifest File Format

As mentioned before, the OpenXR loader on Windows and Linux uses manifest files to discover the active runtime. The loader only loads the actual runtime library when necessary. Because of this, the manifest files contain important information about the runtime. The JSON file itself does not have any requirements for naming, beyond the requirement of using the ".json" extension.

Here is an example runtime JSON manifest file:

Example 8. Runtime Manifest
{
   "file_format_version": "1.0.0",
   "runtime": {
      "name": "openxr_sample_runtime",
      "library_path": "./dbuild/src/impl/libopenxr_sample_impl.so"
   }
}
Table 7. Runtime Manifest File Fields
Field Name Required Field Value

"file_format_version"

Yes

The JSON format major.minor.patch version number of this file. Currently supported version is 1.0.0.

"runtime"

Yes

The identifier used to group all runtime information together.

"library_path"

Yes

The "library_path" specifies either a filename, a relative pathname, or a full pathname to the runtime’s shared library file. If "library_path" specifies a relative pathname, it is relative to the path of the JSON manifest file (e.g. for cases when an application provides a runtime that is in the same folder hierarchy as the rest of the application files). If "library_path" specifies a filename, the library must live in the system’s shared object search path. There are no rules about the name of the runtime shared library files other than it should end with the appropriate suffix (".DLL" on Windows, and ".so" on Linux).

"functions"

No

This section can be used to identify a different function name for the loader to use in place of standard runtime interface functions. The "functions" node is required if the runtime is using an alternative name for xrNegotiateLoaderRuntimeInterface.

"name"

No

An optional user-facing name that can be used by tooling to refer to this specific runtime.

Vendors may add non-standard fields to their runtime manifest files. These vendor specific fields must be prefixed with their vendor id (e.g: VENDOR_name_of_field).

Note

If the same runtime shared library supports multiple, incompatible versions of OpenXR API, it must have separate JSON files for each API major version (all of which may point to the same shared library).

Runtime Manifest File Version History

The current highest supported runtime manifest file format supported is 1.0.0. Information about each version is detailed in the following sub-sections:

Runtime Manifest File Version 1.0.0

The initial version of the runtime manifest file specified the basic format and fields of a runtime JSON file. The fields of the 1.0.0 file format include:

  • "file_format_version"

  • "runtime"

  • "library_path"

  • "name"

    • This is an optional field, added before the OpenXR 1.0.18 release. As it is not used by the loader nor does it introduce incompatibility, it was added to the format described here without incrementing the manifest file format version number.

Android Runtime Metadata for Installable Broker

The working-group-provided installable RuntimeBroker provides a ContentProvider matching the specification of Android Active Runtime Location. The returned data is determined by a combination of user settings and package metadata, rather than a JSON manifest file. To be identified by the RuntimeBroker as an OpenXR runtime, a package must

  • Specify the <application> attribute android:extractNativeLibs="true", to allow the runtime .so to be loaded dynamically from another package.

  • Provide an exported Service (no specific methods are required)

    • With an intent-filter for the action name org.khronos.openxr.OpenXRRuntimeService

    • Exposing a meta-data value named org.khronos.openxr.OpenXRRuntime.SoFilename with the filename of the runtime .so file

    • Exposing a meta-data value named org.khronos.openxr.OpenXRRuntime.MajorVersion with the major version number of the OpenXR runtime standard supported.

    • Optionally exposing meta-data values named org.khronos.openxr.OpenXRRuntime.Functions.function_name with value of the symbol name, if any functions to be loaded do not have symbol names matching the specification’s function name.

For example, the following markup could be added within the <application> tag to expose a runtime with no function name remapping:

<service
    android:name=".MyOpenXRService"
    android:label="@string/service_name"
    android:exported="true">
    <meta-data
        android:name="org.khronos.openxr.OpenXRRuntime.SoFilename"
        android:value="libopenxr_sample.so" />
    <meta-data
        android:name="org.khronos.openxr.OpenXRRuntime.MajorVersion"
        android:value="1" />

    <intent-filter>
        <action android:name="org.khronos.openxr.OpenXRRuntimeService" />
    </intent-filter>
</service>

To find a runtime, the RuntimeBroker first retrieves all services that advertise the org.khronos.openxr.OpenXRRuntimeService intent with the correct org.khronos.openxr.OpenXRRuntime.MajorVersion value. If exactly one runtime is found (and it is not disabled by user preference in the broker), it is considered the active OpenXR runtime. If more than one is found, user preferences are used to identify the "active" runtime.

The path containing the dynamic library is computed from ApplicationInfo.nativeLibraryDir and the specified ABI, and the filename is returned using the filename found in the OpenXR metadata value. The "hasFunctions" column is dynamically generated based on the presence of any function metadata entries.

Note that system-provided runtimes exposed using the "System" content provider do not need to expose this same metadata, as the implementation of the system content provider can be used to store and report this information.

5.1.4. Loader Distribution

Any application using the OpenXR API is responsible with making sure it can properly execute on a user’s system. Some OpenXR environments may not use an OpenXR loader but instead provide libraries which directly link with their runtime. Other runtime or platform vendors may choose to provide a separate OpenXR loader for debug or developmental reasons. Whatever the scenario, if an application uses an OpenXR loader, then that application is responsible for packaging the OpenXR loader in a location that will not interfere with other applications. If an engine or platform provides an OpenXR loader for applications, it must provide documentation on how to properly package the necessary files.

5.1.5. Overriding the Default Runtime Usage

There may be times that a developer wishes to ignore the standard runtime discovery process and force the loader to use a specific runtime. This could be for many reasons including:

  • Forcing on a Beta runtime

  • Replacing a problematic runtime in favor of another

In order to support this, the loader can be forced to look at specific runtime with the XR_RUNTIME_JSON environment variable. In order to use the setting, simply set it to the full global path location of the desired runtime manifest file.

Important

If the "XR_RUNTIME_JSON" variable is defined, then the loader will not look in the standard location for the active runtime. Instead, the loader will only utilize the filename defined in the environment variable.

Example 9. Setting XR_RUNTIME_JSON Override

Windows

set XR_RUNTIME_JSON=\windows\system32\steam_runtime.json

Linux

export XR_RUNTIME_JSON=/home/user/.config/openxr/1/steamxr.json

5.2. Loader/Runtime Interface Negotiation

The OpenXR symbols exported by a runtime must not clash with the loader’s exported OpenXR symbols. Because of this, all runtimes must export only the following command with beginning with the xr prefix. This command is not a part of the OpenXR API itself, only a private interface between the loader and runtimes for version 1 and higher interfaces. In order to negotiate the loader/runtime interface version, the runtime must implement the xrNegotiateLoaderRuntimeInterface` function (or a renamed version of this function identified in the manifest file). xrNegotiateLoaderRuntimeInterface` is defined in openxr.h.

xrNegotiateLoaderRuntimeInterface` function should be directly exported by a runtime so that using "GetProcAddress" on Windows or "dlsym" on Linux, should return a valid function pointer to it (see Runtime Exporting of Commands for more information).

Important

Remember, during the call to xrNegotiateLoaderRuntimeInterface, the runtime must grab control of the active runtime manifest file. Functions that should be used to grab control of the manifest file are defined in the common code as described in the Active Runtime File Management section of this document. The runtime must also determine when to release control of this file. This may be due to the last instance an application created is destroyed, the application is exiting, or some period of inactivity occurs.

5.2.1. Runtime Exporting of Commands

The xrNegotiateLoaderRuntimeInterface should be directly exported by a runtime so that using "GetProcAddress" on Windows or "dlsym" on Linux, should return a valid function pointer to it. However, all other OpenXR entry points must either:

  • NOT be exported directly from the runtime library

  • or NOT use the official OpenXR command names if they are exported

This requirement is especially for runtime libraries that include other functionality (such as OpenGL) and thus could be loaded by the application prior to when the OpenXR loader library is loaded by the application.

Beware of interposing by dynamic OS library loaders if the official OpenXR names are used. On Linux, if official names are used, the runtime library must be linked with -Bsymbolic.

5.2.2. Runtime Interface Versions

The current Runtime Interface is at version 1. The following sections detail the differences between the various versions.

Runtime Interface Version 1
  • Defined manifest file version 1.0.0.

  • Introduced the concept of negotiation.

    • Requires runtimes to export xrNegotiateLoaderRuntimeInterface function.

5.3. Additional Loader Requirements

  • The loader must not call the runtime for xrEnumerateApiLayerProperties

  • The loader must not call the runtime for xrEnumerateInstanceExtensionProperties, if "layerName" is not equal to NULL.

6. Loader Source

The OpenXR desktop loader is primarily developed by Khronos, who also owns the copyright to it. This decision was reached in order to allow quicker development and releasing of loader changes for all supported platforms.

However, the OpenXR loader is an Open Source project that does accept contributions from the OpenXR community.

6.1. Building Instructions

The latest instructions for building the OpenXR source can always be found in the BUILDING.md file. It is actively maintained and should be used instead of documenting the build steps in this file in order to reduce unnecessary duplication.

6.1.1. CMake Usage

All OpenXR source utilizes the CMake tool to generate platform-specific build files. Currently, the loader requires CMake version 3.5.1 or newer. CMake may be obtained from either the CMake web-site or often using your computer’s software update mechanism.

The CMake files of interest exist in several locations:

  1. CMake Files of interest

CMake File Usage

CMakeLists.txt

Root CMake file to set up some variables and recurse into include/ and src/

src/CMakeLists.txt

Main CMake file used to define and build items used by all source in the tree, as well as build all projects under the "src" folder.

src/cmake/cmake_uninstall.cmake.in

CMake files used to define the uninstall process required if the project was installed on a Linux system.

src/loader/CMakeLists.txt

The loader specific CMake file used to define the build process of the loader source.

src/common_config.h.in

A special file used to generate a header which indicates platform-specific items of interest, such as whether or not the platform being targeted supports the secure environmental variable functions.

Note

A note about common_config.h.in: The supplied CMake build system defines OPENXR_HAVE_COMMON_CONFIG to indicate that the source should include common_config.h.

If you aren’t using the supplied CMake build system, you will need to either generate the header yourself (preferred), or supply equivalent definitions:

  • Define HAVE_SECURE_GETENV on systems where secure_getenv is available.

  • Define HAVE___SECURE_GETENV on systems where __secure_getenv is available.

6.2. Contributing to the Loader

Khronos would be pleased if you decided to contribute to the loader source code.

6.3. Coding Standard

The following sections define what coding standard is in place for the OpenXR loader code. Every attempt must be made to follow these rules when adding new code.

6.3.1. General Format of Code

The loader code is formatted using clang-format with the following settings:

  • Google style using clang-format

  • Indents using 4 spaces in place of tabs

  • Maximum column width of 132 characters

  • Includes not sorted

Clang-format is required to be executed prior to committing new code whenever possible.

6.3.2. Language Selection

Internally, the loader is implemented using the C++ language, taking advantage of C++11 standard features. Since the OpenXR API is exposed using C, all exported commands must be properly wrapped using extern "C" { and }.

types

The loader code should use the standard types defined by stdint.h in order to avoid using platform-specific type defines whenever possible. Some commonly used types include:

  • int8_t/uint8_t

  • int16_t/uint16_t

  • int32_t/uint32_t

  • int64_t/uint64_t

In some cases, it may not be possible to use these generic type, like when calling a platform-specific function. In those cases, this requirement is waived.

Standard Template Library Usage

Additionally, STL may be used in any C++ source code areas.

Namespaces

Namespaces outside of the OpenXR loader must not be enabled by default.

Example 10. Disallowed Namespace Usage

The following is disallowed:

using namespace std;

Instead, any time you use a function, variable, type or other component coming from a namespace, you must list the full namespace of that item. Some examples are as follows:

Example 11. Valid Namespace Usage
std::string my_string;
std::cout << std::to_string(5);
std::experimental::filesystem::path search_path;
Exceptions

The OpenXR loader itself does not internally throw C++ exceptions, for compatibility with environments where exceptions are forbidden. However, since it exposes a C ABI, and the standard library facilities used may throw exceptions, functions exposed to the ABI (those with names matching OpenXR functions) must have XRLOADER_ABI_TRY before the opening { of the function body and XRLOADER_ABI_CATCH_FALLBACK after the closing } of the function body. (This is done automatically for those functions whose trampoline is entirely generated.) In normal cases, these two macros are defined by exception_handling.hpp to expand to try and a full catch clause, respectively. This prevents any exceptions from escaping, in what’s known as a "function-try-block".

In very limited cases, you may choose to disable exception handling through the provided CMake option or by defining XRLOADER_DISABLE_EXCEPTION_HANDLING. The only two reasons you may define this are:

  • Due to a platform or project policy, you’re using a custom standard-library build that has exception throwing disabled.

  • You’re developing or debugging the loader and want exceptions to go uncaught to trigger a debugger.

Experimental Filesystem Usage

In order to simplify the file management, especially with regards to loading JSON manifest files or finding dynamic library files, the experimental/filesystem is used. This is a set of features which are part of the upcoming C++17 feature set designed to make file management easier.

Since no compiler currently supports C++17, most have enabled a chunk of functionality using the "experimental" namespace. When used, you can find elements of this functionality with the prefix std::experimental::filesystem.

Example 12. Experimental Filesystem Usage

Using the experimental filesystem in the source:

#include <experimental/filesystem>

static void checkAllFilesInThePath(const std::string &search_path) {
    try {
        // If the file exists, try to add it
        if (std::experimental::filesystem::is_regular_file(search_path)) {
            std::experimental::filesystem::path absolute_path =
                std::experimental::filesystem::absolute(search_path);
        }
    } catch (...) {
    }
}

6.3.3. API Naming

Identifiers in the OpenXR API (e.g. types, parameters, constants, etc.) all follow a set of naming rules, providing a consistent scheme for developers.

General Naming Rules

Names of all identifiers should generally be written with full words, avoiding abbreviations whenever possible, as a concise description of what that identifier is. Abbreviation is preferred in cases where the identifier name becomes excessive in length (usually when exceeding 25 characters).

For example, the class containing the loader’s version of OpenXR instance information is LoaderInstance.

Names inside the loader not directly associated with an OpenXR identifier or command must not begin with the reserved letters xr in any combination of upper or lower-case characters. The xr prefix is solely reserved for all OpenXR API elements (both hidden and exposed) and defines the OpenXR namespace. Therefore, it must only be in cases of exposing commands for the OpenXR API.

Also, as a general rule, Hungarian notation should not be avoided whenever possible.

Naming of Files and Directories

All files and files must be named with lower-snake-case names. Additionally, any C-language files must end with either .c or .h, while any C++ files must end with either .cpp or .hpp to differentiate them. Python scripts, must be named with a .py suffix.

Example 13. Filenames
loader_instance.hpp
loader_instance.cpp
Naming of #Defines

All #defines must be named in all-caps upper-snake-case and must be defined to a specific value.

Example 14. #define Values
#define CURRENT_LOADER_API_LAYER_VERSION 1
#define ENABLE_LOADER_DEBUG 1
Variable Naming
Local Variables

All local variables and function/method parameters must use lower-snake-case.

Example 15. Variable Names
uint32_t number_of_actual_items;
std::string file_path_location;
Global Variable Naming

Global variables, too, are defined using lower-snake-case with an additional prefix of g_ required to identify them as global variables.

Example 16. Global Variable Names
std::vector<<std::string>> g_my_global_file_list;
Function and Parameter Naming

Functions must use lower-camel-case for their naming and function parameters must use lower-snake-case for their naming.

Example 17. Function and Parameter Naming
void myFunction1(uint32_t my_int_val, bool my_bool) {
}

void thisOtherFunction2() {
}
Structure/Enumeration Naming

Structures and Enumerations must be named using upper-camel-case.

Inside of an enumeration, the values must use the first one or two whole-words as a prefix (the XR, if present, may be optionally used), and must be defined in all-upper-snake-case with underscores ('_') being inserted between a lower-case and upper-case character in the enumeration name. Additionally, at least the first value in the enumeration list must be defined to an integer value.

Example 18. Structure/Enumeration Naming
struct JsonVersion {
     uint32_t major;
     uint32_t minor;
     uint32_t patch;
};
enum XrLoaderInterfaceStructs {
    XR_LOADER_INTERFACE_STRUCT_UNINTIALIZED = 0,
    XR_LOADER_INTERFACE_STRUCT_LOADER_INFO,
    XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST,
    XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST,
    XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO,
    XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO,
};
Class Component Naming

The specific components of a class must be named in the following ways:

  • The class name must be upper-camel-case

  • Class methods must be lower-camel-case and parameters must be lower-snake-case (just as functions defined above)

  • Class members must be lower-snake-case with a preceding underscore ('_')

Example 19. Class Component Naming
class MyClass {
    ...
   private
    uint32_t _my_integer_member;
    XrInstanceCreateInfo _my_xr_instance_create_info;
}

7. Loader Design

This section of the document is focused on the internal design of the OpenXR loader provided in the src/loader folder.

The OpenXR loader is composed of several classes. The overall class diagram looks roughly like the following:

class diagram
Figure 7. Loader Class Diagram

7.1. Class Roles

7.1.1. Manifest File Classes

The Desktop OpenXR loader uses JSON manifest files for information about available API layers and runtimes. All the functionality for finding, parsing, and processing the various manifest file formats are found in the three manifest file classes: ManifestFile, RuntimeManifestFile, and ApiLayerManifestFile. All three classes are defined in the manifest_file header (.hpp) and source (.cpp) files.

ManifestFile

ManifestFile is the base class containing the majority of common code. It only performs a minimal set of validation and provides accessor functions. These functions perform all the work using two primary sources:

  • The std::experimental::filesystem C++ functionality exposed by most modern compilers for finding the manifest files.

  • The JsonCPP library for processing and validating the manifest files once located.

RuntimeManifestFile

RuntimeManifestFile is a derived class (with ManifestFile as the parent) which handles specifically finding and parsing any runtime manifest files. It implements a static function FindManifestFiles which is to be used as a factory method for finding all available runtime manifest files, creating an instance of RuntimeManifestFiles for each, and saving them if they appear to be valid. This is the command that any OpenXR command inside the loader should call if it requires a list of available runtime manifest files.

static XrResult RuntimeManifestFile::FindManifestFiles(
            const std::string &openxr_command,
            std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files);
  • manifest_files is a vector that will be used to store a unique_ptr to each valid runtime manifest file found.

The RuntimeManifestFile also provides a utility function for creating an instance of itself if it determines everything is valid inside the JSON file. CreateIfValid not only validates the JSON format, but also verifies that the specific fields required by the RuntimeManifestFile class are available.

void RuntimeManifestFile::CreateIfValid(
            std::string filename,
            std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files);
  • filename indicates the absolute file name path to the manifest file that needs to be loaded and verified.

  • manifest_files is a vector that will be used to store a unique_ptr to each valid runtime manifest file found. If this call determines the file exists and is valid, it will create an instance of RuntimeManifestFile and add it to this vector.

ApiLayerManifestFile

Similar to RuntimeManifestFile, ApiLayerManifestFile is a derived class (with ManifestFile as the parent). This class handles specifically finding and parsing any API layer manifest files. It also implements a static function FindManifestFiles which is to be used as a factory method for finding all available API layer manifest files, creating an instance of ApiLayerManifestFiles for each, and saving them if they appear to be valid. This is the command that any OpenXR command inside the loader should call if it requires a list of available API layer manifest files.

static XrResult ApiLayerManifestFile::FindManifestFiles(
            const std::string &openxr_command, ManifestFileType type,
            std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files);
  • type indicates the specific type of manifest file being searched for. In this case, it must be either MANIFEST_TYPE_IMPLICIT_API_LAYER or MANIFEST_TYPE_EXPLICIT_API_LAYER.

  • manifest_files is a vector that will be used to store a unique_ptr to each valid API layer manifest file found.

The ApiLayerManifestFile also provides a utility function for creating an instance of itself if it determines everything is valid inside the JSON file. CreateIfValid not only validates the JSON format, but also verifies that the specific fields required by the ApiLayerManifestFile class are available.

void ApiLayerManifestFile::CreateIfValid(
            std::string filename,
            std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files);
  • filename indicates the absolute file name path to the manifest file that needs to be loaded and verified.

  • manifest_files is a vector that will be used to store a unique_ptr to each valid runtime manifest file found. If this call determines the file exists and is valid, it will create an instance of ApiLayerManifestFile and add it to this vector.

7.1.2. Library Interface Classes

The OpenXR loader is responsible with interfacing with other libraries to complete its tasks. Typically, there will be some interface negotiation between these other libraries and the loader (see Loader/Layer Interface Negotiation or Loader/Runtime Interface Negotiation for more information). The Interface classes are responsible with using the contents of a ManifestFile to setup the interface. Each Interface class must be used with the appropriate ManifestFile type to properly interface with a specific type of library.

RuntimeInterface

The RuntimeInterface is tasked with working with runtime libraries. Once the runtime manifest file information is found and parsed by the RuntimeManifestFile class, it is passed along to this class which will load the appropriate library and perform the required Loader/Runtime Interface Negotiation.

The RuntimeInterface implements a static function LoadRuntime which is to be used as a factory method for loading the current active runtime, performing the appropriate negotiation, and then creating an instance of RuntimeInterface. This is the command that any OpenXR command inside the loader should call if it requires loading the runtime. The command will only truly load the runtime library if it is not currently loaded, and then keep a reference count of how many requests to load it have been made.

static XrResult RuntimeInterface::LoadRuntime();

Likewise, the RuntimeInterface implements a static function UnloadRuntime which is to be used to unload the current active runtime. This will reduce the reference count until it is 0 and then unload the active runtime library.

static XrResult RuntimeInterface::UnloadRuntime();

Finally, the RuntimeInterface implements a static function GetRuntime which is used to return the single instance of the RuntimeInterface class.

static RuntimeInterface& GetRuntime();

During XrInstance creation and destruction, the RuntimeInterface needs to work directly with the runtime library. To do this, the RuntimeInterface has two methods that are used by the OpenXR loader:

XrResult RuntimeInterface::CreateInstance(
        const XrInstanceCreateInfo* info);
  • info is a pointer to the XrInstanceCreateInfo passed into the OpenXR xrCreateInstance command that triggered this call.

Once successful completion occurs, the RuntimeInterface will:

  • Store the runtime’s version of the XrInstance

  • Generate a dispatch table to all known OpenXR commands implemented by the runtime.

To destroy a runtime’s instance as part of the xrDestroyInstance command, the loader calls DestroyInstance;

XrResult RuntimeInterface::DestroyInstance();
ApiLayerInterface

Similarly, the ApiLayerInterface class is tasked with working with API layer libraries. Once the API layer manifest file information is found and parsed by the ApiLayerManifestFile class, it is passed along to this class which will load the appropriate library and perform the required Loader/Layer Interface Negotiation.

The ApiLayerInterface implements a static function LoadApiLayers which is to be used as a factory method for loading all available API layers, performing the appropriate negotiation, and then creating an instance of ApiLayerInterface for each. This is the command that any OpenXR command inside the loader should call if it requires load one or more API layers:

static XrResult ApiLayerInterface::LoadApiLayers(
        std::vector<std::unique_ptr<ApiLayerManifestFile>>& manifest_files,
        std::vector<std::string> enabled_layers,
        std::vector<std::unique_ptr<ApiLayerInterface>>& api_layer_interfaces);
  • manifest_files is a vector of unique_ptr elements containing the loaded API layer manifest information. The contents of this vector will be either transferred to a new ApiLayerInterface object placed in the api_layer_interfaces vector, or deleted when the call to this method completes.

  • enabled_layers is a vector of names for all API layers that are enabled by the environment or the user.

  • api_layer_interfaces is a vector that will be used to store a unique_ptr to a ApiLayerInterface object representing each valid API layer that is enabled and has completed loading and negotiation.

7.1.3. The LoaderInstance Class

The primary OpenXR object is the XrInstance, and from that most other data is either queried or created.

A LoaderInstance is created during the OpenXR xrCreateInstance call, and destroyed during the xrDestroyInstance call. During xrCreateInstance the loader code calls LoaderInstance::CreateInstance factory method:

static XrResult LoaderInstance::CreateInstance(
        PFN_xrGetInstanceProcAddr get_instance_proc_addr_term,
        PFN_xrCreateInstance create_instance_term,
        PFN_xrCreateApiLayerInstance create_api_layer_instance_term,
        std::vector<std::unique_ptr<ApiLayerInterface>> layer_interfaces,
        const XrInstanceCreateInfo* createInfo,
        std::unique_ptr<LoaderInstance>* loader_instance);
  • get_instance_proc_addr_term is the function pointer to the terminator for xrGetInstanceProcAddr.

  • create_instance_term is the function pointer to the terminator for xrCreateInstance.

  • create_api_layer_instance_term is the function pointer to the terminator for xrCreateApiLayerInstance.

  • api_layer_interfaces is a vector that contains a unique_ptr to all ApiLayerInterface objects that are valid and enabled. All of these pointers will be moved to the LoaderInstance on successful completion of the CreateInstance call.

  • info is a pointer to the XrInstanceCreateInfo passed into the OpenXR xrCreateInstance command that triggered this call.

  • instance contains a returned pointer to the XrInstance that will be returned upon successful execution and associated with this LoaderInstance object.

During the CreateInstance call, the loader will perform the following work:

  • Generate the call chain for both xrCreateInstance and xrGetInstanceProcAddr that passes through all enabled API layers and the runtime.

  • Create the instance using the generated xrCreateInstance call chain.

  • Create a parallel LoaderInstance associated with the returned XrInstance.

  • Generate a top-level dispatch table containing all the supported commands.

    • This table is built by using the generated xrGetInstanceProcAddr call chain

Because the loader knows what runtime need to be called as part of the create sequence, it inserts a terminator during the xrCreateInstance sequence called loaderXrTermCreateInstance after the last API layer in order to create the runtime instance.

7.1.4. Logging Classes

LoaderLogger

The LoaderLogger class was created to provide global logging capability to the OpenXR loader. It was implemented as a Singleton to reduce the overhead of passing pointers/references around to the various loader objects.

To get a reference to the LoaderLogger singleton, use the GetInstance method:

static LoaderLogger& LoaderLogger::GetInstance();

The LoaderLogger works by sending all received messages to various instances of <<loaderlogrecorder, LoaderLogRecorder> objects. To add a LoaderLogRecorder to the LoaderLogger, call AddLogRecorder:

void LoaderLogger::AddLogRecorder(
        std::unique_ptr<LoaderLogRecorder>& recorder);
  • recorder is a unique_ptr to a create LoaderLogRecorder or derived object.

Once added, general log messages will be passed to each of the LoaderLogRecorder stored in an internal vector. Any source inside of the loader may trigger a log message by using the LogMessage command:

bool LoaderLogger::LogMessage(
        XrLoaderLogMessageSeverityFlagBits message_severity,
        XrLoaderLogMessageTypeFlags message_type,
        const std::string& message_id,
        const std::string& command_name
        const std::string& message,
        const std::vector<XrLoaderLogObjectInfo>& objects = {});
  • message_severity the severity of the message

  • message_type is type of the message

  • message_id is the message id, typically for loader messages this is "OpenXR-Loader"

  • command_name is the name of the OpenXR command associated with the message. May be an empty string.

  • message is the message.

  • objects a vector of objects that are relevant to this message. May be empty.

Because of the complex nature of that method, and the fact that most log messages can be simplified, the OpenXR loader also supplies the following static methods for logging:

static bool LogErrorMessage(
        const std::string& command_name,
        const std::string& message,
        const std::vector<XrLoaderLogObjectInfo>& objects = {});
static bool LogWarningMessage(
        const std::string& command_name,
        const std::string& message,
        const std::vector<XrLoaderLogObjectInfo>& objects = {});
static bool LogInfoMessage(
        const std::string& command_name,
        const std::string& message,
        const std::vector<XrLoaderLogObjectInfo>& objects = {});
static bool LogVerboseMessage(
        const std::string& command_name,
        const std::string& message,
        const std::vector<XrLoaderLogObjectInfo>& objects = {});
  • command_name the OpenXR command that is related to the message. May be an empty string.

  • message the message that needs to be logged

  • objects an optional array of OpenXR object handles that are related to the log message.

It’s important to note that these static methods also take care of grabbing the LoaderLogger::GetInstance() and making the appropriate call to LoaderLogger::LogMessage().

Example 20. Using the Log Messages

Here are a few examples of triggering a log message:

XrResult res = xrCreateInstance(info, instance);
if (XR_SUCCESS != res) {
    std::string error_message = "xrCreateInstance failed with result ";
    error_message += std::to_string(res);
    LoaderLogger::LogErrorMessage("", error_message);
}
// After successfully adding all API layers
LoaderLogger::LogInfoMessage("", "Loaded all API layers");

In these examples, the message does not correspond to a named command so an empty string is passed.

LoaderLogRecorder

The LoaderLogRecorder is a base class that defines the basics used for recording a log message somewhere. LoaderLogRecorder provides no protections for multithreading logging. Any required protections should be implemented by the derived class that is multithread sensitive (i.e. if a logger wrote to a file).

The pure virtual method the base class defines that is used to record log messages is the LogMessage method. Each derived class is responsible for defining exactly how log messages are recorded.

virtual bool LogMessage(
        XrLoaderLogMessageSeverityFlagBits message_severity,
        XrLoaderLogMessageTypeFlags message_type,
        const XrLoaderLogMessengerCallbackData* callback_data) = 0;

Some utility methods that the LoaderLogRecorder base class supplies allow controls over whether or not log messages are actually recorded. Upon creation, a LoaderLogRecorder is set to actively record all messages that contain the appropriate flags. However, if we want to pause recording to one or more of the LoaderLogRecorders at some point and then resume recording again later, the following utilities can be used:

virtual void Pause();
virtual void Resume();
bool IsPaused();
LoaderLogRecorder Derived classes

Currently, there are two private classes derived from LoaderLogRecorder, providing three basic behaviors:

  • OstreamLoaderLogRecorder

    • Outputs to std::cerr when created with MakeStdErrLoaderLogRecorder()

    • Outputs to std::cout when created with MakeStdOutLoaderLogRecorder()

  • DebugUtilsLogRecorder

    • Created by MakeDebugUtilsLoaderLogRecorder()

The recorder created by MakeStdErrLoaderLogRecorder() handles recording all error messages that occur in the loader out to std::cerr. This logger is always enabled and is intended to always provide error messages for easier issue diagnosis.

The recorder created by MakeStdOutLoaderLogRecorder() records messages out to std::cout. This logger is enabled when the XR_LOADER_DEBUG environment variable is defined.

The recorder created by MakeDebugUtilsLoaderLogRecorder() triggers an XR_EXT_debug_utils callback every time a log message occurs. Two steps are required before the loader enables this class:

  1. The XR_EXT_debug_utils must be enabled during xrCreateInstance call.

  2. The application must create a XrDebugUtilsMessengerEXT by

    • Supplying an XrDebugUtilsMessengerCreateInfoEXT structure to the XrInstanceCreateInfo::next chain during xrCreateInstance, or

    • Calling xrCreateDebugUtilsMessengerEXT.

7.2. Automatically Generated Code

In order to allow the OpenXR loader to be as flexible as possible, we generate a large portion of the code automatically using the xr.xml registry file. The generation process is triggered during the build. This is done inside the CMakelists.txt files in both the src and src/loader folders, using the macro run_xml_generate_dependency. This macro (defined in the src/CMakelists.txt file, triggers python and generates the source. The generation scripts are based on the functionality originally defined in the of the specification/scripts folder, but here they’ve been extended to generate loader source code.

The loader automatic code generation scripts are found in the src/scripts folder. The main script of interest for OpenXR loader code generation is automatic_source_generation.py which generates 4 files during the build process:

Table 8. Automatically Generated Loader Files
Location Filename

<build>/src folder

 xr_generated_dispatch_table.h
 xr_generated_dispatch_table.c
 xr_generated_dispatch_table_core.h
 xr_generated_dispatch_table_core.c

<build>/src/loader folder

 xr_loader_generated.hpp
 xr_loader_generated.cpp

7.2.1. xr_generated_dispatch_table.h

This C-style header contains the definition of the XrGeneratedDispatchTable structure. This structure can be used to store function pointers for any OpenXR commands defined in the xr.xml at the time the loader was built. It includes slots for both core and extension function pointers. Currently, the provided API Layers use this structure and the loader uses a structure which has a subset of the function pointers (defined in xr_generated_dispatch_table_core.h).

A partial listing from the generated table follows:

// Generated dispatch table
struct XrGeneratedDispatchTable {

    // ---- Core 1.0 commands
    PFN_xrGetInstanceProcAddr GetInstanceProcAddr;
    PFN_xrEnumerateApiLayerProperties EnumerateApiLayerProperties;
    PFN_xrEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties;
    PFN_xrCreateInstance CreateInstance;
    PFN_xrDestroyInstance DestroyInstance;
    ...
};

You’ll notice that the xr prefix was dropped on the name of the elements within the structure to simplify naming as well as avoid any potential compilation conflicts.

The xr_generated_dispatch_table.h header also includes a utility function that can be used to populate a dispatch table once it has been created:

void GeneratedXrPopulateDispatchTable(
        struct XrGeneratedDispatchTable *table,
        XrInstance instance,
        PFN_xrGetInstanceProcAddr get_inst_proc_addr);
  • table is a pointer to the XrGeneratedDispatchTable to populate.

  • instance is the instance required by get_inst_proc_addr. NOTE: This may have a value of XR_NULL_HANDLE, but many of the commands may be NULL if this is used.

  • get_inst_proc_addr is a pointer to the xrGetInstanceProcAddr command to use to populate the table. If you’re calling into the OpenXR loader, this would be the standard xrGetInstanceProcAddr call. However, if you were calling this from an API layer, you would want to use the next level’s (API layer or runtime) implementation of xrGetInstanceProcAddr.

7.2.2. xr_generated_dispatch_table.c

This file is paired with the above xr_generated_dispatch_table.h header and only implements the GeneratedXrPopulateDispatchTable function used to populate the elements of a dispatch table.

7.2.3. xr_loader_generated.hpp

xr_loader_generated.hpp contains prototypes for all the manually defined instance command trampoline and terminator functions. This is done so that they can be referenced in the xr_loader_generated.cpp source file which is used for xrGetInstanceProcAddr as well as setting up the loader dispatch table.

7.2.4. xr_loader_generated.cpp

The xr_loader_generated.cpp source file contains the implementation of all generated OpenXR trampoline functions.

7.3. Manually Implemented Code

Some OpenXR command terminator and trampoline functions need to be manually implemented in the loader.

Table 9. Manually Implemented OpenXR Commands
Command Terminator/Trampoline Reason

xrEnumerateApiLayerProperties

Both (although terminator should never get called)

Loader needs to find and parse the various API layer manifest files.

xrEnumerateInstanceExtensionProperties

Both

Loader needs to find and parse the various API layer manifest files. Also needs to call into runtime and query extensions supported by it.

xrCreateInstance

Both

Loader needs to do all API layer and runtime discovery and processing as well as storing the results. The storage is done inside a LoaderInstance class object, which is created during this call.

xrDestroyInstance

Both

Loader needs to call down to all API layers destroying the instance, and then clean up its internal storage (i.e. the LoaderInstance class that was created earlier).

xrCreateApiLayerInstance

Terminator

Loader uses this to capture the xrCreateApiLayerInstance chain used to create API layer instances. This terminator will then re-direct the chain back to the standard xrCreateInstance path.

7.4. Functional Flow

The loader supports a single XrInstance at a time in order to avoid tracking handle values and their relationship to the LoaderInstance. Every XR function call is assumed to be for the single XrInstance that has been created. This enables the loader to work with future extensions and handle types without change.

7.5. Platform-Specific Behavior

The OpenXR loader design is intended to be flexible on supported on a variety of platforms. However, the loader on certain platforms will require behavior not necessary in other environments. This section describes the common platform-specific behavior expected in the OpenXR loader.

7.5.1. Library Handling

The loader works with libraries and runtimes which are exposed as either a static or dynamic external library files. Each operating system provides their own utilities for interfacing with these files, which the loader abstracts. Most loader platform code can be found in the following source file:

src/loader/loader_platform.hpp

The OpenXR loader uses a general handle define for all platform library functions. This handle is identified as LoaderPlatformLibraryHandle and is used to interact with all the platform-specific library functions.

Opening A Platform-Specific Library File

To open a platform-specific library file, and therefore retrieve the platform-specific LoaderPlatformLibraryHandle relative to that file, the loader calls the LoaderPlatformLibraryOpen function which has the following prototype:

LoaderPlatformLibraryHandle LoaderPlatformLibraryOpen(
    const std::string &path);
  • path must be a constant string containing the absolute path to the library file that needs to be opened.

If the function succeeds, the returned value will be non-NULL. If a failure occurs during this call, the returned value will be NULL. In the case of failure, the loader can call the LoaderPlatformLibraryOpenError function:

const char *LoaderPlatformLibraryOpenError(
    const std::string &path);
  • path must be a constant string containing the absolute path to the library file that the loader previously attempted to load using LoaderPlatformLibraryOpen.

The returned C-style character string contains any available platform-specific error code that may have occurred.

Closing A Platform-Specific Library File

When the loader is done using the platform library, it calls LoaderPlatformLibraryClose to release it.

void LoaderPlatformLibraryClose(
    LoaderPlatformLibraryHandle library)
  • library must be valid LoaderPlatformLibraryHandle opened using LoaderPlatformLibraryOpen

Querying Content In a Platform-Specific Library File

Once a library is opened, the loader will query for the important functions exported by a library using the LoaderPlatformLibraryGetProcAddr function:

void *LoaderPlatformLibraryGetProcAddr
    LoaderPlatformLibraryHandle library,
    const std::string &name)
  • library must be valid LoaderPlatformLibraryHandle opened using LoaderPlatformLibraryOpen, but not yet closed using LoaderPlatformLibraryClose.

  • name must contain the name of the library supplied function who’s function pointer is desired.

If the function succeeds, the returned value will be a valid function pointer address. If the function fails, it will return NULL. A NULL return value could imply that the function simply isn’t exported by the library, or that an error occurred during the platform call. To determine what might have happened, the loader will call the LoaderPlatformLibraryGetProcAddrError function:

const char *LoaderPlatformLibraryGetProcAddrError(
    const std::string &path);
  • path must be a constant string containing the name of the entry point that was attempted to be queried during the previous LoaderPlatformLibraryGetProcAddr call.

The returned C-style character string contains any available platform-specific error code that may have occurred.

7.5.2. Environment Variable Usage

Several environment variables are used in the OpenXR loader, especially on the Desktop (Windows/Linux). However, accessing environment variables is different based on each operating system, so we have added global interface functions to use for accessing environment variables. These are defined in src/common/platform_utils.hpp

NOTE: This is outside of the loader source to allow other items in the source folder to use these utilities.

Reading a Standard Environment Variable

To read an environment variable, the loader calls PlatformUtilsGetEnv:

std::string PlatformUtilsGetEnv(
    const char *name);
  • name must be a non-NULL NULL-terminated C-style string indicating the name of the environment variable to get the value of.

If the environment variable identified by name exists on the system, the C-style NULL-terminated string will be returned. If the environment variable can not be found, an empty string is returned.

If you want to distinguish between an empty value and a variable not set at all, if the underlying platform distinguishes these cases, use PlatformUtilsGetEnvSet:

bool PlatformUtilsGetEnvSet(
    const char *name);

It returns true if the environment variable is set.

Reading a Secure Environment Variable

Access to certain environment variables needs to be done in a way that maintains operational security of the program.

Note

Specifically, the loader will not use environment variables which control code loading for admin (high integrity) processes on Windows as the environment variables can be set by non-admin (medium integrity) processes.

To read a secure environment variable, the loader calls PlatformUtilsGetSecureEnv:

char *PlatformUtilsGetSecureEnv(
    const char *name);
  • name must be a non-NULL NULL-terminated C-style string indicating the name of the environment variable to get the value of.

If the platform supports secure environment variable reading, the appropriate method will be used. Otherwise, it will fall back to the standard PlatformUtilsGetEnv call.

7.5.3. Active Runtime File Management

Querying the Active Runtime File name

Since the runtime file name can vary based on the supporting system, the command PlatformGetGlobalRuntimeFileName provides a quick mechanism for querying the name of the file.

Note

Not all platforms implement this call.

bool PlatformGetGlobalRuntimeFileName(
    uint16_t major_version,
    std::string &file_name);
  • major_version is the major API version for the OpenXR you are querying the active runtime file name for.

  • file_name is the returned name of the runtime file. This is only valid if the command returns true.

8. Loader Debugging

There may be times that a developer wishes to debug the loader. The following sections define useful tips in debugging the OpenXR loader.

8.1. Loader Logging

The user can define the XR_LOADER_DEBUG environment variable which will enable logging. In order to use the setting, simply define the level of debug information you wish to see logged by the loader to std::cerr.

The available log levels are as follows:

Value Behavior

error

Log any error messages that occur in the loader

warn

Log any warning and error messages that occur in the loader

info

Log any general information messages from the loader, as well as warnings and errors

debug

Log any verbose debug messages from the loader, as well as the general information, warning, and error messages

all

Log any messages originating from the loader.

Notice that each level logs not only messages of it’s type, but also those of any levels above it.

Example 21. Setting XR_LOADER_DEBUG

Windows

set XR_LOADER_DEBUG=warn

Linux

export XR_LOADER_DEBUG=error

8.2. Additional Debug Suggestions

If you are seeing issues which may be related to the loader’s use of either an API layer or a runtime, there is another setting that may assist you in tracking down problems. Manually define the environment variable LD_BIND_NOW to a value of 1. This forces every dynamic library’s symbols to be fully resolved on load. If there is a problem with either an API layer or a runtime missing symbols on your system, enabling this will expose the problem. When enabled, this setting causes the OpenXR loader to fail on loading the problem library. It is recommended that you enable LD_BIND_NOW along with XR_LOADER_DEBUG=warn to expose any important issues.

9. Appendices

9.1. Table of Debug Environment Variables

The following are all the Debug Environment Variables available for use with the loader. These are referenced throughout the text, but collected here for ease of discovery.

Table 10. Debug Environment Variables
Environment Variable Behavior Example Format

XR_RUNTIME_JSON

Force the loader to use the specific runtime JSON file. The value should contain the full path to runtime JSON Manifest files. NOTE: If you fail to use the global path to a JSON file, you may encounter issues.

  • export XR_RUNTIME_JSON=<folder_a>\xr_vendor_1.json

  • set XR_RUNTIME_JSON=<folder_a>\xr_vendor_2.json

XR_ENABLE_API_LAYERS

Force the loader to add the given API layers to the list of Enabled API layers normally passed into xrCreateInstance. These API layers are added after the implicit API layers, but before the standard explicit API layers normally added by enableLayerNames in xrCreateInstance. The loader will remove any duplicate API layers that appear in both this list by using the first occurrence of any API layer.

  • export XR_ENABLE_API_LAYERS=<layer_a>:<layer_b>

  • set XR_ENABLE_API_LAYERS=<layer_a>;<layer_b>

XR_API_LAYER_PATH

Override the loader’s standard API Layer library search folders and use the provided delimited folders to search for API layer Manifest files.

  • export XR_API_LAYER_PATH=<path_a>:<path_b>

  • set XR_API_LAYER_PATH=<path_a>;<pathb>

XR_LOADER_DEBUG

Enable loader debug messages. Options are: * error (only errors) * warn (warnings and errors) * info (info, warning, and errors) * debug (debug + all before) * all (report out all messages)

  • export XR_LOADER_DEBUG=all

  • set XR_LOADER_DEBUG=warn

9.2. Glossary of Terms

Table 11. Glossary of Terms
Term Definition

API Layer

API layers are optional components that augment the OpenXR system. They can intercept, evaluate, and modify existing OpenXR functions on their way from the application down to the hardware.

Call Chain

The sequence of calls made for a given OpenXR command. The first call in a call chain is the application. The last call in a call chain sequence is always the runtime function. In between can be: a loader trampoline function, one or more API layer functions, and a loader terminator function.

Core Function

A function that is defined in a version of OpenXR and not an extension. For example, xrCreateInstance is a core function defined in the OpenXR API as well as openxr.h.

Discovery

The process the loader performs to determine the available list of OpenXR runtimes and API layers. On Windows and Linux, the discovery process typically focuses on searching for Manifest files. While on Android, the discovery process uses APK packaged Manifest json files for API layers and the ContentProvider for the runtime.

Extension

A concept of OpenXR used to expand the core OpenXR functionality. Extensions may be vendor-specific, platform-specific, or more broadly available. You should always query if an extension exists using xrEnumerateInstanceExtensionProperties, and enable it during xrCreateInstance.

Loader

The Khronos-developed middle-ware program which acts as the mediator between OpenXR applications, OpenXR API layers and OpenXR runtimes.

Manifest Files

Data files in JSON format used by the desktop loader. These files contain specific information for either a API layer or runtime.

Runtime

A complete VR/XR/MR system available on a end-user’s environment which supports one or more devices that may be used collectively.

Terminator Function

The last function in the call chain before the runtime and owned by the loader. Only used it a few limited cases. Currently xrCreateInstance, xrDestroyInstance and xrGetInstanceProcAddr are the three primary users of a terminator.

Trampoline Function

The first function in the call chain after the runtime and owned by the loader.