Spaces:
Build error
Build error
| /* | |
| * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| * SPDX-License-Identifier: Apache-2.0 | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /** @file openxr_hmd.cu | |
| * @author Thomas Müller & Ingo Esser & Robert Menzel, NVIDIA | |
| * @brief Wrapper around the OpenXR API, providing access to | |
| * per-eye framebuffers, lens parameters, visible area, | |
| * view, hand, and eye poses, as well as controller inputs. | |
| */ | |
| namespace ngp { | |
| // function XrEnumStr turns enum into string for printing | |
| // uses expansion macro and data provided in openxr_reflection.h | |
| XR_ENUM_STR(XrViewConfigurationType) | |
| XR_ENUM_STR(XrEnvironmentBlendMode) | |
| XR_ENUM_STR(XrReferenceSpaceType) | |
| XR_ENUM_STR(XrStructureType) | |
| XR_ENUM_STR(XrSessionState) | |
| /// Checks the result of a xrXXXXXX call and throws an error on failure | |
| OpenXRHMD::Swapchain::Swapchain(XrSwapchainCreateInfo& rgba_create_info, XrSwapchainCreateInfo& depth_create_info, XrSession& session, XrInstance& m_instance) { | |
| ScopeGuard cleanup_guard{[&]() { clear(); }}; | |
| XR_CHECK_THROW(xrCreateSwapchain(session, &rgba_create_info, &handle)); | |
| width = rgba_create_info.width; | |
| height = rgba_create_info.height; | |
| { | |
| uint32_t size; | |
| XR_CHECK_THROW(xrEnumerateSwapchainImages(handle, 0, &size, nullptr)); | |
| images_gl.resize(size, {XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR}); | |
| XR_CHECK_THROW(xrEnumerateSwapchainImages(handle, size, &size, (XrSwapchainImageBaseHeader*)images_gl.data())); | |
| // One framebuffer per swapchain image | |
| framebuffers_gl.resize(size); | |
| } | |
| if (depth_create_info.format != 0) { | |
| XR_CHECK_THROW(xrCreateSwapchain(session, &depth_create_info, &depth_handle)); | |
| uint32_t depth_size; | |
| XR_CHECK_THROW(xrEnumerateSwapchainImages(depth_handle, 0, &depth_size, nullptr)); | |
| depth_images_gl.resize(depth_size, {XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR}); | |
| XR_CHECK_THROW(xrEnumerateSwapchainImages(depth_handle, depth_size, &depth_size, (XrSwapchainImageBaseHeader*)depth_images_gl.data())); | |
| // We might have a different number of depth swapchain images as we have framebuffers, | |
| // so we will need to bind an acquired depth image to the current framebuffer on the | |
| // fly later on. | |
| } | |
| glGenFramebuffers(framebuffers_gl.size(), framebuffers_gl.data()); | |
| cleanup_guard.disarm(); | |
| } | |
| OpenXRHMD::Swapchain::~Swapchain() { | |
| clear(); | |
| } | |
| void OpenXRHMD::Swapchain::clear() { | |
| if (!framebuffers_gl.empty()) { | |
| glDeleteFramebuffers(framebuffers_gl.size(), framebuffers_gl.data()); | |
| } | |
| if (depth_handle != XR_NULL_HANDLE) { | |
| xrDestroySwapchain(depth_handle); | |
| depth_handle = XR_NULL_HANDLE; | |
| } | |
| if (handle != XR_NULL_HANDLE) { | |
| xrDestroySwapchain(handle); | |
| handle = XR_NULL_HANDLE; | |
| } | |
| } | |
| OpenXRHMD::OpenXRHMD(HDC hdc, HGLRC hglrc) { | |
| OpenXRHMD::OpenXRHMD(Display* xDisplay, uint32_t visualid, GLXFBConfig glxFBConfig, GLXDrawable glxDrawable, GLXContext glxContext) { | |
| OpenXRHMD::OpenXRHMD(wl_display* display) { | |
| ScopeGuard cleanup_guard{[&]() { clear(); }}; | |
| init_create_xr_instance(); | |
| init_get_xr_system(); | |
| init_configure_xr_views(); | |
| init_check_for_xr_blend_mode(); | |
| init_open_gl(hdc, hglrc); | |
| init_open_gl(xDisplay, visualid, glxFBConfig, glxDrawable, glxContext); | |
| init_open_gl(display); | |
| init_xr_session(); | |
| init_xr_actions(); | |
| init_xr_spaces(); | |
| init_xr_swapchain_open_gl(); | |
| init_open_gl_shaders(); | |
| cleanup_guard.disarm(); | |
| tlog::success() << "Initialized OpenXR for " << m_system_properties.systemName; | |
| // tlog::success() << " " | |
| // << " depth=" << (m_supports_composition_layer_depth ? "true" : "false") | |
| // << " mask=" << (m_supports_hidden_area_mask ? "true" : "false") | |
| // << " eye=" << (m_supports_eye_tracking ? "true" : "false") | |
| // ; | |
| } | |
| OpenXRHMD::~OpenXRHMD() { | |
| clear(); | |
| } | |
| void OpenXRHMD::clear() { | |
| auto xr_destroy = [&](auto& handle, auto destroy_fun) { | |
| if (handle != XR_NULL_HANDLE) { | |
| destroy_fun(handle); | |
| handle = XR_NULL_HANDLE; | |
| } | |
| }; | |
| xr_destroy(m_pose_action, xrDestroyAction); | |
| xr_destroy(m_thumbstick_actions[0], xrDestroyAction); | |
| xr_destroy(m_thumbstick_actions[1], xrDestroyAction); | |
| xr_destroy(m_press_action, xrDestroyAction); | |
| xr_destroy(m_grab_action, xrDestroyAction); | |
| xr_destroy(m_action_set, xrDestroyActionSet); | |
| m_swapchains.clear(); | |
| xr_destroy(m_space, xrDestroySpace); | |
| xr_destroy(m_session, xrDestroySession); | |
| xr_destroy(m_instance, xrDestroyInstance); | |
| } | |
| void OpenXRHMD::init_create_xr_instance() { | |
| std::vector<const char*> layers = {}; | |
| std::vector<const char*> extensions = { | |
| XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, | |
| }; | |
| auto print_extension_properties = [](const char* layer_name) { | |
| uint32_t size; | |
| xrEnumerateInstanceExtensionProperties(layer_name, 0, &size, nullptr); | |
| std::vector<XrExtensionProperties> props(size, {XR_TYPE_EXTENSION_PROPERTIES}); | |
| xrEnumerateInstanceExtensionProperties(layer_name, size, &size, props.data()); | |
| tlog::info() << fmt::format("Extensions ({}):", props.size()); | |
| for (XrExtensionProperties extension : props) { | |
| tlog::info() << fmt::format("\t{} (Version {})", extension.extensionName, extension.extensionVersion); | |
| } | |
| }; | |
| uint32_t size; | |
| xrEnumerateApiLayerProperties(0, &size, nullptr); | |
| m_api_layer_properties.clear(); | |
| m_api_layer_properties.resize(size, {XR_TYPE_API_LAYER_PROPERTIES}); | |
| xrEnumerateApiLayerProperties(size, &size, m_api_layer_properties.data()); | |
| if (m_print_api_layers) { | |
| tlog::info() << fmt::format("API Layers ({}):", m_api_layer_properties.size()); | |
| for (auto p : m_api_layer_properties) { | |
| tlog::info() << fmt::format( | |
| "{} (v {}.{}.{}, {}) {}", | |
| p.layerName, | |
| XR_VERSION_MAJOR(p.specVersion), | |
| XR_VERSION_MINOR(p.specVersion), | |
| XR_VERSION_PATCH(p.specVersion), | |
| p.layerVersion, | |
| p.description | |
| ); | |
| print_extension_properties(p.layerName); | |
| } | |
| } | |
| if (layers.size() != 0) { | |
| for (const auto& e : layers) { | |
| bool found = false; | |
| for (XrApiLayerProperties layer : m_api_layer_properties) { | |
| if (strcmp(e, layer.layerName) == 0) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| throw std::runtime_error{fmt::format("OpenXR API layer {} not found", e)}; | |
| } | |
| } | |
| } | |
| xrEnumerateInstanceExtensionProperties(nullptr, 0, &size, nullptr); | |
| m_instance_extension_properties.clear(); | |
| m_instance_extension_properties.resize(size, {XR_TYPE_EXTENSION_PROPERTIES}); | |
| xrEnumerateInstanceExtensionProperties(nullptr, size, &size, m_instance_extension_properties.data()); | |
| if (m_print_extensions) { | |
| tlog::info() << fmt::format("Instance extensions ({}):", m_instance_extension_properties.size()); | |
| for (XrExtensionProperties extension : m_instance_extension_properties) { | |
| tlog::info() << fmt::format("\t{} (Version {})", extension.extensionName, extension.extensionVersion); | |
| } | |
| } | |
| auto has_extension = [&](const char* e) { | |
| for (XrExtensionProperties extension : m_instance_extension_properties) { | |
| if (strcmp(e, extension.extensionName) == 0) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| for (const auto& e : extensions) { | |
| if (!has_extension(e)) { | |
| throw std::runtime_error{fmt::format("Required OpenXR extension {} not found", e)}; | |
| } | |
| } | |
| auto add_extension_if_supported = [&](const char* extension) { | |
| if (has_extension(extension)) { | |
| extensions.emplace_back(extension); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| if (add_extension_if_supported(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME)) { | |
| m_supports_composition_layer_depth = true; | |
| } | |
| if (add_extension_if_supported(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME)) { | |
| m_supports_hidden_area_mask = true; | |
| } | |
| if (add_extension_if_supported(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME)) { | |
| m_supports_eye_tracking = true; | |
| } | |
| XrInstanceCreateInfo instance_create_info = {XR_TYPE_INSTANCE_CREATE_INFO}; | |
| instance_create_info.applicationInfo = {}; | |
| strncpy(instance_create_info.applicationInfo.applicationName, "Gen3C GUI v" NGP_VERSION, XR_MAX_APPLICATION_NAME_SIZE); | |
| instance_create_info.applicationInfo.applicationVersion = 1; | |
| strncpy(instance_create_info.applicationInfo.engineName, "Gen3C GUI v" NGP_VERSION, XR_MAX_ENGINE_NAME_SIZE); | |
| instance_create_info.applicationInfo.engineVersion = 1; | |
| instance_create_info.applicationInfo.apiVersion = XR_CURRENT_API_VERSION; | |
| instance_create_info.enabledExtensionCount = (uint32_t)extensions.size(); | |
| instance_create_info.enabledExtensionNames = extensions.data(); | |
| instance_create_info.enabledApiLayerCount = (uint32_t)layers.size(); | |
| instance_create_info.enabledApiLayerNames = layers.data(); | |
| if (XR_FAILED(xrCreateInstance(&instance_create_info, &m_instance))) { | |
| throw std::runtime_error{"Failed to create OpenXR instance"}; | |
| } | |
| XR_CHECK_THROW(xrGetInstanceProperties(m_instance, &m_instance_properties)); | |
| if (m_print_instance_properties) { | |
| tlog::info() << "Instance Properties"; | |
| tlog::info() << fmt::format("\t runtime name: '{}'", m_instance_properties.runtimeName); | |
| const auto& v = m_instance_properties.runtimeVersion; | |
| tlog::info() << fmt::format( | |
| "\t runtime version: {}.{}.{}", | |
| XR_VERSION_MAJOR(v), | |
| XR_VERSION_MINOR(v), | |
| XR_VERSION_PATCH(v) | |
| ); | |
| } | |
| } | |
| void OpenXRHMD::init_get_xr_system() { | |
| XrSystemGetInfo system_get_info = {XR_TYPE_SYSTEM_GET_INFO, nullptr, XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY}; | |
| XR_CHECK_THROW(xrGetSystem(m_instance, &system_get_info, &m_system_id)); | |
| XR_CHECK_THROW(xrGetSystemProperties(m_instance, m_system_id, &m_system_properties)); | |
| if (m_print_system_properties) { | |
| tlog::info() << "System Properties"; | |
| tlog::info() << fmt::format("\t name: '{}'", m_system_properties.systemName); | |
| tlog::info() << fmt::format("\t vendorId: {:#x}", m_system_properties.vendorId); | |
| tlog::info() << fmt::format("\t systemId: {:#x}", m_system_properties.systemId); | |
| tlog::info() << fmt::format("\t max layer count: {}", m_system_properties.graphicsProperties.maxLayerCount); | |
| tlog::info() << fmt::format("\t max img width: {}", m_system_properties.graphicsProperties.maxSwapchainImageWidth); | |
| tlog::info() << fmt::format("\t max img height: {}", m_system_properties.graphicsProperties.maxSwapchainImageHeight); | |
| tlog::info() << fmt::format("\torientation tracking: {}", m_system_properties.trackingProperties.orientationTracking ? "YES" : "NO"); | |
| tlog::info() << fmt::format("\t position tracking: {}", m_system_properties.trackingProperties.orientationTracking ? "YES" : "NO"); | |
| } | |
| } | |
| void OpenXRHMD::init_configure_xr_views() { | |
| uint32_t size; | |
| XR_CHECK_THROW(xrEnumerateViewConfigurations(m_instance, m_system_id, 0, &size, nullptr)); | |
| std::vector<XrViewConfigurationType> view_config_types(size); | |
| XR_CHECK_THROW(xrEnumerateViewConfigurations(m_instance, m_system_id, size, &size, view_config_types.data())); | |
| if (m_print_view_configuration_types) { | |
| tlog::info() << fmt::format("View Configuration Types ({}):", view_config_types.size()); | |
| for (const auto& t : view_config_types) { | |
| tlog::info() << fmt::format("\t{}", XrEnumStr(t)); | |
| } | |
| } | |
| // view configurations we support, in descending preference | |
| const std::vector<XrViewConfigurationType> preferred_view_config_types = { | |
| //XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO, | |
| XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO | |
| }; | |
| bool found = false; | |
| for (const auto& p : preferred_view_config_types) { | |
| for (const auto& t : view_config_types) { | |
| if (p == t) { | |
| found = true; | |
| m_view_configuration_type = t; | |
| } | |
| } | |
| } | |
| if (!found) { | |
| throw std::runtime_error{"Could not find a suitable OpenXR view configuration type"}; | |
| } | |
| // get view configuration properties | |
| XR_CHECK_THROW(xrGetViewConfigurationProperties(m_instance, m_system_id, m_view_configuration_type, &m_view_configuration_properties)); | |
| if (m_print_view_configuration_properties) { | |
| tlog::info() << "View Configuration Properties:"; | |
| tlog::info() << fmt::format("\t Type: {}", XrEnumStr(m_view_configuration_type)); | |
| tlog::info() << fmt::format("\t FOV Mutable: {}", m_view_configuration_properties.fovMutable ? "YES" : "NO"); | |
| } | |
| // enumerate view configuration views | |
| XR_CHECK_THROW(xrEnumerateViewConfigurationViews(m_instance, m_system_id, m_view_configuration_type, 0, &size, nullptr)); | |
| m_view_configuration_views.clear(); | |
| m_view_configuration_views.resize(size, {XR_TYPE_VIEW_CONFIGURATION_VIEW}); | |
| XR_CHECK_THROW(xrEnumerateViewConfigurationViews( | |
| m_instance, | |
| m_system_id, | |
| m_view_configuration_type, | |
| size, | |
| &size, | |
| m_view_configuration_views.data() | |
| )); | |
| if (m_print_view_configuration_view) { | |
| tlog::info() << "View Configuration Views, Width x Height x Samples"; | |
| for (size_t i = 0; i < m_view_configuration_views.size(); ++i) { | |
| const auto& view = m_view_configuration_views[i]; | |
| tlog::info() << fmt::format( | |
| "\tView {}\tRecommended: {}x{}x{} Max: {}x{}x{}", | |
| i, | |
| view.recommendedImageRectWidth, | |
| view.recommendedImageRectHeight, | |
| view.recommendedSwapchainSampleCount, | |
| view.maxImageRectWidth, | |
| view.maxImageRectHeight, | |
| view.maxSwapchainSampleCount | |
| ); | |
| } | |
| } | |
| } | |
| void OpenXRHMD::init_check_for_xr_blend_mode() { | |
| // enumerate environment blend modes | |
| uint32_t size; | |
| XR_CHECK_THROW(xrEnumerateEnvironmentBlendModes(m_instance, m_system_id, m_view_configuration_type, 0, &size, nullptr)); | |
| std::vector<XrEnvironmentBlendMode> supported_blend_modes(size); | |
| XR_CHECK_THROW(xrEnumerateEnvironmentBlendModes( | |
| m_instance, | |
| m_system_id, | |
| m_view_configuration_type, | |
| size, | |
| &size, | |
| supported_blend_modes.data() | |
| )); | |
| if (supported_blend_modes.empty()) { | |
| throw std::runtime_error{"No OpenXR environment blend modes found"}; | |
| } | |
| std::sort(std::begin(supported_blend_modes), std::end(supported_blend_modes)); | |
| if (m_print_environment_blend_modes) { | |
| tlog::info() << fmt::format("Environment Blend Modes ({}):", supported_blend_modes.size()); | |
| } | |
| m_supported_environment_blend_modes.resize(supported_blend_modes.size()); | |
| m_supported_environment_blend_modes_imgui_string.clear(); | |
| for (size_t i = 0; i < supported_blend_modes.size(); ++i) { | |
| if (m_print_environment_blend_modes) { | |
| tlog::info() << fmt::format("\t{}", XrEnumStr(supported_blend_modes[i])); | |
| } | |
| auto b = (EEnvironmentBlendMode)supported_blend_modes[i]; | |
| m_supported_environment_blend_modes[i] = b; | |
| auto b_str = to_string(b); | |
| std::copy(std::begin(b_str), std::end(b_str), std::back_inserter(m_supported_environment_blend_modes_imgui_string)); | |
| m_supported_environment_blend_modes_imgui_string.emplace_back('\0'); | |
| } | |
| m_supported_environment_blend_modes_imgui_string.emplace_back('\0'); | |
| m_environment_blend_mode = m_supported_environment_blend_modes.front(); | |
| } | |
| void OpenXRHMD::init_xr_actions() { | |
| // paths for left (0) and right (1) hands | |
| XR_CHECK_THROW(xrStringToPath(m_instance, "/user/hand/left", &m_hand_paths[0])); | |
| XR_CHECK_THROW(xrStringToPath(m_instance, "/user/hand/right", &m_hand_paths[1])); | |
| // create action set | |
| XrActionSetCreateInfo action_set_create_info{XR_TYPE_ACTION_SET_CREATE_INFO, nullptr, "actionset", "actionset", 0}; | |
| XR_CHECK_THROW(xrCreateActionSet(m_instance, &action_set_create_info, &m_action_set)); | |
| { | |
| XrActionCreateInfo action_create_info{ | |
| XR_TYPE_ACTION_CREATE_INFO, | |
| nullptr, | |
| "hand_pose", | |
| XR_ACTION_TYPE_POSE_INPUT, | |
| (uint32_t)m_hand_paths.size(), | |
| m_hand_paths.data(), | |
| "Hand pose" | |
| }; | |
| XR_CHECK_THROW(xrCreateAction(m_action_set, &action_create_info, &m_pose_action)); | |
| } | |
| { | |
| XrActionCreateInfo action_create_info{ | |
| XR_TYPE_ACTION_CREATE_INFO, | |
| nullptr, | |
| "thumbstick_left", | |
| XR_ACTION_TYPE_VECTOR2F_INPUT, | |
| 0, | |
| nullptr, | |
| "Left thumbstick" | |
| }; | |
| XR_CHECK_THROW(xrCreateAction(m_action_set, &action_create_info, &m_thumbstick_actions[0])); | |
| } | |
| { | |
| XrActionCreateInfo action_create_info{ | |
| XR_TYPE_ACTION_CREATE_INFO, | |
| nullptr, | |
| "thumbstick_right", | |
| XR_ACTION_TYPE_VECTOR2F_INPUT, | |
| 0, | |
| nullptr, | |
| "Right thumbstick" | |
| }; | |
| XR_CHECK_THROW(xrCreateAction(m_action_set, &action_create_info, &m_thumbstick_actions[1])); | |
| } | |
| { | |
| XrActionCreateInfo action_create_info{ | |
| XR_TYPE_ACTION_CREATE_INFO, | |
| nullptr, | |
| "press", | |
| XR_ACTION_TYPE_BOOLEAN_INPUT, | |
| (uint32_t)m_hand_paths.size(), | |
| m_hand_paths.data(), | |
| "Press" | |
| }; | |
| XR_CHECK_THROW(xrCreateAction(m_action_set, &action_create_info, &m_press_action)); | |
| } | |
| { | |
| XrActionCreateInfo action_create_info{ | |
| XR_TYPE_ACTION_CREATE_INFO, | |
| nullptr, | |
| "grab", | |
| XR_ACTION_TYPE_FLOAT_INPUT, | |
| (uint32_t)m_hand_paths.size(), | |
| m_hand_paths.data(), | |
| "Grab" | |
| }; | |
| XR_CHECK_THROW(xrCreateAction(m_action_set, &action_create_info, &m_grab_action)); | |
| } | |
| auto create_binding = [&](XrAction action, const std::string& binding_path_str) { | |
| XrPath binding; | |
| XR_CHECK_THROW(xrStringToPath(m_instance, binding_path_str.c_str(), &binding)); | |
| return XrActionSuggestedBinding{action, binding}; | |
| }; | |
| auto suggest_bindings = [&](const std::string& interaction_profile_path_str, const std::vector<XrActionSuggestedBinding>& bindings) { | |
| XrPath interaction_profile; | |
| XR_CHECK_THROW(xrStringToPath(m_instance, interaction_profile_path_str.c_str(), &interaction_profile)); | |
| XrInteractionProfileSuggestedBinding suggested_binding{ | |
| XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, | |
| nullptr, | |
| interaction_profile, | |
| (uint32_t)bindings.size(), | |
| bindings.data() | |
| }; | |
| XR_CHECK_THROW(xrSuggestInteractionProfileBindings(m_instance, &suggested_binding)); | |
| }; | |
| suggest_bindings("/interaction_profiles/khr/simple_controller", { | |
| create_binding(m_pose_action, "/user/hand/left/input/grip/pose"), | |
| create_binding(m_pose_action, "/user/hand/right/input/grip/pose"), | |
| }); | |
| auto suggest_controller_bindings = [&](const std::string& xy, const std::string& press, const std::string& grab, const std::string& squeeze, const std::string& interaction_profile_path_str) { | |
| std::vector<XrActionSuggestedBinding> bindings = { | |
| create_binding(m_pose_action, "/user/hand/left/input/grip/pose"), | |
| create_binding(m_pose_action, "/user/hand/right/input/grip/pose"), | |
| create_binding(m_thumbstick_actions[0], std::string{"/user/hand/left/input/"} + xy), | |
| create_binding(m_thumbstick_actions[1], std::string{"/user/hand/right/input/"} + xy), | |
| create_binding(m_press_action, std::string{"/user/hand/left/input/"} + press), | |
| create_binding(m_press_action, std::string{"/user/hand/right/input/"} + press), | |
| create_binding(m_grab_action, std::string{"/user/hand/left/input/"} + grab), | |
| create_binding(m_grab_action, std::string{"/user/hand/right/input/"} + grab), | |
| }; | |
| if (!squeeze.empty()) { | |
| bindings.emplace_back(create_binding(m_grab_action, std::string{"/user/hand/left/input/"} + squeeze)); | |
| bindings.emplace_back(create_binding(m_grab_action, std::string{"/user/hand/right/input/"} + squeeze)); | |
| } | |
| suggest_bindings(interaction_profile_path_str, bindings); | |
| }; | |
| suggest_controller_bindings("trackpad", "select/click", "trackpad/click", "", "/interaction_profiles/google/daydream_controller"); | |
| suggest_controller_bindings("trackpad", "trackpad/click", "trigger/click", "squeeze/click", "/interaction_profiles/htc/vive_controller"); | |
| suggest_controller_bindings("thumbstick", "thumbstick/click", "trigger/value", "squeeze/click", "/interaction_profiles/microsoft/motion_controller"); | |
| suggest_controller_bindings("trackpad", "trackpad/click", "trigger/click", "", "/interaction_profiles/oculus/go_controller"); | |
| suggest_controller_bindings("thumbstick", "thumbstick/click", "trigger/value", "squeeze/value", "/interaction_profiles/oculus/touch_controller"); | |
| // Valve Index force squeeze is very sensitive and can cause unwanted grabbing. Only permit trigger-grabbing for now. | |
| suggest_controller_bindings("thumbstick", "thumbstick/click", "trigger/value", ""/*squeeze/force*/, "/interaction_profiles/valve/index_controller"); | |
| // Xbox controller (currently not functional) | |
| suggest_bindings("/interaction_profiles/microsoft/xbox_controller", { | |
| create_binding(m_thumbstick_actions[0], std::string{"/user/gamepad/input/thumbstick_left"}), | |
| create_binding(m_thumbstick_actions[1], std::string{"/user/gamepad/input/thumbstick_right"}), | |
| }); | |
| } | |
| void OpenXRHMD::init_open_gl(HDC hdc, HGLRC hglrc) { | |
| void OpenXRHMD::init_open_gl(Display* xDisplay, uint32_t visualid, GLXFBConfig glxFBConfig, GLXDrawable glxDrawable, GLXContext glxContext) { | |
| void OpenXRHMD::init_open_gl(wl_display* display) { | |
| // GL graphics requirements | |
| PFN_xrGetOpenGLGraphicsRequirementsKHR xrGetOpenGLGraphicsRequirementsKHR = nullptr; | |
| XR_CHECK_THROW(xrGetInstanceProcAddr( | |
| m_instance, | |
| "xrGetOpenGLGraphicsRequirementsKHR", | |
| reinterpret_cast<PFN_xrVoidFunction*>(&xrGetOpenGLGraphicsRequirementsKHR) | |
| )); | |
| XrGraphicsRequirementsOpenGLKHR graphics_requirements{XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR}; | |
| xrGetOpenGLGraphicsRequirementsKHR(m_instance, m_system_id, &graphics_requirements); | |
| XrVersion min_version = graphics_requirements.minApiVersionSupported; | |
| GLint major = 0; | |
| GLint minor = 0; | |
| glGetIntegerv(GL_MAJOR_VERSION, &major); | |
| glGetIntegerv(GL_MINOR_VERSION, &minor); | |
| const XrVersion have_version = XR_MAKE_VERSION(major, minor, 0); | |
| if (have_version < min_version) { | |
| tlog::info() << fmt::format( | |
| "Required OpenGL version: {}.{}, found OpenGL version: {}.{}", | |
| XR_VERSION_MAJOR(min_version), | |
| XR_VERSION_MINOR(min_version), | |
| major, | |
| minor | |
| ); | |
| throw std::runtime_error{"Insufficient graphics API support"}; | |
| } | |
| m_graphics_binding.hDC = hdc; | |
| m_graphics_binding.hGLRC = hglrc; | |
| m_graphics_binding.xDisplay = xDisplay; | |
| m_graphics_binding.visualid = visualid; | |
| m_graphics_binding.glxFBConfig = glxFBConfig; | |
| m_graphics_binding.glxDrawable = glxDrawable; | |
| m_graphics_binding.glxContext = glxContext; | |
| m_graphics_binding.display = display; | |
| } | |
| void OpenXRHMD::init_xr_session() { | |
| // create session | |
| XrSessionCreateInfo create_info{ | |
| XR_TYPE_SESSION_CREATE_INFO, | |
| reinterpret_cast<const XrBaseInStructure*>(&m_graphics_binding), | |
| 0, | |
| m_system_id | |
| }; | |
| XR_CHECK_THROW(xrCreateSession(m_instance, &create_info, &m_session)); | |
| // tlog::info() << fmt::format("Created session {}", fmt::ptr(m_session)); | |
| } | |
| void OpenXRHMD::init_xr_spaces() { | |
| // reference space | |
| uint32_t size; | |
| XR_CHECK_THROW(xrEnumerateReferenceSpaces(m_session, 0, &size, nullptr)); | |
| m_reference_spaces.clear(); | |
| m_reference_spaces.resize(size); | |
| XR_CHECK_THROW(xrEnumerateReferenceSpaces(m_session, size, &size, m_reference_spaces.data())); | |
| if (m_print_reference_spaces) { | |
| tlog::info() << fmt::format("Reference spaces ({}):", m_reference_spaces.size()); | |
| for (const auto& r : m_reference_spaces) { | |
| tlog::info() << fmt::format("\t{}", XrEnumStr(r)); | |
| } | |
| } | |
| XrReferenceSpaceCreateInfo reference_space_create_info{XR_TYPE_REFERENCE_SPACE_CREATE_INFO}; | |
| reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; | |
| reference_space_create_info.poseInReferenceSpace = XrPosef{}; | |
| reference_space_create_info.poseInReferenceSpace.orientation.w = 1.0f; | |
| XR_CHECK_THROW(xrCreateReferenceSpace(m_session, &reference_space_create_info, &m_space)); | |
| XR_CHECK_THROW(xrGetReferenceSpaceBoundsRect(m_session, reference_space_create_info.referenceSpaceType, &m_bounds)); | |
| if (m_print_reference_spaces) { | |
| tlog::info() << fmt::format("Using reference space {}", XrEnumStr(reference_space_create_info.referenceSpaceType)); | |
| tlog::info() << fmt::format("Reference space boundaries: {} x {}", m_bounds.width, m_bounds.height); | |
| } | |
| // action space | |
| XrActionSpaceCreateInfo action_space_create_info{XR_TYPE_ACTION_SPACE_CREATE_INFO}; | |
| action_space_create_info.action = m_pose_action; | |
| action_space_create_info.poseInActionSpace.orientation.w = 1.0f; | |
| action_space_create_info.subactionPath = m_hand_paths[0]; | |
| XR_CHECK_THROW(xrCreateActionSpace(m_session, &action_space_create_info, &m_hand_spaces[0])); | |
| action_space_create_info.subactionPath = m_hand_paths[1]; | |
| XR_CHECK_THROW(xrCreateActionSpace(m_session, &action_space_create_info, &m_hand_spaces[1])); | |
| // attach action set | |
| XrSessionActionSetsAttachInfo attach_info{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; | |
| attach_info.countActionSets = 1; | |
| attach_info.actionSets = &m_action_set; | |
| XR_CHECK_THROW(xrAttachSessionActionSets(m_session, &attach_info)); | |
| } | |
| void OpenXRHMD::init_xr_swapchain_open_gl() { | |
| // swap chains | |
| uint32_t size; | |
| XR_CHECK_THROW(xrEnumerateSwapchainFormats(m_session, 0, &size, nullptr)); | |
| std::vector<int64_t> swapchain_formats(size); | |
| XR_CHECK_THROW(xrEnumerateSwapchainFormats(m_session, size, &size, swapchain_formats.data())); | |
| if (m_print_available_swapchain_formats) { | |
| tlog::info() << fmt::format("Swapchain formats ({}):", swapchain_formats.size()); | |
| for (const auto& f : swapchain_formats) { | |
| tlog::info() << fmt::format("\t{:#x}", f); | |
| } | |
| } | |
| auto find_compatible_swapchain_format = [&](const std::vector<int64_t>& candidates) { | |
| for (auto format : candidates) { | |
| if (std::find(std::begin(swapchain_formats), std::end(swapchain_formats), format) != std::end(swapchain_formats)) { | |
| return format; | |
| } | |
| } | |
| throw std::runtime_error{"No compatible OpenXR swapchain format found"}; | |
| }; | |
| m_swapchain_rgba_format = find_compatible_swapchain_format({ | |
| GL_SRGB8_ALPHA8, | |
| GL_SRGB8, | |
| GL_RGBA8, | |
| }); | |
| if (m_supports_composition_layer_depth) { | |
| m_swapchain_depth_format = find_compatible_swapchain_format({ | |
| GL_DEPTH_COMPONENT32F, | |
| GL_DEPTH_COMPONENT24, | |
| GL_DEPTH_COMPONENT16, | |
| }); | |
| } | |
| // tlog::info() << fmt::format("Chosen swapchain format: {:#x}", m_swapchain_rgba_format); | |
| for (const auto& vcv : m_view_configuration_views) { | |
| XrSwapchainCreateInfo rgba_swapchain_create_info{XR_TYPE_SWAPCHAIN_CREATE_INFO}; | |
| rgba_swapchain_create_info.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; | |
| rgba_swapchain_create_info.format = m_swapchain_rgba_format; | |
| rgba_swapchain_create_info.sampleCount = 1; | |
| rgba_swapchain_create_info.width = vcv.recommendedImageRectWidth; | |
| rgba_swapchain_create_info.height = vcv.recommendedImageRectHeight; | |
| rgba_swapchain_create_info.faceCount = 1; | |
| rgba_swapchain_create_info.arraySize = 1; | |
| rgba_swapchain_create_info.mipCount = 1; | |
| XrSwapchainCreateInfo depth_swapchain_create_info = rgba_swapchain_create_info; | |
| depth_swapchain_create_info.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; | |
| depth_swapchain_create_info.format = m_swapchain_depth_format; | |
| m_swapchains.emplace_back(rgba_swapchain_create_info, depth_swapchain_create_info, m_session, m_instance); | |
| } | |
| } | |
| void OpenXRHMD::init_open_gl_shaders() { | |
| // Hidden area mask program | |
| { | |
| static const char* shader_vert = R"(#version 140 | |
| in vec2 pos; | |
| uniform mat4 project; | |
| void main() { | |
| vec4 pos = project * vec4(pos, -1.0, 1.0); | |
| pos.xyz /= pos.w; | |
| pos.y *= -1.0; | |
| gl_Position = pos; | |
| })"; | |
| static const char* shader_frag = R"(#version 140 | |
| out vec4 frag_color; | |
| void main() { | |
| frag_color = vec4(0.0, 0.0, 0.0, 1.0); | |
| })"; | |
| GLuint vert = glCreateShader(GL_VERTEX_SHADER); | |
| glShaderSource(vert, 1, &shader_vert, NULL); | |
| glCompileShader(vert); | |
| check_shader(vert, "OpenXR hidden area mask vertex shader", false); | |
| GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); | |
| glShaderSource(frag, 1, &shader_frag, NULL); | |
| glCompileShader(frag); | |
| check_shader(frag, "OpenXR hidden area mask fragment shader", false); | |
| m_hidden_area_mask_program = glCreateProgram(); | |
| glAttachShader(m_hidden_area_mask_program, vert); | |
| glAttachShader(m_hidden_area_mask_program, frag); | |
| glLinkProgram(m_hidden_area_mask_program); | |
| check_shader(m_hidden_area_mask_program, "OpenXR hidden area mask shader program", true); | |
| glDeleteShader(vert); | |
| glDeleteShader(frag); | |
| } | |
| } | |
| void OpenXRHMD::session_state_change(XrSessionState state, EControlFlow& flow) { | |
| //tlog::info() << fmt::format("New session state {}", XrEnumStr(state)); | |
| switch (state) { | |
| case XR_SESSION_STATE_READY: { | |
| XrSessionBeginInfo sessionBeginInfo {XR_TYPE_SESSION_BEGIN_INFO}; | |
| sessionBeginInfo.primaryViewConfigurationType = m_view_configuration_type; | |
| XR_CHECK_THROW(xrBeginSession(m_session, &sessionBeginInfo)); | |
| break; | |
| } | |
| case XR_SESSION_STATE_STOPPING: { | |
| XR_CHECK_THROW(xrEndSession(m_session)); | |
| break; | |
| } | |
| case XR_SESSION_STATE_EXITING: { | |
| flow = EControlFlow::Quit; | |
| break; | |
| } | |
| case XR_SESSION_STATE_LOSS_PENDING: { | |
| flow = EControlFlow::Restart; | |
| break; | |
| } | |
| default: { | |
| break; | |
| } | |
| } | |
| } | |
| OpenXRHMD::EControlFlow OpenXRHMD::poll_events() { | |
| bool more = true; | |
| EControlFlow flow = EControlFlow::Continue; | |
| while (more) { | |
| // poll events | |
| XrEventDataBuffer event {XR_TYPE_EVENT_DATA_BUFFER, nullptr}; | |
| XrResult result = xrPollEvent(m_instance, &event); | |
| if (XR_FAILED(result)) { | |
| tlog::error() << "xrPollEvent failed"; | |
| } else if (XR_SUCCESS == result) { | |
| switch (event.type) { | |
| case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { | |
| const XrEventDataSessionStateChanged& e = *reinterpret_cast<XrEventDataSessionStateChanged*>(&event); | |
| //tlog::info() << "Session state change"; | |
| //tlog::info() << fmt::format("\t from {}\t to {}", XrEnumStr(m_session_state), XrEnumStr(e.state)); | |
| //tlog::info() << fmt::format("\t session {}, time {}", fmt::ptr(e.session), e.time); | |
| m_session_state = e.state; | |
| session_state_change(e.state, flow); | |
| break; | |
| } | |
| case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { | |
| flow = EControlFlow::Restart; | |
| break; | |
| } | |
| case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { | |
| m_hidden_area_masks.clear(); | |
| break; | |
| } | |
| case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { | |
| break; // Can ignore | |
| } | |
| default: { | |
| tlog::info() << fmt::format("Unhandled event type {}", XrEnumStr(event.type)); | |
| break; | |
| } | |
| } | |
| } else if (XR_EVENT_UNAVAILABLE == result) { | |
| more = false; | |
| } | |
| } | |
| return flow; | |
| } | |
| __global__ void read_hidden_area_mask_kernel(const ivec2 resolution, cudaSurfaceObject_t surface, uint8_t* __restrict__ mask) { | |
| uint32_t x = threadIdx.x + blockDim.x * blockIdx.x; | |
| uint32_t y = threadIdx.y + blockDim.y * blockIdx.y; | |
| if (x >= resolution.x || y >= resolution.y) { | |
| return; | |
| } | |
| uint32_t idx = x + resolution.x * y; | |
| surf2Dread(&mask[idx], surface, x, y); | |
| } | |
| std::shared_ptr<Buffer2D<uint8_t>> OpenXRHMD::rasterize_hidden_area_mask(uint32_t view_index, const XrCompositionLayerProjectionView& view) { | |
| if (!m_supports_hidden_area_mask) { | |
| return {}; | |
| } | |
| PFN_xrGetVisibilityMaskKHR xrGetVisibilityMaskKHR = nullptr; | |
| XR_CHECK_THROW(xrGetInstanceProcAddr( | |
| m_instance, | |
| "xrGetVisibilityMaskKHR", | |
| reinterpret_cast<PFN_xrVoidFunction*>(&xrGetVisibilityMaskKHR) | |
| )); | |
| XrVisibilityMaskKHR visibility_mask{XR_TYPE_VISIBILITY_MASK_KHR}; | |
| XR_CHECK_THROW(xrGetVisibilityMaskKHR(m_session, m_view_configuration_type, view_index, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask)); | |
| if (visibility_mask.vertexCountOutput == 0 || visibility_mask.indexCountOutput == 0) { | |
| return nullptr; | |
| } | |
| std::vector<XrVector2f> vertices(visibility_mask.vertexCountOutput); | |
| std::vector<uint32_t> indices(visibility_mask.indexCountOutput); | |
| visibility_mask.vertices = vertices.data(); | |
| visibility_mask.indices = indices.data(); | |
| visibility_mask.vertexCapacityInput = visibility_mask.vertexCountOutput; | |
| visibility_mask.indexCapacityInput = visibility_mask.indexCountOutput; | |
| XR_CHECK_THROW(xrGetVisibilityMaskKHR(m_session, m_view_configuration_type, view_index, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask)); | |
| CUDA_CHECK_THROW(cudaDeviceSynchronize()); | |
| ivec2 size = {view.subImage.imageRect.extent.width, view.subImage.imageRect.extent.height}; | |
| bool tex = glIsEnabled(GL_TEXTURE_2D); | |
| bool depth = glIsEnabled(GL_DEPTH_TEST); | |
| bool cull = glIsEnabled(GL_CULL_FACE); | |
| GLint previous_texture_id; | |
| glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous_texture_id); | |
| if (!tex) glEnable(GL_TEXTURE_2D); | |
| if (depth) glDisable(GL_DEPTH_TEST); | |
| if (cull) glDisable(GL_CULL_FACE); | |
| // Generate texture to hold hidden area mask. Single channel, value of 1 means visible and 0 means masked away | |
| ngp::GLTexture mask_texture; | |
| mask_texture.resize(size, 1, true); | |
| glBindTexture(GL_TEXTURE_2D, mask_texture.texture()); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| GLuint framebuffer = 0; | |
| glGenFramebuffers(1, &framebuffer); | |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); | |
| glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mask_texture.texture(), 0); | |
| GLenum draw_buffers[1] = {GL_COLOR_ATTACHMENT0}; | |
| glDrawBuffers(1, draw_buffers); | |
| glViewport(0, 0, size.x, size.y); | |
| // Draw hidden area mask | |
| GLuint vao; | |
| glGenVertexArrays(1, &vao); | |
| glBindVertexArray(vao); | |
| GLuint vertex_buffer; | |
| glGenBuffers(1, &vertex_buffer); | |
| glEnableVertexAttribArray(0); | |
| glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(XrVector2f) * vertices.size(), vertices.data(), GL_STATIC_DRAW); | |
| glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0); | |
| GLuint index_buffer; | |
| glGenBuffers(1, &index_buffer); | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer); | |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * indices.size(), indices.data(), GL_STATIC_DRAW); | |
| glClearColor(1.0f, 1.0f, 1.0f, 1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| glUseProgram(m_hidden_area_mask_program); | |
| XrMatrix4x4f proj; | |
| XrMatrix4x4f_CreateProjectionFov(&proj, GRAPHICS_OPENGL, view.fov, 1.0f / 128.0f, 128.0f); | |
| GLuint project_id = glGetUniformLocation(m_hidden_area_mask_program, "project"); | |
| glUniformMatrix4fv(project_id, 1, GL_FALSE, &proj.m[0]); | |
| glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, (void*)0); | |
| glFinish(); | |
| glDisableVertexAttribArray(0); | |
| glDeleteBuffers(1, &vertex_buffer); | |
| glDeleteBuffers(1, &index_buffer); | |
| glDeleteVertexArrays(1, &vao); | |
| glDeleteFramebuffers(1, &framebuffer); | |
| glBindVertexArray(0); | |
| glUseProgram(0); | |
| // restore old state | |
| if (!tex) glDisable(GL_TEXTURE_2D); | |
| if (depth) glEnable(GL_DEPTH_TEST); | |
| if (cull) glEnable(GL_CULL_FACE); | |
| glBindTexture(GL_TEXTURE_2D, previous_texture_id); | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| std::shared_ptr<Buffer2D<uint8_t>> mask = std::make_shared<Buffer2D<uint8_t>>(size); | |
| const dim3 threads = { 16, 8, 1 }; | |
| const dim3 blocks = { div_round_up((uint32_t)size.x, threads.x), div_round_up((uint32_t)size.y, threads.y), 1 }; | |
| read_hidden_area_mask_kernel<<<blocks, threads>>>(size, mask_texture.surface(), mask->data()); | |
| CUDA_CHECK_THROW(cudaDeviceSynchronize()); | |
| return mask; | |
| } | |
| mat4x3 convert_xr_matrix_to_glm(const XrMatrix4x4f& m) { | |
| mat4x3 out; | |
| for (size_t i = 0; i < 3; ++i) { | |
| for (size_t j = 0; j < 4; ++j) { | |
| out[j][i] = m.m[i + j * 4]; | |
| } | |
| } | |
| // Flip Y and Z axes to match NGP conventions | |
| out[1][0] *= -1.f; | |
| out[0][1] *= -1.f; | |
| out[2][0] *= -1.f; | |
| out[0][2] *= -1.f; | |
| out[3][1] *= -1.f; | |
| out[3][2] *= -1.f; | |
| return out; | |
| } | |
| mat4x3 convert_xr_pose_to_eigen(const XrPosef& pose) { | |
| XrMatrix4x4f matrix; | |
| XrVector3f unit_scale{1.0f, 1.0f, 1.0f}; | |
| XrMatrix4x4f_CreateTranslationRotationScale(&matrix, &pose.position, &pose.orientation, &unit_scale); | |
| return convert_xr_matrix_to_glm(matrix); | |
| } | |
| OpenXRHMD::FrameInfoPtr OpenXRHMD::begin_frame() { | |
| XrFrameWaitInfo frame_wait_info{XR_TYPE_FRAME_WAIT_INFO}; | |
| XR_CHECK_THROW(xrWaitFrame(m_session, &frame_wait_info, &m_frame_state)); | |
| XrFrameBeginInfo frame_begin_info{XR_TYPE_FRAME_BEGIN_INFO}; | |
| XR_CHECK_THROW(xrBeginFrame(m_session, &frame_begin_info)); | |
| if (!m_frame_state.shouldRender) { | |
| return std::make_shared<FrameInfo>(); | |
| } | |
| uint32_t num_views = (uint32_t)m_swapchains.size(); | |
| // TODO assert m_view_configuration_views.size() == m_swapchains.size() | |
| // locate views | |
| std::vector<XrView> views(num_views, {XR_TYPE_VIEW}); | |
| XrViewState viewState{XR_TYPE_VIEW_STATE}; | |
| XrViewLocateInfo view_locate_info{XR_TYPE_VIEW_LOCATE_INFO}; | |
| view_locate_info.viewConfigurationType = m_view_configuration_type; | |
| view_locate_info.displayTime = m_frame_state.predictedDisplayTime; | |
| view_locate_info.space = m_space; | |
| XR_CHECK_THROW(xrLocateViews(m_session, &view_locate_info, &viewState, uint32_t(views.size()), &num_views, views.data())); | |
| if (!(viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) || !(viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT)) { | |
| return std::make_shared<FrameInfo>(); | |
| } | |
| m_hidden_area_masks.resize(num_views); | |
| // Fill frame information | |
| if (!m_previous_frame_info) { | |
| m_previous_frame_info = std::make_shared<FrameInfo>(); | |
| } | |
| FrameInfoPtr frame_info = std::make_shared<FrameInfo>(*m_previous_frame_info); | |
| frame_info->views.resize(m_swapchains.size()); | |
| for (size_t i = 0; i < m_swapchains.size(); ++i) { | |
| const auto& sc = m_swapchains[i]; | |
| XrSwapchainImageAcquireInfo image_acquire_info{XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO}; | |
| XrSwapchainImageWaitInfo image_wait_info{XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, nullptr, XR_INFINITE_DURATION}; | |
| uint32_t image_index; | |
| XR_CHECK_THROW(xrAcquireSwapchainImage(sc.handle, &image_acquire_info, &image_index)); | |
| XR_CHECK_THROW(xrWaitSwapchainImage(sc.handle, &image_wait_info)); | |
| FrameInfo::View& v = frame_info->views[i]; | |
| v.framebuffer = sc.framebuffers_gl[image_index]; | |
| v.view.pose = views[i].pose; | |
| v.view.fov = views[i].fov; | |
| v.view.subImage.imageRect = XrRect2Di{{0, 0}, {sc.width, sc.height}}; | |
| v.view.subImage.imageArrayIndex = 0; | |
| v.view.subImage.swapchain = sc.handle; | |
| glBindFramebuffer(GL_FRAMEBUFFER, sc.framebuffers_gl[image_index]); | |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sc.images_gl.at(image_index).image, 0); | |
| if (sc.depth_handle != XR_NULL_HANDLE) { | |
| uint32_t depth_image_index; | |
| XR_CHECK_THROW(xrAcquireSwapchainImage(sc.depth_handle, &image_acquire_info, &depth_image_index)); | |
| XR_CHECK_THROW(xrWaitSwapchainImage(sc.depth_handle, &image_wait_info)); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, sc.depth_images_gl.at(depth_image_index).image, 0); | |
| v.depth_info.subImage.imageRect = XrRect2Di{{0, 0}, {sc.width, sc.height}}; | |
| v.depth_info.subImage.imageArrayIndex = 0; | |
| v.depth_info.subImage.swapchain = sc.depth_handle; | |
| v.depth_info.minDepth = 0.0f; | |
| v.depth_info.maxDepth = 1.0f; | |
| // To be overwritten with actual near and far planes by end_frame | |
| v.depth_info.nearZ = 1.0f / 128.0f; | |
| v.depth_info.farZ = 128.0f; | |
| } | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| if (!m_hidden_area_masks.at(i)) { | |
| m_hidden_area_masks.at(i) = rasterize_hidden_area_mask(i, v.view); | |
| } | |
| v.hidden_area_mask = m_hidden_area_masks.at(i); | |
| v.pose = convert_xr_pose_to_eigen(v.view.pose); | |
| } | |
| XrActiveActionSet active_action_set{m_action_set, XR_NULL_PATH}; | |
| XrActionsSyncInfo sync_info{XR_TYPE_ACTIONS_SYNC_INFO}; | |
| sync_info.countActiveActionSets = 1; | |
| sync_info.activeActionSets = &active_action_set; | |
| XR_CHECK_THROW(xrSyncActions(m_session, &sync_info)); | |
| for (size_t i = 0; i < 2; ++i) { | |
| // Hand pose | |
| { | |
| XrActionStatePose pose_state{XR_TYPE_ACTION_STATE_POSE}; | |
| XrActionStateGetInfo get_info{XR_TYPE_ACTION_STATE_GET_INFO}; | |
| get_info.action = m_pose_action; | |
| get_info.subactionPath = m_hand_paths[i]; | |
| XR_CHECK_THROW(xrGetActionStatePose(m_session, &get_info, &pose_state)); | |
| frame_info->hands[i].pose_active = pose_state.isActive; | |
| if (frame_info->hands[i].pose_active) { | |
| XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION}; | |
| XR_CHECK_THROW(xrLocateSpace(m_hand_spaces[i], m_space, m_frame_state.predictedDisplayTime, &space_location)); | |
| frame_info->hands[i].pose = convert_xr_pose_to_eigen(space_location.pose); | |
| } | |
| } | |
| // Stick | |
| { | |
| XrActionStateVector2f thumbstick_state{XR_TYPE_ACTION_STATE_VECTOR2F}; | |
| XrActionStateGetInfo get_info{XR_TYPE_ACTION_STATE_GET_INFO}; | |
| get_info.action = m_thumbstick_actions[i]; | |
| XR_CHECK_THROW(xrGetActionStateVector2f(m_session, &get_info, &thumbstick_state)); | |
| if (thumbstick_state.isActive) { | |
| frame_info->hands[i].thumbstick.x = thumbstick_state.currentState.x; | |
| frame_info->hands[i].thumbstick.y = thumbstick_state.currentState.y; | |
| } else { | |
| frame_info->hands[i].thumbstick = vec2(0.0f); | |
| } | |
| } | |
| // Press | |
| { | |
| XrActionStateBoolean press_state{XR_TYPE_ACTION_STATE_BOOLEAN}; | |
| XrActionStateGetInfo get_info{XR_TYPE_ACTION_STATE_GET_INFO}; | |
| get_info.action = m_press_action; | |
| get_info.subactionPath = m_hand_paths[i]; | |
| XR_CHECK_THROW(xrGetActionStateBoolean(m_session, &get_info, &press_state)); | |
| if (press_state.isActive) { | |
| frame_info->hands[i].pressing = press_state.currentState; | |
| } else { | |
| frame_info->hands[i].pressing = 0.0f; | |
| } | |
| } | |
| // Grab | |
| { | |
| XrActionStateFloat grab_state{XR_TYPE_ACTION_STATE_FLOAT}; | |
| XrActionStateGetInfo get_info{XR_TYPE_ACTION_STATE_GET_INFO}; | |
| get_info.action = m_grab_action; | |
| get_info.subactionPath = m_hand_paths[i]; | |
| XR_CHECK_THROW(xrGetActionStateFloat(m_session, &get_info, &grab_state)); | |
| if (grab_state.isActive) { | |
| frame_info->hands[i].grab_strength = grab_state.currentState; | |
| } else { | |
| frame_info->hands[i].grab_strength = 0.0f; | |
| } | |
| bool was_grabbing = frame_info->hands[i].grabbing; | |
| frame_info->hands[i].grabbing = frame_info->hands[i].grab_strength >= 0.5f; | |
| if (frame_info->hands[i].grabbing) { | |
| frame_info->hands[i].prev_grab_pos = was_grabbing ? frame_info->hands[i].grab_pos : frame_info->hands[i].pose[3]; | |
| frame_info->hands[i].grab_pos = frame_info->hands[i].pose[3]; | |
| } | |
| } | |
| } | |
| m_previous_frame_info = frame_info; | |
| return frame_info; | |
| } | |
| void OpenXRHMD::end_frame(FrameInfoPtr frame_info, float znear, float zfar, bool submit_depth) { | |
| std::vector<XrCompositionLayerProjectionView> layer_projection_views(frame_info->views.size()); | |
| for (size_t i = 0; i < layer_projection_views.size(); ++i) { | |
| auto& v = frame_info->views[i]; | |
| auto& view = layer_projection_views[i]; | |
| view = v.view; | |
| // release swapchain image | |
| XrSwapchainImageReleaseInfo release_info{XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO}; | |
| XR_CHECK_THROW(xrReleaseSwapchainImage(v.view.subImage.swapchain, &release_info)); | |
| if (v.depth_info.subImage.swapchain != XR_NULL_HANDLE) { | |
| XR_CHECK_THROW(xrReleaseSwapchainImage(v.depth_info.subImage.swapchain, &release_info)); | |
| v.depth_info.nearZ = znear; | |
| v.depth_info.farZ = zfar; | |
| // Submitting the depth buffer to the runtime for reprojection is optional, | |
| // because, while depth-based reprojection can make the experience smoother, | |
| // it also results in distortion around geometric edges. Many users prefer | |
| // a more stuttery experience without this distortion. | |
| if (submit_depth) { | |
| view.next = &v.depth_info; | |
| } | |
| } | |
| } | |
| XrCompositionLayerProjection layer{XR_TYPE_COMPOSITION_LAYER_PROJECTION}; | |
| layer.space = m_space; | |
| if (m_environment_blend_mode != EEnvironmentBlendMode::Opaque) { | |
| layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; | |
| } | |
| layer.viewCount = uint32_t(layer_projection_views.size()); | |
| layer.views = layer_projection_views.data(); | |
| std::vector<XrCompositionLayerBaseHeader*> layers; | |
| if (layer.viewCount) { | |
| layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader*>(&layer)); | |
| } | |
| XrFrameEndInfo frame_end_info{XR_TYPE_FRAME_END_INFO}; | |
| frame_end_info.displayTime = m_frame_state.predictedDisplayTime; | |
| frame_end_info.environmentBlendMode = (XrEnvironmentBlendMode)m_environment_blend_mode; | |
| frame_end_info.layerCount = (uint32_t)layers.size(); | |
| frame_end_info.layers = layers.data(); | |
| XR_CHECK_THROW(xrEndFrame(m_session, &frame_end_info)); | |
| } | |
| } | |