This document describes the extra steps required when implementing OpenXR extensions that expose asynchronous functions returning XrFutureEXT in Monado. The overall process is the same as implementing normal extensions (see implementing-extension), but there are a few additional points to keep in mind — mostly around future result types, lifetime management, and IPC support.
Future result types
- If the future's data result is a struct, the data type must be an xrt data-type (typically defined in
xrt_defines.h).
- Register that xrt data-type in
xrt_future_value (in xrt_future_value.h) by adding a new entry to XRT_FUTURE_VALUE_TYPES[_WITH].
Server-side / driver overview
OpenXR async functions come in pairs of xrDoWorkAsync[Suffix] and xrDoWorkComplete[Suffix]. When adding these functions to the server-side/driver (and for IPC) you typically do not need to implement the Complete function — for simple use-cases where you only need to obtain results, the Complete implementation is not required. For more complex scenarios you may still need to implement the Complete function.
Server-side / driver — implementing async callbacks
- Add a callback to the device (for example
xrt_device::create_foo_object_async).
- Name the callback data member with a
_async suffix.
- The last parameter must be an output parameter of type
struct xrt_future **.
- In the implementation that is hooked up to this callback:
- Create/derive a specific future instance (for example via
u_future_create).
xrt_future objects are reference-counted for shared access and thread-safe destruction.
- If the asynchronous work runs on a different thread than the callback caller, or if you need a local reference to the future, increment and decrement the reference count when crossing thread/object boundaries using
xrt_future_reference.
Example callback (driver-side)
{
if (
hmd ==
nullptr || out_future ==
nullptr) {
}
if (xft == nullptr) {
}
assert(th_xft != nullptr);
std::thread t([th_xft]() mutable {
using namespace std::chrono_literals;
for (uint32_t x = 0; x < 100; ++x) {
break;
}
std::this_thread::sleep_for(250ms);
}
if (!is_cancel_requested) {
struct xrt_foo foo_bar = {
.foo = 65.f,
.bar = 19,
};
}
});
t.detach();
*out_future = xft;
}
#define U_LOG_I(...)
Log a message at U_LOGGING_INFO level, conditional on the global log level.
Definition: u_logging.h:404
static void xrt_future_reference(struct xrt_future **dst, struct xrt_future *src)
Update the reference counts on xrt_future(s).
Definition: xrt_future.h:196
enum xrt_result xrt_result_t
Result type used across Monado.
@ XRT_ERROR_ALLOCATION
Could not allocate native image buffer(s).
Definition: xrt_results.h:87
@ XRT_SUCCESS
The operation succeeded.
Definition: xrt_results.h:27
A example HMD device.
Definition: simulated_hmd.c:43
A single HMD or input device.
Definition: xrt_device.h:282
struct xrt_hmd_parts * hmd
Null if this device does not interface with the users head.
Definition: xrt_device.h:294
The (future) result of an asynchronous operation.
Definition: xrt_future.h:38
A future is a concurrency primitive that provides a mechanism to access results of asynchronous opera...
Definition: xrt_future.h:74
static xrt_result_t xrt_future_complete(struct xrt_future *xft, const struct xrt_future_result *ft_result)
Helper function for xrt_future::complete.
Definition: xrt_future.h:296
xrt_result_t(* is_cancel_requested)(const struct xrt_future *xft, bool *out_request_cancel)
Waits on a cancelled future.
Definition: xrt_future.h:161
static xrt_result_t xrt_future_is_cancel_requested(const struct xrt_future *xft, bool *out_request_cancel)
Helper function for xrt_future::is_cancel_requested.
Definition: xrt_future.h:282
Server-side IPC
When adding async support to IPC, keep these conventions in mind:
In proto.json:
xrt_future** output parameters are represented by integer IDs.
- Use
uint32_t for future IDs. Example:
"device_create_foo_object_async": {
"in": [
{"name": "id", "type": "uint32_t"}
],
"out": [
{"name": "out_future_id", "type": "uint32_t"}
]
}
- In the server-side handler (
ipc_server_handler.c):
- Use
get_new_future_id to obtain a new future ID.
- Add the newly created
xrt_future returned from the callback into the client's future list (struct ipc_client_state::xfts) using that ID.
Example server handler
ipc_handle_device_create_foo_object_async(
volatile struct ipc_client_state *ics, uint32_t
id, uint32_t *out_future_id)
{
GET_XDEV_OR_RETURN(ics, id, xdev);
uint32_t new_future_id;
return xret;
}
xret = xrt_device_create_foo_object_async(xdev, &xft);
return xret;
}
assert(xft != NULL);
assert(new_future_id < IPC_MAX_CLIENT_FUTURES);
ics->
xfts[new_future_id] = xft;
*out_future_id = new_future_id;
}
Holds the state for a single client.
Definition: ipc_server.h:89
struct xrt_future * xfts[128]
Ptrs to the futures.
Definition: ipc_server.h:118
Client-side IPC
On the client side, create an IPC-backed future using the ID returned by the server; call ipc_client_future_create to construct a client-side xrt_future wrapper for that ID.
Example client handler
{
if (xdev == NULL || out_future == NULL) {
}
uint32_t future_id;
xrt_result_t r = ipc_call_device_create_foo_object_async(icx->ipc_c, icx->device_id, &future_id);
IPC_ERROR(icx->ipc_c, "Error sending create_foo_object_async!");
return r;
}
struct xrt_future *new_future = ipc_client_future_create(icx->ipc_c, future_id);
if (new_future == NULL) {
}
*out_future = new_future;
}
static struct ipc_client_xdev * ipc_client_xdev(struct xrt_device *xdev)
Convenience helper to go from a xdev to ipc_client_xdev.
Definition: ipc_client_xdev.h:44
@ XRT_ERROR_INVALID_ARGUMENT
Invalid function arguments passed in, e.g.
Definition: xrt_results.h:243
An IPC client proxy for an xrt_device.
Definition: ipc_client_xdev.h:30
State tracker (OpenXR layer)
Implementing the OpenXR async function hooks requires implementing both functions in the async pair. In those functions:
- Use
oxr_future_ext and the operations declared in oxr_object.h. This provides a close 1:1 mapping between OpenXR futures/async functions and Monado's internal future types.
- Note: OpenXR describes
XrFutureEXT as a new primitive that is neither a handle nor an atom. In Monado, however, oxr_future_ext is still represented like other oxr_* types (i.e., it behaves like a handle/atom in the internal mapping).
- The lifetime of
oxr_future_ext is tied to the parent handle passed into oxr_future_create. This means:
- You do not need to store the future by value in parent
oxr_ types.
- You must ensure the future's parent handle is set correctly. Do not parent the future to
oxr_instance or oxr_session unless that truly represents the future's intended lifetime scope.
Example / reference
A full end-to-end example of adding an OpenXR extension with async functions is available here