···1+# OpenXR async functions (XrFutureEXT)
2+3+<!--
4+Copyright 2025, Collabora, Ltd. and the Monado contributors
5+SPDX-License-Identifier: BSL-1.0
6+-->
7+8+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](./implementing-extension.md)), but there are a few additional points to keep in mind — mostly around future result types, lifetime management, and IPC support.
9+10+---
11+12+## Future result types
13+14+1. If the future's data result is a struct, the data type must be an **xrt** data-type (typically defined in `xrt_defines.h`).
15+2. 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]`.
16+17+---
18+19+## Server-side / driver overview
20+21+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.
22+23+---
24+25+## Server-side / driver — implementing async callbacks
26+27+1. Add a callback to the device (for example `xrt_device::create_foo_object_async`).
28+29+ - Name the callback data member with a `_async` suffix.
30+ - The last parameter **must** be an output parameter of type `struct xrt_future **`.
31+32+2. In the implementation that is hooked up to this callback:
33+34+ - Create/derive a specific future instance (for example via `u_future_create`).
35+ - `xrt_future` objects are reference-counted for shared access and thread-safe destruction.
36+ - 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`.
37+38+### Example callback (driver-side)
39+40+```cpp
41+//! simulated driver
42+static xrt_result_t
43+simulated_create_foo_object_async(struct xrt_device *xdev, struct xrt_future **out_future)
44+{
45+ struct simulated_hmd *hmd = simulated_hmd(xdev);
46+ if (hmd == nullptr || out_future == nullptr) {
47+ return XRT_ERROR_ALLOCATION;
48+ }
49+50+ struct xrt_future *xft = u_future_create();
51+ if (xft == nullptr) {
52+ return XRT_ERROR_ALLOCATION;
53+ }
54+55+ struct xrt_future *th_xft = NULL;
56+ xrt_future_reference(&xft, xft);
57+ assert(th_xft != nullptr);
58+59+ std::thread t([th_xft]() mutable {
60+ using namespace std::chrono_literals;
61+62+ bool is_cancel_requested = false;
63+ for (uint32_t x = 0; x < 100; ++x) {
64+ if (xrt_future_is_cancel_requested(th_xft, &is_cancel_requested) != XRT_SUCCESS ||
65+ is_cancel_requested) {
66+ U_LOG_I("cancelling work...");
67+ break;
68+ }
69+ U_LOG_I("doing work...");
70+ std::this_thread::sleep_for(250ms);
71+ }
72+73+ struct xrt_future_result rest;
74+ if (!is_cancel_requested) {
75+ struct xrt_foo foo_bar = {
76+ .foo = 65.f,
77+ .bar = 19,
78+ };
79+ rest = XRT_FUTURE_RESULT(foo_bar, XRT_SUCCESS);
80+ }
81+ xrt_future_complete(th_xft, &rest);
82+ xrt_future_reference(&th_xft, nullptr);
83+ U_LOG_I("work finished...");
84+ });
85+ t.detach();
86+87+ *out_future = xft;
88+89+ return XRT_SUCCESS;
90+}
91+```
92+93+---
94+95+## Server-side IPC
96+97+When adding async support to IPC, keep these conventions in mind:
98+99+1. In `proto.json`:
100+101+ - `xrt_future**` output parameters are represented by integer IDs.
102+ - Use `uint32_t` for future IDs. Example:
103+104+ ```json
105+ "device_create_foo_object_async": {
106+ "in": [
107+ {"name": "id", "type": "uint32_t"}
108+ ],
109+ "out": [
110+ {"name": "out_future_id", "type": "uint32_t"}
111+ ]
112+ }
113+ ```
114+115+2. In the server-side handler (`ipc_server_handler.c`):
116+117+ - Use `get_new_future_id` to obtain a new future ID.
118+ - Add the newly created `xrt_future` returned from the callback into the client's future list (`struct ipc_client_state::xfts`) using that ID.
119+120+### Example server handler
121+122+```c
123+xrt_result_t
124+ipc_handle_device_create_foo_object_async(volatile struct ipc_client_state *ics, uint32_t id, uint32_t *out_future_id)
125+{
126+ struct xrt_device *xdev = NULL;
127+ GET_XDEV_OR_RETURN(ics, id, xdev);
128+129+ uint32_t new_future_id;
130+ xrt_result_t xret = get_new_future_id(ics, &new_future_id);
131+ if (xret != XRT_SUCCESS) {
132+ return xret;
133+ }
134+135+ struct xrt_future *xft = NULL;
136+ xret = xrt_device_create_foo_object_async(xdev, &xft);
137+ if (xret != XRT_SUCCESS) {
138+ return xret;
139+ }
140+141+ assert(xft != NULL);
142+ assert(new_future_id < IPC_MAX_CLIENT_FUTURES);
143+ ics->xfts[new_future_id] = xft;
144+145+ *out_future_id = new_future_id;
146+ return XRT_SUCCESS;
147+}
148+```
149+150+---
151+152+## Client-side IPC
153+154+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.
155+156+### Example client handler
157+158+```c
159+static xrt_result_t
160+ipc_client_xdev_create_foo_object_async(struct xrt_device *xdev, struct xrt_future **out_future)
161+{
162+ if (xdev == NULL || out_future == NULL) {
163+ return XRT_ERROR_INVALID_ARGUMENT;
164+ }
165+ struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
166+167+ uint32_t future_id;
168+ xrt_result_t r = ipc_call_device_create_foo_object_async(icx->ipc_c, icx->device_id, &future_id);
169+ if (r != XRT_SUCCESS) {
170+ IPC_ERROR(icx->ipc_c, "Error sending create_foo_object_async!");
171+ return r;
172+ }
173+174+ struct xrt_future *new_future = ipc_client_future_create(icx->ipc_c, future_id);
175+ if (new_future == NULL) {
176+ return XRT_ERROR_ALLOCATION;
177+ }
178+179+ *out_future = new_future;
180+ return XRT_SUCCESS;
181+}
182+```
183+184+---
185+186+## State tracker (OpenXR layer)
187+188+Implementing the OpenXR async function hooks requires implementing both functions in the async pair. In those functions:
189+190+- 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.
191+- 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).
192+- The lifetime of `oxr_future_ext` is tied to the parent handle passed into `oxr_future_create`. This means:
193+ - You do not need to store the future by value in parent `oxr_` types.
194+ - 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.
195+196+---
197+198+## Example / reference
199+200+A full end-to-end example of adding an OpenXR extension with async functions is available [here](https://gitlab.freedesktop.org/korejan/monado/-/commits/korejan/ext_future_example)