The open source OpenXR runtime
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

d/rift: Fix distortion and render parameters

This appears to be fully correct on DK2 now. There are still some fixes
to this code required for CV1 (which is slightly off-center, and there's
a bug in this code making that not work correctly),
but those will be upstreamed at a later point, since it isn't relevant
to DK2 specifically, and would be hard to cherry-pick.

Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2428>

+677 -173
+1
src/xrt/drivers/CMakeLists.txt
··· 244 244 add_library( 245 245 drv_rift STATIC 246 246 rift/rift_hmd.c 247 + rift/rift_distortion.c 247 248 rift/rift_prober.c 248 249 ) 249 250 target_include_directories(drv_rift SYSTEM PRIVATE)
+498
src/xrt/drivers/rift/rift_distortion.c
··· 1 + // Copyright 2025, Beyley Cardellio 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Handles calculating the distortion profile for Oculus Rift devices. 6 + * @author Beyley Cardellio <ep1cm1n10n123@gmail.com> 7 + * @ingroup drv_rift 8 + */ 9 + 10 + #include "rift_distortion.h" 11 + 12 + #include "math/m_vec2.h" 13 + 14 + #define HMD_TRACE(hmd, ...) U_LOG_XDEV_IFL_T(&hmd->base, hmd->log_level, __VA_ARGS__) 15 + #define HMD_DEBUG(hmd, ...) U_LOG_XDEV_IFL_D(&hmd->base, hmd->log_level, __VA_ARGS__) 16 + #define HMD_INFO(hmd, ...) U_LOG_XDEV_IFL_I(&hmd->base, hmd->log_level, __VA_ARGS__) 17 + #define HMD_WARN(hmd, ...) U_LOG_XDEV_IFL_W(&hmd->base, hmd->log_level, __VA_ARGS__) 18 + #define HMD_ERROR(hmd, ...) U_LOG_XDEV_IFL_E(&hmd->base, hmd->log_level, __VA_ARGS__) 19 + 20 + static float 21 + rift_catmull_rom_spline(struct rift_catmull_rom_distortion_data *catmull, float scaled_value) 22 + { 23 + float scaled_value_floor = floorf(scaled_value); 24 + scaled_value_floor = CLAMP(scaled_value_floor, 0, CATMULL_COEFFICIENTS - 1); 25 + 26 + float t = scaled_value - scaled_value_floor; 27 + int k = (int)scaled_value_floor; 28 + 29 + float p0, p1, m0, m1; 30 + switch (k) { 31 + case 0: 32 + p0 = 1.0f; 33 + m0 = (catmull->k[1] - catmull->k[0]); 34 + p1 = catmull->k[1]; 35 + m1 = 0.5f * (catmull->k[2] - catmull->k[0]); 36 + break; 37 + default: 38 + p0 = catmull->k[k]; 39 + m0 = 0.5f * (catmull->k[k + 1] - catmull->k[k - 1]); 40 + p1 = catmull->k[k + 1]; 41 + m1 = 0.5f * (catmull->k[k + 2] - catmull->k[k]); 42 + break; 43 + case CATMULL_COEFFICIENTS - 2: 44 + p0 = catmull->k[CATMULL_COEFFICIENTS - 2]; 45 + m0 = 0.5f * (catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]); 46 + p1 = catmull->k[CATMULL_COEFFICIENTS - 1]; 47 + m1 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; 48 + break; 49 + case CATMULL_COEFFICIENTS - 1: 50 + p0 = catmull->k[CATMULL_COEFFICIENTS - 1]; 51 + m0 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; 52 + p1 = p0 + m0; 53 + m1 = m0; 54 + break; 55 + } 56 + 57 + float omt = 1.0f - t; 58 + 59 + float res = (p0 * (1.0f + 2.0f * t) + m0 * t) * omt * omt + (p1 * (1.0f + 2.0f * omt) - m1 * omt) * t * t; 60 + 61 + return res; 62 + } 63 + 64 + static float 65 + rift_distortion_distance_scale_squared(struct rift_lens_distortion *lens_distortion, float distance_squared) 66 + { 67 + float scale = 1.0f; 68 + 69 + switch (lens_distortion->distortion_version) { 70 + case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { 71 + struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; 72 + 73 + float scaled_distance_squared = 74 + (float)(CATMULL_COEFFICIENTS - 1) * distance_squared / (data.max_r * data.max_r); 75 + 76 + return rift_catmull_rom_spline(&data, scaled_distance_squared); 77 + } 78 + default: return scale; 79 + } 80 + } 81 + 82 + struct xrt_vec3 83 + rift_distortion_distance_scale_squared_split_chroma(struct rift_lens_distortion *lens_distortion, 84 + float distance_squared) 85 + { 86 + float scale = rift_distortion_distance_scale_squared(lens_distortion, distance_squared); 87 + 88 + struct xrt_vec3 scale_split; 89 + scale_split.x = scale; 90 + scale_split.y = scale; 91 + scale_split.z = scale; 92 + 93 + switch (lens_distortion->distortion_version) { 94 + case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { 95 + struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; 96 + 97 + scale_split.x *= 1.0f + data.chromatic_abberation[0] + distance_squared * data.chromatic_abberation[1]; 98 + scale_split.z *= 1.0f + data.chromatic_abberation[2] + distance_squared * data.chromatic_abberation[3]; 99 + break; 100 + } 101 + } 102 + 103 + return scale_split; 104 + } 105 + 106 + struct rift_distortion_render_info 107 + rift_get_distortion_render_info(struct rift_hmd *hmd, uint32_t view) 108 + { 109 + struct rift_lens_distortion *distortion = &hmd->lens_distortions[hmd->distortion_in_use]; 110 + 111 + float display_width_meters = MICROMETERS_TO_METERS(hmd->display_info.display_width); 112 + float display_height_meters = MICROMETERS_TO_METERS(hmd->display_info.display_height); 113 + 114 + float lens_separation_meters = MICROMETERS_TO_METERS(hmd->display_info.lens_separation); 115 + float center_from_top_meters = MICROMETERS_TO_METERS(hmd->display_info.center_v); 116 + 117 + struct xrt_vec2 pixels_per_meter; 118 + pixels_per_meter.x = 119 + (float)hmd->display_info.resolution_x / (display_width_meters - hmd->extra_display_info.screen_gap_meters); 120 + pixels_per_meter.y = (float)hmd->display_info.resolution_y / display_height_meters; 121 + 122 + struct xrt_vec2 pixels_per_tan_angle_at_center; 123 + pixels_per_tan_angle_at_center.x = 124 + pixels_per_meter.x * distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center; 125 + pixels_per_tan_angle_at_center.y = 126 + pixels_per_meter.y * distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center; 127 + 128 + struct xrt_vec2 tan_eye_angle_scale; 129 + tan_eye_angle_scale.x = 130 + 0.25f * (display_width_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center); 131 + tan_eye_angle_scale.y = 132 + 0.5f * (display_height_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center); 133 + 134 + float visible_width_of_one_eye = 0.5f * (display_width_meters - hmd->extra_display_info.screen_gap_meters); 135 + float center_from_left_meters = (display_width_meters - lens_separation_meters) * 0.5f; 136 + 137 + struct xrt_vec2 lens_center; 138 + lens_center.x = (center_from_left_meters / visible_width_of_one_eye) * 2.0f - 1.0f; 139 + lens_center.y = (center_from_top_meters / display_height_meters) * 2.0f - 1.0f; 140 + 141 + // if we're on the right eye, flip the lens center 142 + if (view != 0) { 143 + lens_center.x *= -1; 144 + } 145 + 146 + return (struct rift_distortion_render_info){ 147 + .distortion = distortion, 148 + .lens_center = lens_center, 149 + .pixels_per_tan_angle_at_center = pixels_per_tan_angle_at_center, 150 + .tan_eye_angle_scale = tan_eye_angle_scale, 151 + }; 152 + } 153 + 154 + static struct rift_viewport_fov_tan 155 + rift_calculate_fov_from_eye_position( 156 + float eye_relief, float offset_to_right, float offset_downwards, float lens_diameter, float extra_eye_rotation) 157 + { 158 + float half_lens_diameter = lens_diameter * 0.5f; 159 + 160 + struct rift_viewport_fov_tan fov_port; 161 + fov_port.up_tan = (half_lens_diameter + offset_downwards) / eye_relief; 162 + fov_port.down_tan = (half_lens_diameter - offset_downwards) / eye_relief; 163 + fov_port.left_tan = (half_lens_diameter + offset_to_right) / eye_relief; 164 + fov_port.right_tan = (half_lens_diameter - offset_to_right) / eye_relief; 165 + 166 + if (extra_eye_rotation > 0.0f) { 167 + extra_eye_rotation = CLAMP(extra_eye_rotation, 0, 30.0f); 168 + 169 + float eyeball_center_to_pupil = 0.0135f; 170 + float eyeball_lateral_pull = 0.001f * (extra_eye_rotation / DEG_TO_RAD(30.0f)); 171 + float extra_translation = eyeball_center_to_pupil * sinf(extra_eye_rotation) + eyeball_lateral_pull; 172 + float extra_relief = eyeball_center_to_pupil * (1.0f - cosf(extra_eye_rotation)); 173 + 174 + fov_port.up_tan = fmaxf(fov_port.up_tan, (half_lens_diameter + offset_downwards + extra_translation) / 175 + (eye_relief + extra_relief)); 176 + fov_port.down_tan = 177 + fmaxf(fov_port.down_tan, 178 + (half_lens_diameter - offset_downwards + extra_translation) / (eye_relief + extra_relief)); 179 + fov_port.left_tan = 180 + fmaxf(fov_port.left_tan, 181 + (half_lens_diameter + offset_to_right + extra_translation) / (eye_relief + extra_relief)); 182 + fov_port.right_tan = 183 + fmaxf(fov_port.right_tan, 184 + (half_lens_diameter - offset_to_right + extra_translation) / (eye_relief + extra_relief)); 185 + } 186 + 187 + return fov_port; 188 + } 189 + 190 + static struct xrt_vec2 191 + rift_transform_screen_ndc_to_tan_fov_space(struct rift_distortion_render_info *distortion, struct xrt_vec2 screen_ndc) 192 + { 193 + struct xrt_vec2 tan_eye_angle_distorted = { 194 + (screen_ndc.x - distortion->lens_center.x) * distortion->tan_eye_angle_scale.x, 195 + (screen_ndc.y - distortion->lens_center.y) * distortion->tan_eye_angle_scale.y}; 196 + 197 + float distance_squared = (tan_eye_angle_distorted.x * tan_eye_angle_distorted.x) + 198 + (tan_eye_angle_distorted.y * tan_eye_angle_distorted.y); 199 + 200 + float distortion_scale = rift_distortion_distance_scale_squared(distortion->distortion, distance_squared); 201 + 202 + return m_vec2_mul_scalar(tan_eye_angle_distorted, distortion_scale); 203 + } 204 + 205 + static struct rift_viewport_fov_tan 206 + rift_fov_find_range(struct xrt_vec2 from, 207 + struct xrt_vec2 to, 208 + int num_steps, 209 + struct rift_distortion_render_info *distortion) 210 + { 211 + struct rift_viewport_fov_tan fov_port = {0.0f, 0.0f, 0.0f, 0.0f}; 212 + 213 + float step_scale = 1.0f / (num_steps - 1); 214 + for (int step = 0; step < num_steps; step++) { 215 + float lerp_factor = step_scale * (float)step; 216 + struct xrt_vec2 sample = m_vec2_add(from, m_vec2_mul_scalar(m_vec2_sub(to, from), lerp_factor)); 217 + struct xrt_vec2 tan_eye_angle = rift_transform_screen_ndc_to_tan_fov_space(distortion, sample); 218 + 219 + fov_port.left_tan = fmaxf(fov_port.left_tan, -tan_eye_angle.x); 220 + fov_port.right_tan = fmaxf(fov_port.right_tan, tan_eye_angle.x); 221 + fov_port.up_tan = fmaxf(fov_port.up_tan, -tan_eye_angle.y); 222 + fov_port.down_tan = fmaxf(fov_port.down_tan, tan_eye_angle.y); 223 + } 224 + 225 + return fov_port; 226 + } 227 + 228 + static struct rift_viewport_fov_tan 229 + rift_get_physical_screen_fov(struct rift_distortion_render_info *distortion) 230 + { 231 + struct xrt_vec2 lens_center = distortion->lens_center; 232 + 233 + struct rift_viewport_fov_tan left_fov_port = 234 + rift_fov_find_range(lens_center, (struct xrt_vec2){-1.0f, lens_center.y}, 10, distortion); 235 + 236 + struct rift_viewport_fov_tan right_fov_port = 237 + rift_fov_find_range(lens_center, (struct xrt_vec2){1.0f, lens_center.y}, 10, distortion); 238 + 239 + struct rift_viewport_fov_tan up_fov_port = 240 + rift_fov_find_range(lens_center, (struct xrt_vec2){lens_center.x, -1.0f}, 10, distortion); 241 + 242 + struct rift_viewport_fov_tan down_fov_port = 243 + rift_fov_find_range(lens_center, (struct xrt_vec2){lens_center.x, 1.0f}, 10, distortion); 244 + 245 + return (struct rift_viewport_fov_tan){.left_tan = left_fov_port.left_tan, 246 + .right_tan = right_fov_port.right_tan, 247 + .up_tan = up_fov_port.up_tan, 248 + .down_tan = down_fov_port.down_tan}; 249 + } 250 + 251 + static struct rift_viewport_fov_tan 252 + rift_clamp_fov_to_physical_screen_fov(struct rift_distortion_render_info *distortion, 253 + struct rift_viewport_fov_tan fov_port) 254 + { 255 + struct rift_viewport_fov_tan result_fov_port; 256 + struct rift_viewport_fov_tan physical_fov_port = rift_get_physical_screen_fov(distortion); 257 + 258 + result_fov_port.left_tan = fminf(fov_port.left_tan, physical_fov_port.left_tan); 259 + result_fov_port.right_tan = fminf(fov_port.right_tan, physical_fov_port.right_tan); 260 + result_fov_port.up_tan = fminf(fov_port.up_tan, physical_fov_port.up_tan); 261 + result_fov_port.down_tan = fminf(fov_port.down_tan, physical_fov_port.down_tan); 262 + 263 + return result_fov_port; 264 + } 265 + 266 + struct rift_viewport_fov_tan 267 + rift_calculate_fov_from_hmd(struct rift_hmd *hmd, struct rift_distortion_render_info *distortion, uint32_t view) 268 + { 269 + float eye_relief = distortion->distortion->eye_relief; 270 + 271 + struct rift_viewport_fov_tan fov_port; 272 + fov_port = rift_calculate_fov_from_eye_position(eye_relief, 0, 0, hmd->extra_display_info.lens_diameter_meters, 273 + DEFAULT_EXTRA_EYE_ROTATION); 274 + 275 + fov_port = rift_clamp_fov_to_physical_screen_fov(distortion, fov_port); 276 + 277 + return fov_port; 278 + } 279 + 280 + struct rift_scale_and_offset 281 + rift_calculate_ndc_scale_and_offset_from_fov(struct rift_viewport_fov_tan *fov) 282 + { 283 + struct xrt_vec2 proj_scale = {.x = 2.0f / (fov->left_tan + fov->right_tan), 284 + .y = 2.0f / (fov->up_tan + fov->down_tan)}; 285 + 286 + struct xrt_vec2 proj_offset = {.x = (fov->left_tan - fov->right_tan) * proj_scale.x * 0.5, 287 + .y = (fov->up_tan - fov->down_tan) * proj_scale.y * 0.5f}; 288 + 289 + return (struct rift_scale_and_offset){.scale = proj_scale, .offset = proj_offset}; 290 + } 291 + 292 + struct rift_scale_and_offset 293 + rift_calculate_uv_scale_and_offset_from_ndc_scale_and_offset(struct rift_scale_and_offset eye_to_source_ndc) 294 + { 295 + struct rift_scale_and_offset result = eye_to_source_ndc; 296 + result.scale = m_vec2_mul_scalar(result.scale, 0.5f); 297 + result.offset = m_vec2_add_scalar(m_vec2_mul_scalar(result.offset, 0.5f), 0.5f); 298 + return result; 299 + } 300 + 301 + static struct xrt_uv_triplet 302 + rift_transform_screen_ndc_to_tan_fov_space_chroma(struct rift_distortion_render_info *distortion, 303 + struct xrt_vec2 screen_ndc) 304 + { 305 + struct xrt_vec2 tan_eye_angle_distorted = { 306 + (screen_ndc.x - distortion->lens_center.x) * distortion->tan_eye_angle_scale.x, 307 + (screen_ndc.y - distortion->lens_center.y) * distortion->tan_eye_angle_scale.y}; 308 + 309 + float distance_squared = (tan_eye_angle_distorted.x * tan_eye_angle_distorted.x) + 310 + (tan_eye_angle_distorted.y * tan_eye_angle_distorted.y); 311 + 312 + struct xrt_vec3 distortion_scales = 313 + rift_distortion_distance_scale_squared_split_chroma(distortion->distortion, distance_squared); 314 + 315 + return (struct xrt_uv_triplet){ 316 + .r = {tan_eye_angle_distorted.x * distortion_scales.x, tan_eye_angle_distorted.y * distortion_scales.x}, 317 + .g = {tan_eye_angle_distorted.x * distortion_scales.y, tan_eye_angle_distorted.y * distortion_scales.y}, 318 + .b = {tan_eye_angle_distorted.x * distortion_scales.z, tan_eye_angle_distorted.y * distortion_scales.z}, 319 + }; 320 + } 321 + 322 + // unused math functions which may be useful in the future for stuff like calculating FOV. 323 + // disabled to not give unused warnings 324 + #if 0 325 + static float 326 + rift_distortion(struct rift_lens_distortion *lens_distortion, float distance) 327 + { 328 + return distance * rift_distortion_distance_scale_squared(lens_distortion, distance * distance); 329 + } 330 + 331 + static float 332 + rift_distortion_distance_inverse(struct rift_lens_distortion *lens_distortion, float distance) 333 + { 334 + assert(distance <= 20.0f); 335 + 336 + float delta = distance * 0.25f; 337 + 338 + float s = distance * 0.25f; 339 + float d = fabsf(distance - rift_distortion(lens_distortion, s)); 340 + 341 + for (int i = 0; i < 20; i++) { 342 + float s_up = s + delta; 343 + float s_down = s - delta; 344 + float d_up = fabsf(distance - rift_distortion(lens_distortion, s_up)); 345 + float d_down = fabsf(distance - rift_distortion(lens_distortion, s_down)); 346 + 347 + if (d_up < d) { 348 + s = s_up; 349 + d = d_up; 350 + } else if (d_down < d) { 351 + s = s_down; 352 + d = d_down; 353 + } else { 354 + delta *= 0.5f; 355 + } 356 + } 357 + 358 + return s; 359 + } 360 + 361 + static struct xrt_vec2 362 + rift_transform_tan_fov_space_to_render_target_tex_uv(struct rift_scale_and_offset *eye_to_source_uv, 363 + struct xrt_vec2 tan_eye_angle) 364 + { 365 + return m_vec2_add(m_vec2_mul(tan_eye_angle, eye_to_source_uv->scale), eye_to_source_uv->offset); 366 + } 367 + 368 + static struct xrt_vec2 369 + rift_transform_render_target_ndc_to_tan_fov_space(struct rift_scale_and_offset *eye_to_source_ndc, struct xrt_vec2 ndc) 370 + { 371 + return m_vec2_div(m_vec2_sub(ndc, eye_to_source_ndc->offset), eye_to_source_ndc->scale); 372 + } 373 + 374 + static struct xrt_vec2 375 + rift_transform_tan_fov_space_to_screen_ndc(struct rift_distortion_render_info *distortion, 376 + struct xrt_vec2 tan_eye_angle) 377 + { 378 + float tan_eye_angle_radius = m_vec2_len(tan_eye_angle); 379 + float tan_eye_angle_distorted_radius = 380 + rift_distortion_distance_inverse(distortion->distortion, tan_eye_angle_radius); 381 + 382 + struct xrt_vec2 tan_eye_angle_distorted = tan_eye_angle; 383 + if (tan_eye_angle_radius > 0) { 384 + tan_eye_angle_distorted = 385 + m_vec2_mul_scalar(tan_eye_angle, tan_eye_angle_distorted_radius / tan_eye_angle_radius); 386 + } 387 + 388 + return m_vec2_add(m_vec2_div(tan_eye_angle_distorted, distortion->tan_eye_angle_scale), 389 + distortion->lens_center); 390 + } 391 + #endif 392 + 393 + bool 394 + rift_hmd_compute_distortion(struct xrt_device *dev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result) 395 + { 396 + #define TO_NDC(x) ((x * 2) - 1) 397 + 398 + struct rift_hmd *hmd = rift_hmd(dev); 399 + 400 + struct xrt_vec2 source_ndc = {TO_NDC(u), TO_NDC(v)}; 401 + 402 + struct rift_distortion_render_info distortion_render_info = rift_get_distortion_render_info(hmd, 0); 403 + 404 + struct rift_scale_and_offset *eye_to_source_uv = &hmd->extra_display_info.eye_to_source_uv; 405 + 406 + struct xrt_uv_triplet tan_fov_chroma = 407 + rift_transform_screen_ndc_to_tan_fov_space_chroma(&distortion_render_info, source_ndc); 408 + 409 + #if 0 // no distortion 410 + struct xrt_uv_triplet sample_tex_coord = { 411 + .r = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset), 412 + .g = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset), 413 + .b = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset)}; 414 + #else 415 + struct xrt_uv_triplet sample_tex_coord = { 416 + .r = m_vec2_add(m_vec2_mul(tan_fov_chroma.r, eye_to_source_uv->scale), eye_to_source_uv->offset), 417 + .g = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset), 418 + .b = m_vec2_add(m_vec2_mul(tan_fov_chroma.b, eye_to_source_uv->scale), eye_to_source_uv->offset)}; 419 + #endif 420 + 421 + *out_result = sample_tex_coord; 422 + 423 + return true; 424 + 425 + #undef TO_NDC 426 + } 427 + 428 + void 429 + rift_fill_in_default_distortions(struct rift_hmd *hmd) 430 + { 431 + hmd->num_lens_distortions = 2; 432 + hmd->lens_distortions = calloc(hmd->num_lens_distortions, sizeof(struct rift_lens_distortion)); 433 + 434 + uint16_t i = 0; 435 + 436 + struct rift_catmull_rom_distortion_data distortion_data; 437 + 438 + // TODO: dump these from the latest oculus runtime 439 + // TODO: select the right distorions based on rift variant, right now this is hard-coded to the DK2 440 + 441 + hmd->lens_distortions[i].distortion_version = RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1; 442 + hmd->lens_distortions[i].eye_relief = 0.008f; 443 + 444 + distortion_data.meters_per_tan_angle_at_center = 0.036f; 445 + distortion_data.max_r = 1.0f; 446 + 447 + distortion_data.k[0] = 1.003f; 448 + distortion_data.k[1] = 1.02f; 449 + distortion_data.k[2] = 1.042f; 450 + distortion_data.k[3] = 1.066f; 451 + distortion_data.k[4] = 1.094f; 452 + distortion_data.k[5] = 1.126f; 453 + distortion_data.k[6] = 1.162f; 454 + distortion_data.k[7] = 1.203f; 455 + distortion_data.k[8] = 1.25f; 456 + distortion_data.k[9] = 1.31f; 457 + distortion_data.k[10] = 1.38f; 458 + 459 + distortion_data.chromatic_abberation[0] = -0.0112f; 460 + distortion_data.chromatic_abberation[1] = -0.015f; 461 + distortion_data.chromatic_abberation[2] = 0.0187f; 462 + distortion_data.chromatic_abberation[3] = 0.015f; 463 + 464 + hmd->lens_distortions[i].data.lcsv_catmull_rom_10 = distortion_data; 465 + 466 + i += 1; 467 + 468 + hmd->lens_distortions[i].distortion_version = RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1; 469 + hmd->lens_distortions[i].eye_relief = 0.018f; 470 + 471 + distortion_data.meters_per_tan_angle_at_center = 0.036f; 472 + distortion_data.max_r = 1.0f; 473 + 474 + distortion_data.k[0] = 1.003f; 475 + distortion_data.k[1] = 1.02f; 476 + distortion_data.k[2] = 1.042f; 477 + distortion_data.k[3] = 1.066f; 478 + distortion_data.k[4] = 1.094f; 479 + distortion_data.k[5] = 1.126f; 480 + distortion_data.k[6] = 1.162f; 481 + distortion_data.k[7] = 1.203f; 482 + distortion_data.k[8] = 1.25f; 483 + distortion_data.k[9] = 1.31f; 484 + distortion_data.k[10] = 1.38f; 485 + 486 + distortion_data.chromatic_abberation[0] = -0.015f; 487 + distortion_data.chromatic_abberation[1] = -0.02f; 488 + distortion_data.chromatic_abberation[2] = 0.025f; 489 + distortion_data.chromatic_abberation[3] = 0.02f; 490 + 491 + hmd->lens_distortions[i].data.lcsv_catmull_rom_10 = distortion_data; 492 + 493 + i += 1; 494 + 495 + // TODO: let the user specify which distortion is in use with an env var, 496 + // and interpolate the distortions for the user's specific eye relief setting 497 + hmd->distortion_in_use = 1; 498 + }
+40
src/xrt/drivers/rift/rift_distortion.h
··· 1 + // Copyright 2025, Beyley Cardellio 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Public functions for the Oculus Rift distortion correction. 6 + * @author Beyley Cardellio <ep1cm1n10n123@gmail.com> 7 + * @ingroup drv_rift 8 + */ 9 + 10 + #include "rift_interface.h" 11 + 12 + struct rift_distortion_render_info 13 + { 14 + struct rift_lens_distortion *distortion; 15 + struct xrt_vec2 lens_center; 16 + struct xrt_vec2 tan_eye_angle_scale; 17 + struct xrt_vec2 pixels_per_tan_angle_at_center; 18 + }; 19 + 20 + struct rift_distortion_render_info 21 + rift_get_distortion_render_info(struct rift_hmd *hmd, uint32_t view); 22 + 23 + struct xrt_vec3 24 + rift_distortion_distance_scale_squared_split_chroma(struct rift_lens_distortion *lens_distortion, 25 + float distance_squared); 26 + 27 + struct rift_viewport_fov_tan 28 + rift_calculate_fov_from_hmd(struct rift_hmd *hmd, struct rift_distortion_render_info *distortion, uint32_t view); 29 + 30 + struct rift_scale_and_offset 31 + rift_calculate_ndc_scale_and_offset_from_fov(struct rift_viewport_fov_tan *fov); 32 + 33 + struct rift_scale_and_offset 34 + rift_calculate_uv_scale_and_offset_from_ndc_scale_and_offset(struct rift_scale_and_offset eye_to_source_ndc); 35 + 36 + void 37 + rift_fill_in_default_distortions(struct rift_hmd *hmd); 38 + 39 + bool 40 + rift_hmd_compute_distortion(struct xrt_device *dev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result);
+83 -169
src/xrt/drivers/rift/rift_hmd.c
··· 16 16 #include "os/os_time.h" 17 17 #include "xrt/xrt_defines.h" 18 18 #include "xrt/xrt_device.h" 19 + 19 20 #include "rift_interface.h" 21 + #include "rift_distortion.h" 20 22 21 23 #include "math/m_relation_history.h" 22 24 #include "math/m_api.h" 25 + #include "math/m_vec2.h" 23 26 #include "math/m_mathinclude.h" // IWYU pragma: keep 24 27 25 28 #include "util/u_debug.h" ··· 41 44 * Structs and defines. 42 45 * 43 46 */ 44 - 45 - /// Casting helper function 46 - static inline struct rift_hmd * 47 - rift_hmd(struct xrt_device *xdev) 48 - { 49 - return (struct rift_hmd *)xdev; 50 - } 51 47 52 48 DEBUG_GET_ONCE_LOG_OPTION(rift_log, "RIFT_LOG", U_LOGGING_WARN) 53 49 ··· 96 92 struct dk2_report_keepalive_mux report = {0, IN_REPORT_DK2, 97 93 KEEPALIVE_INTERVAL_NS / 1000000}; // convert ns to ms 98 94 99 - int result = 100 - rift_send_report(hmd, FEATURE_REPORT_KEEPALIVE_MUX, &report, sizeof(report)); 95 + int result = rift_send_report(hmd, FEATURE_REPORT_KEEPALIVE_MUX, &report, sizeof(report)); 101 96 102 97 if (result < 0) { 103 98 return result; ··· 236 231 struct xrt_fov *out_fovs, 237 232 struct xrt_pose *out_poses) 238 233 { 239 - /* 240 - * For HMDs you can call this function or directly set 241 - * the `get_view_poses` function on the device to it. 242 - */ 243 - return u_device_get_view_poses( // 244 - xdev, // 245 - default_eye_relation, // 246 - at_timestamp_ns, // 247 - view_count, // 248 - out_head_relation, // 249 - out_fovs, // 250 - out_poses); // 234 + struct rift_hmd *hmd = rift_hmd(xdev); 235 + 236 + return u_device_get_view_poses(xdev, // 237 + &(struct xrt_vec3){hmd->extra_display_info.icd, 0.0f, 0.0f}, // 238 + at_timestamp_ns, // 239 + view_count, // 240 + out_head_relation, // 241 + out_fovs, // 242 + out_poses); 251 243 } 252 244 253 245 static xrt_result_t ··· 262 254 } 263 255 264 256 static float 265 - rift_catmull_rom_spline(struct rift_catmull_rom_distortion_data *catmull, float scaled_value) 266 - { 267 - float scaled_value_floor = floorf(scaled_value); 268 - scaled_value_floor = CLAMP(scaled_value_floor, 0, CATMULL_COEFFICIENTS - 1); 269 - 270 - float t = scaled_value - scaled_value_floor; 271 - int k = (int)scaled_value_floor; 272 - 273 - float p0, p1, m0, m1; 274 - switch (k) { 275 - case 0: 276 - p0 = 1.0f; 277 - m0 = (catmull->k[1] - catmull->k[0]); 278 - p1 = catmull->k[1]; 279 - m1 = 0.5f * (catmull->k[2] - catmull->k[0]); 280 - break; 281 - default: 282 - p0 = catmull->k[k]; 283 - m0 = 0.5f * (catmull->k[k + 1] - catmull->k[k - 1]); 284 - p1 = catmull->k[k + 1]; 285 - m1 = 0.5f * (catmull->k[k + 2] - catmull->k[k]); 286 - break; 287 - case CATMULL_COEFFICIENTS - 2: 288 - p0 = catmull->k[CATMULL_COEFFICIENTS - 2]; 289 - m0 = 0.5f * (catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]); 290 - p1 = catmull->k[CATMULL_COEFFICIENTS - 1]; 291 - m1 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; 292 - break; 293 - case CATMULL_COEFFICIENTS - 1: 294 - p0 = catmull->k[CATMULL_COEFFICIENTS - 1]; 295 - m0 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; 296 - p1 = p0 + m0; 297 - m1 = m0; 298 - break; 299 - } 300 - 301 - float omt = 1.0f - t; 302 - 303 - float res = (p0 * (1.0f + 2.0f * t) + m0 * t) * omt * omt + (p1 * (1.0f + 2.0f * omt) - m1 * omt) * t * t; 304 - 305 - return res; 306 - } 307 - 308 - static float 309 - rift_distortion_distance_squared(struct rift_lens_distortion *lens_distortion, float distance_squared) 310 - { 311 - float scale = 1.0f; 312 - 313 - switch (lens_distortion->distortion_version) { 314 - case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { 315 - struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; 316 - 317 - float scaled_distance_squared = 318 - (float)(CATMULL_COEFFICIENTS - 1) * distance_squared / (data.max_r * data.max_r); 319 - 320 - return rift_catmull_rom_spline(&data, scaled_distance_squared); 321 - } 322 - default: return scale; 323 - } 324 - } 325 - 326 - static struct xrt_vec3 327 - rift_distortion_distance_squared_split_chroma(struct rift_lens_distortion *lens_distortion, float distance_squared) 328 - { 329 - float scale = rift_distortion_distance_squared(lens_distortion, distance_squared); 330 - 331 - struct xrt_vec3 scale_split; 332 - scale_split.x = scale; 333 - scale_split.y = scale; 334 - scale_split.z = scale; 335 - 336 - switch (lens_distortion->distortion_version) { 337 - case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { 338 - struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; 339 - 340 - scale_split.x *= 1.0f + data.chromatic_abberation[0] + distance_squared * data.chromatic_abberation[1]; 341 - scale_split.z *= 1.0f + data.chromatic_abberation[2] + distance_squared * data.chromatic_abberation[3]; 342 - break; 343 - } 344 - } 345 - 346 - return scale_split; 347 - } 348 - 349 - static bool 350 - rift_hmd_compute_distortion(struct xrt_device *dev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result) 351 - { 352 - #define ZERO_ONE_TO_N_ONE_ONE(x) ((x * 2) - 1) 353 - #define N_ONE_ONE_TO_ZERO_ONE(x) ((x + 1) / 2) 354 - 355 - struct rift_hmd *hmd = rift_hmd(dev); 356 - 357 - struct rift_lens_distortion *distortion = &hmd->lens_distortions[0]; 358 - 359 - float display_width_meters = (float)hmd->display_info.display_width / 1000000.0f; 360 - float display_height_meters = (float)hmd->display_info.display_height / 1000000.0f; 361 - 362 - float tan_eye_angle_scale_x = 363 - display_width_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center * 0.25f; 364 - float tan_eye_angle_scale_y = 365 - display_height_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center * 0.5f; 366 - 367 - u = ZERO_ONE_TO_N_ONE_ONE(u) * tan_eye_angle_scale_x; 368 - v = ZERO_ONE_TO_N_ONE_ONE(v) * tan_eye_angle_scale_y; 369 - 370 - float distance_squared = fabsf(u * u + v * v); 371 - 372 - struct xrt_vec3 chroma_distortions = 373 - rift_distortion_distance_squared_split_chroma(distortion, distance_squared); 374 - 375 - out_result->r = (struct xrt_vec2){N_ONE_ONE_TO_ZERO_ONE(u * chroma_distortions.x), 376 - N_ONE_ONE_TO_ZERO_ONE(v * chroma_distortions.x)}; 377 - out_result->g = (struct xrt_vec2){N_ONE_ONE_TO_ZERO_ONE(u * chroma_distortions.y), 378 - N_ONE_ONE_TO_ZERO_ONE(v * chroma_distortions.y)}; 379 - out_result->b = (struct xrt_vec2){N_ONE_ONE_TO_ZERO_ONE(u * chroma_distortions.z), 380 - N_ONE_ONE_TO_ZERO_ONE(v * chroma_distortions.z)}; 381 - 382 - return true; 383 - #undef ZERO_ONE_TO_N_ONE_ONE 384 - #undef N_ONE_ONE_TO_ZERO_ONE 385 - } 386 - 387 - static float 388 257 rift_decode_fixed_point_uint16(uint16_t value, uint16_t zero_value, int fractional_bits) 389 258 { 390 259 float value_float = (float)value; ··· 402 271 case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { 403 272 struct rift_catmull_rom_distortion_report_data report_data = report->data.lcsv_catmull_rom_10; 404 273 struct rift_catmull_rom_distortion_data data; 274 + 275 + out->eye_relief = MICROMETERS_TO_METERS(report_data.eye_relief); 405 276 406 277 for (uint16_t i = 0; i < CATMULL_COEFFICIENTS; i += 1) { 407 278 data.k[i] = rift_decode_fixed_point_uint16(report_data.k[i], 0, 14); ··· 614 485 } 615 486 HMD_INFO(hmd, "After writing, HMD has config flags: %X", hmd->config.config_flags); 616 487 617 - // get the lens distortions 618 - struct rift_lens_distortion_report lens_distortion; 619 - result = rift_get_lens_distortion(hmd, &lens_distortion); 620 - if (result < 0) { 621 - HMD_ERROR(hmd, "Failed to get lens distortion, reason %d", result); 622 - goto error; 623 - } 624 - 625 - hmd->num_lens_distortions = lens_distortion.num_distortions; 626 - hmd->lens_distortions = calloc(lens_distortion.num_distortions, sizeof(struct rift_lens_distortion)); 627 - 628 - rift_parse_distortion_report(&lens_distortion, &hmd->lens_distortions[lens_distortion.distortion_idx]); 629 - // TODO: actually verify we initialize all the distortions. if the headset is working correctly, this should 630 - // have happened, but you never know. 631 - for (uint16_t i = 1; i < hmd->num_lens_distortions; i++) { 488 + if (getenv("RIFT_USE_FIRMWARE_DISTORTION") != NULL) { 489 + // get the lens distortions 490 + struct rift_lens_distortion_report lens_distortion; 632 491 result = rift_get_lens_distortion(hmd, &lens_distortion); 633 492 if (result < 0) { 634 - HMD_ERROR(hmd, "Failed to get lens distortion idx %d, reason %d", i, result); 493 + HMD_ERROR(hmd, "Failed to get lens distortion, reason %d", result); 635 494 goto error; 636 495 } 637 496 497 + hmd->num_lens_distortions = lens_distortion.num_distortions; 498 + hmd->lens_distortions = calloc(lens_distortion.num_distortions, sizeof(struct rift_lens_distortion)); 499 + 638 500 rift_parse_distortion_report(&lens_distortion, &hmd->lens_distortions[lens_distortion.distortion_idx]); 501 + // TODO: actually verify we initialize all the distortions. if the headset is working correctly, this 502 + // should have happened, but you never know. 503 + for (uint16_t i = 1; i < hmd->num_lens_distortions; i++) { 504 + result = rift_get_lens_distortion(hmd, &lens_distortion); 505 + if (result < 0) { 506 + HMD_ERROR(hmd, "Failed to get lens distortion idx %d, reason %d", i, result); 507 + goto error; 508 + } 509 + 510 + rift_parse_distortion_report(&lens_distortion, 511 + &hmd->lens_distortions[lens_distortion.distortion_idx]); 512 + } 513 + 514 + // TODO: pick the correct distortion for the eye relief setting the user has picked 515 + hmd->distortion_in_use = 0; 516 + } else { 517 + rift_fill_in_default_distortions(hmd); 639 518 } 640 519 520 + // fill in extra display info about the headset 521 + 522 + switch (hmd->variant) { 523 + case RIFT_VARIANT_DK2: 524 + hmd->extra_display_info.screen_gap_meters = 0.0f; 525 + hmd->extra_display_info.lens_diameter_meters = 0.04f; 526 + break; 527 + default: break; 528 + } 529 + 530 + // hardcode left eye, probably not ideal, but sure, why not 531 + struct rift_distortion_render_info distortion_render_info = rift_get_distortion_render_info(hmd, 0); 532 + hmd->extra_display_info.fov = rift_calculate_fov_from_hmd(hmd, &distortion_render_info, 0); 533 + hmd->extra_display_info.eye_to_source_ndc = 534 + rift_calculate_ndc_scale_and_offset_from_fov(&hmd->extra_display_info.fov); 535 + hmd->extra_display_info.eye_to_source_uv = 536 + rift_calculate_uv_scale_and_offset_from_ndc_scale_and_offset(hmd->extra_display_info.eye_to_source_ndc); 537 + 641 538 // This list should be ordered, most preferred first. 642 539 size_t idx = 0; 643 540 hmd->base.hmd->blend_modes[idx++] = XRT_BLEND_MODE_OPAQUE; ··· 674 571 struct u_device_simple_info info; 675 572 info.display.w_pixels = hmd->display_info.resolution_x; 676 573 info.display.h_pixels = hmd->display_info.resolution_y; 677 - info.display.w_meters = (float)hmd->display_info.display_width / 1000000.0f; // micrometers -> meters 678 - info.display.h_meters = (float)hmd->display_info.display_height / 1000000.0f; 574 + info.display.w_meters = MICROMETERS_TO_METERS(hmd->display_info.display_width); 575 + info.display.h_meters = MICROMETERS_TO_METERS(hmd->display_info.display_height); 679 576 680 - info.lens_horizontal_separation_meters = (float)hmd->display_info.lens_separation / 1000000.0f; 577 + info.lens_horizontal_separation_meters = MICROMETERS_TO_METERS(hmd->display_info.lens_separation); 578 + info.lens_vertical_position_meters = MICROMETERS_TO_METERS(hmd->display_info.center_v); 681 579 682 - // TODO: this is per eye on the headset, but we're just taking the left eye for this, we should be using both 683 - // eyes, ideally 684 - info.lens_vertical_position_meters = (float)hmd->display_info.lens_distance_l / 1000000.0f; 580 + hmd->extra_display_info.icd = info.lens_horizontal_separation_meters; 685 581 686 - // TODO: calculate this 687 - info.fov[0] = (float)(93.0 * (M_PI / 180.0)); 688 - info.fov[1] = (float)(99.0 * (M_PI / 180.0)); 582 + for (int i = 0; i < 2; i++) { 583 + info.fov[i] = 93; 584 + } 689 585 690 586 if (!u_device_setup_split_side_by_side(&hmd->base, &info)) { 691 587 HMD_ERROR(hmd, "Failed to setup basic device info"); 692 588 goto error; 589 + } 590 + 591 + switch (hmd->variant) { 592 + case RIFT_VARIANT_DK2: 593 + // TODO: figure out how to calculate this programatically, right now this is hardcoded with data dumped 594 + // from oculus' OpenXR runtime 595 + hmd->base.hmd->distortion.fov[0].angle_up = 0.92667186; 596 + hmd->base.hmd->distortion.fov[0].angle_down = -0.92667186; 597 + hmd->base.hmd->distortion.fov[0].angle_left = -0.8138836; 598 + hmd->base.hmd->distortion.fov[0].angle_right = 0.82951474; 599 + 600 + hmd->base.hmd->distortion.fov[1].angle_up = 0.92667186; 601 + hmd->base.hmd->distortion.fov[1].angle_down = -0.92667186; 602 + hmd->base.hmd->distortion.fov[1].angle_left = -0.82951474; 603 + hmd->base.hmd->distortion.fov[1].angle_right = 0.8138836; 604 + break; 605 + default: break; 693 606 } 694 607 695 608 // Just put an initial identity value in the tracker ··· 718 631 // Setup variable tracker: Optional but useful for debugging 719 632 u_var_add_root(hmd, "Rift HMD", true); 720 633 u_var_add_log_level(hmd, &hmd->log_level, "log_level"); 634 + m_imu_3dof_add_vars(&hmd->fusion, hmd, "3dof_"); 721 635 722 636 return hmd; 723 637 error:
+55 -4
src/xrt/drivers/rift/rift_interface.h
··· 20 20 #include "util/u_logging.h" 21 21 22 22 #include "math/m_imu_3dof.h" 23 + #include "math/m_api.h" 24 + #include "math/m_mathinclude.h" 23 25 24 26 #include <stdlib.h> 25 27 #include <stdio.h> ··· 28 30 extern "C" { 29 31 #endif 30 32 31 - #define REPORT_MAX_SIZE 69 // max size of a feature report (FEATURE_REPORT_CALIBRATE) 33 + #define REPORT_MAX_SIZE 69 // max size of a feature report (FEATURE_REPORT_CALIBRATE) 32 34 #define KEEPALIVE_INTERVAL_NS 10000000000 // 10 seconds 33 - #define KEEPALIVE_SEND_RATE_NS (KEEPALIVE_INTERVAL_NS * 19) / 20 // give a 5% breathing room (at 10 seconds, this is 500 milliseconds of breathing room) 35 + #define KEEPALIVE_SEND_RATE_NS \ 36 + (KEEPALIVE_INTERVAL_NS * 19) / \ 37 + 20 // give a 5% breathing room (at 10 seconds, this is 500 milliseconds of breathing room) 34 38 #define IMU_SAMPLE_RATE 1000 39 + 40 + #define DEG_TO_RAD(DEG) (DEG * M_PI / 180.0) 41 + #define MICROMETERS_TO_METERS(microns) (float)microns / 1000000.0f 42 + 43 + // value taken from LibOVR 0.4.4 44 + #define DEFAULT_EXTRA_EYE_ROTATION DEG_TO_RAD(30.0f) 35 45 36 46 enum rift_feature_reports 37 47 { ··· 148 158 uint32_t center_v; 149 159 // the separation between the two lenses, in micrometers 150 160 uint32_t lens_separation; 151 - uint32_t lens_distance_l; 152 - uint32_t lens_distance_r; 161 + uint32_t lens_distance[2]; 153 162 float distortion[6]; 154 163 } RIFT_PACKED; 155 164 ··· 239 248 { 240 249 // the version of the lens distortion data 241 250 uint16_t distortion_version; 251 + // eye relief setting, in meters from surface of lens 252 + float eye_relief; 242 253 243 254 union { 244 255 struct rift_catmull_rom_distortion_data lcsv_catmull_rom_10; 245 256 } RIFT_PACKED data; 246 257 }; 247 258 259 + struct rift_scale_and_offset 260 + { 261 + struct xrt_vec2 scale; 262 + struct xrt_vec2 offset; 263 + }; 264 + 265 + struct rift_viewport_fov_tan 266 + { 267 + float up_tan; 268 + float down_tan; 269 + float left_tan; 270 + float right_tan; 271 + }; 272 + 273 + struct rift_extra_display_info 274 + { 275 + // gap left between the two eyes 276 + float screen_gap_meters; 277 + // the diameter of the lenses, may need to be extended to an array 278 + float lens_diameter_meters; 279 + // ipd of the headset 280 + float icd; 281 + 282 + // the fov of the headset 283 + struct rift_viewport_fov_tan fov; 284 + // mapping from tan-angle space to target NDC space 285 + struct rift_scale_and_offset eye_to_source_ndc; 286 + struct rift_scale_and_offset eye_to_source_uv; 287 + }; 288 + 248 289 enum rift_variant 249 290 { 250 291 RIFT_VARIANT_DK1, ··· 296 337 297 338 struct rift_lens_distortion *lens_distortions; 298 339 uint16_t num_lens_distortions; 340 + uint16_t distortion_in_use; 341 + 342 + struct rift_extra_display_info extra_display_info; 299 343 }; 344 + 345 + /// Casting helper function 346 + static inline struct rift_hmd * 347 + rift_hmd(struct xrt_device *xdev) 348 + { 349 + return (struct rift_hmd *)xdev; 350 + } 300 351 301 352 struct rift_hmd * 302 353 rift_hmd_create(struct os_hid_device *dev, enum rift_variant variant, char *device_name, char *serial_number);