···11+# OpenXR async functions (XrFutureEXT)
22+33+<!--
44+Copyright 2025, Collabora, Ltd. and the Monado contributors
55+SPDX-License-Identifier: BSL-1.0
66+-->
77+88+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.
99+1010+---
1111+1212+## Future result types
1313+1414+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`).
1515+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]`.
1616+1717+---
1818+1919+## Server-side / driver overview
2020+2121+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.
2222+2323+---
2424+2525+## Server-side / driver — implementing async callbacks
2626+2727+1. Add a callback to the device (for example `xrt_device::create_foo_object_async`).
2828+2929+ - Name the callback data member with a `_async` suffix.
3030+ - The last parameter **must** be an output parameter of type `struct xrt_future **`.
3131+3232+2. In the implementation that is hooked up to this callback:
3333+3434+ - Create/derive a specific future instance (for example via `u_future_create`).
3535+ - `xrt_future` objects are reference-counted for shared access and thread-safe destruction.
3636+ - 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`.
3737+3838+### Example callback (driver-side)
3939+4040+```cpp
4141+//! simulated driver
4242+static xrt_result_t
4343+simulated_create_foo_object_async(struct xrt_device *xdev, struct xrt_future **out_future)
4444+{
4545+ struct simulated_hmd *hmd = simulated_hmd(xdev);
4646+ if (hmd == nullptr || out_future == nullptr) {
4747+ return XRT_ERROR_ALLOCATION;
4848+ }
4949+5050+ struct xrt_future *xft = u_future_create();
5151+ if (xft == nullptr) {
5252+ return XRT_ERROR_ALLOCATION;
5353+ }
5454+5555+ struct xrt_future *th_xft = NULL;
5656+ xrt_future_reference(&xft, xft);
5757+ assert(th_xft != nullptr);
5858+5959+ std::thread t([th_xft]() mutable {
6060+ using namespace std::chrono_literals;
6161+6262+ bool is_cancel_requested = false;
6363+ for (uint32_t x = 0; x < 100; ++x) {
6464+ if (xrt_future_is_cancel_requested(th_xft, &is_cancel_requested) != XRT_SUCCESS ||
6565+ is_cancel_requested) {
6666+ U_LOG_I("cancelling work...");
6767+ break;
6868+ }
6969+ U_LOG_I("doing work...");
7070+ std::this_thread::sleep_for(250ms);
7171+ }
7272+7373+ struct xrt_future_result rest;
7474+ if (!is_cancel_requested) {
7575+ struct xrt_foo foo_bar = {
7676+ .foo = 65.f,
7777+ .bar = 19,
7878+ };
7979+ rest = XRT_FUTURE_RESULT(foo_bar, XRT_SUCCESS);
8080+ }
8181+ xrt_future_complete(th_xft, &rest);
8282+ xrt_future_reference(&th_xft, nullptr);
8383+ U_LOG_I("work finished...");
8484+ });
8585+ t.detach();
8686+8787+ *out_future = xft;
8888+8989+ return XRT_SUCCESS;
9090+}
9191+```
9292+9393+---
9494+9595+## Server-side IPC
9696+9797+When adding async support to IPC, keep these conventions in mind:
9898+9999+1. In `proto.json`:
100100+101101+ - `xrt_future**` output parameters are represented by integer IDs.
102102+ - Use `uint32_t` for future IDs. Example:
103103+104104+ ```json
105105+ "device_create_foo_object_async": {
106106+ "in": [
107107+ {"name": "id", "type": "uint32_t"}
108108+ ],
109109+ "out": [
110110+ {"name": "out_future_id", "type": "uint32_t"}
111111+ ]
112112+ }
113113+ ```
114114+115115+2. In the server-side handler (`ipc_server_handler.c`):
116116+117117+ - Use `get_new_future_id` to obtain a new future ID.
118118+ - Add the newly created `xrt_future` returned from the callback into the client's future list (`struct ipc_client_state::xfts`) using that ID.
119119+120120+### Example server handler
121121+122122+```c
123123+xrt_result_t
124124+ipc_handle_device_create_foo_object_async(volatile struct ipc_client_state *ics, uint32_t id, uint32_t *out_future_id)
125125+{
126126+ struct xrt_device *xdev = NULL;
127127+ GET_XDEV_OR_RETURN(ics, id, xdev);
128128+129129+ uint32_t new_future_id;
130130+ xrt_result_t xret = get_new_future_id(ics, &new_future_id);
131131+ if (xret != XRT_SUCCESS) {
132132+ return xret;
133133+ }
134134+135135+ struct xrt_future *xft = NULL;
136136+ xret = xrt_device_create_foo_object_async(xdev, &xft);
137137+ if (xret != XRT_SUCCESS) {
138138+ return xret;
139139+ }
140140+141141+ assert(xft != NULL);
142142+ assert(new_future_id < IPC_MAX_CLIENT_FUTURES);
143143+ ics->xfts[new_future_id] = xft;
144144+145145+ *out_future_id = new_future_id;
146146+ return XRT_SUCCESS;
147147+}
148148+```
149149+150150+---
151151+152152+## Client-side IPC
153153+154154+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.
155155+156156+### Example client handler
157157+158158+```c
159159+static xrt_result_t
160160+ipc_client_xdev_create_foo_object_async(struct xrt_device *xdev, struct xrt_future **out_future)
161161+{
162162+ if (xdev == NULL || out_future == NULL) {
163163+ return XRT_ERROR_INVALID_ARGUMENT;
164164+ }
165165+ struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
166166+167167+ uint32_t future_id;
168168+ xrt_result_t r = ipc_call_device_create_foo_object_async(icx->ipc_c, icx->device_id, &future_id);
169169+ if (r != XRT_SUCCESS) {
170170+ IPC_ERROR(icx->ipc_c, "Error sending create_foo_object_async!");
171171+ return r;
172172+ }
173173+174174+ struct xrt_future *new_future = ipc_client_future_create(icx->ipc_c, future_id);
175175+ if (new_future == NULL) {
176176+ return XRT_ERROR_ALLOCATION;
177177+ }
178178+179179+ *out_future = new_future;
180180+ return XRT_SUCCESS;
181181+}
182182+```
183183+184184+---
185185+186186+## State tracker (OpenXR layer)
187187+188188+Implementing the OpenXR async function hooks requires implementing both functions in the async pair. In those functions:
189189+190190+- 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.
191191+- 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).
192192+- The lifetime of `oxr_future_ext` is tied to the parent handle passed into `oxr_future_create`. This means:
193193+ - You do not need to store the future by value in parent `oxr_` types.
194194+ - 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.
195195+196196+---
197197+198198+## Example / reference
199199+200200+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)