The open source OpenXR runtime
1// Copyright 2020-2025, Collabora, Ltd.
2// Copyright 2025, Beyley Cardellio
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief Driver for the Oculus Rift.
7 *
8 * Based largely on simulated_hmd.c, with reference to the DK1/DK2 firmware and OpenHMD's rift driver.
9 *
10 * @author Jakob Bornecrantz <jakob@collabora.com>
11 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
12 * @author Beyley Cardellio <ep1cm1n10n123@gmail.com>
13 * @ingroup drv_rift
14 */
15
16#include "os/os_time.h"
17#include "xrt/xrt_defines.h"
18#include "xrt/xrt_device.h"
19
20#include "rift_interface.h"
21#include "rift_distortion.h"
22
23#include "math/m_relation_history.h"
24#include "math/m_clock_tracking.h"
25#include "math/m_api.h"
26#include "math/m_vec2.h"
27#include "math/m_mathinclude.h" // IWYU pragma: keep
28
29#include "util/u_debug.h"
30#include "util/u_device.h"
31#include "util/u_distortion_mesh.h"
32#include "util/u_logging.h"
33#include "util/u_misc.h"
34#include "util/u_time.h"
35#include "util/u_var.h"
36#include "util/u_visibility_mask.h"
37#include "util/u_trace_marker.h"
38#include "xrt/xrt_results.h"
39
40#include <stdio.h>
41#include <assert.h>
42
43/*
44 *
45 * Structs and defines.
46 *
47 */
48
49DEBUG_GET_ONCE_LOG_OPTION(rift_log, "RIFT_LOG", U_LOGGING_WARN)
50
51#define HMD_TRACE(hmd, ...) U_LOG_XDEV_IFL_T(&hmd->base, hmd->log_level, __VA_ARGS__)
52#define HMD_DEBUG(hmd, ...) U_LOG_XDEV_IFL_D(&hmd->base, hmd->log_level, __VA_ARGS__)
53#define HMD_INFO(hmd, ...) U_LOG_XDEV_IFL_I(&hmd->base, hmd->log_level, __VA_ARGS__)
54#define HMD_WARN(hmd, ...) U_LOG_XDEV_IFL_W(&hmd->base, hmd->log_level, __VA_ARGS__)
55#define HMD_ERROR(hmd, ...) U_LOG_XDEV_IFL_E(&hmd->base, hmd->log_level, __VA_ARGS__)
56
57/*
58 *
59 * Headset functions
60 *
61 */
62
63static int
64rift_send_report(struct rift_hmd *hmd, uint8_t report_id, void *data, size_t data_length)
65{
66 int result;
67
68 if (data_length > REPORT_MAX_SIZE - 1) {
69 return -1;
70 }
71
72 uint8_t buffer[REPORT_MAX_SIZE];
73 buffer[0] = report_id;
74 memcpy(buffer + 1, data, data_length);
75
76 result = os_hid_set_feature(hmd->hid_dev, buffer, data_length + 1);
77 if (result < 0) {
78 return result;
79 }
80
81 return 0;
82}
83
84static int
85rift_get_report(struct rift_hmd *hmd, uint8_t report_id, uint8_t *out, size_t out_len)
86{
87 return os_hid_get_feature(hmd->hid_dev, report_id, out, out_len);
88}
89
90static int
91rift_send_keepalive(struct rift_hmd *hmd)
92{
93 struct dk2_report_keepalive_mux report = {0, IN_REPORT_DK2,
94 KEEPALIVE_INTERVAL_NS / 1000000}; // convert ns to ms
95
96 int result = rift_send_report(hmd, FEATURE_REPORT_KEEPALIVE_MUX, &report, sizeof(report));
97
98 if (result < 0) {
99 return result;
100 }
101
102 hmd->last_keepalive_time = os_monotonic_get_ns();
103 HMD_TRACE(hmd, "Sent keepalive at time %ld", hmd->last_keepalive_time);
104
105 return 0;
106}
107
108static int
109rift_get_config(struct rift_hmd *hmd, struct rift_config_report *config)
110{
111 uint8_t buf[REPORT_MAX_SIZE] = {0};
112
113 int result = rift_get_report(hmd, FEATURE_REPORT_CONFIG, buf, sizeof(buf));
114 if (result < 0) {
115 return result;
116 }
117
118 // FIXME: handle endian differences
119 memcpy(config, buf + 1, sizeof(*config));
120
121 // this value is hardcoded in the DK1 and DK2 firmware
122 if ((hmd->variant == RIFT_VARIANT_DK1 || hmd->variant == RIFT_VARIANT_DK2) &&
123 config->sample_rate != IMU_SAMPLE_RATE) {
124 HMD_ERROR(hmd, "Got invalid config from headset, got sample rate %d when expected %d",
125 config->sample_rate, IMU_SAMPLE_RATE);
126 return -1;
127 }
128
129 return 0;
130}
131
132static int
133rift_get_display_info(struct rift_hmd *hmd, struct rift_display_info_report *display_info)
134{
135 uint8_t buf[REPORT_MAX_SIZE] = {0};
136
137 int result = rift_get_report(hmd, FEATURE_REPORT_DISPLAY_INFO, buf, sizeof(buf));
138 if (result < 0) {
139 return result;
140 }
141
142 // FIXME: handle endian differences
143 memcpy(display_info, buf + 1, sizeof(*display_info));
144
145 return 0;
146}
147
148static int
149rift_get_lens_distortion(struct rift_hmd *hmd, struct rift_lens_distortion_report *lens_distortion)
150{
151 uint8_t buf[REPORT_MAX_SIZE] = {0};
152
153 int result = rift_get_report(hmd, FEATURE_REPORT_LENS_DISTORTION, buf, sizeof(buf));
154 if (result < 0) {
155 return result;
156 }
157
158 memcpy(lens_distortion, buf + 1, sizeof(*lens_distortion));
159
160 return 0;
161}
162
163static int
164rift_set_config(struct rift_hmd *hmd, struct rift_config_report *config)
165{
166 return rift_send_report(hmd, FEATURE_REPORT_CONFIG, config, sizeof(*config));
167}
168
169/*
170 *
171 * Driver functions
172 *
173 */
174
175static void
176rift_hmd_destroy(struct xrt_device *xdev)
177{
178 struct rift_hmd *hmd = rift_hmd(xdev);
179
180 // Remove the variable tracking.
181 u_var_remove_root(hmd);
182
183 if (hmd->sensor_thread.initialized)
184 os_thread_helper_stop_and_wait(&hmd->sensor_thread);
185
186 if (hmd->clock_tracker)
187 m_clock_windowed_skew_tracker_destroy(hmd->clock_tracker);
188
189 m_relation_history_destroy(&hmd->relation_hist);
190
191 if (hmd->lens_distortions)
192 free(hmd->lens_distortions);
193
194 u_device_free(&hmd->base);
195}
196
197static xrt_result_t
198rift_hmd_get_tracked_pose(struct xrt_device *xdev,
199 enum xrt_input_name name,
200 int64_t at_timestamp_ns,
201 struct xrt_space_relation *out_relation)
202{
203 struct rift_hmd *hmd = rift_hmd(xdev);
204
205 if (name != XRT_INPUT_GENERIC_HEAD_POSE) {
206 U_LOG_XDEV_UNSUPPORTED_INPUT(&hmd->base, hmd->log_level, name);
207 return XRT_ERROR_INPUT_UNSUPPORTED;
208 }
209
210 struct xrt_space_relation relation = XRT_SPACE_RELATION_ZERO;
211
212 enum m_relation_history_result history_result =
213 m_relation_history_get(hmd->relation_hist, at_timestamp_ns, &relation);
214 if (history_result == M_RELATION_HISTORY_RESULT_INVALID) {
215 // If you get in here, it means you did not push any poses into the relation history.
216 // You may want to handle this differently.
217 HMD_ERROR(hmd, "Internal error: no poses pushed?");
218 }
219
220 if ((relation.relation_flags & XRT_SPACE_RELATION_ORIENTATION_VALID_BIT) != 0) {
221 // If we provide an orientation, make sure that it is normalized.
222 math_quat_normalize(&relation.pose.orientation);
223 }
224
225 *out_relation = relation;
226 return XRT_SUCCESS;
227}
228
229static xrt_result_t
230rift_hmd_get_view_poses(struct xrt_device *xdev,
231 const struct xrt_vec3 *default_eye_relation,
232 int64_t at_timestamp_ns,
233 enum xrt_view_type view_type,
234 uint32_t view_count,
235 struct xrt_space_relation *out_head_relation,
236 struct xrt_fov *out_fovs,
237 struct xrt_pose *out_poses)
238{
239 struct rift_hmd *hmd = rift_hmd(xdev);
240
241 return u_device_get_view_poses( //
242 xdev, //
243 &(struct xrt_vec3){hmd->extra_display_info.icd, 0.0f, 0.0f}, //
244 at_timestamp_ns, //
245 view_type, //
246 view_count, //
247 out_head_relation, //
248 out_fovs, //
249 out_poses);
250}
251
252static xrt_result_t
253rift_hmd_get_visibility_mask(struct xrt_device *xdev,
254 enum xrt_visibility_mask_type type,
255 uint32_t view_index,
256 struct xrt_visibility_mask **out_mask)
257{
258 struct xrt_fov fov = xdev->hmd->distortion.fov[view_index];
259 u_visibility_mask_get_default(type, &fov, out_mask);
260 return XRT_SUCCESS;
261}
262
263static float
264rift_decode_fixed_point_uint16(uint16_t value, uint16_t zero_value, int fractional_bits)
265{
266 float value_float = (float)value;
267 value_float -= (float)zero_value;
268 value_float *= 1.0f / (float)(1 << fractional_bits);
269 return value_float;
270}
271
272static void
273rift_parse_distortion_report(struct rift_lens_distortion_report *report, struct rift_lens_distortion *out)
274{
275 out->distortion_version = report->distortion_version;
276
277 switch (report->distortion_version) {
278 case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: {
279 struct rift_catmull_rom_distortion_report_data report_data = report->data.lcsv_catmull_rom_10;
280 struct rift_catmull_rom_distortion_data data;
281
282 out->eye_relief = MICROMETERS_TO_METERS(report_data.eye_relief);
283
284 for (uint16_t i = 0; i < CATMULL_COEFFICIENTS; i += 1) {
285 data.k[i] = rift_decode_fixed_point_uint16(report_data.k[i], 0, 14);
286 }
287 data.max_r = rift_decode_fixed_point_uint16(report_data.max_r, 0, 14);
288 data.meters_per_tan_angle_at_center =
289 rift_decode_fixed_point_uint16(report_data.meters_per_tan_angle_at_center, 0, 19);
290 for (uint16_t i = 0; i < CHROMATIC_ABBERATION_COEFFEICENT_COUNT; i += 1) {
291 data.chromatic_abberation[i] =
292 rift_decode_fixed_point_uint16(report_data.chromatic_abberation[i], 0x8000, 19);
293 }
294
295 out->data.lcsv_catmull_rom_10 = data;
296 break;
297 }
298 default: return;
299 }
300}
301
302/*
303 * Decode 3 tightly packed 21 bit values from 4 bytes.
304 * We unpack them in the higher 21 bit values first and then shift
305 * them down to the lower in order to get the sign bits correct.
306 *
307 * Code taken/reformatted from OpenHMD's rift driver
308 */
309static void
310rift_decode_sample(const uint8_t *in, int32_t *out)
311{
312 int x = (in[0] << 24) | (in[1] << 16) | ((in[2] & 0xF8) << 8);
313 int y = ((in[2] & 0x07) << 29) | (in[3] << 21) | (in[4] << 13) | ((in[5] & 0xC0) << 5);
314 int z = ((in[5] & 0x3F) << 26) | (in[6] << 18) | (in[7] << 10);
315
316 out[0] = x >> 11;
317 out[1] = y >> 11;
318 out[2] = z >> 11;
319}
320
321static void
322rift_sample_to_imu_space(const int32_t *in, struct xrt_vec3 *out)
323{
324 out->x = (float)in[0] * 0.0001f;
325 out->y = (float)in[1] * 0.0001f;
326 out->z = (float)in[2] * 0.0001f;
327}
328
329static int
330sensor_thread_tick(struct rift_hmd *hmd)
331{
332 uint8_t buf[REPORT_MAX_SIZE];
333 int result;
334
335 int64_t now = os_monotonic_get_ns();
336
337 if (now - hmd->last_keepalive_time > KEEPALIVE_SEND_RATE_NS) {
338 result = rift_send_keepalive(hmd);
339
340 if (result < 0) {
341 HMD_ERROR(hmd, "Got error sending keepalive, assuming fatal, reason %d", result);
342 return result;
343 }
344 }
345
346 result = os_hid_read(hmd->hid_dev, buf, sizeof(buf), IMU_SAMPLE_RATE);
347
348 if (result < 0) {
349 HMD_ERROR(hmd, "Got error reading from device, assuming fatal, reason %d", result);
350 return result;
351 }
352
353 if (result == 0) {
354 HMD_WARN(hmd, "Timed out waiting for packet from headset, packets should come in at %dhz",
355 IMU_SAMPLE_RATE);
356 return 0;
357 }
358
359 switch (hmd->variant) {
360 case RIFT_VARIANT_DK2: {
361 // skip unknown commands
362 if (buf[0] != IN_REPORT_DK2) {
363 HMD_WARN(hmd, "Skipping unknown IN command %d", buf[0]);
364 return 0;
365 }
366
367 struct dk2_in_report report;
368
369 // don't treat invalid IN reports as fatal, just ignore them
370 if (result < (int)sizeof(report)) {
371 HMD_WARN(hmd, "Got malformed DK2 IN report with size %d", result);
372 return 0;
373 }
374
375 // TODO: handle endianness
376 memcpy(&report, buf + 1, sizeof(report));
377
378 // if there's no samples, just do nothing.
379 if (report.num_samples == 0) {
380 return 0;
381 }
382
383 if (!hmd->processed_sample_packet) {
384 hmd->last_remote_sample_time_us = report.sample_timestamp;
385 hmd->processed_sample_packet = true;
386 }
387
388 // wrap-around intentional and A-OK, given these are unsigned
389 uint32_t remote_sample_delta_us = report.sample_timestamp - hmd->last_remote_sample_time_us;
390
391 hmd->last_remote_sample_time_us = report.sample_timestamp;
392
393 hmd->last_remote_sample_time_ns += (int64_t)remote_sample_delta_us * OS_NS_PER_USEC;
394
395 m_clock_windowed_skew_tracker_push(hmd->clock_tracker, os_monotonic_get_ns(),
396 hmd->last_remote_sample_time_ns);
397
398 int64_t local_timestamp_ns;
399 // if we haven't synchronized our clocks, just do nothing
400 if (!m_clock_windowed_skew_tracker_to_local(hmd->clock_tracker, hmd->last_remote_sample_time_ns,
401 &local_timestamp_ns)) {
402 return 0;
403 }
404
405 if (report.num_samples > 1)
406 HMD_TRACE(hmd,
407 "Had more than one sample queued! We aren't receiving IN reports fast enough, HMD "
408 "had %d samples in the queue! Having to work back that first sample...",
409 report.num_samples);
410
411 for (int i = 0; i < MIN(DK2_MAX_SAMPLES, report.num_samples); i++) {
412 struct dk2_sample_pack latest_sample_pack = report.samples[i];
413
414 int32_t accel_raw[3], gyro_raw[3];
415 rift_decode_sample(latest_sample_pack.accel.data, accel_raw);
416 rift_decode_sample(latest_sample_pack.gyro.data, gyro_raw);
417
418 struct xrt_vec3 accel, gyro;
419 rift_sample_to_imu_space(accel_raw, &accel);
420 rift_sample_to_imu_space(gyro_raw, &gyro);
421
422 // work back the likely timestamp of the current sample
423 // if there's only one sample, then this will always be zero, if there's two or more samples,
424 // the previous samples will be offset by the sample rate of the IMU
425 int64_t sample_local_timestamp_ns =
426 local_timestamp_ns - ((MIN(report.num_samples, DK2_MAX_SAMPLES) - 1) * NS_PER_SAMPLE);
427
428 // update the IMU for that sample
429 m_imu_3dof_update(&hmd->fusion, sample_local_timestamp_ns, &accel, &gyro);
430
431 // push the pose of the IMU for that sample, doing so per sample
432 struct xrt_space_relation relation = XRT_SPACE_RELATION_ZERO;
433 relation.relation_flags = (enum xrt_space_relation_flags)(
434 XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | XRT_SPACE_RELATION_ORIENTATION_VALID_BIT);
435 relation.pose.orientation = hmd->fusion.rot;
436 m_relation_history_push(hmd->relation_hist, &relation, sample_local_timestamp_ns);
437 }
438
439 break;
440 }
441 case RIFT_VARIANT_DK1: return 0;
442 }
443
444 return 0;
445}
446
447static void *
448sensor_thread(void *ptr)
449{
450 U_TRACE_SET_THREAD_NAME("Rift sensor thread");
451
452 struct rift_hmd *hmd = (struct rift_hmd *)ptr;
453
454 os_thread_helper_lock(&hmd->sensor_thread);
455
456 // uncomment this to be able to see if things are actually progressing as expected in a debugger, without having
457 // to count yourself
458 // #define TICK_DEBUG
459
460 int result = 0;
461#ifdef TICK_DEBUG
462 int ticks = 0;
463#endif
464
465 while (os_thread_helper_is_running_locked(&hmd->sensor_thread) && result >= 0) {
466 os_thread_helper_unlock(&hmd->sensor_thread);
467
468 result = sensor_thread_tick(hmd);
469
470 os_thread_helper_lock(&hmd->sensor_thread);
471#ifdef TICK_DEBUG
472 ticks += 1;
473#endif
474 }
475
476 os_thread_helper_unlock(&hmd->sensor_thread);
477
478 return NULL;
479}
480
481struct rift_hmd *
482rift_hmd_create(struct os_hid_device *dev, enum rift_variant variant, char *device_name, char *serial_number)
483{
484 int result;
485
486 // This indicates you won't be using Monado's built-in tracking algorithms.
487 enum u_device_alloc_flags flags =
488 (enum u_device_alloc_flags)(U_DEVICE_ALLOC_HMD | U_DEVICE_ALLOC_TRACKING_NONE);
489
490 struct rift_hmd *hmd = U_DEVICE_ALLOCATE(struct rift_hmd, flags, 1, 0);
491
492 hmd->variant = variant;
493 hmd->hid_dev = dev;
494
495 result = rift_send_keepalive(hmd);
496 if (result < 0) {
497 HMD_ERROR(hmd, "Failed to send keepalive to spin up headset, reason %d", result);
498 goto error;
499 }
500
501 result = rift_get_display_info(hmd, &hmd->display_info);
502 if (result < 0) {
503 HMD_ERROR(hmd, "Failed to get device config, reason %d", result);
504 goto error;
505 }
506 HMD_DEBUG(hmd, "Got display info from hmd, res: %dx%d", hmd->display_info.resolution_x,
507 hmd->display_info.resolution_y);
508
509 result = rift_get_config(hmd, &hmd->config);
510 if (result < 0) {
511 HMD_ERROR(hmd, "Failed to get device config, reason %d", result);
512 goto error;
513 }
514 HMD_DEBUG(hmd, "Got config from hmd, config flags: %X", hmd->config.config_flags);
515
516 if (getenv("RIFT_POWER_OVERRIDE") != NULL) {
517 hmd->config.config_flags |= RIFT_CONFIG_REPORT_OVERRIDE_POWER;
518 HMD_INFO(hmd, "Enabling the override power config flag.");
519 } else {
520 hmd->config.config_flags &= ~RIFT_CONFIG_REPORT_OVERRIDE_POWER;
521 HMD_DEBUG(hmd, "Disabling the override power config flag.");
522 }
523
524 // force enable calibration use and auto calibration
525 // this is on by default according to the firmware on DK1 and DK2,
526 // but OpenHMD forces them on, we should do the same, they probably had a reason
527 hmd->config.config_flags |= RIFT_CONFIG_REPORT_USE_CALIBRATION;
528 hmd->config.config_flags |= RIFT_CONFIG_REPORT_AUTO_CALIBRATION;
529
530 hmd->config.interval = 0;
531
532 // update the config
533 result = rift_set_config(hmd, &hmd->config);
534 if (result < 0) {
535 HMD_ERROR(hmd, "Failed to set the device config, reason %d", result);
536 goto error;
537 }
538
539 // read it back
540 result = rift_get_config(hmd, &hmd->config);
541 if (result < 0) {
542 HMD_ERROR(hmd, "Failed to set the device config, reason %d", result);
543 goto error;
544 }
545 HMD_DEBUG(hmd, "After writing, HMD has config flags: %X", hmd->config.config_flags);
546
547 if (getenv("RIFT_USE_FIRMWARE_DISTORTION") != NULL) {
548 // get the lens distortions
549 struct rift_lens_distortion_report lens_distortion;
550 result = rift_get_lens_distortion(hmd, &lens_distortion);
551 if (result < 0) {
552 HMD_ERROR(hmd, "Failed to get lens distortion, reason %d", result);
553 goto error;
554 }
555
556 hmd->num_lens_distortions = lens_distortion.num_distortions;
557 hmd->lens_distortions = calloc(lens_distortion.num_distortions, sizeof(struct rift_lens_distortion));
558
559 rift_parse_distortion_report(&lens_distortion, &hmd->lens_distortions[lens_distortion.distortion_idx]);
560 // TODO: actually verify we initialize all the distortions. if the headset is working correctly, this
561 // should have happened, but you never know.
562 for (uint16_t i = 1; i < hmd->num_lens_distortions; i++) {
563 result = rift_get_lens_distortion(hmd, &lens_distortion);
564 if (result < 0) {
565 HMD_ERROR(hmd, "Failed to get lens distortion idx %d, reason %d", i, result);
566 goto error;
567 }
568
569 rift_parse_distortion_report(&lens_distortion,
570 &hmd->lens_distortions[lens_distortion.distortion_idx]);
571 }
572
573 // TODO: pick the correct distortion for the eye relief setting the user has picked
574 hmd->distortion_in_use = 0;
575 } else {
576 rift_fill_in_default_distortions(hmd);
577 }
578
579 // fill in extra display info about the headset
580
581 switch (hmd->variant) {
582 case RIFT_VARIANT_DK2:
583 hmd->extra_display_info.screen_gap_meters = 0.0f;
584 hmd->extra_display_info.lens_diameter_meters = 0.04f;
585 break;
586 default: break;
587 }
588
589 // hardcode left eye, probably not ideal, but sure, why not
590 struct rift_distortion_render_info distortion_render_info = rift_get_distortion_render_info(hmd, 0);
591 hmd->extra_display_info.fov = rift_calculate_fov_from_hmd(hmd, &distortion_render_info, 0);
592 hmd->extra_display_info.eye_to_source_ndc =
593 rift_calculate_ndc_scale_and_offset_from_fov(&hmd->extra_display_info.fov);
594 hmd->extra_display_info.eye_to_source_uv =
595 rift_calculate_uv_scale_and_offset_from_ndc_scale_and_offset(hmd->extra_display_info.eye_to_source_ndc);
596
597 size_t idx = 0;
598 hmd->base.hmd->blend_modes[idx++] = XRT_BLEND_MODE_OPAQUE;
599 hmd->base.hmd->blend_mode_count = idx;
600
601 hmd->base.update_inputs = u_device_noop_update_inputs;
602 hmd->base.get_tracked_pose = rift_hmd_get_tracked_pose;
603 hmd->base.get_view_poses = rift_hmd_get_view_poses;
604 hmd->base.get_visibility_mask = rift_hmd_get_visibility_mask;
605 hmd->base.destroy = rift_hmd_destroy;
606
607 hmd->base.hmd->distortion.models = XRT_DISTORTION_MODEL_COMPUTE;
608 hmd->base.hmd->distortion.preferred = XRT_DISTORTION_MODEL_COMPUTE;
609 hmd->base.compute_distortion = rift_hmd_compute_distortion;
610 u_distortion_mesh_fill_in_compute(&hmd->base);
611
612 hmd->pose = (struct xrt_pose)XRT_POSE_IDENTITY;
613 hmd->log_level = debug_get_log_option_rift_log();
614
615 // Print name.
616 strncpy(hmd->base.str, device_name, XRT_DEVICE_NAME_LEN);
617 strncpy(hmd->base.serial, serial_number, XRT_DEVICE_NAME_LEN);
618
619 m_relation_history_create(&hmd->relation_hist);
620
621 // Setup input.
622 hmd->base.name = XRT_DEVICE_GENERIC_HMD;
623 hmd->base.device_type = XRT_DEVICE_TYPE_HMD;
624 hmd->base.inputs[0].name = XRT_INPUT_GENERIC_HEAD_POSE;
625 hmd->base.supported.orientation_tracking = true;
626 hmd->base.supported.position_tracking = false; // set to true once we are trying to get the sensor 6dof to work
627
628 // Set up display details
629 hmd->base.hmd->screens[0].nominal_frame_interval_ns = time_s_to_ns(1.0f / 75.0f);
630 hmd->base.hmd->screens[0].scanout_direction = XRT_SCANOUT_DIRECTION_NONE;
631 hmd->base.hmd->screens[0].scanout_time_ns = 0;
632
633 hmd->extra_display_info.icd = MICROMETERS_TO_METERS(hmd->display_info.lens_separation);
634
635 char *icd_str = getenv("RIFT_OVERRIDE_ICD");
636 if (icd_str != NULL) {
637 // mm -> meters
638 float icd = strtof(icd_str, NULL) / 1000.0f;
639
640 // 0 is error, and less than zero is invalid
641 if (icd > 0.0f) {
642 hmd->extra_display_info.icd = icd;
643 HMD_INFO(hmd, "Forcing ICD to %f", hmd->extra_display_info.icd);
644 } else {
645 HMD_ERROR(hmd, "Failed to parse ICD override, expected float in millimeters, got %s", icd_str);
646 }
647 } else {
648 HMD_DEBUG(hmd, "Using default ICD of %f", hmd->extra_display_info.icd);
649 }
650
651 // screen is rotated, so we need to undo that here
652 hmd->base.hmd->screens[0].h_pixels = hmd->display_info.resolution_x;
653 hmd->base.hmd->screens[0].w_pixels = hmd->display_info.resolution_y;
654
655 // TODO: properly apply using rift_extra_display_info.screen_gap_meters, but this isn't necessary on DK2, where
656 // the gap is always 0
657 uint16_t view_width = hmd->display_info.resolution_x / 2;
658 uint16_t view_height = hmd->display_info.resolution_y;
659
660 for (uint32_t i = 0; i < 2; ++i) {
661 hmd->base.hmd->views[i].display.w_pixels = view_width;
662 hmd->base.hmd->views[i].display.h_pixels = view_height;
663
664 hmd->base.hmd->views[i].viewport.x_pixels = 0;
665 hmd->base.hmd->views[i].viewport.y_pixels = (1 - i) * (hmd->display_info.resolution_x / 2);
666 hmd->base.hmd->views[i].viewport.w_pixels = view_height; // screen is rotated, so swap w and h
667 hmd->base.hmd->views[i].viewport.h_pixels = view_width;
668 hmd->base.hmd->views[i].rot = u_device_rotation_left;
669 }
670
671 switch (hmd->variant) {
672 default:
673 case RIFT_VARIANT_DK2:
674 // TODO: figure out how to calculate this programmatically, right now this is hardcoded with data dumped
675 // from oculus' OpenXR runtime, some of the math for this is in rift_distortion.c, used for
676 // calculating distortion
677 hmd->base.hmd->distortion.fov[0].angle_up = 0.92667186;
678 hmd->base.hmd->distortion.fov[0].angle_down = -0.92667186;
679 hmd->base.hmd->distortion.fov[0].angle_left = -0.8138836;
680 hmd->base.hmd->distortion.fov[0].angle_right = 0.82951474;
681
682 hmd->base.hmd->distortion.fov[1].angle_up = 0.92667186;
683 hmd->base.hmd->distortion.fov[1].angle_down = -0.92667186;
684 hmd->base.hmd->distortion.fov[1].angle_left = -0.82951474;
685 hmd->base.hmd->distortion.fov[1].angle_right = 0.8138836;
686 break;
687 }
688
689 // Just put an initial identity value in the tracker
690 struct xrt_space_relation identity = XRT_SPACE_RELATION_ZERO;
691 identity.relation_flags = (enum xrt_space_relation_flags)(XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT |
692 XRT_SPACE_RELATION_ORIENTATION_VALID_BIT);
693 uint64_t now = os_monotonic_get_ns();
694 m_relation_history_push(hmd->relation_hist, &identity, now);
695
696 result = os_thread_helper_init(&hmd->sensor_thread);
697
698 if (result < 0) {
699 HMD_ERROR(hmd, "Failed to init os thread helper");
700 goto error;
701 }
702
703 m_imu_3dof_init(&hmd->fusion, M_IMU_3DOF_USE_GRAVITY_DUR_20MS);
704 hmd->clock_tracker = m_clock_windowed_skew_tracker_alloc(64);
705
706 result = os_thread_helper_start(&hmd->sensor_thread, sensor_thread, hmd);
707
708 if (result < 0) {
709 HMD_ERROR(hmd, "Failed to start sensor thread");
710 goto error;
711 }
712
713 // Setup variable tracker: Optional but useful for debugging
714 u_var_add_root(hmd, "Rift HMD", true);
715 u_var_add_log_level(hmd, &hmd->log_level, "log_level");
716 u_var_add_f32(hmd, &hmd->extra_display_info.icd, "ICD");
717 m_imu_3dof_add_vars(&hmd->fusion, hmd, "3dof_");
718
719 return hmd;
720error:
721 rift_hmd_destroy(&hmd->base);
722 return NULL;
723}