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)); | |
} | |
} | |