Persist Content Across Sessions
Spatial Anchors are offered through the following FB OpenXR extensions:
- XR_FB_spatial_entity
- XR_FB_spatial_entity_storage
- XR_FB_spatial_entity_query
Our Spatial Anchors follow an entity-component system architecture pattern. Each Spatial Anchor is an entity represented by a unique identifier (UUID) that is assigned upon creation and remains constant throughout the life of the Spatial Anchor. Spatial Anchor entities can support a subset of the following components given by the
XrSpaceComponentTypeFB enum:
- Locatable: You can use the xrLocateSpace method to track the 6 DOF pose of the Spatial Anchor.
- Storable: You can use the persistence operations save and erase on the Spatial Anchor.
Any operations involving Spatial Anchors use OpenXR
XrSpace handles to identify the affected anchors. In other words, any operation which involves anchors uses XrSpace handles to identify the affected anchors. Spatial Anchors support the following operations that are mediated through the component entity model:
create,
locate,
save,
erase,
destroy, and
query.
For the complete API reference as part of the OpenXR spec, read:
This section describes setting up an OpenXR project that uses our OpenXR Spatial Anchor extensions.
Prerequisites
- Meta Quest headset OS update 40
- Follow the typical steps for setting up a native OpenXR project.
Add the following to the Android manifest to enable Spatial Anchors with Passthrough:
<uses-permission android:name="com.oculus.permission.USE_ANCHOR_API" />
In your source code, add the following lines to include the extension headers for Spatial Anchors:
#include <openxr/openxr.h>
#include <openxr/fb_spatial_entity.h>
Enumerate the OpenXR extensions:
const char* const requiredExtensionNames[] = {
XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME,
XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME,
XR_FB_SPATIAL_ENTITY_EXTENSION_NAME,
XR_FB_SPATIAL_ENTITY_QUERY_EXTENSION_NAME,
XR_FB_SPATIAL_ENTITY_STORAGE_EXTENSION_NAME};
const uint32_t numRequiredExtensions =
sizeof(requiredExtensionNames) / sizeof(requiredExtensionNames[0]);
Create the OpenXR instance:
XrInstanceCreateInfo instanceCreateInfo = {XR_TYPE_INSTANCE_CREATE_INFO};
instanceCreateInfo.createFlags = 0;
instanceCreateInfo.applicationInfo = appInfo;
instanceCreateInfo.enabledApiLayerCount = 0;
instanceCreateInfo.enabledApiLayerNames = NULL;
instanceCreateInfo.enabledExtensionCount = numRequiredExtensions;
instanceCreateInfo.enabledExtensionNames = requiredExtensionNames;
XrResult initResult;
OXR(initResult = xrCreateInstance(&instanceCreateInfo, &instance));
if (initResult != XR_SUCCESS) {
ALOGE("Failed to create XR instance: %d.", initResult);
exit(1);
}
Create the OpenXR session. This example uses OpenGLES:
XrGraphicsBindingOpenGLESAndroidKHR graphicsBindingAndroidGLES = {XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR};
graphicsBindingAndroidGLES.display = app.Egl.Display;
graphicsBindingAndroidGLES.config = app.Egl.Config;
graphicsBindingAndroidGLES.context = app.Egl.Context;
XrSessionCreateInfo sessionCreateInfo = {XR_TYPE_SESSION_CREATE_INFO};
sessionCreateInfo.next = &graphicsBindingAndroidGLES;
sessionCreateInfo.createFlags = 0;
sessionCreateInfo.systemId = app.SystemId;
OXR(initResult = xrCreateSession(instance, &sessionCreateInfo, &app.Session));
if (initResult != XR_SUCCESS) {
ALOGE("Failed to create XR session: %d.", initResult);
exit(1);
}
To use the functions defined in the extension headers, you must hook up the function pointers to their implementations. The following is an example of how to do this:
OXR(xrGetInstanceProcAddr(
instance,
"xrCreateSpatialAnchorFB",
(PFN_xrVoidFunction*)(&app.FunPtrs.xrCreateSpatialAnchorFB)));
OXR(xrGetInstanceProcAddr(
instance,
"xrEnumerateSupportedComponentsFB",
(PFN_xrVoidFunction*)(&app.FunPtrs.xrEnumerateSupportedComponentsFB)));
OXR(xrGetInstanceProcAddr(
instance,
"xrGetSpaceComponentStatusFB",
(PFN_xrVoidFunction*)(&app.FunPtrs.xrGetSpaceComponentStatusFB)));
OXR(xrGetInstanceProcAddr(
instance,
"xrSetSpaceComponentStatusFB",
(PFN_xrVoidFunction*)(&app.FunPtrs.xrSetSpaceComponentStatusFB)));
OXR(xrGetInstanceProcAddr(
instance,
"xrQuerySpacesFB",
(PFN_xrVoidFunction*)(&app.FunPtrs.xrQuerySpacesFB)));
OXR(xrGetInstanceProcAddr(
instance,
"xrSaveSpaceFB",
(PFN_xrVoidFunction*)(&app.FunPtrs.xrSaveSpaceFB)));
OXR(xrGetInstanceProcAddr(
instance,
"xrEraseSpaceFB",
(PFN_xrVoidFunction*)(&app.FunPtrs.xrEraseSpaceFB)));
In this example, the app is of type ovrApp, which is a struct defined in the XrSpatialAnchor sample app. The FunPtrs field is a struct, which is defined as follows:
struct ovrExtensionFunctionPointers {
PFN_xrEnumerateSpaceSupportedComponentsFB xrEnumerateSpaceSupportedComponentsFB = nullptr;
PFN_xrSetSpaceComponentStatusFB xrSetSpaceComponentStatusFB = nullptr;
PFN_xrGetSpaceComponentStatusFB xrGetSpaceComponentStatusFB = nullptr;
PFN_xrCreateSpatialAnchorFB xrCreateSpatialAnchorFB = nullptr;
PFN_xrQuerySpacesFB xrQuerySpacesFB = nullptr;
PFN_xrSaveSpaceFB xrSaveSpaceFB = nullptr;
PFN_xrEraseSpaceFB xrEraseSpaceFB = nullptr;
};
Asynchronous Operations and Event Handling Potentially long-running operations, such as saving, erasing, and querying Spatial Anchors, are asynchronous and return a request identifier, which is part of the messages returned via events. This asynchronous request identifier requestId is returned immediately at request-time and can be used to identify the request that triggered the event response.
You can handle events using the event loop in the android_main function:
while (androidApp->destroyRequested == 0) {
// Read all pending events.
for (;;) {
int events;
struct android_poll_source* source;
// If the timeout is zero, returns immediately without blocking.
// If the timeout is negative, waits indefinitely until an event appears.
const int timeoutMilliseconds = (app.Resumed == false && app.SessionActive == false && androidApp->destroyRequested == 0) ? -1 : 0;
if (ALooper_pollAll(timeoutMilliseconds, NULL, &events, (void**)&source) < 0) {
break;
}
// Process this event.
if (source != NULL) {
source->process(androidApp, source);
}
}
app.HandleXrEvents();
...
}
The HandleXrEvents function can get quite long. See the ovrApp::HandleXrEvents function defined in the XrSpatialAnchor sample app for a detailed example.
Spatial Anchors Sample App
The Spatial Anchor sample project for OpenXR demonstrates the capabilities of our Spatial Anchor system while providing example code for handling and maintaining Spatial Anchors that you can use in your own project. You can find the project in XRSamples\XRSpatialAnchor.
The persistent Spatial Anchors you create in the sample will persist between sessions and headset reboots.
Press A on the right Touch controller to place a persistent Spatial Anchor that mirrors the pose of the right controller. When you do, the system will place a rectangular object at the origin of the Spatial Anchor aligned to its frame of reference.
Press B on the right Touch controller to delete the most recent persistent Spatial Anchor and remove its object.
Press X on the left Touch controller to retrieve previously persisted Spatial Anchors.