Copyright (c) 2017-2020 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.
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 don’t actually 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 runtime version is reported and the API layer version is culled.
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 don’t 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>
API Layer Manifest File Format
On Windows and Linux, the desktop loader uses manifest files to discover
API layers. The desktop 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. |
xrEnumerateApiLayerProperties |
"name" |
Required for Implicit / Explicit |
The string used to uniquely identify this API layer to applications. |
xrEnumerateApiLayerProperties |
"library_path" |
Required for Implicit / Explicit |
The "library_path" specifies either a filename, a relative pathname, or a full pathname to the API layer’s shared library file. If "library_path" specifies a relative pathname, it is relative to the path of the JSON manifest file (e.g. for cases when an application provides an API layer that is in the same folder hierarchy as the rest of the application files). If "library_path" specifies a filename, the library must live in the system’s shared object search path. There are no rules about the name of the API layer shared library files other than it should end with the appropriate suffix (".DLL" on Windows, and ".so" on Linux). |
N/A |
"api_version" |
Required for Implicit / Explicit |
The major.minor (but not patch) version number of the OpenXR API that the shared library file for the library was built against. For example: 1.0. |
xrEnumerateApiLayerProperties |
"implementation_version" |
Required for Implicit / Explicit |
The version of the API layer implemented. If the API layer itself has any major changes, this number should change so the loader and/or application can identify it properly. |
xrEnumerateApiLayerProperties |
"description" |
Required for Implicit / Explicit |
A high-level description of the API layer and it’s intended use. |
xrEnumerateApiLayerProperties |
"functions" |
Optional for Implicit / Explicit |
This section can be used to identify a different function name for
the loader to use in place of standard API layer interface functions. The
"functions" node is required if the API layer is using an alternative name
for |
xrGet*ProcAddr (except 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
|
xrEnumerateInstanceExtensionProperties |
"enable_environment" |
Optional for Implicit |
Indicates an environment variable used to enable the implicit API layer. If provided in the JSON file, this environment variable (which should vary with each "version" of the API layer) must be set in the environment or else the implicit API layer is not loaded. This is for application environments (e.g. Steam) which want to enable an API layer(s) only for applications that they launch, and allows for applications run outside of that environment to not get that implicit API layer(s). |
N/A |
"disable_environment" |
Required for Implicit |
Indicates an environment variable used to disable the implicit API layer. Required to allow users or applications to disable implicit layers that are not desired or that cause problems for the application. The user/application can set this environment variable (before calling OpenXR functions) to "blacklist" the 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).
XrResult xrNegotiateLoaderApiLayerInterface(
const XrNegotiateLoaderInfo *loaderInfo,
const char *layerName,
XrNegotiateApiLayerRequest *apiLayerRequest);
-
loaderInfo
must be a valid pointer to a constant XrNegotiateLoaderInfo structure. -
layerName
must be NULL or a valid C-style NULL-terminated string listing the name of an API layer which the loader is attempting to negotiate with. -
layerRequest
must be a valid pointer to a XrNegotiateApiLayerRequest structure.
This function should be directly exported by an API layer so that using
"GetProcAddress" on Windows or "dlsym" on Linux, should return a valid function
pointer to it. If the function succeeds, the API layer must return XR_SUCCESS
.
If the function fails, the API layer must return XR_ERROR_INITIALIZATION_FAILED
.
The entire negotiation process is defined
in more detail below.
The XrNegotiateLoaderInfo
struct is defined in the
src/common/loader_interfaces.h
header. It is used to pass information
about the loader to an API layer during the negotiation process.
struct XrNegotiateLoaderInfo {
XrLoaderInterfaceStructs structType;
uint32_t structVersion;
size_t structSize;
uint32_t minInterfaceVersion;
uint32_t maxInterfaceVersion;
XrVersion minApiVersion;
XrVersion maxApiVersion;
};
-
structType
must be a valid value of XrLoaderInterfaceStructs. In this case, it must specifically beXR_LOADER_INTERFACE_STRUCT_LOADER_INFO
. -
structVersion
must be a valid version of the structure. Theloader_interfaces.h
header uses the valueXR_LOADER_INFO_STRUCT_VERSION
to describe the current latest version of this structure. -
structSize
must be the size in bytes of the current version of the structure (i.e. sizeof(XrNegotiateLoaderInfo)). -
minInterfaceVersion
is the minimum loader/API layer interface version supported by the loader. -
maxInterfaceVersion
is the maximum valid version of the loader/API layer interface version supported by the loader, currently defined usingXR_CURRENT_LOADER_API_LAYER_VERSION
. -
minApiVersion
is the minimum supported version of the OpenXR API by this loader as formatted byXR_MAKE_VERSION
defined inopenxr.h
. Patch is ignored. -
maxApiVersion
is the maximum supported version of the OpenXR API by this loader as formatted byXR_MAKE_VERSION
defined inopenxr.h
. Patch is ignored.
You’ll notice the structures are similar to other OpenXR structures. The "structType" field, in this case takes a new enum defined just for internal loader interfacing use.
The XrLoaderInterfaceStructs
enumeration is also defined in the
src/common/loader_interfaces.h
header and currently looks like:
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,
};
Just like standard OpenXR structs, the XrLoaderInterfaceStructs
enum defines
the set of valid structures and can grow in the future.
The XrNegotiateApiLayerRequest
can also be found in the
src/common/loader_interfaces.h
header. It is used to pass information
about the API layer back to the loader during the negotiation process.
struct XrNegotiateApiLayerRequest {
XrLoaderInterfaceStructs structType;
uint32_t structVersion;
size_t structSize;
uint32_t layerInterfaceVersion;
XrVersion layerApiVersion;
PFN_xrGetInstanceProcAddr getInstanceProcAddr;
PFN_xrCreateApiLayerInstance createApiLayerInstance;
};
-
structType
must be a valid value of XrLoaderInterfaceStructs. In this case, it must specifically beXR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST
. -
structVersion
must be a valid version of the structure. Theloader_interfaces.h
header uses the valueXR_API_LAYER_INFO_STRUCT_VERSION
to describe the current latest version of this structure. -
structSize
must be the size in bytes of the current version of the structure (i.e. sizeof(XrNegotiateApiLayerRequest)). -
layerInterfaceVersion
is the version of the loader/API layer interface version being requested by the API layer. Should not be outside of the bounds of theXrNegotiateLoaderInfo
::`minInterfaceVersion` andXrNegotiateLoaderInfo
::`maxInterfaceVersion` values (inclusive). -
layerApiVersion
is the version of the OpenXR API supported by this API layer as formatted byXR_MAKE_VERSION
defined inopenxr.h
. Patch is ignored. -
getInstanceProcAddr
is a pointer to the API layer’sxrGetInstanceProcAddr
command that will be used by the loader to complete a dispatch table to all valid OpenXR commands supported by the API layer. -
createLayerInstance
is a pointer to the API layer’sxrCreateApiLayerInstance
command that will be used by the loader when axrCreateInstance
command is triggered and an API layer exists. This is used because API layers need additional information atxrCreateInstance
time.
4.3.1. Loader/API Layer Negotiation Process
Once the loader has obtained a valid address to the API layer’s xrNegotiateLoaderApiLayerInterface function, the loader will create a variable of type XrNegotiateLoaderInfo and initialize it in the following ways:
-
Set the structure "structType" to
XR_LOADER_INTERFACE_STRUCT_LOADER_INFO
-
Set the structure "structVersion" to the current version,
XR_LOADER_INFO_STRUCT_VERSION
-
Set the structure "structSize" to the current size of the
XrNegotiateLoaderInfo
structure -
Set "minInterfaceVersion" to the minimum loader/API layer interface version that the loader supports
-
Set "maxInterfaceVersion" to the current version of the loader/API layer interface version at the time of loader compilation
-
Set "minApiVersion" to the minimum version of OpenXR supported by the loader
-
Set "maxApiVersion" to the maximum version of OpenXR supported by the loader (the current version at the time of loader compilation).
The loader also creates and initializes a variable of type XrNegotiateApiLayerRequest to allow the API layer to properly respond to the request. The structure will be initialized by the loader in the following way:
-
Set the structure "structType" to
XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST
-
Set the structure "structVersion" to the current version,
XR_API_LAYER_INFO_STRUCT_VERSION
-
Set the structure "structSize" to the current size of the
XrNegotiateApiLayerRequest
structure
The loader will leave the remaining fields uninitialized to allow each API layer to fill in the appropriate information for itself. The loader will then individually call each API layer’s xrNegotiateLoaderApiLayerInterface function and each API layer then must :
-
Determine if it can support the loader’s request:
-
Does the API layer support any loader/API layer interface version between
loaderInfo
→minInterfaceVersion
andloaderInfo
→maxInterfaceVersion
: -
AND does the API layer support any OpenXR API version between
loaderInfo
→minApiVersion
andloaderInfo
→maxApiVersion
:
-
-
If it is able to support the request, it must return
XR_SUCCESS
and:-
Fill in
layerRequest
→layerInterfaceVersion
with the API layer interface version it desires to support. -
Fill in
layerRequest
→layerApiVersion
with the API version of OpenXR it will execute under. -
Fill in
layerRequest
→getInstanceProcAddr
with a valid function pointer so that the loader can query function pointers to the remaining OpenXR commands supported by the API layer. -
Fill in
layerRequest
→createLayerInstance
with a valid function pointer so that the loader can create the instance through the API layer call chain.
-
-
Otherwise, it must return
XR_ERROR_INITIALIZATION_FAILED
Note
The API layer must not call to another API layer while inside of the
|
4.3.2. 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
command, which is a special API layer
command.
XrResult xrCreateApiLayerInstance(
const XrInstanceCreateInfo *info,
const struct XrApiLayerCreateInfo *layerInfo,
XrInstance *instance);
-
info
is a pointer to the XrInstanceCreateInfo information passed into the standard xrCreateInstance command. -
layerInfo
is a pointer to an XrApiLayerCreateInfo structure that contains special information required by a API layer during its create instance process. This is generated by the loader. -
instance
is a pointer to store the returned instance in, just as in the standardxrCreateInstance
command.
The XrApiLayerCreateInfo
structure is defined in src/common/loader_interfaces.h
and looks like:
struct XrApiLayerCreateInfo {
XrLoaderInterfaceStructs structType;
uint32_t structVersion;
size_t structSize;
XrInstance loaderInstance;
char settings_file_location[XR_API_LAYER_MAX_SETTINGS_PATH_SIZE];
XrApiLayerNextInfo *nextInfo;
};
-
structType
is the type of structure, or XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO in this case. -
structVersion
is the version of the structure being supplied by the loader. -
structSize
is the size of the structure supplied by the loader. -
loaderInstance
is deprecated and must be ignored. -
settings_file_location
is the location of any API layer settings file that can be used. This is currently unused. -
nextInfo
is a pointer to the XrApiLayerNextInfo structure which contains information to work with the next API layer in the chain.
The XrApiLayerNextInfo
structure is also defined in src/common/loader_interfaces.h
and looks like:
struct XrApiLayerNextInfo {
XrLoaderInterfaceStructs structType;
uint32_t structVersion;
size_t structSize;
char layerName[XR_MAX_API_LAYER_NAME_SIZE];
PFN_xrGetInstanceProcAddr nextGetInstanceProcAddr;
PFN_xrCreateApiLayerInstance nextCreateApiLayerInstance;
XrApiLayerNextInfo *next;
};
-
structType
is the type of structure, or XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO in this case. -
structVersion
is the version of the structure being supplied by the loader. -
structSize
is the size of the structure supplied by the loader. -
layerName
is the name of the API layer which should be receiving this information. The intent is to make sure the API layer chain information is behaving properly. -
nextGetInstanceProcAddr
is a pointer to the next API layer’sxrGetInstanceProcAddr
. This can be used to generate a dispatch table to the next commands in the chain. -
nextCreateLayerInstance
is a pointer to the next API layer’sxrCreateApiLayerInstance
command. This is to be called after the API layer has done any localized creation, but before the API layer records any command addresses from the next API layer usingxrGetInstanceProcAddr
. -
next
is a pointer to theXrApiLayerNextInfo
for the next API layer. If no API layer is after this, it will be NULL. However, the loader implements axrTerminatorCreateLayerInstance
command to re-direct the call-chain back to the runtime’sxrCreateInstance
command, so this should be the last command to receive this information.
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 created XrApiLayerCreateInfo
-
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 setting
nextInfo
to point to the XrApiLayerNextInfo 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 its XrApiLayerCreateInfo 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.
The JSON file is named "active_runtime.json" and 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 might be at:
/etc/xdg/openxr/1/active_runtime.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. This allows the user’s
preference to easily override a global default.
Windows Active Runtime Location
The Windows OpenXR active runtime information is actually 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 major version.
Important
If using a 32-bit application on a 64-bit Windows install, "WOW6432Node" is added before "SOFTWARE", in the above path like so:
|
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
5.1.2. 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": {
"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 |
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"
5.1.3. 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.4. 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/.local/share/openxr/runtime.d/steam_runtime.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).
XrResult xrNegotiateLoaderRuntimeInterface(
const XrNegotiateLoaderInfo *loaderInfo,
XrNegotiateRuntimeRequest *runtimeRequest);
-
loaderInfo
must be a valid pointer to a constant XrNegotiateLoaderInfo structure. -
runtimeRequest
must be a valid pointer to a XrNegotiateRuntimeRequest structure.
This 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).
If the function succeeds, the runtime should return XR_SUCCESS
.
If the function fails, the runtime should return
XR_ERROR_INITIALIZATION_FAILED
. The entire
negotiation process is defined in more
detail below.
The XrNegotiateLoaderInfo
struct is defined in the
src/common/loader_interfaces.h
header. It is used to pass information
about the loader to a runtime during the negotiation process. See the
definition of XrNegotiateLoaderInfo under the
layer negotiation section for
more information.
The XrNegotiateRuntimeRequest
can also be found in the
src/common/loader_interfaces.h
header. It is used to pass information
about the runtime back to the loader during the negotiation process.
struct XrNegotiateRuntimeRequest {
XrLoaderInterfaceStructs structType;
uint32_t structVersion;
size_t structSize;
uint32_t runtimeInterfaceVersion;
uint32_t runtimeApiVersion;
PFN_xrGetInstanceProcAddr getInstanceProcAddr;
};
-
structType
must be a valid value of XrLoaderInterfaceStructs. In this case, it must specifically beXR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST
. -
structVersion
must be a valid version of the structure. Theloader_interfaces.h
header uses the valueXR_RUNTIME_INFO_STRUCT_VERSION
to describe the current latest version of this structure. -
structSize
must be the size in bytes of the current version of the structure (i.e. sizeof(XrNegotiateRuntimeRequest)) -
runtimeInterfaceVersion
is the version of the loader/runtime interface version being requested by the runtime. Should not be outside of the bounds of the XrNegotiateLoaderInfo::`minInterfaceVersion` andXrNegotiateLoaderInfo
::`maxInterfaceVersion` values (inclusive). -
runtimeApiVersion
is the version of the OpenXR API supported by this runtime as formatted byXR_MAKE_VERSION
defined inopenxr.h
. Patch is ignored. -
getInstanceProcAddr
is a pointer to the runtime’sxrGetInstanceProcAddr
call that will be used by the loader to complete a dispatch table to all valid OpenXR commands supported by the runtime.
Important
Remember, during the call to |
5.2.1. Loader/Runtime Negotiation Process
Once the loader has obtained a valid address to the runtime’s xrNegotiateLoaderRuntimeInterface function, the loader will create a variable of type XrNegotiateLoaderInfo and initialize it in the following ways:
-
Set the structure "structType" to
XR_LOADER_INTERFACE_STRUCT_LOADER_INFO
-
Set the structure "structVersion" to the current version,
XR_LOADER_INFO_STRUCT_VERSION
-
Set the structure "structSize" to the current size of the
XrNegotiateLoaderInfo
structure -
Set "minInterfaceVersion" to the minimum loader/runtime interface version that the loader supports
-
Set "maxInterfaceVersion" to the current version of the loader/runtime interface at the time of loader compilation
-
Set "minApiVersion" to the minimum version of OpenXR supported by the loader
-
Set "maxApiVersion" to the maximum version of OpenXR supported by the loader (the current version at the time of loader compilation).
The loader will also create an initialize a variable of type XrNegotiateRuntimeRequest to allow the runtime to properly respond to the request. The structure will be initialized by the loader in the following way:
-
Set the structure "structType" to
XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST
-
Set the structure "structVersion" to the current version,
XR_RUNTIME_INFO_STRUCT_VERSION
-
Set the structure "structSize" to the current size of the
XrNegotiateRuntimeRequest
structure
The loader will leave the remaining fields uninitialized to allow each runtime to fill in the appropriate information for itself. The loader will then individually call each runtime’s xrNegotiateLoaderRuntimeInterface function and each runtime then must :
-
Determine if it can support the loader’s request:
-
Does the runtime support any loader/runtime interface version between
loaderInfo
→minInterfaceVersion
andloaderInfo
→maxInterfaceVersion
: -
AND does the runtime support any OpenXR API version between
loaderInfo
→minApiVersion
andloaderInfo
→maxApiVersion
:
-
-
If it is able to support the request, it must return
XR_SUCCESS
and:-
Fill in
runtimeRequest
→runtimeInterfaceVersion
with the runtime interface version it desires to support -
Fill in
runtimeRequest
→runtimeApiVersion
with the API version of OpenXR it will execute under. -
Fill in
runtimeRequest
→getInstanceProcAddr
with a valid function pointer so that the loader can query function pointers to the remaining OpenXR commands supported by the runtime.
-
-
Otherwise, it must return
XR_ERROR_INITIALIZATION_FAILED
5.2.2. 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.3. 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.2.4. Android Runtime Negotiation
The Android loader uses the same protocol for initializing the dispatch table as described above. The only difference is that the Android loader queries API layer and extension information directly from the respective libraries and does not use the json manifest files used by the Windows and Linux loaders.
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
loader_interfaces.h
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(
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(
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 |
|
<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 loader uses this structure as well as the provided API Layers.
A partial listing from the generated table follows:
// Generated dispatch table
struct XrGeneratedDispatchTable {
// ---- Core 1.0 commands
PFN_xrGetInstanceProcAddr GetInstanceProcAddr;
PFN_xrEnumerateApiLayerProperties EnumerateApiLayerProperties;
PFN_xrEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties;
PFN_xrCreateInstance CreateInstance;
PFN_xrDestroyInstance DestroyInstance;
...
};
You’ll notice that the xr
prefix was dropped on the name of the elements
within the structure to simplify naming as well as avoid any potential
compilation conflicts.
The xr_generated_dispatch_table.h
header also includes a utility function that
can be used to populate a dispatch table once it has been created:
void GeneratedXrPopulateDispatchTable(
struct XrGeneratedDispatchTable *table,
XrInstance instance,
PFN_xrGetInstanceProcAddr get_inst_proc_addr);
-
table
is a pointer to the XrGeneratedDispatchTable to populate. -
instance
is the instance required 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 using LoaderPlatformLibraryOpen.
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 using LoaderPlatformLibraryOpen
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 using LoaderPlatformLibraryOpen, but not yet closed using LoaderPlatformLibraryClose. -
name
must contain the name of the library supplied function who’s function pointer is desired.
If the function succeeds, the returned value will be a valid function
pointer address. If the function fails, it will return NULL. A NULL return
value could imply that the function simply isn’t exported by the library,
or that an error occurred during the platform call. To determine what
might have happened, the loader will call the
LoaderPlatformLibraryGetProcAddrError
function:
const char *LoaderPlatformLibraryGetProcAddrError(
const std::string &path);
-
path
must be a constant string containing the name of the entry-point that was attempted to be queried during the previous LoaderPlatformLibraryGetProcAddr call.
The returned C-style character string contains any available platform-specific error code that may have occurred.
7.5.2. Environment Variable Usage
Several environment variables are used in the OpenXR loader, especially
on the Desktop (Windows/Linux). However, accessing environment variables
is different based on each operating system, so we have added global
interface functions to use for accessing environment variables. These
are defined in src/common/platform_utils.hpp
NOTE: This is outside of the loader source to allow other items in the source folder to use these utilities.
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. Appendicies
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/Linux, the discovery process typically focuses on searching for Manifest files. While on Android, the process focuses on searching for library files. |
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. |