OculusXR (OVRPlugin) Compatibility Mode - mbucchia/VirtualDesktop-OpenXR GitHub Wiki

The problem

Meta publishes a plugin for Unity and Unreal Engine, internally called OVRPlugin (sometimes called Oculus XR Plugin in their public documentation). This plugin claims to implement OpenXR, an open standard that promotes cross-vendor and cross-plaform. Contrary to what the name implies, OVRPlugin is not just targeted at the legacy OVR API, but is also Meta's middleware for OpenXR.

Unfortunately, OVRPlugin takes intentional precautions to exclude non-Meta platforms. This means that content developed with OVRPlugin will only work with Quest Link, and it will not work with any other runtime.

  • This includes blocking applications from running with Virtual Desktop, SteamLink or ALVR, even on a Meta Quest headset.
  • This includes blocking applications from running on non-Meta headsets such as Pimax, Pico, Varjo, Vive, etc.

There is a more worded description of the damage to the ecosystem here.

We highly recommend that application developers NOT USE OVRPLUGIN (AKA "OCULUS XR" PLUGIN) FOR ANY CONTENT THAT YOU DEVELOP.

Virtual Desktop's commitment

Virtual Desktop takes great care to make your favorite games and applications compatible with our runtimes. To this end, the developers of Virtual Desktop have relentlessly investigated the numerous issues created by OVRPlugin since 2023, and today we are documenting them in great length. With this documentation, other runtime developers (including our direct competitors) stand a chance at delivering a superior compatibility experience. These steps are necessary in light of Meta's complete refusal to resolve the issues in their OVRPlugin directly. Our team also hopes that other runtimes implementing these mitigations will create a positive ecosystem where game studios, whether large or small, can successfully launch their content without the numerous impediments generated by Meta's carelessness with OVRPlugin.

OVRPlugin Compatibility mode

The paragraphs below explain what an OpenXR runtime implementation shall do in order to be able to run OVRPlugin applications. These are not things to do in your application: if you want your application to avoid these issues, do not use OVRPlugin!

Identifying OVRPlugin

In your xrCreateInstance() implementation, we check for the application name passed in the XrInstanceCreateInfo and also check whether the DLL "OVRPlugin.dll" is loaded in the process' memory.

m_isOculusXrPlugin =
    m_applicationName.find("Oculus VR Plugin") == 0 ||
    GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, "OVRPlugin.dll", &ovrPlugin);

Runtime impersonation (Phase 1)

  • Return "Oculus" as the runtime name. You can simply make your runtime always return "Oculus" in xrGetInstanceProperties(). Our implementation only does this after detecting OVRPlugin, as shown here.

  • Advertise the XR_META_headset_id extension. This extension does not do anything useful, and it is just used by Meta to facilitate locking down applications to their platform. You can find an implementation of this extension here.

Runtime impersonation (Phase 2)

In some extreme cases of brokenness, in particular with Unreal Engine, OVRPlugin will use legacy OVR API to detect the Oculus platform.

  • Impersonate LibOVR to make ovr_Detect() pass. This one is tricky and it requires to fake the existence and state of the named event OculusHMDConnected. Because creating the event ourselves can be problematic (permissions, but also importantly the Oculus Services might be running in background and force the event to a specific state), you may use a Detours or hook to redirect the named event when ovr_Detect() (which is often statically linked into the application) calls OpenEventW(). You can find an implementation of this strategy here.

Advertise and implement the XR_KHR_convert_timespec_time extension

While the XR_KHR_convert_timespec_time extension is typically used on UNIX systems, and Windows systems prefer the use of XR_KHR_win32_convert_performance_counter_time, the OVRPlugin requires this extension, or will otherwise submit API calls to the runtime with XrTime=0, leading to XR_ERROR_TIME_INVALID errors.

You can find an implementation of this extension here.

Note that we also recommend to still "handle" the xrLocateViews() and xrLocateSpace() calls with XrTime=0 successfully, ideally gating this behavior behind the OVRPlugin detection to avoid violating the OpenXR specification.

...xrLocateViews()...
  if (viewLocateInfo->displayTime <= 0) {
      // Workaround: the OculusXR plugin is passing a time of 0 during early init and will refuse to submit frames
      // if we error out.
      if (!m_isOculusXrPlugin) {
          return XR_ERROR_TIME_INVALID;
      }
  }

We also recommend to clamp "how far in the past" an XrTime value can be. The OpenXR specification does not specify the behavior for obviously incorrect timestamps. We recommend to clamp to a reasonable value, to avoid "over-predicted poses" when passing timestamps largely in the past or largely in the future.

Do not allow cubemap swapchains unless your runtime supports XR_KHR_composition_layer_cube

The OVRPlugin does not check properly for the XR_KHR_composition_layer_cube, and if OVRPlugin manages to create cubemap swapchains using xrCreateSwapchain() with a faceCount of 6, then OVRPlugin will automatically assumes that it can submit a cubemap layer to xrEndFrame(). This will ultimately result in an XR_ERROR_LAYER_INVALID error.

If your runtime does not support XR_KHR_composition_layer_cube, make sure to return XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED when xrCreateSwapchain() is called with faceCount=6.

Advertise and implement XR_KHR_vulkan_enable

Even when OVRPlugin will use Direct3D, the plugin will request Vulkan support for no reason and not actually use Vulkan. Failure to create an XrInstance with Vulkan or failure to complete the xrGetVulkanInstanceExtensionsKHR() and xrGetVulkanDeviceExtensionsKHR() calls successfully will cause OVRPlugin to terminate initialization.

It is not acceptable to advertise the extension and only implement these two functions, otherwise applications legitimately attempting to use Vulkan will break and not have an opportunity to fallback to another graphics backend.

Incorrect controller poses or hands are facing upward/downward

Unfortunately there is no good solution to these issue at the time. This issue can be "resolved" in two manners:

  • For the hands facing upward and downward, the known workaround is for the runtime to un-advertise XR_EXT_hand_tracking or set supportsHandTracking to false when the application uses xrGetSystemProperties().

  • For the incorrect offset - typically 10cm off the actual controller pose - the known workaround involves correcting the offset per-game, an example of it is shown for Contractors VR here.