diff options
Diffstat (limited to 'intern/ghost/intern')
-rw-r--r-- | intern/ghost/intern/GHOST_C-api.cpp | 144 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_Util.h | 45 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_XrAction.cpp | 477 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_XrAction.h | 145 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_XrContext.cpp | 20 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_XrContext.h | 5 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_XrException.h | 5 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_XrSession.cpp | 365 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_XrSession.h | 37 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_Xr_intern.h | 24 |
10 files changed, 1238 insertions, 29 deletions
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 1d96354c504..955f35274ea 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -33,6 +33,7 @@ #include "intern/GHOST_Debug.h" #ifdef WITH_XR_OPENXR # include "GHOST_IXrContext.h" +# include "intern/GHOST_XrSession.h" #endif #include "intern/GHOST_CallbackEventConsumer.h" #include "intern/GHOST_XrException.h" @@ -953,4 +954,145 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context return 0; /* Only reached if exception is thrown. */ } -#endif +int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_contexthandle, + const GHOST_XrActionSetInfo *info) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSet(*info), xr_context); + return 0; +} + +void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSet(action_set_name), xr_context); +} + +int GHOST_XrCreateActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActions(action_set_name, count, infos), xr_context); + return 0; +} + +void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const char *const *action_names) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActions(action_set_name, count, action_names), xr_context); +} + +int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSpaces(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSpaces(action_set_name, count, infos), xr_context); +} + +int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionBindings(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionBindings(action_set_name, count, infos), xr_context); +} + +int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_contexthandle) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->attachActionSets(), xr_context); + return 0; +} + +int GHOST_XrSyncActions(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->syncActions(action_set_name), xr_context); + return 0; +} + +int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name, + const GHOST_TInt64 *duration, + const float *frequency, + const float *amplitude) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->applyHapticAction( + action_set_name, action_name, *duration, *frequency, *amplitude), + xr_context); + return 0; +} + +void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->stopHapticAction(action_set_name, action_name), xr_context); +} + +void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionSetCustomdata(action_set_name), xr_context); + return 0; +} + +void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionCustomdata(action_set_name, action_name), + xr_context); + return 0; +} + +#endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_Util.h b/intern/ghost/intern/GHOST_Util.h new file mode 100644 index 00000000000..8be5e373b28 --- /dev/null +++ b/intern/ghost/intern/GHOST_Util.h @@ -0,0 +1,45 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#pragma once + +#include <functional> + +/** + * RAII wrapper for typical C `void *` custom data. + * Used for exception safe custom-data handling during constructor calls. + */ +struct GHOST_C_CustomDataWrapper { + using FreeFn = std::function<void(void *)>; + + void *custom_data_; + FreeFn free_fn_; + + GHOST_C_CustomDataWrapper(void *custom_data, FreeFn free_fn) + : custom_data_(custom_data), free_fn_(free_fn) + { + } + ~GHOST_C_CustomDataWrapper() + { + if (free_fn_ != nullptr && custom_data_ != nullptr) { + free_fn_(custom_data_); + } + } +}; diff --git a/intern/ghost/intern/GHOST_XrAction.cpp b/intern/ghost/intern/GHOST_XrAction.cpp new file mode 100644 index 00000000000..172ac40c84f --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.cpp @@ -0,0 +1,477 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#include <cassert> +#include <cstring> + +#include "GHOST_Types.h" + +#include "GHOST_XrException.h" +#include "GHOST_Xr_intern.h" + +#include "GHOST_XrAction.h" + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSpace + * + * \{ */ + +GHOST_XrActionSpace::GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx) +{ + const char *subaction_path = info.subaction_paths[subaction_idx]; + CHECK_XR(xrStringToPath(instance, subaction_path, &m_subaction_path), + (std::string("Failed to get user path \"") + subaction_path + "\".").data()); + + XrActionSpaceCreateInfo action_space_info{XR_TYPE_ACTION_SPACE_CREATE_INFO}; + action_space_info.action = action; + action_space_info.subactionPath = m_subaction_path; + copy_ghost_pose_to_openxr_pose(info.poses[subaction_idx], action_space_info.poseInActionSpace); + + CHECK_XR(xrCreateActionSpace(session, &action_space_info, &m_space), + (std::string("Failed to create space \"") + subaction_path + "\" for action \"" + + info.action_name + "\".") + .data()); +} + +GHOST_XrActionSpace::~GHOST_XrActionSpace() +{ + if (m_space != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroySpace(m_space)); + } +} + +XrSpace GHOST_XrActionSpace::getSpace() const +{ + return m_space; +} + +const XrPath &GHOST_XrActionSpace::getSubactionPath() const +{ + return m_subaction_path; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionProfile + * + * \{ */ + +GHOST_XrActionProfile::GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + CHECK_XR( + xrStringToPath(instance, profile_path, &m_profile), + (std::string("Failed to get interaction profile path \"") + profile_path + "\".").data()); + + /* Create bindings. */ + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + bindings_info.interactionProfile = m_profile; + bindings_info.countSuggestedBindings = 1; + + for (uint32_t interaction_idx = 0; interaction_idx < info.count_interaction_paths; + ++interaction_idx) { + const char *interaction_path = info.interaction_paths[interaction_idx]; + if (m_bindings.find(interaction_path) != m_bindings.end()) { + continue; + } + + XrActionSuggestedBinding sbinding; + sbinding.action = action; + CHECK_XR(xrStringToPath(instance, interaction_path, &sbinding.binding), + (std::string("Failed to get interaction path \"") + interaction_path + "\".").data()); + bindings_info.suggestedBindings = &sbinding; + + /* Although the bindings will be re-suggested in GHOST_XrSession::attachActionSets(), it + * greatly improves error checking to suggest them here first. */ + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + (std::string("Failed to create binding for profile \"") + profile_path + + "\" and action \"" + info.action_name + + "\". Are the profile and action paths correct?") + .data()); + + m_bindings.insert({interaction_path, sbinding.binding}); + } +} + +void GHOST_XrActionProfile::getBindings( + XrAction action, std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + std::map<XrPath, std::vector<XrActionSuggestedBinding>>::iterator it = r_bindings.find( + m_profile); + if (it == r_bindings.end()) { + it = r_bindings + .emplace(std::piecewise_construct, std::make_tuple(m_profile), std::make_tuple()) + .first; + } + + std::vector<XrActionSuggestedBinding> &sbindings = it->second; + + for (auto &[path, binding] : m_bindings) { + XrActionSuggestedBinding sbinding; + sbinding.action = action; + sbinding.binding = binding; + + sbindings.push_back(std::move(sbinding)); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrAction + * + * \{ */ + +GHOST_XrAction::GHOST_XrAction(XrInstance instance, + XrActionSet action_set, + const GHOST_XrActionInfo &info) + : m_type(info.type), + m_states(info.states), + m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + m_subaction_paths.resize(info.count_subaction_paths); + + for (uint32_t i = 0; i < info.count_subaction_paths; ++i) { + CHECK_XR(xrStringToPath(instance, info.subaction_paths[i], &m_subaction_paths[i]), + (std::string("Failed to get user path \"") + info.subaction_paths[i] + "\".").data()); + } + + XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO}; + strcpy(action_info.actionName, info.name); + strcpy(action_info.localizedActionName, info.name); /* Just use same name for localized. This can + be changed in the future if necessary. */ + + switch (info.type) { + case GHOST_kXrActionTypeBooleanInput: + action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; + break; + case GHOST_kXrActionTypeFloatInput: + action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT; + break; + case GHOST_kXrActionTypeVector2fInput: + action_info.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT; + break; + case GHOST_kXrActionTypePoseInput: + action_info.actionType = XR_ACTION_TYPE_POSE_INPUT; + break; + case GHOST_kXrActionTypeVibrationOutput: + action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT; + break; + } + action_info.countSubactionPaths = info.count_subaction_paths; + action_info.subactionPaths = m_subaction_paths.data(); + + CHECK_XR(xrCreateAction(action_set, &action_info, &m_action), + (std::string("Failed to create action \"") + info.name + + "\". Action name and/or paths are invalid. Name must not contain upper " + "case letters or special characters other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrAction::~GHOST_XrAction() +{ + if (m_action != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyAction(m_action)); + } +} + +bool GHOST_XrAction::createSpace(XrInstance instance, + XrSession session, + const GHOST_XrActionSpaceInfo &info) +{ + uint32_t subaction_idx = 0; + for (; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + if (m_spaces.find(info.subaction_paths[subaction_idx]) != m_spaces.end()) { + return false; + } + } + + for (subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + m_spaces.emplace(std::piecewise_construct, + std::make_tuple(info.subaction_paths[subaction_idx]), + std::make_tuple(instance, session, m_action, info, subaction_idx)); + } + + return true; +} + +void GHOST_XrAction::destroySpace(const char *subaction_path) +{ + if (m_spaces.find(subaction_path) != m_spaces.end()) { + m_spaces.erase(subaction_path); + } +} + +bool GHOST_XrAction::createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + if (m_profiles.find(profile_path) != m_profiles.end()) { + return false; + } + + m_profiles.emplace(std::piecewise_construct, + std::make_tuple(profile_path), + std::make_tuple(instance, m_action, profile_path, info)); + + return true; +} + +void GHOST_XrAction::destroyBinding(const char *interaction_profile_path) +{ + if (m_profiles.find(interaction_profile_path) != m_profiles.end()) { + m_profiles.erase(interaction_profile_path); + } +} + +void GHOST_XrAction::updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + XrActionStateGetInfo state_info{XR_TYPE_ACTION_STATE_GET_INFO}; + state_info.action = m_action; + + const size_t count_subaction_paths = m_subaction_paths.size(); + for (size_t subaction_idx = 0; subaction_idx < count_subaction_paths; ++subaction_idx) { + state_info.subactionPath = m_subaction_paths[subaction_idx]; + + switch (m_type) { + case GHOST_kXrActionTypeBooleanInput: { + XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN}; + CHECK_XR(xrGetActionStateBoolean(session, &state_info, &state), + (std::string("Failed to get state for boolean action \"") + action_name + "\".") + .data()); + if (state.isActive) { + ((bool *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeFloatInput: { + XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT}; + CHECK_XR( + xrGetActionStateFloat(session, &state_info, &state), + (std::string("Failed to get state for float action \"") + action_name + "\".").data()); + if (state.isActive) { + ((float *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeVector2fInput: { + XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F}; + CHECK_XR(xrGetActionStateVector2f(session, &state_info, &state), + (std::string("Failed to get state for vector2f action \"") + action_name + "\".") + .data()); + if (state.isActive) { + memcpy(((float(*)[2])m_states)[subaction_idx], &state.currentState, sizeof(float[2])); + } + break; + } + case GHOST_kXrActionTypePoseInput: { + XrActionStatePose state{XR_TYPE_ACTION_STATE_POSE}; + CHECK_XR( + xrGetActionStatePose(session, &state_info, &state), + (std::string("Failed to get state for pose action \"") + action_name + "\".").data()); + if (state.isActive) { + XrSpace pose_space = XR_NULL_HANDLE; + for (auto &[path, space] : m_spaces) { + if (space.getSubactionPath() == state_info.subactionPath) { + pose_space = space.getSpace(); + break; + } + } + + if (pose_space != XR_NULL_HANDLE) { + XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION}; + CHECK_XR( + xrLocateSpace( + pose_space, reference_space, predicted_display_time, &space_location), + (std::string("Failed to query pose space for action \"") + action_name + "\".") + .data()); + copy_openxr_pose_to_ghost_pose(space_location.pose, + ((GHOST_XrPose *)m_states)[subaction_idx]); + } + } + break; + } + case GHOST_kXrActionTypeVibrationOutput: { + break; + } + } + } +} + +void GHOST_XrAction::applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION}; + vibration.duration = (duration == 0) ? XR_MIN_HAPTIC_DURATION : + static_cast<XrDuration>(duration); + vibration.frequency = frequency; + vibration.amplitude = amplitude; + + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration), + (std::string("Failed to apply haptic action \"") + action_name + "\".").data()); + } +} + +void GHOST_XrAction::stopHapticFeedback(XrSession session, const char *action_name) +{ + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrStopHapticFeedback(session, &haptic_info), + (std::string("Failed to stop haptic action \"") + action_name + "\".").data()); + } +} + +void *GHOST_XrAction::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrAction::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[path, profile] : m_profiles) { + profile.getBindings(m_action, r_bindings); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSet + * + * \{ */ + +GHOST_XrActionSet::GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info) + : m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + XrActionSetCreateInfo action_set_info{XR_TYPE_ACTION_SET_CREATE_INFO}; + strcpy(action_set_info.actionSetName, info.name); + strcpy(action_set_info.localizedActionSetName, + info.name); /* Just use same name for localized. This can be changed in the future if + necessary. */ + action_set_info.priority = 0; /* Use same (default) priority for all action sets. */ + + CHECK_XR(xrCreateActionSet(instance, &action_set_info, &m_action_set), + (std::string("Failed to create action set \"") + info.name + + "\". Name must not contain upper case letters or special characters " + "other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrActionSet::~GHOST_XrActionSet() +{ + /* This needs to be done before xrDestroyActionSet() to avoid an assertion in the GHOST_XrAction + * destructor (which calls xrDestroyAction()). */ + m_actions.clear(); + + if (m_action_set != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyActionSet(m_action_set)); + } +} + +bool GHOST_XrActionSet::createAction(XrInstance instance, const GHOST_XrActionInfo &info) +{ + if (m_actions.find(info.name) != m_actions.end()) { + return false; + } + + m_actions.emplace(std::piecewise_construct, + std::make_tuple(info.name), + std::make_tuple(instance, m_action_set, info)); + + return true; +} + +void GHOST_XrActionSet::destroyAction(const char *action_name) +{ + if (m_actions.find(action_name) != m_actions.end()) { + m_actions.erase(action_name); + } +} + +GHOST_XrAction *GHOST_XrActionSet::findAction(const char *action_name) +{ + std::map<std::string, GHOST_XrAction>::iterator it = m_actions.find(action_name); + if (it == m_actions.end()) { + return nullptr; + } + return &it->second; +} + +void GHOST_XrActionSet::updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + for (auto &[name, action] : m_actions) { + action.updateState(session, name.data(), reference_space, predicted_display_time); + } +} + +XrActionSet GHOST_XrActionSet::getActionSet() const +{ + return m_action_set; +} + +void *GHOST_XrActionSet::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrActionSet::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[name, action] : m_actions) { + action.getBindings(r_bindings); + } +} + +/** \} */ diff --git a/intern/ghost/intern/GHOST_XrAction.h b/intern/ghost/intern/GHOST_XrAction.h new file mode 100644 index 00000000000..bdc6cafb4a9 --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.h @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrSpace, XrPath, + * etc.). */ + +#pragma once + +#include <map> +#include <memory> +#include <string> + +#include "GHOST_Util.h" + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSpace { + public: + GHOST_XrActionSpace() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx); + ~GHOST_XrActionSpace(); + + XrSpace getSpace() const; + const XrPath &getSubactionPath() const; + + private: + XrSpace m_space = XR_NULL_HANDLE; + XrPath m_subaction_path = XR_NULL_PATH; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionProfile { + public: + GHOST_XrActionProfile() = delete; /* Default constructor for map storage. */ + GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + ~GHOST_XrActionProfile() = default; + + void getBindings(XrAction action, + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrPath m_profile = XR_NULL_PATH; + /* Bindings identified by interaction (user (subaction) + component) path. */ + std::map<std::string, XrPath> m_bindings; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrAction { + public: + GHOST_XrAction() = delete; /* Default constructor for map storage. */ + GHOST_XrAction(XrInstance instance, XrActionSet action_set, const GHOST_XrActionInfo &info); + ~GHOST_XrAction(); + + bool createSpace(XrInstance instance, XrSession session, const GHOST_XrActionSpaceInfo &info); + void destroySpace(const char *subaction_path); + + bool createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + void destroyBinding(const char *profile_path); + + void updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time); + void applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticFeedback(XrSession session, const char *action_name); + + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrAction m_action = XR_NULL_HANDLE; + GHOST_XrActionType m_type; + std::vector<XrPath> m_subaction_paths; + /** States for each subaction path. */ + void *m_states; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrAction */ + + /* Spaces identified by user (subaction) path. */ + std::map<std::string, GHOST_XrActionSpace> m_spaces; + /* Profiles identified by interaction profile path. */ + std::map<std::string, GHOST_XrActionProfile> m_profiles; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSet { + public: + GHOST_XrActionSet() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info); + ~GHOST_XrActionSet(); + + bool createAction(XrInstance instance, const GHOST_XrActionInfo &info); + void destroyAction(const char *action_name); + GHOST_XrAction *findAction(const char *action_name); + + void updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time); + + XrActionSet getActionSet() const; + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrActionSet m_action_set = XR_NULL_HANDLE; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrActionSet */ + + std::map<std::string, GHOST_XrAction> m_actions; +}; + +/* -------------------------------------------------------------------- */ diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index 2bf67c121f8..daad0b8190a 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -55,7 +55,6 @@ void *GHOST_XrContext::s_error_handler_customdata = nullptr; /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrContext::GHOST_XrContext(const GHOST_XrContextCreateInfo *create_info) @@ -153,7 +152,6 @@ void GHOST_XrContext::storeInstanceProperties() /* -------------------------------------------------------------------- */ /** \name Debug Printing - * * \{ */ void GHOST_XrContext::printInstanceInfo() @@ -242,14 +240,13 @@ void GHOST_XrContext::initDebugMessenger() /* -------------------------------------------------------------------- */ /** \name Error handling - * * \{ */ void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) const { GHOST_XrError error; - error.user_message = exception->m_msg; + error.user_message = exception->m_msg.data(); error.customdata = s_error_handler_customdata; if (isDebugMode()) { @@ -273,7 +270,6 @@ void GHOST_XrContext::setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *c /* -------------------------------------------------------------------- */ /** \name OpenXR API-Layers and Extensions - * * \{ */ /** @@ -378,7 +374,7 @@ void GHOST_XrContext::getAPILayersToEnable(std::vector<const char *> &r_ext_name for (const std::string &layer : try_layers) { if (openxr_layer_is_available(m_oxr->layers, layer)) { - r_ext_names.push_back(layer.c_str()); + r_ext_names.push_back(layer.data()); } } } @@ -488,6 +484,7 @@ GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse( void GHOST_XrContext::startSession(const GHOST_XrSessionBeginInfo *begin_info) { + m_custom_funcs.session_create_fn = begin_info->create_fn; m_custom_funcs.session_exit_fn = begin_info->exit_fn; m_custom_funcs.session_exit_customdata = begin_info->exit_customdata; @@ -538,6 +535,16 @@ void GHOST_XrContext::handleSessionStateChange(const XrEventDataSessionStateChan * Public as in, exposed in the Ghost API. * \{ */ +GHOST_XrSession *GHOST_XrContext::getSession() +{ + return m_session.get(); +} + +const GHOST_XrSession *GHOST_XrContext::getSession() const +{ + return m_session.get(); +} + void GHOST_XrContext::setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn, GHOST_XrGraphicsContextUnbindFn unbind_fn) { @@ -564,7 +571,6 @@ bool GHOST_XrContext::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name Ghost Internal Accessors and Mutators - * * \{ */ GHOST_TXrOpenXRRuntimeID GHOST_XrContext::getOpenXRRuntimeID() const diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h index 59c7786ed7a..f29d7349f7e 100644 --- a/intern/ghost/intern/GHOST_XrContext.h +++ b/intern/ghost/intern/GHOST_XrContext.h @@ -35,6 +35,7 @@ struct GHOST_XrCustomFuncs { /** Function to release (possibly free) a graphics context. */ GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn = nullptr; + GHOST_XrSessionCreateFn session_create_fn = nullptr; GHOST_XrSessionExitFn session_exit_fn = nullptr; void *session_exit_customdata = nullptr; @@ -72,6 +73,10 @@ class GHOST_XrContext : public GHOST_IXrContext { bool isSessionRunning() const override; void drawSessionViews(void *draw_customdata) override; + /** Needed for the GHOST C api. */ + GHOST_XrSession *getSession() override; + const GHOST_XrSession *getSession() const override; + static void setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata); void dispatchErrorMessage(const class GHOST_XrException *exception) const override; diff --git a/intern/ghost/intern/GHOST_XrException.h b/intern/ghost/intern/GHOST_XrException.h index 30c33eaf98f..e93164e04c8 100644 --- a/intern/ghost/intern/GHOST_XrException.h +++ b/intern/ghost/intern/GHOST_XrException.h @@ -21,6 +21,7 @@ #pragma once #include <exception> +#include <string> class GHOST_XrException : public std::exception { friend class GHOST_XrContext; @@ -33,10 +34,10 @@ class GHOST_XrException : public std::exception { const char *what() const noexcept override { - return m_msg; + return m_msg.data(); } private: - const char *m_msg; + std::string m_msg; int m_result; }; diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp index a1fd7fc2781..a7438fae13c 100644 --- a/intern/ghost/intern/GHOST_XrSession.cpp +++ b/intern/ghost/intern/GHOST_XrSession.cpp @@ -28,6 +28,7 @@ #include "GHOST_C-api.h" #include "GHOST_IXrGraphicsBinding.h" +#include "GHOST_XrAction.h" #include "GHOST_XrContext.h" #include "GHOST_XrException.h" #include "GHOST_XrSwapchain.h" @@ -46,6 +47,8 @@ struct OpenXRSessionData { XrSpace view_space; std::vector<XrView> views; std::vector<GHOST_XrSwapchain> swapchains; + + std::map<std::string, GHOST_XrActionSet> action_sets; }; struct GHOST_XrDrawInfo { @@ -59,7 +62,6 @@ struct GHOST_XrDrawInfo { /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrSession::GHOST_XrSession(GHOST_XrContext &xr_context) @@ -72,6 +74,7 @@ GHOST_XrSession::~GHOST_XrSession() unbindGraphicsContext(); m_oxr->swapchains.clear(); + m_oxr->action_sets.clear(); if (m_oxr->reference_space != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space)); @@ -110,7 +113,6 @@ void GHOST_XrSession::initSystem() /* -------------------------------------------------------------------- */ /** \name State Management - * * \{ */ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose) @@ -179,7 +181,7 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) std::ostringstream strstream; strstream << "Available graphics context version does not meet the following requirements: " << requirement_str; - throw GHOST_XrException(strstream.str().c_str()); + throw GHOST_XrException(strstream.str().data()); } m_gpu_binding->initFromGhostContext(*m_gpu_ctx); @@ -196,6 +198,9 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) prepareDrawing(); create_reference_spaces(*m_oxr, begin_info->base_pose); + + /* Create and bind actions here. */ + m_context->getCustomFuncs().session_create_fn(); } void GHOST_XrSession::requestEnd() @@ -225,10 +230,9 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session); switch (lifecycle.state) { - case XR_SESSION_STATE_READY: { + case XR_SESSION_STATE_READY: beginSession(); break; - } case XR_SESSION_STATE_STOPPING: endSession(); break; @@ -245,7 +249,6 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( /* -------------------------------------------------------------------- */ /** \name Drawing - * * \{ */ void GHOST_XrSession::prepareDrawing() @@ -352,18 +355,6 @@ void GHOST_XrSession::draw(void *draw_customdata) endFrameDrawing(layers); } -static void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) -{ - /* Set and convert to Blender coordinate space. */ - r_ghost_pose.position[0] = oxr_pose.position.x; - r_ghost_pose.position[1] = oxr_pose.position.y; - r_ghost_pose.position[2] = oxr_pose.position.z; - r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; - r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; - r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; - r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; -} - static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info) { /* Set and convert to Blender coordinate space. */ @@ -457,7 +448,6 @@ bool GHOST_XrSession::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name State Queries - * * \{ */ bool GHOST_XrSession::isRunning() const @@ -505,3 +495,340 @@ void GHOST_XrSession::unbindGraphicsContext() } /** \} */ /* Graphics Context Injection */ + +/* -------------------------------------------------------------------- */ +/** \name Actions + * + * \{ */ + +static GHOST_XrActionSet *find_action_set(OpenXRSessionData *oxr, const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet>::iterator it = oxr->action_sets.find(action_set_name); + if (it == oxr->action_sets.end()) { + return nullptr; + } + return &it->second; +} + +bool GHOST_XrSession::createActionSet(const GHOST_XrActionSetInfo &info) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(info.name) != action_sets.end()) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + action_sets.emplace( + std::piecewise_construct, std::make_tuple(info.name), std::make_tuple(instance, info)); + + return true; +} + +void GHOST_XrSession::destroyActionSet(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(action_set_name) != action_sets.end()) { + action_sets.erase(action_set_name); + } +} + +bool GHOST_XrSession::createActions(const char *action_set_name, + uint32_t count, + const GHOST_XrActionInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t i = 0; i < count; ++i) { + if (!action_set->createAction(instance, infos[i])) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t i = 0; i < count; ++i) { + action_set->destroyAction(action_names[i]); + } +} + +bool GHOST_XrSession::createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + XrSession session = m_oxr->session; + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + if (!action->createSpace(instance, session, info)) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + for (uint32_t subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + action->destroySpace(info.subaction_paths[subaction_idx]); + } + } +} + +bool GHOST_XrSession::createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->createBinding(instance, profile_path, binding); + } + } + + return true; +} + +void GHOST_XrSession::destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->destroyBinding(profile_path); + } + } +} + +bool GHOST_XrSession::attachActionSets() +{ + /* Suggest action bindings for all action sets. */ + std::map<XrPath, std::vector<XrActionSuggestedBinding>> profile_bindings; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_set.getBindings(profile_bindings); + } + + if (profile_bindings.size() < 1) { + return false; + } + + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + XrInstance instance = m_context->getInstance(); + + for (auto &[profile, bindings] : profile_bindings) { + bindings_info.interactionProfile = profile; + bindings_info.countSuggestedBindings = (uint32_t)bindings.size(); + bindings_info.suggestedBindings = bindings.data(); + + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + "Failed to suggest interaction profile bindings."); + } + + /* Attach action sets. */ + XrSessionActionSetsAttachInfo attach_info{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; + attach_info.countActionSets = (uint32_t)m_oxr->action_sets.size(); + + /* Create an aligned copy of the action sets to pass to xrAttachSessionActionSets(). */ + std::vector<XrActionSet> action_sets(attach_info.countActionSets); + uint32_t i = 0; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_sets[i++] = action_set.getActionSet(); + } + attach_info.actionSets = action_sets.data(); + + CHECK_XR(xrAttachSessionActionSets(m_oxr->session, &attach_info), + "Failed to attach XR action sets."); + + return true; +} + +bool GHOST_XrSession::syncActions(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + + XrActionsSyncInfo sync_info{XR_TYPE_ACTIONS_SYNC_INFO}; + sync_info.countActiveActionSets = (action_set_name != nullptr) ? 1 : + (uint32_t)action_sets.size(); + if (sync_info.countActiveActionSets < 1) { + return false; + } + + std::vector<XrActiveActionSet> active_action_sets(sync_info.countActiveActionSets); + GHOST_XrActionSet *action_set = nullptr; + + if (action_set_name != nullptr) { + action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrActiveActionSet &active_action_set = active_action_sets[0]; + active_action_set.actionSet = action_set->getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + else { + uint32_t i = 0; + for (auto &[name, action_set] : action_sets) { + XrActiveActionSet &active_action_set = active_action_sets[i++]; + active_action_set.actionSet = action_set.getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + } + sync_info.activeActionSets = active_action_sets.data(); + + CHECK_XR(xrSyncActions(m_oxr->session, &sync_info), "Failed to synchronize XR actions."); + + /* Update action states (i.e. Blender custom data). */ + XrSession session = m_oxr->session; + XrSpace reference_space = m_oxr->reference_space; + const XrTime &predicted_display_time = m_draw_info->frame_state.predictedDisplayTime; + + if (action_set != nullptr) { + action_set->updateStates(session, reference_space, predicted_display_time); + } + else { + for (auto &[name, action_set] : action_sets) { + action_set.updateStates(session, reference_space, predicted_display_time); + } + } + + return true; +} + +bool GHOST_XrSession::applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return false; + } + + action->applyHapticFeedback(m_oxr->session, action_name, duration, frequency, amplitude); + + return true; +} + +void GHOST_XrSession::stopHapticAction(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return; + } + + action->stopHapticFeedback(m_oxr->session, action_name); +} + +void *GHOST_XrSession::getActionSetCustomdata(const char *action_set_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + return action_set->getCustomdata(); +} + +void *GHOST_XrSession::getActionCustomdata(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return nullptr; + } + + return action->getCustomdata(); +} + +/** \} */ /* Actions */ diff --git a/intern/ghost/intern/GHOST_XrSession.h b/intern/ghost/intern/GHOST_XrSession.h index 79a586411e9..d09c78e1ea7 100644 --- a/intern/ghost/intern/GHOST_XrSession.h +++ b/intern/ghost/intern/GHOST_XrSession.h @@ -52,6 +52,43 @@ class GHOST_XrSession { void draw(void *draw_customdata); + /** Action functions to be called pre-session start. + * Note: The "destroy" functions can also be called post-session start. */ + bool createActionSet(const GHOST_XrActionSetInfo &info); + void destroyActionSet(const char *action_set_name); + bool createActions(const char *action_set_name, uint32_t count, const GHOST_XrActionInfo *infos); + void destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names); + bool createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + void destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + bool createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + void destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + bool attachActionSets(); + + /** Action functions to be called post-session start. */ + bool syncActions( + const char *action_set_name = nullptr); /* If action_set_name is nullptr, all attached + * action sets will be synced. */ + bool applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticAction(const char *action_set_name, const char *action_name); + + /* Custom data (owned by Blender, not GHOST) accessors. */ + void *getActionSetCustomdata(const char *action_set_name); + void *getActionCustomdata(const char *action_set_name, const char *action_name); + private: /** Pointer back to context managing this session. Would be nice to avoid, but needed to access * custom callbacks set before session start. */ diff --git a/intern/ghost/intern/GHOST_Xr_intern.h b/intern/ghost/intern/GHOST_Xr_intern.h index 137541c4528..0616e426da3 100644 --- a/intern/ghost/intern/GHOST_Xr_intern.h +++ b/intern/ghost/intern/GHOST_Xr_intern.h @@ -45,3 +45,27 @@ (void)_res; \ } \ (void)0 + +inline void copy_ghost_pose_to_openxr_pose(const GHOST_XrPose &ghost_pose, XrPosef &r_oxr_pose) +{ + /* Set and convert to OpenXR coordinate space. */ + r_oxr_pose.position.x = ghost_pose.position[0]; + r_oxr_pose.position.y = ghost_pose.position[1]; + r_oxr_pose.position.z = ghost_pose.position[2]; + r_oxr_pose.orientation.w = ghost_pose.orientation_quat[0]; + r_oxr_pose.orientation.x = ghost_pose.orientation_quat[1]; + r_oxr_pose.orientation.y = ghost_pose.orientation_quat[2]; + r_oxr_pose.orientation.z = ghost_pose.orientation_quat[3]; +} + +inline void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) +{ + /* Set and convert to Blender coordinate space. */ + r_ghost_pose.position[0] = oxr_pose.position.x; + r_ghost_pose.position[1] = oxr_pose.position.y; + r_ghost_pose.position[2] = oxr_pose.position.z; + r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; + r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; + r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; + r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; +} |