From 1250f8a9ff52b9be72d995cb93bf8a567b3ee3fc Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Sun, 2 Mar 2025 14:48:53 -0800 Subject: [PATCH] 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: --- src/xrt/drivers/CMakeLists.txt | 1 + src/xrt/drivers/rift/rift_distortion.c | 498 +++++++++++++++++++++++++ src/xrt/drivers/rift/rift_distortion.h | 40 ++ src/xrt/drivers/rift/rift_hmd.c | 252 +++++-------- src/xrt/drivers/rift/rift_interface.h | 59 ++- 5 files changed, 677 insertions(+), 173 deletions(-) create mode 100644 src/xrt/drivers/rift/rift_distortion.c create mode 100644 src/xrt/drivers/rift/rift_distortion.h diff --git a/src/xrt/drivers/CMakeLists.txt b/src/xrt/drivers/CMakeLists.txt index 8d6874891..113cbf582 100644 --- a/src/xrt/drivers/CMakeLists.txt +++ b/src/xrt/drivers/CMakeLists.txt @@ -244,6 +244,7 @@ if(XRT_BUILD_DRIVER_RIFT) add_library( drv_rift STATIC rift/rift_hmd.c + rift/rift_distortion.c rift/rift_prober.c ) target_include_directories(drv_rift SYSTEM PRIVATE) diff --git a/src/xrt/drivers/rift/rift_distortion.c b/src/xrt/drivers/rift/rift_distortion.c new file mode 100644 index 000000000..1ada3ff40 --- /dev/null +++ b/src/xrt/drivers/rift/rift_distortion.c @@ -0,0 +1,498 @@ +// Copyright 2025, Beyley Cardellio +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Handles calculating the distortion profile for Oculus Rift devices. + * @author Beyley Cardellio + * @ingroup drv_rift + */ + +#include "rift_distortion.h" + +#include "math/m_vec2.h" + +#define HMD_TRACE(hmd, ...) U_LOG_XDEV_IFL_T(&hmd->base, hmd->log_level, __VA_ARGS__) +#define HMD_DEBUG(hmd, ...) U_LOG_XDEV_IFL_D(&hmd->base, hmd->log_level, __VA_ARGS__) +#define HMD_INFO(hmd, ...) U_LOG_XDEV_IFL_I(&hmd->base, hmd->log_level, __VA_ARGS__) +#define HMD_WARN(hmd, ...) U_LOG_XDEV_IFL_W(&hmd->base, hmd->log_level, __VA_ARGS__) +#define HMD_ERROR(hmd, ...) U_LOG_XDEV_IFL_E(&hmd->base, hmd->log_level, __VA_ARGS__) + +static float +rift_catmull_rom_spline(struct rift_catmull_rom_distortion_data *catmull, float scaled_value) +{ + float scaled_value_floor = floorf(scaled_value); + scaled_value_floor = CLAMP(scaled_value_floor, 0, CATMULL_COEFFICIENTS - 1); + + float t = scaled_value - scaled_value_floor; + int k = (int)scaled_value_floor; + + float p0, p1, m0, m1; + switch (k) { + case 0: + p0 = 1.0f; + m0 = (catmull->k[1] - catmull->k[0]); + p1 = catmull->k[1]; + m1 = 0.5f * (catmull->k[2] - catmull->k[0]); + break; + default: + p0 = catmull->k[k]; + m0 = 0.5f * (catmull->k[k + 1] - catmull->k[k - 1]); + p1 = catmull->k[k + 1]; + m1 = 0.5f * (catmull->k[k + 2] - catmull->k[k]); + break; + case CATMULL_COEFFICIENTS - 2: + p0 = catmull->k[CATMULL_COEFFICIENTS - 2]; + m0 = 0.5f * (catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]); + p1 = catmull->k[CATMULL_COEFFICIENTS - 1]; + m1 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; + break; + case CATMULL_COEFFICIENTS - 1: + p0 = catmull->k[CATMULL_COEFFICIENTS - 1]; + m0 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; + p1 = p0 + m0; + m1 = m0; + break; + } + + float omt = 1.0f - t; + + float res = (p0 * (1.0f + 2.0f * t) + m0 * t) * omt * omt + (p1 * (1.0f + 2.0f * omt) - m1 * omt) * t * t; + + return res; +} + +static float +rift_distortion_distance_scale_squared(struct rift_lens_distortion *lens_distortion, float distance_squared) +{ + float scale = 1.0f; + + switch (lens_distortion->distortion_version) { + case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { + struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; + + float scaled_distance_squared = + (float)(CATMULL_COEFFICIENTS - 1) * distance_squared / (data.max_r * data.max_r); + + return rift_catmull_rom_spline(&data, scaled_distance_squared); + } + default: return scale; + } +} + +struct xrt_vec3 +rift_distortion_distance_scale_squared_split_chroma(struct rift_lens_distortion *lens_distortion, + float distance_squared) +{ + float scale = rift_distortion_distance_scale_squared(lens_distortion, distance_squared); + + struct xrt_vec3 scale_split; + scale_split.x = scale; + scale_split.y = scale; + scale_split.z = scale; + + switch (lens_distortion->distortion_version) { + case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { + struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; + + scale_split.x *= 1.0f + data.chromatic_abberation[0] + distance_squared * data.chromatic_abberation[1]; + scale_split.z *= 1.0f + data.chromatic_abberation[2] + distance_squared * data.chromatic_abberation[3]; + break; + } + } + + return scale_split; +} + +struct rift_distortion_render_info +rift_get_distortion_render_info(struct rift_hmd *hmd, uint32_t view) +{ + struct rift_lens_distortion *distortion = &hmd->lens_distortions[hmd->distortion_in_use]; + + float display_width_meters = MICROMETERS_TO_METERS(hmd->display_info.display_width); + float display_height_meters = MICROMETERS_TO_METERS(hmd->display_info.display_height); + + float lens_separation_meters = MICROMETERS_TO_METERS(hmd->display_info.lens_separation); + float center_from_top_meters = MICROMETERS_TO_METERS(hmd->display_info.center_v); + + struct xrt_vec2 pixels_per_meter; + pixels_per_meter.x = + (float)hmd->display_info.resolution_x / (display_width_meters - hmd->extra_display_info.screen_gap_meters); + pixels_per_meter.y = (float)hmd->display_info.resolution_y / display_height_meters; + + struct xrt_vec2 pixels_per_tan_angle_at_center; + pixels_per_tan_angle_at_center.x = + pixels_per_meter.x * distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center; + pixels_per_tan_angle_at_center.y = + pixels_per_meter.y * distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center; + + struct xrt_vec2 tan_eye_angle_scale; + tan_eye_angle_scale.x = + 0.25f * (display_width_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center); + tan_eye_angle_scale.y = + 0.5f * (display_height_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center); + + float visible_width_of_one_eye = 0.5f * (display_width_meters - hmd->extra_display_info.screen_gap_meters); + float center_from_left_meters = (display_width_meters - lens_separation_meters) * 0.5f; + + struct xrt_vec2 lens_center; + lens_center.x = (center_from_left_meters / visible_width_of_one_eye) * 2.0f - 1.0f; + lens_center.y = (center_from_top_meters / display_height_meters) * 2.0f - 1.0f; + + // if we're on the right eye, flip the lens center + if (view != 0) { + lens_center.x *= -1; + } + + return (struct rift_distortion_render_info){ + .distortion = distortion, + .lens_center = lens_center, + .pixels_per_tan_angle_at_center = pixels_per_tan_angle_at_center, + .tan_eye_angle_scale = tan_eye_angle_scale, + }; +} + +static struct rift_viewport_fov_tan +rift_calculate_fov_from_eye_position( + float eye_relief, float offset_to_right, float offset_downwards, float lens_diameter, float extra_eye_rotation) +{ + float half_lens_diameter = lens_diameter * 0.5f; + + struct rift_viewport_fov_tan fov_port; + fov_port.up_tan = (half_lens_diameter + offset_downwards) / eye_relief; + fov_port.down_tan = (half_lens_diameter - offset_downwards) / eye_relief; + fov_port.left_tan = (half_lens_diameter + offset_to_right) / eye_relief; + fov_port.right_tan = (half_lens_diameter - offset_to_right) / eye_relief; + + if (extra_eye_rotation > 0.0f) { + extra_eye_rotation = CLAMP(extra_eye_rotation, 0, 30.0f); + + float eyeball_center_to_pupil = 0.0135f; + float eyeball_lateral_pull = 0.001f * (extra_eye_rotation / DEG_TO_RAD(30.0f)); + float extra_translation = eyeball_center_to_pupil * sinf(extra_eye_rotation) + eyeball_lateral_pull; + float extra_relief = eyeball_center_to_pupil * (1.0f - cosf(extra_eye_rotation)); + + fov_port.up_tan = fmaxf(fov_port.up_tan, (half_lens_diameter + offset_downwards + extra_translation) / + (eye_relief + extra_relief)); + fov_port.down_tan = + fmaxf(fov_port.down_tan, + (half_lens_diameter - offset_downwards + extra_translation) / (eye_relief + extra_relief)); + fov_port.left_tan = + fmaxf(fov_port.left_tan, + (half_lens_diameter + offset_to_right + extra_translation) / (eye_relief + extra_relief)); + fov_port.right_tan = + fmaxf(fov_port.right_tan, + (half_lens_diameter - offset_to_right + extra_translation) / (eye_relief + extra_relief)); + } + + return fov_port; +} + +static struct xrt_vec2 +rift_transform_screen_ndc_to_tan_fov_space(struct rift_distortion_render_info *distortion, struct xrt_vec2 screen_ndc) +{ + struct xrt_vec2 tan_eye_angle_distorted = { + (screen_ndc.x - distortion->lens_center.x) * distortion->tan_eye_angle_scale.x, + (screen_ndc.y - distortion->lens_center.y) * distortion->tan_eye_angle_scale.y}; + + float distance_squared = (tan_eye_angle_distorted.x * tan_eye_angle_distorted.x) + + (tan_eye_angle_distorted.y * tan_eye_angle_distorted.y); + + float distortion_scale = rift_distortion_distance_scale_squared(distortion->distortion, distance_squared); + + return m_vec2_mul_scalar(tan_eye_angle_distorted, distortion_scale); +} + +static struct rift_viewport_fov_tan +rift_fov_find_range(struct xrt_vec2 from, + struct xrt_vec2 to, + int num_steps, + struct rift_distortion_render_info *distortion) +{ + struct rift_viewport_fov_tan fov_port = {0.0f, 0.0f, 0.0f, 0.0f}; + + float step_scale = 1.0f / (num_steps - 1); + for (int step = 0; step < num_steps; step++) { + float lerp_factor = step_scale * (float)step; + struct xrt_vec2 sample = m_vec2_add(from, m_vec2_mul_scalar(m_vec2_sub(to, from), lerp_factor)); + struct xrt_vec2 tan_eye_angle = rift_transform_screen_ndc_to_tan_fov_space(distortion, sample); + + fov_port.left_tan = fmaxf(fov_port.left_tan, -tan_eye_angle.x); + fov_port.right_tan = fmaxf(fov_port.right_tan, tan_eye_angle.x); + fov_port.up_tan = fmaxf(fov_port.up_tan, -tan_eye_angle.y); + fov_port.down_tan = fmaxf(fov_port.down_tan, tan_eye_angle.y); + } + + return fov_port; +} + +static struct rift_viewport_fov_tan +rift_get_physical_screen_fov(struct rift_distortion_render_info *distortion) +{ + struct xrt_vec2 lens_center = distortion->lens_center; + + struct rift_viewport_fov_tan left_fov_port = + rift_fov_find_range(lens_center, (struct xrt_vec2){-1.0f, lens_center.y}, 10, distortion); + + struct rift_viewport_fov_tan right_fov_port = + rift_fov_find_range(lens_center, (struct xrt_vec2){1.0f, lens_center.y}, 10, distortion); + + struct rift_viewport_fov_tan up_fov_port = + rift_fov_find_range(lens_center, (struct xrt_vec2){lens_center.x, -1.0f}, 10, distortion); + + struct rift_viewport_fov_tan down_fov_port = + rift_fov_find_range(lens_center, (struct xrt_vec2){lens_center.x, 1.0f}, 10, distortion); + + return (struct rift_viewport_fov_tan){.left_tan = left_fov_port.left_tan, + .right_tan = right_fov_port.right_tan, + .up_tan = up_fov_port.up_tan, + .down_tan = down_fov_port.down_tan}; +} + +static struct rift_viewport_fov_tan +rift_clamp_fov_to_physical_screen_fov(struct rift_distortion_render_info *distortion, + struct rift_viewport_fov_tan fov_port) +{ + struct rift_viewport_fov_tan result_fov_port; + struct rift_viewport_fov_tan physical_fov_port = rift_get_physical_screen_fov(distortion); + + result_fov_port.left_tan = fminf(fov_port.left_tan, physical_fov_port.left_tan); + result_fov_port.right_tan = fminf(fov_port.right_tan, physical_fov_port.right_tan); + result_fov_port.up_tan = fminf(fov_port.up_tan, physical_fov_port.up_tan); + result_fov_port.down_tan = fminf(fov_port.down_tan, physical_fov_port.down_tan); + + return result_fov_port; +} + +struct rift_viewport_fov_tan +rift_calculate_fov_from_hmd(struct rift_hmd *hmd, struct rift_distortion_render_info *distortion, uint32_t view) +{ + float eye_relief = distortion->distortion->eye_relief; + + struct rift_viewport_fov_tan fov_port; + fov_port = rift_calculate_fov_from_eye_position(eye_relief, 0, 0, hmd->extra_display_info.lens_diameter_meters, + DEFAULT_EXTRA_EYE_ROTATION); + + fov_port = rift_clamp_fov_to_physical_screen_fov(distortion, fov_port); + + return fov_port; +} + +struct rift_scale_and_offset +rift_calculate_ndc_scale_and_offset_from_fov(struct rift_viewport_fov_tan *fov) +{ + struct xrt_vec2 proj_scale = {.x = 2.0f / (fov->left_tan + fov->right_tan), + .y = 2.0f / (fov->up_tan + fov->down_tan)}; + + struct xrt_vec2 proj_offset = {.x = (fov->left_tan - fov->right_tan) * proj_scale.x * 0.5, + .y = (fov->up_tan - fov->down_tan) * proj_scale.y * 0.5f}; + + return (struct rift_scale_and_offset){.scale = proj_scale, .offset = proj_offset}; +} + +struct rift_scale_and_offset +rift_calculate_uv_scale_and_offset_from_ndc_scale_and_offset(struct rift_scale_and_offset eye_to_source_ndc) +{ + struct rift_scale_and_offset result = eye_to_source_ndc; + result.scale = m_vec2_mul_scalar(result.scale, 0.5f); + result.offset = m_vec2_add_scalar(m_vec2_mul_scalar(result.offset, 0.5f), 0.5f); + return result; +} + +static struct xrt_uv_triplet +rift_transform_screen_ndc_to_tan_fov_space_chroma(struct rift_distortion_render_info *distortion, + struct xrt_vec2 screen_ndc) +{ + struct xrt_vec2 tan_eye_angle_distorted = { + (screen_ndc.x - distortion->lens_center.x) * distortion->tan_eye_angle_scale.x, + (screen_ndc.y - distortion->lens_center.y) * distortion->tan_eye_angle_scale.y}; + + float distance_squared = (tan_eye_angle_distorted.x * tan_eye_angle_distorted.x) + + (tan_eye_angle_distorted.y * tan_eye_angle_distorted.y); + + struct xrt_vec3 distortion_scales = + rift_distortion_distance_scale_squared_split_chroma(distortion->distortion, distance_squared); + + return (struct xrt_uv_triplet){ + .r = {tan_eye_angle_distorted.x * distortion_scales.x, tan_eye_angle_distorted.y * distortion_scales.x}, + .g = {tan_eye_angle_distorted.x * distortion_scales.y, tan_eye_angle_distorted.y * distortion_scales.y}, + .b = {tan_eye_angle_distorted.x * distortion_scales.z, tan_eye_angle_distorted.y * distortion_scales.z}, + }; +} + +// unused math functions which may be useful in the future for stuff like calculating FOV. +// disabled to not give unused warnings +#if 0 +static float +rift_distortion(struct rift_lens_distortion *lens_distortion, float distance) +{ + return distance * rift_distortion_distance_scale_squared(lens_distortion, distance * distance); +} + +static float +rift_distortion_distance_inverse(struct rift_lens_distortion *lens_distortion, float distance) +{ + assert(distance <= 20.0f); + + float delta = distance * 0.25f; + + float s = distance * 0.25f; + float d = fabsf(distance - rift_distortion(lens_distortion, s)); + + for (int i = 0; i < 20; i++) { + float s_up = s + delta; + float s_down = s - delta; + float d_up = fabsf(distance - rift_distortion(lens_distortion, s_up)); + float d_down = fabsf(distance - rift_distortion(lens_distortion, s_down)); + + if (d_up < d) { + s = s_up; + d = d_up; + } else if (d_down < d) { + s = s_down; + d = d_down; + } else { + delta *= 0.5f; + } + } + + return s; +} + +static struct xrt_vec2 +rift_transform_tan_fov_space_to_render_target_tex_uv(struct rift_scale_and_offset *eye_to_source_uv, + struct xrt_vec2 tan_eye_angle) +{ + return m_vec2_add(m_vec2_mul(tan_eye_angle, eye_to_source_uv->scale), eye_to_source_uv->offset); +} + +static struct xrt_vec2 +rift_transform_render_target_ndc_to_tan_fov_space(struct rift_scale_and_offset *eye_to_source_ndc, struct xrt_vec2 ndc) +{ + return m_vec2_div(m_vec2_sub(ndc, eye_to_source_ndc->offset), eye_to_source_ndc->scale); +} + +static struct xrt_vec2 +rift_transform_tan_fov_space_to_screen_ndc(struct rift_distortion_render_info *distortion, + struct xrt_vec2 tan_eye_angle) +{ + float tan_eye_angle_radius = m_vec2_len(tan_eye_angle); + float tan_eye_angle_distorted_radius = + rift_distortion_distance_inverse(distortion->distortion, tan_eye_angle_radius); + + struct xrt_vec2 tan_eye_angle_distorted = tan_eye_angle; + if (tan_eye_angle_radius > 0) { + tan_eye_angle_distorted = + m_vec2_mul_scalar(tan_eye_angle, tan_eye_angle_distorted_radius / tan_eye_angle_radius); + } + + return m_vec2_add(m_vec2_div(tan_eye_angle_distorted, distortion->tan_eye_angle_scale), + distortion->lens_center); +} +#endif + +bool +rift_hmd_compute_distortion(struct xrt_device *dev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result) +{ +#define TO_NDC(x) ((x * 2) - 1) + + struct rift_hmd *hmd = rift_hmd(dev); + + struct xrt_vec2 source_ndc = {TO_NDC(u), TO_NDC(v)}; + + struct rift_distortion_render_info distortion_render_info = rift_get_distortion_render_info(hmd, 0); + + struct rift_scale_and_offset *eye_to_source_uv = &hmd->extra_display_info.eye_to_source_uv; + + struct xrt_uv_triplet tan_fov_chroma = + rift_transform_screen_ndc_to_tan_fov_space_chroma(&distortion_render_info, source_ndc); + +#if 0 // no distortion + struct xrt_uv_triplet sample_tex_coord = { + .r = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset), + .g = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset), + .b = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset)}; +#else + struct xrt_uv_triplet sample_tex_coord = { + .r = m_vec2_add(m_vec2_mul(tan_fov_chroma.r, eye_to_source_uv->scale), eye_to_source_uv->offset), + .g = m_vec2_add(m_vec2_mul(tan_fov_chroma.g, eye_to_source_uv->scale), eye_to_source_uv->offset), + .b = m_vec2_add(m_vec2_mul(tan_fov_chroma.b, eye_to_source_uv->scale), eye_to_source_uv->offset)}; +#endif + + *out_result = sample_tex_coord; + + return true; + +#undef TO_NDC +} + +void +rift_fill_in_default_distortions(struct rift_hmd *hmd) +{ + hmd->num_lens_distortions = 2; + hmd->lens_distortions = calloc(hmd->num_lens_distortions, sizeof(struct rift_lens_distortion)); + + uint16_t i = 0; + + struct rift_catmull_rom_distortion_data distortion_data; + + // TODO: dump these from the latest oculus runtime + // TODO: select the right distorions based on rift variant, right now this is hard-coded to the DK2 + + hmd->lens_distortions[i].distortion_version = RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1; + hmd->lens_distortions[i].eye_relief = 0.008f; + + distortion_data.meters_per_tan_angle_at_center = 0.036f; + distortion_data.max_r = 1.0f; + + distortion_data.k[0] = 1.003f; + distortion_data.k[1] = 1.02f; + distortion_data.k[2] = 1.042f; + distortion_data.k[3] = 1.066f; + distortion_data.k[4] = 1.094f; + distortion_data.k[5] = 1.126f; + distortion_data.k[6] = 1.162f; + distortion_data.k[7] = 1.203f; + distortion_data.k[8] = 1.25f; + distortion_data.k[9] = 1.31f; + distortion_data.k[10] = 1.38f; + + distortion_data.chromatic_abberation[0] = -0.0112f; + distortion_data.chromatic_abberation[1] = -0.015f; + distortion_data.chromatic_abberation[2] = 0.0187f; + distortion_data.chromatic_abberation[3] = 0.015f; + + hmd->lens_distortions[i].data.lcsv_catmull_rom_10 = distortion_data; + + i += 1; + + hmd->lens_distortions[i].distortion_version = RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1; + hmd->lens_distortions[i].eye_relief = 0.018f; + + distortion_data.meters_per_tan_angle_at_center = 0.036f; + distortion_data.max_r = 1.0f; + + distortion_data.k[0] = 1.003f; + distortion_data.k[1] = 1.02f; + distortion_data.k[2] = 1.042f; + distortion_data.k[3] = 1.066f; + distortion_data.k[4] = 1.094f; + distortion_data.k[5] = 1.126f; + distortion_data.k[6] = 1.162f; + distortion_data.k[7] = 1.203f; + distortion_data.k[8] = 1.25f; + distortion_data.k[9] = 1.31f; + distortion_data.k[10] = 1.38f; + + distortion_data.chromatic_abberation[0] = -0.015f; + distortion_data.chromatic_abberation[1] = -0.02f; + distortion_data.chromatic_abberation[2] = 0.025f; + distortion_data.chromatic_abberation[3] = 0.02f; + + hmd->lens_distortions[i].data.lcsv_catmull_rom_10 = distortion_data; + + i += 1; + + // TODO: let the user specify which distortion is in use with an env var, + // and interpolate the distortions for the user's specific eye relief setting + hmd->distortion_in_use = 1; +} \ No newline at end of file diff --git a/src/xrt/drivers/rift/rift_distortion.h b/src/xrt/drivers/rift/rift_distortion.h new file mode 100644 index 000000000..1d10da57d --- /dev/null +++ b/src/xrt/drivers/rift/rift_distortion.h @@ -0,0 +1,40 @@ +// Copyright 2025, Beyley Cardellio +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Public functions for the Oculus Rift distortion correction. + * @author Beyley Cardellio + * @ingroup drv_rift + */ + +#include "rift_interface.h" + +struct rift_distortion_render_info +{ + struct rift_lens_distortion *distortion; + struct xrt_vec2 lens_center; + struct xrt_vec2 tan_eye_angle_scale; + struct xrt_vec2 pixels_per_tan_angle_at_center; +}; + +struct rift_distortion_render_info +rift_get_distortion_render_info(struct rift_hmd *hmd, uint32_t view); + +struct xrt_vec3 +rift_distortion_distance_scale_squared_split_chroma(struct rift_lens_distortion *lens_distortion, + float distance_squared); + +struct rift_viewport_fov_tan +rift_calculate_fov_from_hmd(struct rift_hmd *hmd, struct rift_distortion_render_info *distortion, uint32_t view); + +struct rift_scale_and_offset +rift_calculate_ndc_scale_and_offset_from_fov(struct rift_viewport_fov_tan *fov); + +struct rift_scale_and_offset +rift_calculate_uv_scale_and_offset_from_ndc_scale_and_offset(struct rift_scale_and_offset eye_to_source_ndc); + +void +rift_fill_in_default_distortions(struct rift_hmd *hmd); + +bool +rift_hmd_compute_distortion(struct xrt_device *dev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result); diff --git a/src/xrt/drivers/rift/rift_hmd.c b/src/xrt/drivers/rift/rift_hmd.c index d88da7e58..bd890c2eb 100644 --- a/src/xrt/drivers/rift/rift_hmd.c +++ b/src/xrt/drivers/rift/rift_hmd.c @@ -16,10 +16,13 @@ #include "os/os_time.h" #include "xrt/xrt_defines.h" #include "xrt/xrt_device.h" + #include "rift_interface.h" +#include "rift_distortion.h" #include "math/m_relation_history.h" #include "math/m_api.h" +#include "math/m_vec2.h" #include "math/m_mathinclude.h" // IWYU pragma: keep #include "util/u_debug.h" @@ -42,13 +45,6 @@ * */ -/// Casting helper function -static inline struct rift_hmd * -rift_hmd(struct xrt_device *xdev) -{ - return (struct rift_hmd *)xdev; -} - DEBUG_GET_ONCE_LOG_OPTION(rift_log, "RIFT_LOG", U_LOGGING_WARN) #define HMD_TRACE(hmd, ...) U_LOG_XDEV_IFL_T(&hmd->base, hmd->log_level, __VA_ARGS__) @@ -96,8 +92,7 @@ rift_send_keepalive(struct rift_hmd *hmd) struct dk2_report_keepalive_mux report = {0, IN_REPORT_DK2, KEEPALIVE_INTERVAL_NS / 1000000}; // convert ns to ms - int result = - rift_send_report(hmd, FEATURE_REPORT_KEEPALIVE_MUX, &report, sizeof(report)); + int result = rift_send_report(hmd, FEATURE_REPORT_KEEPALIVE_MUX, &report, sizeof(report)); if (result < 0) { return result; @@ -236,18 +231,15 @@ rift_hmd_get_view_poses(struct xrt_device *xdev, struct xrt_fov *out_fovs, struct xrt_pose *out_poses) { - /* - * For HMDs you can call this function or directly set - * the `get_view_poses` function on the device to it. - */ - return u_device_get_view_poses( // - xdev, // - default_eye_relation, // - at_timestamp_ns, // - view_count, // - out_head_relation, // - out_fovs, // - out_poses); // + struct rift_hmd *hmd = rift_hmd(xdev); + + return u_device_get_view_poses(xdev, // + &(struct xrt_vec3){hmd->extra_display_info.icd, 0.0f, 0.0f}, // + at_timestamp_ns, // + view_count, // + out_head_relation, // + out_fovs, // + out_poses); } static xrt_result_t @@ -261,129 +253,6 @@ rift_hmd_get_visibility_mask(struct xrt_device *xdev, return XRT_SUCCESS; } -static float -rift_catmull_rom_spline(struct rift_catmull_rom_distortion_data *catmull, float scaled_value) -{ - float scaled_value_floor = floorf(scaled_value); - scaled_value_floor = CLAMP(scaled_value_floor, 0, CATMULL_COEFFICIENTS - 1); - - float t = scaled_value - scaled_value_floor; - int k = (int)scaled_value_floor; - - float p0, p1, m0, m1; - switch (k) { - case 0: - p0 = 1.0f; - m0 = (catmull->k[1] - catmull->k[0]); - p1 = catmull->k[1]; - m1 = 0.5f * (catmull->k[2] - catmull->k[0]); - break; - default: - p0 = catmull->k[k]; - m0 = 0.5f * (catmull->k[k + 1] - catmull->k[k - 1]); - p1 = catmull->k[k + 1]; - m1 = 0.5f * (catmull->k[k + 2] - catmull->k[k]); - break; - case CATMULL_COEFFICIENTS - 2: - p0 = catmull->k[CATMULL_COEFFICIENTS - 2]; - m0 = 0.5f * (catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]); - p1 = catmull->k[CATMULL_COEFFICIENTS - 1]; - m1 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; - break; - case CATMULL_COEFFICIENTS - 1: - p0 = catmull->k[CATMULL_COEFFICIENTS - 1]; - m0 = catmull->k[CATMULL_COEFFICIENTS - 1] - catmull->k[CATMULL_COEFFICIENTS - 2]; - p1 = p0 + m0; - m1 = m0; - break; - } - - float omt = 1.0f - t; - - float res = (p0 * (1.0f + 2.0f * t) + m0 * t) * omt * omt + (p1 * (1.0f + 2.0f * omt) - m1 * omt) * t * t; - - return res; -} - -static float -rift_distortion_distance_squared(struct rift_lens_distortion *lens_distortion, float distance_squared) -{ - float scale = 1.0f; - - switch (lens_distortion->distortion_version) { - case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { - struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; - - float scaled_distance_squared = - (float)(CATMULL_COEFFICIENTS - 1) * distance_squared / (data.max_r * data.max_r); - - return rift_catmull_rom_spline(&data, scaled_distance_squared); - } - default: return scale; - } -} - -static struct xrt_vec3 -rift_distortion_distance_squared_split_chroma(struct rift_lens_distortion *lens_distortion, float distance_squared) -{ - float scale = rift_distortion_distance_squared(lens_distortion, distance_squared); - - struct xrt_vec3 scale_split; - scale_split.x = scale; - scale_split.y = scale; - scale_split.z = scale; - - switch (lens_distortion->distortion_version) { - case RIFT_LENS_DISTORTION_LCSV_CATMULL_ROM_10_VERSION_1: { - struct rift_catmull_rom_distortion_data data = lens_distortion->data.lcsv_catmull_rom_10; - - scale_split.x *= 1.0f + data.chromatic_abberation[0] + distance_squared * data.chromatic_abberation[1]; - scale_split.z *= 1.0f + data.chromatic_abberation[2] + distance_squared * data.chromatic_abberation[3]; - break; - } - } - - return scale_split; -} - -static bool -rift_hmd_compute_distortion(struct xrt_device *dev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result) -{ -#define ZERO_ONE_TO_N_ONE_ONE(x) ((x * 2) - 1) -#define N_ONE_ONE_TO_ZERO_ONE(x) ((x + 1) / 2) - - struct rift_hmd *hmd = rift_hmd(dev); - - struct rift_lens_distortion *distortion = &hmd->lens_distortions[0]; - - float display_width_meters = (float)hmd->display_info.display_width / 1000000.0f; - float display_height_meters = (float)hmd->display_info.display_height / 1000000.0f; - - float tan_eye_angle_scale_x = - display_width_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center * 0.25f; - float tan_eye_angle_scale_y = - display_height_meters / distortion->data.lcsv_catmull_rom_10.meters_per_tan_angle_at_center * 0.5f; - - u = ZERO_ONE_TO_N_ONE_ONE(u) * tan_eye_angle_scale_x; - v = ZERO_ONE_TO_N_ONE_ONE(v) * tan_eye_angle_scale_y; - - float distance_squared = fabsf(u * u + v * v); - - struct xrt_vec3 chroma_distortions = - rift_distortion_distance_squared_split_chroma(distortion, distance_squared); - - out_result->r = (struct xrt_vec2){N_ONE_ONE_TO_ZERO_ONE(u * chroma_distortions.x), - N_ONE_ONE_TO_ZERO_ONE(v * chroma_distortions.x)}; - out_result->g = (struct xrt_vec2){N_ONE_ONE_TO_ZERO_ONE(u * chroma_distortions.y), - N_ONE_ONE_TO_ZERO_ONE(v * chroma_distortions.y)}; - out_result->b = (struct xrt_vec2){N_ONE_ONE_TO_ZERO_ONE(u * chroma_distortions.z), - N_ONE_ONE_TO_ZERO_ONE(v * chroma_distortions.z)}; - - return true; -#undef ZERO_ONE_TO_N_ONE_ONE -#undef N_ONE_ONE_TO_ZERO_ONE -} - static float rift_decode_fixed_point_uint16(uint16_t value, uint16_t zero_value, int fractional_bits) { @@ -403,6 +272,8 @@ rift_parse_distortion_report(struct rift_lens_distortion_report *report, struct struct rift_catmull_rom_distortion_report_data report_data = report->data.lcsv_catmull_rom_10; struct rift_catmull_rom_distortion_data data; + out->eye_relief = MICROMETERS_TO_METERS(report_data.eye_relief); + for (uint16_t i = 0; i < CATMULL_COEFFICIENTS; i += 1) { data.k[i] = rift_decode_fixed_point_uint16(report_data.k[i], 0, 14); } @@ -614,30 +485,56 @@ rift_hmd_create(struct os_hid_device *dev, enum rift_variant variant, char *devi } HMD_INFO(hmd, "After writing, HMD has config flags: %X", hmd->config.config_flags); - // get the lens distortions - struct rift_lens_distortion_report lens_distortion; - result = rift_get_lens_distortion(hmd, &lens_distortion); - if (result < 0) { - HMD_ERROR(hmd, "Failed to get lens distortion, reason %d", result); - goto error; - } - - hmd->num_lens_distortions = lens_distortion.num_distortions; - hmd->lens_distortions = calloc(lens_distortion.num_distortions, sizeof(struct rift_lens_distortion)); - - rift_parse_distortion_report(&lens_distortion, &hmd->lens_distortions[lens_distortion.distortion_idx]); - // TODO: actually verify we initialize all the distortions. if the headset is working correctly, this should - // have happened, but you never know. - for (uint16_t i = 1; i < hmd->num_lens_distortions; i++) { + if (getenv("RIFT_USE_FIRMWARE_DISTORTION") != NULL) { + // get the lens distortions + struct rift_lens_distortion_report lens_distortion; result = rift_get_lens_distortion(hmd, &lens_distortion); if (result < 0) { - HMD_ERROR(hmd, "Failed to get lens distortion idx %d, reason %d", i, result); + HMD_ERROR(hmd, "Failed to get lens distortion, reason %d", result); goto error; } + hmd->num_lens_distortions = lens_distortion.num_distortions; + hmd->lens_distortions = calloc(lens_distortion.num_distortions, sizeof(struct rift_lens_distortion)); + rift_parse_distortion_report(&lens_distortion, &hmd->lens_distortions[lens_distortion.distortion_idx]); + // TODO: actually verify we initialize all the distortions. if the headset is working correctly, this + // should have happened, but you never know. + for (uint16_t i = 1; i < hmd->num_lens_distortions; i++) { + result = rift_get_lens_distortion(hmd, &lens_distortion); + if (result < 0) { + HMD_ERROR(hmd, "Failed to get lens distortion idx %d, reason %d", i, result); + goto error; + } + + rift_parse_distortion_report(&lens_distortion, + &hmd->lens_distortions[lens_distortion.distortion_idx]); + } + + // TODO: pick the correct distortion for the eye relief setting the user has picked + hmd->distortion_in_use = 0; + } else { + rift_fill_in_default_distortions(hmd); } + // fill in extra display info about the headset + + switch (hmd->variant) { + case RIFT_VARIANT_DK2: + hmd->extra_display_info.screen_gap_meters = 0.0f; + hmd->extra_display_info.lens_diameter_meters = 0.04f; + break; + default: break; + } + + // hardcode left eye, probably not ideal, but sure, why not + struct rift_distortion_render_info distortion_render_info = rift_get_distortion_render_info(hmd, 0); + hmd->extra_display_info.fov = rift_calculate_fov_from_hmd(hmd, &distortion_render_info, 0); + hmd->extra_display_info.eye_to_source_ndc = + rift_calculate_ndc_scale_and_offset_from_fov(&hmd->extra_display_info.fov); + hmd->extra_display_info.eye_to_source_uv = + rift_calculate_uv_scale_and_offset_from_ndc_scale_and_offset(hmd->extra_display_info.eye_to_source_ndc); + // This list should be ordered, most preferred first. size_t idx = 0; hmd->base.hmd->blend_modes[idx++] = XRT_BLEND_MODE_OPAQUE; @@ -674,24 +571,40 @@ rift_hmd_create(struct os_hid_device *dev, enum rift_variant variant, char *devi struct u_device_simple_info info; info.display.w_pixels = hmd->display_info.resolution_x; info.display.h_pixels = hmd->display_info.resolution_y; - info.display.w_meters = (float)hmd->display_info.display_width / 1000000.0f; // micrometers -> meters - info.display.h_meters = (float)hmd->display_info.display_height / 1000000.0f; + info.display.w_meters = MICROMETERS_TO_METERS(hmd->display_info.display_width); + info.display.h_meters = MICROMETERS_TO_METERS(hmd->display_info.display_height); - info.lens_horizontal_separation_meters = (float)hmd->display_info.lens_separation / 1000000.0f; + info.lens_horizontal_separation_meters = MICROMETERS_TO_METERS(hmd->display_info.lens_separation); + info.lens_vertical_position_meters = MICROMETERS_TO_METERS(hmd->display_info.center_v); - // TODO: this is per eye on the headset, but we're just taking the left eye for this, we should be using both - // eyes, ideally - info.lens_vertical_position_meters = (float)hmd->display_info.lens_distance_l / 1000000.0f; + hmd->extra_display_info.icd = info.lens_horizontal_separation_meters; - // TODO: calculate this - info.fov[0] = (float)(93.0 * (M_PI / 180.0)); - info.fov[1] = (float)(99.0 * (M_PI / 180.0)); + for (int i = 0; i < 2; i++) { + info.fov[i] = 93; + } if (!u_device_setup_split_side_by_side(&hmd->base, &info)) { HMD_ERROR(hmd, "Failed to setup basic device info"); goto error; } + switch (hmd->variant) { + case RIFT_VARIANT_DK2: + // TODO: figure out how to calculate this programatically, right now this is hardcoded with data dumped + // from oculus' OpenXR runtime + hmd->base.hmd->distortion.fov[0].angle_up = 0.92667186; + hmd->base.hmd->distortion.fov[0].angle_down = -0.92667186; + hmd->base.hmd->distortion.fov[0].angle_left = -0.8138836; + hmd->base.hmd->distortion.fov[0].angle_right = 0.82951474; + + hmd->base.hmd->distortion.fov[1].angle_up = 0.92667186; + hmd->base.hmd->distortion.fov[1].angle_down = -0.92667186; + hmd->base.hmd->distortion.fov[1].angle_left = -0.82951474; + hmd->base.hmd->distortion.fov[1].angle_right = 0.8138836; + break; + default: break; + } + // Just put an initial identity value in the tracker struct xrt_space_relation identity = XRT_SPACE_RELATION_ZERO; identity.relation_flags = (enum xrt_space_relation_flags)(XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | @@ -718,6 +631,7 @@ rift_hmd_create(struct os_hid_device *dev, enum rift_variant variant, char *devi // Setup variable tracker: Optional but useful for debugging u_var_add_root(hmd, "Rift HMD", true); u_var_add_log_level(hmd, &hmd->log_level, "log_level"); + m_imu_3dof_add_vars(&hmd->fusion, hmd, "3dof_"); return hmd; error: diff --git a/src/xrt/drivers/rift/rift_interface.h b/src/xrt/drivers/rift/rift_interface.h index 925f4b31f..fd4c383d9 100644 --- a/src/xrt/drivers/rift/rift_interface.h +++ b/src/xrt/drivers/rift/rift_interface.h @@ -20,6 +20,8 @@ #include "util/u_logging.h" #include "math/m_imu_3dof.h" +#include "math/m_api.h" +#include "math/m_mathinclude.h" #include #include @@ -28,11 +30,19 @@ extern "C" { #endif -#define REPORT_MAX_SIZE 69 // max size of a feature report (FEATURE_REPORT_CALIBRATE) +#define REPORT_MAX_SIZE 69 // max size of a feature report (FEATURE_REPORT_CALIBRATE) #define KEEPALIVE_INTERVAL_NS 10000000000 // 10 seconds -#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) +#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) #define IMU_SAMPLE_RATE 1000 +#define DEG_TO_RAD(DEG) (DEG * M_PI / 180.0) +#define MICROMETERS_TO_METERS(microns) (float)microns / 1000000.0f + +// value taken from LibOVR 0.4.4 +#define DEFAULT_EXTRA_EYE_ROTATION DEG_TO_RAD(30.0f) + enum rift_feature_reports { // DK1 @@ -148,8 +158,7 @@ struct rift_display_info_report uint32_t center_v; // the separation between the two lenses, in micrometers uint32_t lens_separation; - uint32_t lens_distance_l; - uint32_t lens_distance_r; + uint32_t lens_distance[2]; float distortion[6]; } RIFT_PACKED; @@ -239,12 +248,44 @@ struct rift_lens_distortion { // the version of the lens distortion data uint16_t distortion_version; + // eye relief setting, in meters from surface of lens + float eye_relief; union { struct rift_catmull_rom_distortion_data lcsv_catmull_rom_10; } RIFT_PACKED data; }; +struct rift_scale_and_offset +{ + struct xrt_vec2 scale; + struct xrt_vec2 offset; +}; + +struct rift_viewport_fov_tan +{ + float up_tan; + float down_tan; + float left_tan; + float right_tan; +}; + +struct rift_extra_display_info +{ + // gap left between the two eyes + float screen_gap_meters; + // the diameter of the lenses, may need to be extended to an array + float lens_diameter_meters; + // ipd of the headset + float icd; + + // the fov of the headset + struct rift_viewport_fov_tan fov; + // mapping from tan-angle space to target NDC space + struct rift_scale_and_offset eye_to_source_ndc; + struct rift_scale_and_offset eye_to_source_uv; +}; + enum rift_variant { RIFT_VARIANT_DK1, @@ -296,8 +337,18 @@ struct rift_hmd struct rift_lens_distortion *lens_distortions; uint16_t num_lens_distortions; + uint16_t distortion_in_use; + + struct rift_extra_display_info extra_display_info; }; +/// Casting helper function +static inline struct rift_hmd * +rift_hmd(struct xrt_device *xdev) +{ + return (struct rift_hmd *)xdev; +} + struct rift_hmd * rift_hmd_create(struct os_hid_device *dev, enum rift_variant variant, char *device_name, char *serial_number);