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:
-
OpenXR Application
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.
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.
-
It must support one or more OpenXR-capable runtimes on a user’s computer system.
-
It must support OpenXR API layers (optional modules that can be enabled by an application, developer, or standard system settings).
-
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
|
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.
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:
-
Directly linking to the core OpenXR commands exposed and exported by the loader.
-
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):
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:
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.
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
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:
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:
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 ( |
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:
This is done purely to simplify the reading of the table |
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:
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.
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.
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:
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:
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).
/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.
/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. |
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
-
/product/etc/openxr/major_ver/api_layers/implicit.d
-
/odm/etc/openxr/major_ver/api_layers/implicit.d
-
/oem/etc/openxr/major_ver/api_layers/implicit.d
-
/system/etc/openxr/major_ver/api_layers/implicit.d
-
/vendor/etc/openxr/major_ver/api_layers/implicit.d
Explicit API Layers
-
/product/etc/openxr/major_ver/api_layers/explicit.d
-
/oem/etc/openxr/major_ver/api_layers/explicit.d
-
/vendor/etc/openxr/major_ver/api_layers/explicit.d
-
/system/etc/openxr/major_ver/api_layers/explicit.d
-
/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".
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"
}
}
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. |
|
"name" |
Required for Implicit / Explicit |
The string used to uniquely identify this API layer to applications. |
|
"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. |
|
"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. |
|
"description" |
Required for Implicit / Explicit |
A high-level description of the API layer and its intended use. |
|
"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 |
|
"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
|
|
"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:
-
The call enters the loader’s trampoline function
xrCreateInstance
-
The loader will generate an instance of the
XrApiLayerCreateInfo
structure -
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):
-
Record the API layer’s name,
xrGetInstanceProcAddr
address, and thexrCreateApiLayerInstance
address. -
Build a
XrApiLayerNextInfo
structure for the API layer recording the name and command addresses.-
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:
-
XrApiLayerNextInfo.nextGetInstanceProcAddr
=loaderXrTermGetInstanceProcAddr
-
XrApiLayerNextInfo.nextCreateLayerInstance
=loaderXrTermCreateLayerInstance
-
XrApiLayerNextInfo.next
= NULL
-
-
Otherwise, the loader sets the information to the previous API layer’s information:
-
XrApiLayerNextInfo.nextGetInstanceProcAddr
= PreviousXrApiLayerNextInfo.loaderXrTermGetInstanceProcAddr
-
XrApiLayerNextInfo.nextCreateLayerInstance
= PreviousXrApiLayerNextInfo.loaderXrTermCreateLayerInstance
-
XrApiLayerNextInfo.next
= address to previousXrApiLayerNextInfo
-
-
-
-
The loader will then update the
XrApiLayerCreateInfo.nextInfo
to point to the last createdXrApiLayerNextInfo
since this is the first API layer in the call-chain. -
The loader calls the first API layer’s
xrCreateApiLayerInstance
command passing in the pointer to the createdXrApiLayerCreateInfo
-
The API layer receives the information in its
xrCreateApiLayerInstance
command. -
The API layer copies the
XrApiLayerCreateInfo
structure into it’s own structure. -
The API layer then updates it’s version of the
XrApiLayerCreateInfo
structure settingnextInfo
to point to theXrApiLayerNextInfo
for the next API layer (i.e.XrApiLayerCreateInfo→nextInfo = XrApiLayerCreateInfo→nextInfo→next;
). -
The API layer may validate that it is getting the correct next information by checking that the
layerName
matches. -
The API layer then uses the information out of its
XrApiLayerNextInfo
to call down the call-chain to the nextxrCreateApiLayerInstance
, using a pointer to itsXrApiLayerCreateInfo
structure instead of the one that was passed in during itsxrCreateApiLayerInstance
command.-
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’sxrGetInstanceProcAddr
and theGeneratedXrPopulateDispatchTable
utility command provided in the generatedxr_generated_dispatch_table.h
header.
-
-
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
|
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:
|
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 namedorg.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 namedorg.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 withnative_lib_dir
to provide the absolute path of the shared object. -
has_functions
- Boolean, if true, it indicates the loader should query thefunctions/
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:
-
/product/etc/openxr/major_ver
-
/odm/etc/openxr/major_ver
-
/oem/etc/openxr/major_ver
-
/vendor/etc/openxr/major_ver
-
/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
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:
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.
|
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 |
---|---|---|---|
|
|
64-bit x86 instructions, using an ILP32 model (32-bit pointers) |
|
|
|
|
64-bit x86 |
|
|
|
32-bit x86 |
|
|
|
64-bit ARM architecture, little endian |
|
|
|
32-bit ARMv7-A architecture, little endian, with hardware floating point and VFP PCS ABI |
|
|
32-bit ARMv5TE architecture or compatible, little endian |
|
|
|
64-bit MIPS architecture, little endian |
|
|
|
32-bit MIPS architecture, little endian |
|
|
|
64-bit PowerPC architecture, big endian |
|
|
|
64-bit POWER8/POWER9 architecture, little endian (OpenPOWER ELF ABI v2) |
|
|
|
64-bit S390/z-Series architecture, big endian |
|
|
|
32-bit HP PA-RISC architecture, big endian |
|
|
|
64-bit Alpha architecture |
|
|
|
64-bit IA-64 architecture |
|
|
|
32-bit Motorola 68000-based architecture, big endian |
|
|
|
64-bit RISC-V architecture, little endian |
|
|
|
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:
{
"file_format_version": "1.0.0",
"runtime": {
"name": "openxr_sample_runtime",
"library_path": "./dbuild/src/impl/libopenxr_sample_impl.so"
}
}
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 |
"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>
attributeandroid: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. |
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 |
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 toNULL
.
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:
-
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 If you aren’t using the supplied CMake build system, you will need to either generate the header yourself (preferred), or supply equivalent definitions:
|
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 }
.
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.
Additionally, STL may be used in any C++ source code areas.
Namespaces outside of the OpenXR loader must not be enabled by default.
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:
std::string my_string;
std::cout << std::to_string(5);
std::experimental::filesystem::path search_path;
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.
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
.
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.
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.
#define CURRENT_LOADER_API_LAYER_VERSION 1
#define ENABLE_LOADER_DEBUG 1
Variable Naming
All local variables and function/method parameters must use lower-snake-case.
uint32_t number_of_actual_items;
std::string file_path_location;
Global variables, too, are defined using lower-snake-case with an additional
prefix of g_
required to identify them as global variables.
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.
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.
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 ('_')
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:
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
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
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 ofRuntimeManifestFile
and add it to this vector.
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 eitherMANIFEST_TYPE_IMPLICIT_API_LAYER
orMANIFEST_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 ofApiLayerManifestFile
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.
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 theXrInstanceCreateInfo
passed into the OpenXRxrCreateInstance
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();
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 newApiLayerInterface
object placed in theapi_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 aApiLayerInterface
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 allApiLayerInterface
objects that are valid and enabled. All of these pointers will be moved to theLoaderInstance
on successful completion of theCreateInstance
call. -
info
is a pointer to theXrInstanceCreateInfo
passed into the OpenXRxrCreateInstance
command that triggered this call. -
instance
contains a returned pointer to theXrInstance
that will be returned upon successful execution and associated with thisLoaderInstance
object.
During the CreateInstance
call, the loader will perform the following
work:
-
Generate the call chain for both
xrCreateInstance
andxrGetInstanceProcAddr
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 returnedXrInstance
. -
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
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 createLoaderLogRecorder
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
().
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.
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();
Currently, there are two private classes derived from LoaderLogRecorder
,
providing three basic behaviors:
-
OstreamLoaderLogRecorder
-
Outputs to
std::cerr
when created withMakeStdErrLoaderLogRecorder()
-
Outputs to
std::cout
when created withMakeStdOutLoaderLogRecorder()
-
-
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:
-
The
XR_EXT_debug_utils
must be enabled duringxrCreateInstance
call. -
The application must create a
XrDebugUtilsMessengerEXT
by-
Supplying an
XrDebugUtilsMessengerCreateInfoEXT
structure to theXrInstanceCreateInfo
::next
chain duringxrCreateInstance
, 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:
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 theXrGeneratedDispatchTable
to populate. -
instance
is the instance required byget_inst_proc_addr
. NOTE: This may have a value ofXR_NULL_HANDLE
, but many of the commands may beNULL
if this is used. -
get_inst_proc_addr
is a pointer to thexrGetInstanceProcAddr
command to use to populate the table. If you’re calling into the OpenXR loader, this would be the standardxrGetInstanceProcAddr
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 ofxrGetInstanceProcAddr
.
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.
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
|
xrDestroyInstance |
Both |
Loader needs to call down to all API layers destroying the instance, and
then clean up its internal storage (i.e. the |
xrCreateApiLayerInstance |
Terminator |
Loader uses this to capture the
|
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.
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 usingLoaderPlatformLibraryOpen
.
The returned C-style character string contains any available platform-specific error code that may have occurred.
When the loader is done using the platform library, it calls
LoaderPlatformLibraryClose
to release it.
void LoaderPlatformLibraryClose(
LoaderPlatformLibraryHandle library)
-
library
must be validLoaderPlatformLibraryHandle
opened usingLoaderPlatformLibraryOpen
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 validLoaderPlatformLibraryHandle
opened usingLoaderPlatformLibraryOpen
, but not yet closed usingLoaderPlatformLibraryClose
. -
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 previousLoaderPlatformLibraryGetProcAddr
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.
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.
Access to certain environment variables needs to be done in a way that maintains operational security of the program.
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
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.
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 returnstrue
.
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.
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.
Environment Variable | Behavior | Example Format |
---|---|---|
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. |
|
|
Force the loader to add the given API layers to the list of Enabled API layers
normally passed into |
|
|
Override the loader’s standard API Layer library search folders and use the provided delimited folders to search for API layer Manifest files. |
|
|
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) |
|
9.2. Glossary of Terms
Term | Definition |
---|---|
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. |
|
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, |
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
|
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. |
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
|
Trampoline Function |
The first function in the call chain after the runtime and owned by the loader. |