The open source OpenXR runtime
1// Copyright 2020-2024, Collabora, Ltd.
2// Copyright 2025, NVIDIA CORPORATION.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief Shared functions for IPC client @ref xrt_device.
7 * @author Jakob Bornecrantz <jakob@collabora.com>
8 * @author Jakob Bornecrantz <tbornecrantz@nvidia.com>
9 * @author Korcan Hussein <korcan.hussein@collabora.com>
10 * @ingroup ipc_client
11 */
12
13#include "xrt/xrt_device.h"
14
15#include "os/os_time.h"
16
17#include "math/m_api.h"
18
19#include "util/u_var.h"
20#include "util/u_misc.h"
21#include "util/u_debug.h"
22#include "util/u_device.h"
23
24#include "client/ipc_client.h"
25#include "client/ipc_client_connection.h"
26#include "client/ipc_client_xdev.h"
27#include "ipc_client_generated.h"
28
29
30/*
31 *
32 * Functions from xrt_device.
33 *
34 */
35
36static xrt_result_t
37ipc_client_xdev_update_inputs(struct xrt_device *xdev)
38{
39 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
40
41 xrt_result_t xret = ipc_call_device_update_input(icx->ipc_c, icx->device_id);
42 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_update_input");
43}
44
45static xrt_result_t
46ipc_client_xdev_get_tracked_pose(struct xrt_device *xdev,
47 enum xrt_input_name name,
48 int64_t at_timestamp_ns,
49 struct xrt_space_relation *out_relation)
50{
51 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
52
53 xrt_result_t xret = ipc_call_device_get_tracked_pose( //
54 icx->ipc_c, //
55 icx->device_id, //
56 name, //
57 at_timestamp_ns, //
58 out_relation); //
59 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_get_tracked_pose");
60}
61
62static xrt_result_t
63ipc_client_xdev_get_hand_tracking(struct xrt_device *xdev,
64 enum xrt_input_name name,
65 int64_t at_timestamp_ns,
66 struct xrt_hand_joint_set *out_value,
67 int64_t *out_timestamp_ns)
68{
69 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
70
71 xrt_result_t xret = ipc_call_device_get_hand_tracking( //
72 icx->ipc_c, //
73 icx->device_id, //
74 name, //
75 at_timestamp_ns, //
76 out_value, //
77 out_timestamp_ns); //
78 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_get_hand_tracking");
79}
80
81static xrt_result_t
82ipc_client_xdev_get_face_tracking(struct xrt_device *xdev,
83 enum xrt_input_name facial_expression_type,
84 int64_t at_timestamp_ns,
85 struct xrt_facial_expression_set *out_value)
86{
87 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
88
89 xrt_result_t xret = ipc_call_device_get_face_tracking( //
90 icx->ipc_c, //
91 icx->device_id, //
92 facial_expression_type, //
93 at_timestamp_ns, //
94 out_value); //
95 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_get_face_tracking");
96}
97
98static xrt_result_t
99ipc_client_xdev_get_body_skeleton(struct xrt_device *xdev,
100 enum xrt_input_name body_tracking_type,
101 struct xrt_body_skeleton *out_value)
102{
103 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
104
105 xrt_result_t xret = ipc_call_device_get_body_skeleton( //
106 icx->ipc_c, //
107 icx->device_id, //
108 body_tracking_type, //
109 out_value); //
110 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_get_body_skeleton");
111}
112
113static xrt_result_t
114ipc_client_xdev_get_body_joints(struct xrt_device *xdev,
115 enum xrt_input_name body_tracking_type,
116 int64_t desired_timestamp_ns,
117 struct xrt_body_joint_set *out_value)
118{
119 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
120
121 xrt_result_t xret = ipc_call_device_get_body_joints( //
122 icx->ipc_c, //
123 icx->device_id, //
124 body_tracking_type, //
125 desired_timestamp_ns, //
126 out_value); //
127 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_get_body_joints");
128}
129
130static xrt_result_t
131ipc_client_xdev_reset_body_tracking_calibration_meta(struct xrt_device *xdev)
132{
133 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
134
135 xrt_result_t xret = ipc_call_device_reset_body_tracking_calibration_meta( //
136 icx->ipc_c, //
137 icx->device_id); //
138 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_reset_body_tracking_calibration_meta");
139}
140
141static xrt_result_t
142ipc_client_xdev_set_body_tracking_calibration_override_meta(struct xrt_device *xdev, float new_body_height)
143{
144 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
145
146 xrt_result_t xret = ipc_call_device_set_body_tracking_calibration_override_meta( //
147 icx->ipc_c, //
148 icx->device_id, //
149 new_body_height); //
150 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_set_body_tracking_calibration_override_meta");
151}
152
153static xrt_result_t
154ipc_client_xdev_get_presence(struct xrt_device *xdev, bool *presence)
155{
156 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
157
158 xrt_result_t xret = ipc_call_device_get_presence( //
159 icx->ipc_c, //
160 icx->device_id, //
161 presence); //
162 IPC_CHK_ALWAYS_RET(icx->ipc_c, xret, "ipc_call_device_get_presence");
163}
164
165static xrt_result_t
166ipc_client_xdev_set_output(struct xrt_device *xdev, enum xrt_output_name name, const struct xrt_output_value *value)
167{
168 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
169 struct ipc_connection *ipc_c = icx->ipc_c;
170
171 xrt_result_t xret;
172 if (value->type == XRT_OUTPUT_VALUE_TYPE_PCM_VIBRATION) {
173 uint32_t samples_sent = MIN(value->pcm_vibration.sample_rate, 4000);
174
175 struct ipc_pcm_haptic_buffer samples = {
176 .append = value->pcm_vibration.append,
177 .num_samples = samples_sent,
178 .sample_rate = value->pcm_vibration.sample_rate,
179 };
180
181 ipc_client_connection_lock(ipc_c);
182
183 xret = ipc_send_device_set_haptic_output_locked(ipc_c, icx->device_id, name, &samples);
184 IPC_CHK_WITH_GOTO(ipc_c, xret, "ipc_send_device_set_haptic_output_locked", send_haptic_output_end);
185
186 xrt_result_t alloc_xret;
187 xret = ipc_receive(&ipc_c->imc, &alloc_xret, sizeof alloc_xret);
188 if (xret != XRT_SUCCESS || alloc_xret != XRT_SUCCESS) {
189 goto send_haptic_output_end;
190 }
191
192 xret = ipc_send(&ipc_c->imc, value->pcm_vibration.buffer, sizeof(float) * samples_sent);
193 if (xret != XRT_SUCCESS) {
194 goto send_haptic_output_end;
195 }
196
197 xret = ipc_receive(&ipc_c->imc, value->pcm_vibration.samples_consumed,
198 sizeof(*value->pcm_vibration.samples_consumed));
199 if (xret != XRT_SUCCESS) {
200 goto send_haptic_output_end;
201 }
202
203 send_haptic_output_end:
204 ipc_client_connection_unlock(ipc_c);
205 } else {
206 xret = ipc_call_device_set_output(ipc_c, icx->device_id, name, value);
207 IPC_CHK_ONLY_PRINT(ipc_c, xret, "ipc_call_device_set_output");
208 }
209
210 return xret;
211}
212
213xrt_result_t
214ipc_client_xdev_get_output_limits(struct xrt_device *xdev, struct xrt_output_limits *limits)
215{
216 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
217
218 xrt_result_t xret = ipc_call_device_get_output_limits(icx->ipc_c, icx->device_id, limits);
219 IPC_CHK_ONLY_PRINT(icx->ipc_c, xret, "ipc_call_device_get_output_limits");
220
221 return xret;
222}
223
224
225/*
226 *
227 * Plane detection functions.
228 *
229 */
230
231static xrt_result_t
232ipc_client_xdev_begin_plane_detection_ext(struct xrt_device *xdev,
233 const struct xrt_plane_detector_begin_info_ext *begin_info,
234 uint64_t plane_detection_id,
235 uint64_t *out_plane_detection_id)
236{
237 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
238
239 icx->ipc_c->ism->plane_begin_info_ext = *begin_info;
240
241 xrt_result_t r = ipc_call_device_begin_plane_detection_ext(icx->ipc_c, icx->device_id, plane_detection_id,
242 out_plane_detection_id);
243 if (r != XRT_SUCCESS) {
244 IPC_ERROR(icx->ipc_c, "Error sending hmd_begin_plane_detection_ext!");
245 return r;
246 }
247
248 return XRT_SUCCESS;
249}
250
251static xrt_result_t
252ipc_client_xdev_destroy_plane_detection_ext(struct xrt_device *xdev, uint64_t plane_detection_id)
253{
254 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
255
256 xrt_result_t r = ipc_call_device_destroy_plane_detection_ext(icx->ipc_c, icx->device_id, plane_detection_id);
257 if (r != XRT_SUCCESS) {
258 IPC_ERROR(icx->ipc_c, "Error sending destroy_plane_detection_ext!");
259 return r;
260 }
261
262 return XRT_SUCCESS;
263}
264
265/*!
266 * Helper function for @ref xrt_device::get_plane_detection_state.
267 *
268 * @public @memberof xrt_device
269 */
270static xrt_result_t
271ipc_client_xdev_get_plane_detection_state_ext(struct xrt_device *xdev,
272 uint64_t plane_detection_id,
273 enum xrt_plane_detector_state_ext *out_state)
274{
275 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
276
277 xrt_result_t r =
278 ipc_call_device_get_plane_detection_state_ext(icx->ipc_c, icx->device_id, plane_detection_id, out_state);
279 if (r != XRT_SUCCESS) {
280 IPC_ERROR(icx->ipc_c, "Error sending get_plane_detection_state_ext!");
281 return r;
282 }
283
284 return XRT_SUCCESS;
285}
286
287/*!
288 * Helper function for @ref xrt_device::get_plane_detections.
289 *
290 * @public @memberof xrt_device
291 */
292static xrt_result_t
293ipc_client_xdev_get_plane_detections_ext(struct xrt_device *xdev,
294 uint64_t plane_detection_id,
295 struct xrt_plane_detections_ext *out_detections)
296{
297 struct ipc_client_xdev *icx = ipc_client_xdev(xdev);
298 struct ipc_connection *ipc_c = icx->ipc_c;
299
300 ipc_client_connection_lock(ipc_c);
301
302 xrt_result_t xret = ipc_send_device_get_plane_detections_ext_locked(ipc_c, icx->device_id, plane_detection_id);
303 IPC_CHK_WITH_GOTO(icx->ipc_c, xret, "ipc_send_device_get_plane_detections_ext_locked", out);
304
305 // in this case, size == count
306 uint32_t location_size = 0;
307 uint32_t polygon_size = 0;
308 uint32_t vertex_size = 0;
309
310 xret = ipc_receive_device_get_plane_detections_ext_locked(ipc_c, &location_size, &polygon_size, &vertex_size);
311 IPC_CHK_WITH_GOTO(icx->ipc_c, xret, "ipc_receive_device_get_plane_detections_ext_locked", out);
312
313
314 // With no locations, the service won't send anything else
315 if (location_size < 1) {
316 out_detections->location_count = 0;
317 goto out;
318 }
319
320 // realloc arrays in out_detections if necessary, then receive contents
321
322 out_detections->location_count = location_size;
323 if (out_detections->location_size < location_size) {
324 U_ARRAY_REALLOC_OR_FREE(out_detections->locations, struct xrt_plane_detector_location_ext,
325 location_size);
326 U_ARRAY_REALLOC_OR_FREE(out_detections->polygon_info_start_index, uint32_t, location_size);
327 out_detections->location_size = location_size;
328 }
329
330 if (out_detections->polygon_info_size < polygon_size) {
331 U_ARRAY_REALLOC_OR_FREE(out_detections->polygon_infos, struct xrt_plane_polygon_info_ext, polygon_size);
332 out_detections->polygon_info_size = polygon_size;
333 }
334
335 if (out_detections->vertex_size < vertex_size) {
336 U_ARRAY_REALLOC_OR_FREE(out_detections->vertices, struct xrt_vec2, vertex_size);
337 out_detections->vertex_size = vertex_size;
338 }
339
340 if ((location_size > 0 &&
341 (out_detections->locations == NULL || out_detections->polygon_info_start_index == NULL)) ||
342 (polygon_size > 0 && out_detections->polygon_infos == NULL) ||
343 (vertex_size > 0 && out_detections->vertices == NULL)) {
344 IPC_ERROR(icx->ipc_c, "Error allocating memory for plane detections!");
345 out_detections->location_size = 0;
346 out_detections->polygon_info_size = 0;
347 out_detections->vertex_size = 0;
348 xret = XRT_ERROR_IPC_FAILURE;
349 goto out;
350 }
351
352 if (location_size > 0) {
353 // receive location_count * locations
354 xret = ipc_receive(&ipc_c->imc, out_detections->locations,
355 sizeof(struct xrt_plane_detector_location_ext) * location_size);
356 IPC_CHK_WITH_GOTO(icx->ipc_c, xret, "ipc_receive(1)", out);
357
358 // receive location_count * polygon_info_start_index
359 xret = ipc_receive(&ipc_c->imc, out_detections->polygon_info_start_index,
360 sizeof(uint32_t) * location_size);
361 IPC_CHK_WITH_GOTO(icx->ipc_c, xret, "ipc_receive(2)", out);
362 }
363
364
365 if (polygon_size > 0) {
366 // receive polygon_count * polygon_infos
367 xret = ipc_receive(&ipc_c->imc, out_detections->polygon_infos,
368 sizeof(struct xrt_plane_polygon_info_ext) * polygon_size);
369 IPC_CHK_WITH_GOTO(icx->ipc_c, xret, "ipc_receive(3)", out);
370 }
371
372 if (vertex_size > 0) {
373 // receive vertex_count * vertices
374 xret = ipc_receive(&ipc_c->imc, out_detections->vertices, sizeof(struct xrt_vec2) * vertex_size);
375 IPC_CHK_WITH_GOTO(icx->ipc_c, xret, "ipc_receive(4)", out);
376 }
377
378out:
379 ipc_client_connection_unlock(ipc_c);
380 return xret;
381}
382
383
384/*
385 *
386 * 'Exported' functions.
387 *
388 */
389
390void
391ipc_client_xdev_init(struct ipc_client_xdev *icx,
392 struct ipc_connection *ipc_c,
393 struct xrt_tracking_origin *xtrack,
394 uint32_t device_id,
395 u_device_destroy_function_t destroy_fn)
396{
397 // Helpers.
398 struct ipc_shared_memory *ism = ipc_c->ism;
399 struct ipc_shared_device *isdev = &ism->isdevs[device_id];
400
401 // Important fields.
402 icx->ipc_c = ipc_c;
403 icx->device_id = device_id;
404
405 /*
406 * Fill in not implemented or noop versions first,
407 * destroy gets filled in by either device or HMD.
408 */
409 u_device_populate_function_pointers(&icx->base, ipc_client_xdev_get_tracked_pose, destroy_fn);
410
411 // Shared implemented functions.
412 icx->base.update_inputs = ipc_client_xdev_update_inputs;
413 icx->base.get_hand_tracking = ipc_client_xdev_get_hand_tracking;
414 icx->base.get_face_tracking = ipc_client_xdev_get_face_tracking;
415 icx->base.get_body_skeleton = ipc_client_xdev_get_body_skeleton;
416 icx->base.get_body_joints = ipc_client_xdev_get_body_joints;
417 icx->base.reset_body_tracking_calibration_meta = ipc_client_xdev_reset_body_tracking_calibration_meta;
418 icx->base.set_body_tracking_calibration_override_meta =
419 ipc_client_xdev_set_body_tracking_calibration_override_meta;
420 icx->base.get_presence = ipc_client_xdev_get_presence;
421 icx->base.set_output = ipc_client_xdev_set_output;
422 icx->base.get_output_limits = ipc_client_xdev_get_output_limits;
423
424 // Plane detection EXT.
425 icx->base.begin_plane_detection_ext = ipc_client_xdev_begin_plane_detection_ext;
426 icx->base.destroy_plane_detection_ext = ipc_client_xdev_destroy_plane_detection_ext;
427 icx->base.get_plane_detection_state_ext = ipc_client_xdev_get_plane_detection_state_ext;
428 icx->base.get_plane_detections_ext = ipc_client_xdev_get_plane_detections_ext;
429
430 // Copying the information from the isdev.
431 icx->base.device_type = isdev->device_type;
432 icx->base.supported = isdev->supported;
433 icx->base.tracking_origin = xtrack;
434 icx->base.name = isdev->name;
435
436 // Print name.
437 snprintf(icx->base.str, XRT_DEVICE_NAME_LEN, "%s", isdev->str);
438 snprintf(icx->base.serial, XRT_DEVICE_NAME_LEN, "%s", isdev->serial);
439
440 // Setup inputs, by pointing directly to the shared memory.
441 assert(isdev->input_count > 0);
442 icx->base.inputs = &ism->inputs[isdev->first_input_index];
443 icx->base.input_count = isdev->input_count;
444
445 // Setup outputs, if any point directly into the shared memory.
446 icx->base.output_count = isdev->output_count;
447 if (isdev->output_count > 0) {
448 icx->base.outputs = &ism->outputs[isdev->first_output_index];
449 } else {
450 icx->base.outputs = NULL;
451 }
452
453 // Setup binding profiles.
454 icx->base.binding_profile_count = isdev->binding_profile_count;
455 if (isdev->binding_profile_count > 0) {
456 icx->base.binding_profiles =
457 U_TYPED_ARRAY_CALLOC(struct xrt_binding_profile, isdev->binding_profile_count);
458 }
459
460 for (size_t i = 0; i < isdev->binding_profile_count; i++) {
461 struct xrt_binding_profile *xbp = &icx->base.binding_profiles[i];
462 struct ipc_shared_binding_profile *isbp =
463 &ism->binding_profiles[isdev->first_binding_profile_index + i];
464
465 xbp->name = isbp->name;
466 if (isbp->input_count > 0) {
467 xbp->inputs = &ism->input_pairs[isbp->first_input_index];
468 xbp->input_count = isbp->input_count;
469 }
470 if (isbp->output_count > 0) {
471 xbp->outputs = &ism->output_pairs[isbp->first_output_index];
472 xbp->output_count = isbp->output_count;
473 }
474 }
475}
476
477void
478ipc_client_xdev_fini(struct ipc_client_xdev *icx)
479{
480 // We do not own these, so don't free them.
481 icx->base.inputs = NULL;
482 icx->base.outputs = NULL;
483
484 // We allocated the bindings profiles.
485 if (icx->base.binding_profiles != NULL) {
486 free(icx->base.binding_profiles);
487 icx->base.binding_profiles = NULL;
488 }
489}