/* * 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. */ #include #include #include #include #include #include #include #include #include #include #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" //TODO: XR struct are uninitiaized apart from their type #endif namespace ngp { // function XrEnumStr turns enum into string for printing // uses expansion macro and data provided in openxr_reflection.h #define XR_ENUM_CASE_STR(name, val) \ case name: \ return #name; #define XR_ENUM_STR(enum_type) \ constexpr const char* XrEnumStr(enum_type e) { \ switch (e) { \ XR_LIST_ENUM_##enum_type(XR_ENUM_CASE_STR) default : return "Unknown"; \ } \ } 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 #define XR_CHECK_THROW(x) \ do { \ XrResult result = x; \ if (XR_FAILED(result)) { \ char buffer[XR_MAX_RESULT_STRING_SIZE]; \ XrResult result_to_string_result = xrResultToString(m_instance, result, buffer); \ if (XR_FAILED(result_to_string_result)) { \ throw std::runtime_error{std::string(FILE_LINE " " #x " failed, but could not obtain error string")}; \ } else { \ throw std::runtime_error{std::string(FILE_LINE " " #x " failed with error ") + buffer}; \ } \ } \ } while(0) 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; } } #if defined(XR_USE_PLATFORM_WIN32) OpenXRHMD::OpenXRHMD(HDC hdc, HGLRC hglrc) { #elif defined(XR_USE_PLATFORM_XLIB) OpenXRHMD::OpenXRHMD(Display* xDisplay, uint32_t visualid, GLXFBConfig glxFBConfig, GLXDrawable glxDrawable, GLXContext glxContext) { #elif defined(XR_USE_PLATFORM_WAYLAND) OpenXRHMD::OpenXRHMD(wl_display* display) { #endif ScopeGuard cleanup_guard{[&]() { clear(); }}; init_create_xr_instance(); init_get_xr_system(); init_configure_xr_views(); init_check_for_xr_blend_mode(); #if defined(XR_USE_PLATFORM_WIN32) init_open_gl(hdc, hglrc); #elif defined(XR_USE_PLATFORM_XLIB) init_open_gl(xDisplay, visualid, glxFBConfig, glxDrawable, glxContext); #elif defined(XR_USE_PLATFORM_WAYLAND) init_open_gl(display); #endif 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 layers = {}; std::vector 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 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 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 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 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& 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 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"}), }); } #if defined(XR_USE_PLATFORM_WIN32) void OpenXRHMD::init_open_gl(HDC hdc, HGLRC hglrc) { #elif defined(XR_USE_PLATFORM_XLIB) void OpenXRHMD::init_open_gl(Display* xDisplay, uint32_t visualid, GLXFBConfig glxFBConfig, GLXDrawable glxDrawable, GLXContext glxContext) { #elif defined(XR_USE_PLATFORM_WAYLAND) void OpenXRHMD::init_open_gl(wl_display* display) { #endif // GL graphics requirements PFN_xrGetOpenGLGraphicsRequirementsKHR xrGetOpenGLGraphicsRequirementsKHR = nullptr; XR_CHECK_THROW(xrGetInstanceProcAddr( m_instance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast(&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"}; } #if defined(XR_USE_PLATFORM_WIN32) m_graphics_binding.hDC = hdc; m_graphics_binding.hGLRC = hglrc; #elif defined(XR_USE_PLATFORM_XLIB) 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; #elif defined(XR_USE_PLATFORM_WAYLAND) m_graphics_binding.display = display; #endif } void OpenXRHMD::init_xr_session() { // create session XrSessionCreateInfo create_info{ XR_TYPE_SESSION_CREATE_INFO, reinterpret_cast(&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 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& 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(&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> 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(&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 vertices(visibility_mask.vertexCountOutput); std::vector 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> mask = std::make_shared>(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<<>>(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(); } uint32_t num_views = (uint32_t)m_swapchains.size(); // TODO assert m_view_configuration_views.size() == m_swapchains.size() // locate views std::vector 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(); } m_hidden_area_masks.resize(num_views); // Fill frame information if (!m_previous_frame_info) { m_previous_frame_info = std::make_shared(); } FrameInfoPtr frame_info = std::make_shared(*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 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 layers; if (layer.viewCount) { layers.push_back(reinterpret_cast(&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)); } } #ifdef __GNUC__ #pragma GCC diagnostic pop #endif