The open source OpenXR runtime
1// Copyright 2019-2023, Collabora, Ltd.
2// Copyright 2014, Kevin M. Godby
3// Copyright 2014-2018, Sensics, Inc.
4// SPDX-License-Identifier: BSL-1.0
5/*!
6 * @file
7 * @brief Driver for an OSVR Hacker Dev Kit device.
8 *
9 * Based in part on the corresponding VRPN driver,
10 * available under BSL-1.0.
11 *
12 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
13 * @author Kevin M. Godby <kevin@godby.org>
14 * @ingroup drv_hdk
15 */
16
17
18#include "math/m_mathinclude.h"
19
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <assert.h>
24#include <type_traits>
25
26#include "xrt/xrt_device.h"
27
28#include "os/os_hid.h"
29#include "os/os_time.h"
30
31#include "math/m_api.h"
32
33#include "util/u_debug.h"
34#include "util/u_misc.h"
35#include "util/u_device.h"
36#include "util/u_time.h"
37#include "util/u_distortion_mesh.h"
38
39#include "hdk_device.h"
40
41static constexpr uint8_t BITS_PER_BYTE = 8;
42
43DEBUG_GET_ONCE_LOG_OPTION(hdk_log, "HDK_LOG", U_LOGGING_WARN)
44
45/**
46 * A fixed-point to float conversion function.
47 *
48 * Values are signed, two's-complement, if the supplied integer is.
49 *
50 * The conversion is effectively from the fixed-point arithmetic type known
51 * "unambiguously" as Q INT_BITS.FRAC_BITS - the number of integer
52 * bits is not inferred, though it is checked to ensure it adds up.
53 *
54 * @tparam INT_BITS The number of bits devoted to the integer part.
55 * @tparam FRAC_BITS The number of bits devoted to the fractional
56 * part.
57 * @tparam IntegerType The input integer type, typically deduced (do not need to
58 * specify explicitly)
59 * @param v An input "integer" that is actually a fixed-point value.
60 *
61 * INT_BITS and FRAC_BITS must sum to 8 * sizeof(v), the bit width of
62 * the input integer, for unsigned values, or to one less than that (for the
63 * sign bit) for signed values.
64 *
65 * Based in part on the VRPN header vrpn_FixedPoint.h,
66 * available under BSL-1.0.
67 */
68template <size_t INT_BITS, size_t FRAC_BITS, typename IntegerType>
69static inline float
70fromFixedPoint(IntegerType v)
71{
72 constexpr size_t SIGN_BIT = std::is_signed<IntegerType>::value ? 1 : 0;
73 static_assert(INT_BITS + FRAC_BITS + SIGN_BIT == BITS_PER_BYTE * sizeof(IntegerType),
74 "INT_BITS and FRAC_BITS, plus 1 for a sign bit "
75 "if applicable, must sum to the input "
76 "integer width, but do not.");
77 return static_cast<float>(v) / (1 << FRAC_BITS);
78}
79
80static inline uint16_t
81hdk_get_le_uint16(uint8_t *&bufPtr)
82{
83 assert(bufPtr != nullptr);
84 uint16_t ret = static_cast<uint16_t>(*bufPtr) | (static_cast<uint16_t>(*(bufPtr + 1)) << BITS_PER_BYTE);
85 bufPtr += 2;
86 return ret;
87}
88
89static inline int16_t
90hdk_get_le_int16(uint8_t *&bufPtr)
91{
92 return static_cast<int16_t>(hdk_get_le_uint16(bufPtr));
93}
94
95static void
96hdk_device_destroy(struct xrt_device *xdev)
97{
98 struct hdk_device *hd = hdk_device(xdev);
99
100 os_thread_helper_destroy(&hd->imu_thread);
101
102 // Now that the thread is not running we can destroy the lock.
103 os_mutex_destroy(&hd->lock);
104
105 if (hd->dev != NULL) {
106 os_hid_destroy(hd->dev);
107 hd->dev = NULL;
108 }
109
110 free(hd);
111}
112
113static constexpr uint8_t MSG_LEN_LARGE = 32;
114static constexpr uint8_t MSG_LEN_SMALL = 16;
115
116static int
117hdk_device_update(struct hdk_device *hd)
118{
119 uint8_t buffer[MSG_LEN_LARGE]{};
120
121 auto bytesRead = os_hid_read(hd->dev, buffer, sizeof(buffer), 100);
122 if (bytesRead == -1) {
123 if (!hd->disconnect_notified) {
124 HDK_ERROR(hd,
125 "%s: HDK appeared to disconnect. Please "
126 "quit, reconnect, and try again.",
127 __func__);
128 hd->disconnect_notified = true;
129 }
130 hd->quat_valid = false;
131 return 0;
132 } else if (bytesRead == 0) {
133 HDK_WARN(hd, "Read 0 bytes from device");
134 return 1;
135 }
136 while (bytesRead > 0) {
137 if (bytesRead != MSG_LEN_LARGE && bytesRead != MSG_LEN_SMALL) {
138 HDK_DEBUG(hd, "Only got %d bytes", bytesRead);
139 hd->quat_valid = false;
140 return 1;
141 }
142 bytesRead = os_hid_read(hd->dev, buffer, sizeof(buffer), 0);
143 }
144
145 uint8_t *buf = &(buffer[0]);
146
147#if 0
148 uint8_t version = uint8_t(0x0f) & *buf;
149 uint8_t hdmi_status = (uint8_t(0xf0) & *buf) >> 4;
150#endif
151 buf++;
152
153 // HDMI status only valid in reports version 3.
154 // Expecting either version 1 (100Hz) or 3 (400Hz):
155 // https://github.com/OSVR/OSVR-HDK-MCU-Firmware/blob/main/Source%20code/Embedded/src/DeviceDrivers/BNO070_using_hostif.c#L511
156
157 // Next byte is sequence number, ignore
158 buf++;
159
160 struct xrt_quat quat;
161 static constexpr int INT_BITS = 1;
162 static constexpr int FRAC_BITS = 14;
163 quat.x = fromFixedPoint<INT_BITS, FRAC_BITS>(hdk_get_le_int16(buf));
164 quat.z = fromFixedPoint<INT_BITS, FRAC_BITS>(hdk_get_le_int16(buf)) * -1;
165 quat.y = fromFixedPoint<INT_BITS, FRAC_BITS>(hdk_get_le_int16(buf));
166 quat.w = fromFixedPoint<INT_BITS, FRAC_BITS>(hdk_get_le_int16(buf));
167
168// Used to produce 90 degree rotations
169#define HDK_SIN_PI_OVER_4 0.7071068f
170 struct xrt_quat rot_90_about_x
171 {
172 HDK_SIN_PI_OVER_4, 0, 0, HDK_SIN_PI_OVER_4
173 };
174 struct xrt_quat negative_90_about_y
175 {
176 0, -HDK_SIN_PI_OVER_4, 0, HDK_SIN_PI_OVER_4
177 };
178 // The flipping of components and this get us close, except we are
179 // looking 90 to the right of where we want.
180 math_quat_rotate(&quat, &rot_90_about_x, &quat);
181
182 // Fix that 90
183 math_quat_rotate(&negative_90_about_y, &quat, &quat);
184
185 hd->quat = quat;
186
187 /// @todo might not be accurate on some version 1 reports??
188
189 // This is in the "world" coordinate system.
190
191 // Note that we must "rotate" this velocity by the first transform from earlier
192 // (90 about x), hence putting it in a pure quat.
193 struct xrt_quat ang_vel_quat;
194 ang_vel_quat.x = fromFixedPoint<6, 9>(hdk_get_le_int16(buf));
195 ang_vel_quat.z = fromFixedPoint<6, 9>(hdk_get_le_int16(buf)) * -1;
196 ang_vel_quat.y = fromFixedPoint<6, 9>(hdk_get_le_int16(buf));
197 ang_vel_quat.w = 0;
198
199 // need the inverse rotation here
200 struct xrt_quat negative_90_about_x
201 {
202 - HDK_SIN_PI_OVER_4, 0, 0, HDK_SIN_PI_OVER_4
203 };
204 math_quat_rotate(&ang_vel_quat, &rot_90_about_x, &ang_vel_quat);
205 math_quat_rotate(&negative_90_about_x, &ang_vel_quat, &ang_vel_quat);
206
207 os_mutex_lock(&hd->lock);
208 hd->ang_vel_quat = ang_vel_quat;
209
210 hd->quat_valid = true;
211 os_mutex_unlock(&hd->lock);
212
213 return 1;
214}
215
216static xrt_result_t
217hdk_device_get_tracked_pose(struct xrt_device *xdev,
218 enum xrt_input_name name,
219 int64_t requested_timestamp_ns,
220 struct xrt_space_relation *out_relation)
221{
222 struct hdk_device *hd = hdk_device(xdev);
223
224 if (name != XRT_INPUT_GENERIC_HEAD_POSE) {
225 U_LOG_XDEV_UNSUPPORTED_INPUT(&hd->base, hd->log_level, name);
226 return XRT_ERROR_INPUT_UNSUPPORTED;
227 }
228
229 os_mutex_lock(&hd->lock);
230 if (!hd->quat_valid) {
231 out_relation->relation_flags = XRT_SPACE_RELATION_BITMASK_NONE;
232 HDK_TRACE(hd, "GET_TRACKED_POSE: No pose");
233 os_mutex_unlock(&hd->lock);
234 return XRT_SUCCESS;
235 }
236
237 out_relation->pose.orientation = hd->quat;
238
239 out_relation->angular_velocity.x = hd->ang_vel_quat.x;
240 out_relation->angular_velocity.y = hd->ang_vel_quat.y;
241 out_relation->angular_velocity.z = hd->ang_vel_quat.z;
242
243 os_mutex_unlock(&hd->lock);
244
245 out_relation->relation_flags = xrt_space_relation_flags(XRT_SPACE_RELATION_ORIENTATION_VALID_BIT |
246 XRT_SPACE_RELATION_ANGULAR_VELOCITY_VALID_BIT |
247 XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT);
248
249 HDK_TRACE(hd, "GET_TRACKED_POSE (%f, %f, %f, %f) ANG_VEL (%f, %f, %f)", hd->quat.x, hd->quat.y, hd->quat.z,
250 hd->quat.w, hd->ang_vel_quat.x, hd->ang_vel_quat.y, hd->ang_vel_quat.z);
251 return XRT_SUCCESS;
252}
253
254static void *
255hdk_device_run_thread(void *ptr)
256{
257 struct hdk_device *hd = hdk_device((struct xrt_device *)ptr);
258
259 os_thread_helper_lock(&hd->imu_thread);
260 while (os_thread_helper_is_running_locked(&hd->imu_thread)) {
261 os_thread_helper_unlock(&hd->imu_thread);
262
263 if (!hdk_device_update(hd)) {
264 return NULL;
265 }
266
267 // Just keep swimming.
268 os_thread_helper_lock(&hd->imu_thread);
269 }
270 return NULL;
271}
272
273#define HDK_DEBUG_INT(hd, name, val) HDK_DEBUG(hd, "\t%s = %u", name, val)
274
275#define HDK_DEBUG_MM(hd, name, val) \
276 HDK_DEBUG(hd, "\t%s = %i.%02imm", name, (int32_t)(val * 1000.f), abs((int32_t)(val * 100000.f)) % 100)
277
278#define HDK_DEBUG_ANGLE(hd, name, val) HDK_DEBUG(hd, "\t%s = %f (%i)", name, val, (int32_t)(val * (180 / M_PI)))
279
280#define HDK_DEBUG_MAT2X2(hd, name, rot) \
281 HDK_DEBUG(hd, "\t%s = {%f, %f} {%f, %f}", name, rot.v[0], rot.v[1], rot.v[2], rot.v[3])
282
283struct hdk_device *
284hdk_device_create(struct os_hid_device *dev, enum HDK_VARIANT variant)
285{
286 enum u_device_alloc_flags flags =
287 (enum u_device_alloc_flags)(U_DEVICE_ALLOC_HMD | U_DEVICE_ALLOC_TRACKING_NONE);
288 struct hdk_device *hd = U_DEVICE_ALLOCATE(struct hdk_device, flags, 1, 0);
289
290 size_t idx = 0;
291 hd->base.hmd->blend_modes[idx++] = XRT_BLEND_MODE_OPAQUE;
292 hd->base.hmd->blend_mode_count = idx;
293
294 hd->base.update_inputs = u_device_noop_update_inputs;
295 hd->base.get_tracked_pose = hdk_device_get_tracked_pose;
296 hd->base.get_view_poses = u_device_get_view_poses;
297 hd->base.destroy = hdk_device_destroy;
298 hd->base.inputs[0].name = XRT_INPUT_GENERIC_HEAD_POSE;
299 hd->base.name = XRT_DEVICE_GENERIC_HMD;
300 hd->dev = dev;
301 hd->log_level = debug_get_log_option_hdk_log();
302
303 snprintf(hd->base.str, XRT_DEVICE_NAME_LEN, "OSVR HDK-family Device");
304 snprintf(hd->base.serial, XRT_DEVICE_NAME_LEN, "OSVR HDK-family Device");
305
306 if (variant == HDK_UNKNOWN) {
307 HDK_ERROR(hd, "Don't know which HDK variant this is.");
308 hdk_device_destroy(&hd->base);
309 return NULL;
310 }
311
312 double hFOV;
313 double vFOV;
314 double hCOP = 0.5;
315 double vCOP = 0.5;
316
317 switch (variant) {
318 default:
319 case HDK_UNKNOWN:
320 HDK_ERROR(hd, "Don't know which HDK variant this is.");
321 hdk_device_destroy(&hd->base);
322 return NULL;
323
324 case HDK_VARIANT_1_2:
325 // Distortion optional - this is for no distortion.
326 hFOV = 90;
327 vFOV = 96.73;
328 break;
329
330 case HDK_VARIANT_1_3_1_4:
331 // Non-mesh distortion.
332 hFOV = 90;
333 vFOV = 96.73;
334 hCOP = 0.529;
335 break;
336
337 case HDK_VARIANT_2:
338 // Mesh distortion (ideally)
339 hFOV = vFOV = 92.0;
340 break;
341 }
342
343 constexpr double DEGREES_TO_RADIANS = M_PI / 180.0;
344 {
345 /* right eye */
346 math_compute_fovs(1.0, hCOP, hFOV * DEGREES_TO_RADIANS, 1, vCOP, vFOV * DEGREES_TO_RADIANS,
347 &hd->base.hmd->distortion.fov[1]);
348 }
349 {
350 /* left eye - just mirroring right eye now */
351 hd->base.hmd->distortion.fov[0].angle_up = hd->base.hmd->distortion.fov[1].angle_up;
352 hd->base.hmd->distortion.fov[0].angle_down = hd->base.hmd->distortion.fov[1].angle_down;
353
354 hd->base.hmd->distortion.fov[0].angle_left = -hd->base.hmd->distortion.fov[1].angle_right;
355 hd->base.hmd->distortion.fov[0].angle_right = -hd->base.hmd->distortion.fov[1].angle_left;
356 }
357
358 hd->base.hmd->screens[0].scanout_direction = XRT_SCANOUT_DIRECTION_NONE;
359 hd->base.hmd->screens[0].scanout_time_ns = 0;
360
361 switch (variant) {
362 case HDK_UNKNOWN: assert(!"unknown device"); break;
363
364 case HDK_VARIANT_2: {
365 hd->base.hmd->screens[0].nominal_frame_interval_ns = time_s_to_ns(1.0f / 90.0f);
366 constexpr int panel_w = 1080;
367 constexpr int panel_h = 1200;
368 // Padding needed horizontally per side.
369 constexpr int vert_padding = (panel_h - panel_w) / 2;
370 // HDK2 is upside down :facepalm:
371
372 // clang-format off
373 // Main display.
374 hd->base.hmd->screens[0].w_pixels = panel_w * 2;
375 hd->base.hmd->screens[0].h_pixels = panel_h;
376#ifndef HDK_DO_NOT_FLIP_HDK2_SCREEN
377 // Left
378 hd->base.hmd->views[0].display.w_pixels = panel_w;
379 hd->base.hmd->views[0].display.h_pixels = panel_h;
380 hd->base.hmd->views[0].viewport.x_pixels = panel_w; // right half of display
381 hd->base.hmd->views[0].viewport.y_pixels = vert_padding;
382 hd->base.hmd->views[0].viewport.w_pixels = panel_w;
383 hd->base.hmd->views[0].viewport.h_pixels = panel_w;
384 hd->base.hmd->views[0].rot = u_device_rotation_180;
385
386 // Right
387 hd->base.hmd->views[1].display.w_pixels = panel_w;
388 hd->base.hmd->views[1].display.h_pixels = panel_h;
389 hd->base.hmd->views[1].viewport.x_pixels = 0;
390 hd->base.hmd->views[1].viewport.y_pixels = vert_padding;
391 hd->base.hmd->views[1].viewport.w_pixels = panel_w;
392 hd->base.hmd->views[1].viewport.h_pixels = panel_w;
393 hd->base.hmd->views[1].rot = u_device_rotation_180;
394#else
395 // Left
396 hd->base.hmd->views[0].display.w_pixels = panel_w;
397 hd->base.hmd->views[0].display.h_pixels = panel_h;
398 hd->base.hmd->views[0].viewport.x_pixels = 0;
399 hd->base.hmd->views[0].viewport.y_pixels = vert_padding;
400 hd->base.hmd->views[0].viewport.w_pixels = panel_w;
401 hd->base.hmd->views[0].viewport.h_pixels = panel_w;
402 hd->base.hmd->views[0].rot = u_device_rotation_ident;
403
404 // Right
405 hd->base.hmd->views[1].display.w_pixels = panel_w;
406 hd->base.hmd->views[1].display.h_pixels = panel_h;
407 hd->base.hmd->views[1].viewport.x_pixels = panel_w;
408 hd->base.hmd->views[1].viewport.y_pixels = vert_padding;
409 hd->base.hmd->views[1].viewport.w_pixels = panel_w;
410 hd->base.hmd->views[1].viewport.h_pixels = panel_w;
411 hd->base.hmd->views[1].rot = u_device_rotation_ident;
412#endif
413
414 // clang-format on
415 break;
416 }
417 case HDK_VARIANT_1_3_1_4:
418 // fallthrough intentional
419 case HDK_VARIANT_1_2: {
420 // 1080x1920 screen, with the top at the left.
421 hd->base.hmd->screens[0].nominal_frame_interval_ns = time_s_to_ns(1.0f / 60.0f);
422
423 constexpr int panel_w = 1080;
424 constexpr int panel_h = 1920;
425 constexpr int panel_half_h = panel_h / 2;
426 // clang-format off
427 // Main display.
428 hd->base.hmd->screens[0].w_pixels = panel_w;
429 hd->base.hmd->screens[0].h_pixels = panel_h;
430
431 // Left
432 hd->base.hmd->views[0].display.w_pixels = panel_half_h;
433 hd->base.hmd->views[0].display.h_pixels = panel_w;
434 hd->base.hmd->views[0].viewport.x_pixels = 0;
435 hd->base.hmd->views[0].viewport.y_pixels = 0;// top half of display
436 hd->base.hmd->views[0].viewport.w_pixels = panel_w;
437 hd->base.hmd->views[0].viewport.h_pixels = panel_half_h;
438 hd->base.hmd->views[0].rot = u_device_rotation_left;
439
440 // Right
441 hd->base.hmd->views[1].display.w_pixels = panel_half_h;
442 hd->base.hmd->views[1].display.h_pixels = panel_w;
443 hd->base.hmd->views[1].viewport.x_pixels = 0;
444 hd->base.hmd->views[1].viewport.y_pixels = panel_half_h; // bottom half of display
445 hd->base.hmd->views[1].viewport.w_pixels = panel_w;
446 hd->base.hmd->views[1].viewport.h_pixels = panel_half_h;
447 hd->base.hmd->views[1].rot = u_device_rotation_left;
448 // clang-format on
449 break;
450 }
451 }
452
453 // Distortion
454 // "None" is correct or at least acceptable for 1.2.
455 // We have coefficients for 1.3/1.4, though the mesh is better.
456 // We only have a mesh for 2, so use "none" there until it's supported.
457 // Distortion information, fills in xdev->compute_distortion().
458 u_distortion_mesh_set_none(&hd->base);
459 // if (variant == HDK_VARIANT_1_3_1_4) {
460 // hd->base.hmd->distortion.models =
461 // xrt_distortion_model(hd->base.hmd->distortion.models |
462 // XRT_DISTORTION_MODEL_PANOTOOLS);
463 // hd->base.hmd->distortion.preferred =
464 // XRT_DISTORTION_MODEL_PANOTOOLS;
465 // }
466
467 int ret = os_thread_helper_init(&hd->imu_thread);
468 if (ret != 0) {
469 HDK_ERROR(hd, "Failed to start imu thread!");
470 hdk_device_destroy((struct xrt_device *)hd);
471 return 0;
472 }
473
474 if (hd->dev) {
475 // Mutex before thread.
476 ret = os_mutex_init(&hd->lock);
477 if (ret != 0) {
478 HDK_ERROR(hd, "Failed to init mutex!");
479 hdk_device_destroy(&hd->base);
480 return NULL;
481 }
482
483 ret = os_thread_helper_start(&hd->imu_thread, hdk_device_run_thread, hd);
484 if (ret != 0) {
485 HDK_ERROR(hd, "Failed to start mainboard thread!");
486 hdk_device_destroy((struct xrt_device *)hd);
487 return 0;
488 }
489 }
490
491 if (hd->log_level <= U_LOGGING_DEBUG) {
492 u_device_dump_config(&hd->base, __func__, hd->base.str);
493 }
494
495 hd->base.supported.orientation_tracking = true;
496 hd->base.supported.position_tracking = false;
497 hd->base.device_type = XRT_DEVICE_TYPE_HMD;
498
499 return hd;
500}