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 IPC Client HMD device.
7 * @author Jakob Bornecrantz <jakob@collabora.com>
8 * @author Korcan Hussein <korcan.hussein@collabora.com>
9 * @ingroup ipc_client
10 */
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#include "util/u_distortion_mesh.h"
24
25#include "client/ipc_client.h"
26#include "client/ipc_client_xdev.h"
27#include "client/ipc_client_connection.h"
28#include "ipc_client_generated.h"
29
30#include <math.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <assert.h>
35
36
37/*
38 *
39 * Structs and defines.
40 *
41 */
42
43/*!
44 * An IPC client proxy for an HMD @ref xrt_device and @ref ipc_client_xdev.
45 * Using a typedef reduce impact of refactor change.
46 *
47 * @implements ipc_client_xdev
48 * @ingroup ipc_client
49 */
50typedef struct ipc_client_xdev ipc_client_hmd_t;
51
52
53/*
54 *
55 * Helpers.
56 *
57 */
58
59static inline ipc_client_hmd_t *
60ipc_client_hmd(struct xrt_device *xdev)
61{
62 return (ipc_client_hmd_t *)xdev;
63}
64
65static xrt_result_t
66call_get_view_poses_raw(ipc_client_hmd_t *ich,
67 const struct xrt_vec3 *default_eye_relation,
68 int64_t at_timestamp_ns,
69 enum xrt_view_type view_type,
70 uint32_t view_count,
71 struct xrt_space_relation *out_head_relation,
72 struct xrt_fov *out_fovs,
73 struct xrt_pose *out_poses)
74{
75 struct ipc_connection *ipc_c = ich->ipc_c;
76 xrt_result_t xret;
77
78 ipc_client_connection_lock(ipc_c);
79
80 // Using the raw send helper is the only one that is required.
81 xret = ipc_send_device_get_view_poses_locked( //
82 ipc_c, //
83 ich->device_id, //
84 default_eye_relation, //
85 at_timestamp_ns, //
86 view_type, //
87 view_count); //
88 IPC_CHK_WITH_GOTO(ich->ipc_c, xret, "ipc_send_device_get_view_poses_locked", out);
89
90 // This is the data we get back in the provided reply.
91 uint32_t returned_view_count = 0;
92 struct xrt_space_relation head_relation = XRT_SPACE_RELATION_ZERO;
93
94 // Get the reply, use the raw function helper.
95 xret = ipc_receive_device_get_view_poses_locked( //
96 ipc_c, //
97 &head_relation, //
98 &returned_view_count); //
99 IPC_CHK_WITH_GOTO(ich->ipc_c, xret, "ipc_receive_device_get_view_poses_locked", out);
100
101 if (view_count != returned_view_count) {
102 IPC_ERROR(ich->ipc_c, "Wrong view counts (sent: %u != got: %u)", view_count, returned_view_count);
103 assert(false);
104 }
105
106 // We can read directly to the output variables.
107 xret = ipc_receive(&ipc_c->imc, out_fovs, sizeof(struct xrt_fov) * view_count);
108 IPC_CHK_WITH_GOTO(ich->ipc_c, xret, "ipc_receive(1)", out);
109
110 // We can read directly to the output variables.
111 xret = ipc_receive(&ipc_c->imc, out_poses, sizeof(struct xrt_pose) * view_count);
112 IPC_CHK_WITH_GOTO(ich->ipc_c, xret, "ipc_receive(2)", out);
113
114 /*
115 * Finally set the head_relation that we got in the reply, mostly to
116 * demonstrate that you can use the reply struct in such a way.
117 */
118 *out_head_relation = head_relation;
119
120out:
121 ipc_client_connection_unlock(ipc_c);
122 return xret;
123}
124
125
126/*
127 *
128 * Member functions
129 *
130 */
131
132static xrt_result_t
133ipc_client_hmd_get_view_poses(struct xrt_device *xdev,
134 const struct xrt_vec3 *default_eye_relation,
135 int64_t at_timestamp_ns,
136 enum xrt_view_type view_type,
137 uint32_t view_count,
138 struct xrt_space_relation *out_head_relation,
139 struct xrt_fov *out_fovs,
140 struct xrt_pose *out_poses)
141{
142 ipc_client_hmd_t *ich = ipc_client_hmd(xdev);
143 xrt_result_t xret;
144
145 struct ipc_info_get_view_poses_2 info = {0};
146
147 if (view_count == 2) {
148 // Fast path.
149 xret = ipc_call_device_get_view_poses_2( //
150 ich->ipc_c, //
151 ich->device_id, //
152 default_eye_relation, //
153 at_timestamp_ns, //
154 view_type, //
155 view_count, //
156 &info); //
157 IPC_CHK_AND_RET(ich->ipc_c, xret, "ipc_call_device_get_view_poses_2");
158
159 *out_head_relation = info.head_relation;
160 for (int i = 0; i < 2; i++) {
161 out_fovs[i] = info.fovs[i];
162 out_poses[i] = info.poses[i];
163 }
164
165 } else if (view_count <= IPC_MAX_RAW_VIEWS) {
166 // Artificial limit.
167 xret = call_get_view_poses_raw( //
168 ich, //
169 default_eye_relation, //
170 at_timestamp_ns, //
171 view_type, //
172 view_count, //
173 out_head_relation, //
174 out_fovs, //
175 out_poses); //
176 } else {
177 IPC_ERROR(ich->ipc_c, "Cannot handle %u view_count, %u or less supported.", view_count,
178 (uint32_t)IPC_MAX_RAW_VIEWS);
179 assert(false && !"Too large view_count!");
180 }
181
182 return xret;
183}
184
185static xrt_result_t
186ipc_client_hmd_compute_distortion(
187 struct xrt_device *xdev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result)
188{
189 ipc_client_hmd_t *ich = ipc_client_hmd(xdev);
190 xrt_result_t xret;
191
192 xret = ipc_call_device_compute_distortion( //
193 ich->ipc_c, //
194 ich->device_id, //
195 view, //
196 u, //
197 v, //
198 out_result); //
199
200 IPC_CHK_ALWAYS_RET(ich->ipc_c, xret, "ipc_call_device_compute_distortion");
201}
202
203static bool
204ipc_client_hmd_is_form_factor_available(struct xrt_device *xdev, enum xrt_form_factor form_factor)
205{
206 ipc_client_hmd_t *ich = ipc_client_hmd(xdev);
207 xrt_result_t xret;
208
209 bool available = false;
210 xret = ipc_call_device_is_form_factor_available( //
211 ich->ipc_c, //
212 ich->device_id, //
213 form_factor, //
214 &available); //
215 IPC_CHK_ONLY_PRINT(ich->ipc_c, xret, "ipc_call_device_is_form_factor_available");
216
217 return available;
218}
219
220static xrt_result_t
221ipc_client_hmd_get_visibility_mask(struct xrt_device *xdev,
222 enum xrt_visibility_mask_type type,
223 uint32_t view_index,
224 struct xrt_visibility_mask **out_mask)
225{
226 ipc_client_hmd_t *ich = ipc_client_hmd(xdev);
227 struct ipc_connection *ipc_c = ich->ipc_c;
228 struct xrt_visibility_mask *mask = NULL;
229 xrt_result_t xret;
230
231 ipc_client_connection_lock(ipc_c);
232
233 xret = ipc_send_device_get_visibility_mask_locked(ipc_c, ich->device_id, type, view_index);
234 IPC_CHK_WITH_GOTO(ipc_c, xret, "ipc_send_device_get_visibility_mask_locked", err_mask_unlock);
235
236 uint32_t mask_size;
237 xret = ipc_receive_device_get_visibility_mask_locked(ipc_c, &mask_size);
238 IPC_CHK_WITH_GOTO(ipc_c, xret, "ipc_receive_device_get_visibility_mask_locked", err_mask_unlock);
239
240 mask = U_CALLOC_WITH_CAST(struct xrt_visibility_mask, mask_size);
241 if (mask == NULL) {
242 IPC_ERROR(ich->ipc_c, "failed to allocate xrt_visibility_mask");
243 goto err_mask_unlock;
244 }
245
246 xret = ipc_receive(&ipc_c->imc, mask, mask_size);
247 IPC_CHK_WITH_GOTO(ipc_c, xret, "ipc_receive", err_mask_free);
248
249 *out_mask = mask;
250 ipc_client_connection_unlock(ipc_c);
251
252 return XRT_SUCCESS;
253
254err_mask_free:
255 free(mask);
256err_mask_unlock:
257 ipc_client_connection_unlock(ipc_c);
258 return XRT_ERROR_IPC_FAILURE;
259}
260
261static void
262ipc_client_hmd_destroy(struct xrt_device *xdev)
263{
264 ipc_client_hmd_t *ich = ipc_client_hmd(xdev);
265
266 // Remove the variable tracking.
267 u_var_remove_root(ich);
268
269 // Free and de-init the shared things.
270 ipc_client_xdev_fini(ich);
271
272 // Free this device with the helper.
273 u_device_free(&ich->base);
274}
275
276static xrt_result_t
277ipc_client_hmd_get_brightness(struct xrt_device *xdev, float *out_brightness)
278{
279 ipc_client_hmd_t *ich = ipc_client_hmd(xdev);
280 struct ipc_connection *ipc_c = ich->ipc_c;
281 xrt_result_t xret;
282
283 ipc_client_connection_lock(ipc_c);
284
285 xret = ipc_call_device_get_brightness(ipc_c, ich->device_id, out_brightness);
286 IPC_CHK_ONLY_PRINT(ipc_c, xret, "ipc_call_device_get_brightness");
287
288 ipc_client_connection_unlock(ipc_c);
289
290 return xret;
291}
292
293static xrt_result_t
294ipc_client_hmd_set_brightness(struct xrt_device *xdev, float brightness, bool relative)
295{
296 ipc_client_hmd_t *ich = ipc_client_hmd(xdev);
297 struct ipc_connection *ipc_c = ich->ipc_c;
298 xrt_result_t xret;
299
300 ipc_client_connection_lock(ipc_c);
301
302 xret = ipc_call_device_set_brightness(ipc_c, ich->device_id, brightness, relative);
303 IPC_CHK_ONLY_PRINT(ipc_c, xret, "ipc_call_device_set_brightness");
304
305 ipc_client_connection_unlock(ipc_c);
306
307 return xret;
308}
309
310/*!
311 * @public @memberof ipc_client_hmd
312 */
313struct xrt_device *
314ipc_client_hmd_create(struct ipc_connection *ipc_c, struct xrt_tracking_origin *xtrack, uint32_t device_id)
315{
316 // Convenience helper.
317 struct ipc_shared_memory *ism = ipc_c->ism;
318
319 // Allocate a HMD device.
320 enum u_device_alloc_flags flags = (enum u_device_alloc_flags)(U_DEVICE_ALLOC_HMD);
321 ipc_client_hmd_t *ich = U_DEVICE_ALLOCATE(ipc_client_hmd_t, flags, 0, 0);
322
323 // Fills in almost everything a regular device needs.
324 ipc_client_xdev_init(ich, ipc_c, xtrack, device_id, ipc_client_hmd_destroy);
325
326 // Fill in needed HMD functions, and destroy.
327 ich->base.get_view_poses = ipc_client_hmd_get_view_poses;
328 ich->base.compute_distortion = ipc_client_hmd_compute_distortion;
329 ich->base.is_form_factor_available = ipc_client_hmd_is_form_factor_available;
330 ich->base.get_visibility_mask = ipc_client_hmd_get_visibility_mask;
331 ich->base.get_brightness = ipc_client_hmd_get_brightness;
332 ich->base.set_brightness = ipc_client_hmd_set_brightness;
333
334 // Setup blend-modes.
335 ich->base.hmd->blend_mode_count = ipc_c->ism->hmd.blend_mode_count;
336 for (int i = 0; i < XRT_MAX_DEVICE_BLEND_MODES; i++) {
337 ich->base.hmd->blend_modes[i] = ipc_c->ism->hmd.blend_modes[i];
338 }
339
340 // Setup the views.
341 ich->base.hmd->view_count = ism->hmd.view_count;
342 for (uint32_t i = 0; i < ich->base.hmd->view_count; ++i) {
343 ich->base.hmd->views[i].display.w_pixels = ipc_c->ism->hmd.views[i].display.w_pixels;
344 ich->base.hmd->views[i].display.h_pixels = ipc_c->ism->hmd.views[i].display.h_pixels;
345 }
346
347 // Distortion information, fills in xdev->compute_distortion().
348 u_distortion_mesh_set_none(&ich->base);
349
350 // Setup variable tracker.
351 u_var_add_root(ich, ich->base.str, true);
352 u_var_add_ro_u32(ich, &ich->device_id, "device_id");
353
354 return &ich->base;
355}