Skip to content

Latest commit

 

History

History
769 lines (654 loc) · 29.9 KB

File metadata and controls

769 lines (654 loc) · 29.9 KB

API Layer Interaction

This section details how the loader interacts with API layers.

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 pname:getInstanceProcAddr) down to the next xrGetInstanceProcAddr implementation in the call chain (provided to the API layer through pname:nextGetInstanceProcAddr).

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

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

optional layer function impl
Figure 1. Optional Function Example
Note
Note

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

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

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.

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
Note

The following table uses 2 acronyms:

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

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

This is done purely to simplify the reading of the table

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

32-bit

32-bit

 <HKLM>\SOFTWARE\Khronos\OpenXR

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

 <HKCU>\SOFTWARE\Khronos\OpenXR

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

64-bit

64-bit

 <HKLM>\SOFTWARE\Khronos\OpenXR

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

 <HKCU>\SOFTWARE\Khronos\OpenXR

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

64-bit

32-bit

 <HKLM>\WOW6432Node\SOFTWARE\Khronos\OpenXR

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

 <HKCU>\WOW6432Node\SOFTWARE\Khronos\OpenXR

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

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

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

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

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

Implicit API Layer

ApiLayers\Implicit

Explicit API Layer

ApiLayers\Explicit

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

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

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

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

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

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

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

  • The data for value must: be a DWORD

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

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

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

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

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

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

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

Linux Manifest Search Paths

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

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

XDG_CONFIG_DIRS

/etc/xdg

Defines common paths for config files

SYSCONFDIR

/usr/local/etc

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

EXTRASYSCONFDIR

/etc

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

XDG_DATA_DIRS

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

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

XDG_DATA_HOME

$HOME/.local/share

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

Note
Note

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

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

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

Implicit Layer

openxr/<major_version>/api_layers/implicit.d

Explicit Layer

openxr/<major_version>/api_layers/explicit.d

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

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

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

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

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

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

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

Note
Important

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

Example 5. Setting XR_API_LAYER_PATH Override

Windows

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

Linux

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

Android API Layer Discovery

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

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

The loader accesses these using the Android AssetManager classes.

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

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

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

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

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

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

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

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

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

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

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

API Layer Manifest File Format

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

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

Example 6. API Layer Manifest Examples

Simple Explicit API Layer Manifest File

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

More Complex Implicit API Layer Manifest File

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

"file_format_version"

Required for Implicit / Explicit

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

N/A

"api_layer"

Required for Implicit / Explicit

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

xrEnumerateApiLayerProperties

"name"

Required for Implicit / Explicit

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

xrEnumerateApiLayerProperties

"library_path"

Required for Implicit / Explicit

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

N/A

"api_version"

Required for Implicit / Explicit

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

xrEnumerateApiLayerProperties

"implementation_version"

Required for Implicit / Explicit

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

xrEnumerateApiLayerProperties

"description"

Required for Implicit / Explicit

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

xrEnumerateApiLayerProperties

"functions"

Optional for Implicit / Explicit

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

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

"instance_extensions"

Optional for Implicit / Explicit

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

xrEnumerateInstanceExtensionProperties

"enable_environment"

Optional for Implicit

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

N/A

"disable_environment"

Required for Implicit

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

N/A

Note
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"

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 fname:xrNegotiateLoaderApiLayerInterface function (or a renamed version of this function identified in the manifest file). See the declaration of fname:xrNegotiateLoaderApiLayerInterface in openxr_loader_negotiation.h.

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.

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.

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 and:

        • an XrInstance has been created but not destroyed, it must: add its own instance extension contents to the list of extensions.

        • no XrInstance has been created, or all have been destroyed, it must: return only its own instance extension contents.

  • 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.

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 fname:xrCreateApiLayerInstance function, which is a special API layer function (declared in openxr_loader_negotiation.h).

During the xrCreateInstance call, the following happens:

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

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

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

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

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

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

        1. XrApiLayerNextInfo.nextGetInstanceProcAddr = loaderXrTermGetInstanceProcAddr

        2. XrApiLayerNextInfo.nextCreateLayerInstance = loaderXrTermCreateLayerInstance

        3. XrApiLayerNextInfo.next = NULL

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

        1. XrApiLayerNextInfo.nextGetInstanceProcAddr = Previous XrApiLayerNextInfo.loaderXrTermGetInstanceProcAddr

        2. XrApiLayerNextInfo.nextCreateLayerInstance = Previous XrApiLayerNextInfo.loaderXrTermCreateLayerInstance

        3. XrApiLayerNextInfo.next = address to previous XrApiLayerNextInfo

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

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

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

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

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

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

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

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

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