Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--intern/ghost/CMakeLists.txt3
-rw-r--r--intern/ghost/GHOST_C-api.h105
-rw-r--r--intern/ghost/GHOST_IXrContext.h6
-rw-r--r--intern/ghost/GHOST_Types.h55
-rw-r--r--intern/ghost/intern/GHOST_C-api.cpp144
-rw-r--r--intern/ghost/intern/GHOST_Util.h45
-rw-r--r--intern/ghost/intern/GHOST_XrAction.cpp477
-rw-r--r--intern/ghost/intern/GHOST_XrAction.h145
-rw-r--r--intern/ghost/intern/GHOST_XrContext.cpp15
-rw-r--r--intern/ghost/intern/GHOST_XrContext.h5
-rw-r--r--intern/ghost/intern/GHOST_XrException.h5
-rw-r--r--intern/ghost/intern/GHOST_XrSession.cpp361
-rw-r--r--intern/ghost/intern/GHOST_XrSession.h37
-rw-r--r--intern/ghost/intern/GHOST_Xr_intern.h24
-rw-r--r--source/blender/blenkernel/BKE_callbacks.h1
-rw-r--r--source/blender/makesdna/DNA_xr_types.h15
-rw-r--r--source/blender/python/intern/bpy_app_handlers.c1
-rw-r--r--source/blender/windowmanager/CMakeLists.txt1
-rw-r--r--source/blender/windowmanager/WM_api.h76
-rw-r--r--source/blender/windowmanager/WM_types.h19
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr.c5
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_actions.c480
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_draw.c6
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_intern.h68
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_session.c148
25 files changed, 2223 insertions, 24 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt
index 1739659ab88..f90e8a973bf 100644
--- a/intern/ghost/CMakeLists.txt
+++ b/intern/ghost/CMakeLists.txt
@@ -79,6 +79,7 @@ set(SRC
intern/GHOST_SystemPaths.h
intern/GHOST_TimerManager.h
intern/GHOST_TimerTask.h
+ intern/GHOST_Util.h
intern/GHOST_Window.h
intern/GHOST_WindowManager.h
)
@@ -438,6 +439,7 @@ endif()
if(WITH_XR_OPENXR)
list(APPEND SRC
intern/GHOST_Xr.cpp
+ intern/GHOST_XrAction.cpp
intern/GHOST_XrContext.cpp
intern/GHOST_XrEvent.cpp
intern/GHOST_XrGraphicsBinding.cpp
@@ -446,6 +448,7 @@ if(WITH_XR_OPENXR)
GHOST_IXrContext.h
intern/GHOST_IXrGraphicsBinding.h
+ intern/GHOST_XrAction.h
intern/GHOST_XrContext.h
intern/GHOST_XrException.h
intern/GHOST_XrSession.h
diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h
index c776eb6b44c..2bd9af6df5c 100644
--- a/intern/ghost/GHOST_C-api.h
+++ b/intern/ghost/GHOST_C-api.h
@@ -1059,7 +1059,110 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context
* \returns GHOST_kSuccess if any event was handled, otherwise GHOST_kFailure.
*/
GHOST_TSuccess GHOST_XrEventsHandle(GHOST_XrContextHandle xr_context);
-#endif
+
+/* actions */
+/**
+ * Create an OpenXR action set for input/output.
+ */
+int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_context, const GHOST_XrActionSetInfo *info);
+
+/**
+ * Destroy a previously created OpenXR action set.
+ */
+void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_context, const char *action_set_name);
+
+/**
+ * Create OpenXR input/output actions.
+ */
+int GHOST_XrCreateActions(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ GHOST_TUns32 count,
+ const GHOST_XrActionInfo *infos);
+
+/**
+ * Destroy previously created OpenXR actions.
+ */
+void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ GHOST_TUns32 count,
+ const char *const *action_names);
+
+/**
+ * Create spaces for pose-based OpenXR actions.
+ */
+int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ GHOST_TUns32 count,
+ const GHOST_XrActionSpaceInfo *infos);
+
+/**
+ * Destroy previously created spaces for OpenXR actions.
+ */
+void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ GHOST_TUns32 count,
+ const GHOST_XrActionSpaceInfo *infos);
+
+/**
+ * Create input/output path bindings for OpenXR actions.
+ */
+int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ GHOST_TUns32 count,
+ const GHOST_XrActionProfileInfo *infos);
+
+/**
+ * Destroy previously created bindings for OpenXR actions.
+ */
+void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ GHOST_TUns32 count,
+ const GHOST_XrActionProfileInfo *infos);
+
+/**
+ * Attach all created action sets to the current OpenXR session.
+ */
+int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_context);
+
+/**
+ * Update button/tracking states for OpenXR actions.
+ *
+ * \param action_set_name: The name of the action set to sync. If NULL, all action sets
+ * attached to the session will be synced.
+ */
+int GHOST_XrSyncActions(GHOST_XrContextHandle xr_context, const char *action_set_name);
+
+/**
+ * Apply an OpenXR haptic output action.
+ */
+int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ const char *action_name,
+ const GHOST_TInt64 *duration,
+ const float *frequency,
+ const float *amplitude);
+
+/**
+ * Stop a previously applied OpenXR haptic output action.
+ */
+void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ const char *action_name);
+
+/**
+ * Get action set custom data (owned by Blender, not GHOST).
+ */
+void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_context,
+ const char *action_set_name);
+
+/**
+ * Get action custom data (owned by Blender, not GHOST).
+ */
+void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_context,
+ const char *action_set_name,
+ const char *action_name);
+
+#endif /* WITH_XR_OPENXR */
#ifdef __cplusplus
}
diff --git a/intern/ghost/GHOST_IXrContext.h b/intern/ghost/GHOST_IXrContext.h
index dd266a3b6ae..86fe78814a7 100644
--- a/intern/ghost/GHOST_IXrContext.h
+++ b/intern/ghost/GHOST_IXrContext.h
@@ -22,6 +22,8 @@
#include "GHOST_Types.h"
+class GHOST_XrSession;
+
class GHOST_IXrContext {
public:
virtual ~GHOST_IXrContext() = default;
@@ -31,6 +33,10 @@ class GHOST_IXrContext {
virtual bool isSessionRunning() const = 0;
virtual void drawSessionViews(void *draw_customdata) = 0;
+ /* Needed for the GHOST C api. */
+ virtual GHOST_XrSession *getSession() = 0;
+ virtual const GHOST_XrSession *getSession() const = 0;
+
virtual void dispatchErrorMessage(const class GHOST_XrException *) const = 0;
virtual void setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn,
diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h
index 7d819913efc..3a8d0fbfecf 100644
--- a/intern/ghost/GHOST_Types.h
+++ b/intern/ghost/GHOST_Types.h
@@ -634,7 +634,9 @@ typedef enum GHOST_TXrGraphicsBinding {
typedef void (*GHOST_XrErrorHandlerFn)(const struct GHOST_XrError *);
+typedef void (*GHOST_XrSessionCreateFn)(void);
typedef void (*GHOST_XrSessionExitFn)(void *customdata);
+typedef void (*GHOST_XrCustomdataFreeFn)(void *customdata);
typedef void *(*GHOST_XrGraphicsContextBindFn)(void);
typedef void (*GHOST_XrGraphicsContextUnbindFn)(GHOST_ContextHandle graphics_context);
@@ -665,6 +667,7 @@ typedef struct {
typedef struct {
GHOST_XrPose base_pose;
+ GHOST_XrSessionCreateFn create_fn;
GHOST_XrSessionExitFn exit_fn;
void *exit_customdata;
} GHOST_XrSessionBeginInfo;
@@ -691,4 +694,54 @@ typedef struct GHOST_XrError {
void *customdata;
} GHOST_XrError;
-#endif
+typedef struct GHOST_XrActionSetInfo {
+ const char *name;
+
+ GHOST_XrCustomdataFreeFn customdata_free_fn;
+ void *customdata; /* wmXrActionSet */
+} GHOST_XrActionSetInfo;
+
+/** XR action type. Enum values match those in OpenXR's
+ * XrActionType enum for consistency. */
+typedef enum GHOST_XrActionType {
+ GHOST_kXrActionTypeBooleanInput = 1,
+ GHOST_kXrActionTypeFloatInput = 2,
+ GHOST_kXrActionTypeVector2fInput = 3,
+ GHOST_kXrActionTypePoseInput = 4,
+ GHOST_kXrActionTypeVibrationOutput = 100,
+} GHOST_XrActionType;
+
+typedef struct GHOST_XrActionInfo {
+ const char *name;
+ GHOST_XrActionType type;
+ GHOST_TUns32 count_subaction_paths;
+ const char **subaction_paths;
+ /** States for each subaction path. */
+ void *states;
+
+ GHOST_XrCustomdataFreeFn customdata_free_fn;
+ void *customdata; /* wmXrAction */
+} GHOST_XrActionInfo;
+
+typedef struct GHOST_XrActionSpaceInfo {
+ const char *action_name;
+ GHOST_TUns32 count_subaction_paths;
+ const char **subaction_paths;
+ /** Poses for each subaction path. */
+ const GHOST_XrPose *poses;
+} GHOST_XrActionSpaceInfo;
+
+typedef struct GHOST_XrActionBindingInfo {
+ const char *action_name;
+ GHOST_TUns32 count_interaction_paths;
+ /** Interaction path: User (subaction) path + component path. */
+ const char **interaction_paths;
+} GHOST_XrActionBindingInfo;
+
+typedef struct GHOST_XrActionProfileInfo {
+ const char *profile_path;
+ GHOST_TUns32 count_bindings;
+ const GHOST_XrActionBindingInfo *bindings;
+} GHOST_XrActionProfileInfo;
+
+#endif /* WITH_XR_OPENXR */
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 &amplitude)
+{
+ 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 &amplitude);
+ 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 7a0a6b0168f..daad0b8190a 100644
--- a/intern/ghost/intern/GHOST_XrContext.cpp
+++ b/intern/ghost/intern/GHOST_XrContext.cpp
@@ -246,7 +246,7 @@ void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) c
{
GHOST_XrError error;
- error.user_message = exception->m_msg;
+ error.user_message = exception->m_msg.data();
error.customdata = s_error_handler_customdata;
if (isDebugMode()) {
@@ -374,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());
}
}
}
@@ -484,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;
@@ -534,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)
{
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 85d59582c2e..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 {
@@ -71,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));
@@ -177,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);
@@ -194,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()
@@ -223,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;
@@ -349,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. */
@@ -501,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 &amplitude)
+{
+ 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 &amplitude);
+ 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..87a62ed838b 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 coodinate 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;
+}
diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h
index fadba5644de..f04b5e45720 100644
--- a/source/blender/blenkernel/BKE_callbacks.h
+++ b/source/blender/blenkernel/BKE_callbacks.h
@@ -59,6 +59,7 @@ typedef enum {
BKE_CB_EVT_VERSION_UPDATE,
BKE_CB_EVT_LOAD_FACTORY_USERDEF_POST,
BKE_CB_EVT_LOAD_FACTORY_STARTUP_POST,
+ BKE_CB_EVT_XR_SESSION_START_PRE,
BKE_CB_EVT_TOT,
} eCbEvent;
diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h
index 2ce32a723a7..8e63760fef7 100644
--- a/source/blender/makesdna/DNA_xr_types.h
+++ b/source/blender/makesdna/DNA_xr_types.h
@@ -58,6 +58,21 @@ typedef enum eXRSessionBasePoseType {
XR_BASE_POSE_CUSTOM = 2,
} eXRSessionBasePoseType;
+/** XR action type. Enum values match those in GHOST_XrActionType enum for consistency. */
+typedef enum eXrActionType {
+ XR_BOOLEAN_INPUT = 1,
+ XR_FLOAT_INPUT = 2,
+ XR_VECTOR2F_INPUT = 3,
+ XR_POSE_INPUT = 4,
+ XR_VIBRATION_OUTPUT = 100,
+} eXrActionType;
+
+typedef enum eXrOpFlag {
+ XR_OP_PRESS = 0,
+ XR_OP_RELEASE = 1,
+ XR_OP_MODAL = 2,
+} eXrOpFlag;
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c
index 8ecee9b3f2e..a0b543097e6 100644
--- a/source/blender/python/intern/bpy_app_handlers.c
+++ b/source/blender/python/intern/bpy_app_handlers.c
@@ -74,6 +74,7 @@ static PyStructSequence_Field app_cb_info_fields[] = {
{"version_update", "on ending the versioning code"},
{"load_factory_preferences_post", "on loading factory preferences (after)"},
{"load_factory_startup_post", "on loading factory startup (after)"},
+ {"xr_session_start_pre", "on starting an xr session (before)"},
/* sets the permanent tag */
#define APP_CB_OTHER_FIELDS 1
diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt
index 0f26ec50816..183b22c9791 100644
--- a/source/blender/windowmanager/CMakeLists.txt
+++ b/source/blender/windowmanager/CMakeLists.txt
@@ -203,6 +203,7 @@ if(WITH_XR_OPENXR)
list(APPEND SRC
xr/intern/wm_xr.c
+ xr/intern/wm_xr_actions.c
xr/intern/wm_xr_draw.c
xr/intern/wm_xr_session.c
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 280ee75a50f..edd5b555e2f 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -71,6 +71,11 @@ struct wmTabletData;
struct wmNDOFMotionData;
#endif
+#ifdef WITH_XR_OPENXR
+struct wmXrActionState;
+struct wmXrPose;
+#endif
+
typedef struct wmGizmo wmGizmo;
typedef struct wmGizmoMap wmGizmoMap;
typedef struct wmGizmoMapType wmGizmoMapType;
@@ -929,7 +934,7 @@ void WM_generic_user_data_free(struct wmGenericUserData *wm_userdata);
bool WM_region_use_viewport(struct ScrArea *area, struct ARegion *region);
#ifdef WITH_XR_OPENXR
-/* wm_xr.c */
+/* wm_xr_session.c */
bool WM_xr_session_exists(const wmXrData *xr);
bool WM_xr_session_is_ready(const wmXrData *xr);
struct wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr);
@@ -939,7 +944,74 @@ bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_ro
bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr,
float r_viewmat[4][4],
float *r_focal_len);
-#endif
+bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_location[3]);
+bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_rotation[4]);
+
+/* wm_xr_actions.c */
+/* XR action functions to be called pre-XR session start.
+ * Note: The "destroy" functions can also be called post-session start. */
+bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name);
+void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name);
+bool WM_xr_action_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ eXrActionType type,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const float *float_threshold,
+ struct wmOperatorType *ot,
+ struct IDProperty *op_properties,
+ eXrOpFlag op_flag);
+void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name);
+bool WM_xr_action_space_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const struct wmXrPose *poses);
+void WM_xr_action_space_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths);
+bool WM_xr_action_binding_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths);
+void WM_xr_action_binding_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths);
+
+bool WM_xr_active_action_set_set(
+ wmXrData *xr, const char *action_set_name); /* If action_set_name is NULL, then
+ * all action sets will be treated as active. */
+bool WM_xr_controller_pose_action_set(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name);
+
+/* XR action functions to be called post-XR session start. */
+bool WM_xr_action_state_get(const wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const char *subaction_path,
+ struct wmXrActionState *r_state);
+bool WM_xr_haptic_action_apply(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const long long *duration,
+ const float *frequency,
+ const float *amplitude);
+void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name);
+#endif /* WITH_XR_OPENXR */
#ifdef __cplusplus
}
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index 1d99b605205..168b775d16d 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -684,6 +684,25 @@ typedef struct wmNDOFMotionData {
} wmNDOFMotionData;
#endif /* WITH_INPUT_NDOF */
+#ifdef WITH_XR_OPENXR
+/* Similar to GHOST_XrPose. */
+typedef struct wmXrPose {
+ float position[3];
+ /* Blender convention (w, x, y, z) */
+ float orientation_quat[4];
+} wmXrPose;
+
+typedef struct wmXrActionState {
+ union {
+ bool state_boolean;
+ float state_float;
+ float state_vector2f[2];
+ wmXrPose state_pose;
+ };
+ int type; /* eXrActionType */
+} wmXrActionState;
+#endif
+
/** Timer flags. */
typedef enum {
/** Do not attempt to free customdata pointer even if non-NULL. */
diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c
index 439d611b085..2a67c2bee9f 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr.c
@@ -128,6 +128,11 @@ bool wm_xr_events_handle(wmWindowManager *wm)
if (wm->xr.runtime && wm->xr.runtime->context) {
GHOST_XrEventsHandle(wm->xr.runtime->context);
+ /* Process OpenXR action events. */
+ if (WM_xr_session_is_ready(&wm->xr)) {
+ wm_xr_session_actions_update(&wm->xr);
+ }
+
/* wm_window_process_events() uses the return value to determine if it can put the main thread
* to sleep for some milliseconds. We never want that to happen while the VR session runs on
* the main thread. So always return true. */
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actions.c b/source/blender/windowmanager/xr/intern/wm_xr_actions.c
new file mode 100644
index 00000000000..51ed3dcfd3c
--- /dev/null
+++ b/source/blender/windowmanager/xr/intern/wm_xr_actions.c
@@ -0,0 +1,480 @@
+/*
+ * 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 wm
+ *
+ * \name Window-Manager XR Actions
+ *
+ * Uses the Ghost-XR API to manage OpenXR actions.
+ * All functions are designed to be usable by RNA / the Python API.
+ */
+
+#include "BLI_math.h"
+
+#include "GHOST_C-api.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "wm_xr_intern.h"
+
+/* -------------------------------------------------------------------- */
+/** \name XR-Action API
+ *
+ * API functions for managing OpenXR actions.
+ *
+ * \{ */
+
+static wmXrActionSet *action_set_create(const char *action_set_name)
+{
+ wmXrActionSet *action_set = MEM_callocN(sizeof(*action_set), __func__);
+ action_set->name = MEM_mallocN(strlen(action_set_name) + 1, "XrActionSet_Name");
+ strcpy(action_set->name, action_set_name);
+
+ return action_set;
+}
+
+static void action_set_destroy(void *val)
+{
+ wmXrActionSet *action_set = val;
+
+ MEM_SAFE_FREE(action_set->name);
+
+ MEM_freeN(action_set);
+}
+
+static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name)
+{
+ return GHOST_XrGetActionSetCustomdata(xr->runtime->context, action_set_name);
+}
+
+static wmXrAction *action_create(const char *action_name,
+ eXrActionType type,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const float *float_threshold,
+ wmOperatorType *ot,
+ IDProperty *op_properties,
+ eXrOpFlag op_flag)
+{
+ wmXrAction *action = MEM_callocN(sizeof(*action), __func__);
+ action->name = MEM_mallocN(strlen(action_name) + 1, "XrAction_Name");
+ strcpy(action->name, action_name);
+ action->type = type;
+
+ const unsigned int count = count_subaction_paths;
+ action->count_subaction_paths = count;
+
+ action->subaction_paths = MEM_mallocN(sizeof(*action->subaction_paths) * count,
+ "XrAction_SubactionPaths");
+ for (unsigned int i = 0; i < count; ++i) {
+ action->subaction_paths[i] = MEM_mallocN(strlen(subaction_paths[i]) + 1,
+ "XrAction_SubactionPath");
+ strcpy(action->subaction_paths[i], subaction_paths[i]);
+ }
+
+ size_t size;
+ switch (type) {
+ case XR_BOOLEAN_INPUT:
+ size = sizeof(bool);
+ break;
+ case XR_FLOAT_INPUT:
+ size = sizeof(float);
+ break;
+ case XR_VECTOR2F_INPUT:
+ size = sizeof(float) * 2;
+ break;
+ case XR_POSE_INPUT:
+ size = sizeof(GHOST_XrPose);
+ break;
+ case XR_VIBRATION_OUTPUT:
+ return action;
+ }
+ action->states = MEM_calloc_arrayN(count, size, "XrAction_States");
+ action->states_prev = MEM_calloc_arrayN(count, size, "XrAction_StatesPrev");
+
+ if (float_threshold) {
+ BLI_assert(type == XR_FLOAT_INPUT || type == XR_VECTOR2F_INPUT);
+ action->float_threshold = *float_threshold;
+ CLAMP(action->float_threshold, 0.0f, 1.0f);
+ }
+
+ action->ot = ot;
+ action->op_properties = op_properties;
+ action->op_flag = op_flag;
+
+ return action;
+}
+
+static void action_destroy(void *val)
+{
+ wmXrAction *action = val;
+
+ MEM_SAFE_FREE(action->name);
+
+ const unsigned int count = action->count_subaction_paths;
+ char **subaction_paths = action->subaction_paths;
+ if (subaction_paths) {
+ for (unsigned int i = 0; i < count; ++i) {
+ MEM_SAFE_FREE(subaction_paths[i]);
+ }
+ MEM_freeN(subaction_paths);
+ }
+
+ MEM_SAFE_FREE(action->states);
+ MEM_SAFE_FREE(action->states_prev);
+
+ MEM_freeN(action);
+}
+
+static wmXrAction *action_find(wmXrData *xr, const char *action_set_name, const char *action_name)
+{
+ return GHOST_XrGetActionCustomdata(xr->runtime->context, action_set_name, action_name);
+}
+
+bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name)
+{
+ if (action_set_find(xr, action_set_name)) {
+ return false;
+ }
+
+ wmXrActionSet *action_set = action_set_create(action_set_name);
+
+ GHOST_XrActionSetInfo info = {
+ .name = action_set_name,
+ .customdata_free_fn = action_set_destroy,
+ .customdata = action_set,
+ };
+
+ if (!GHOST_XrCreateActionSet(xr->runtime->context, &info)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return;
+ }
+
+ wmXrSessionState *session_state = &xr->runtime->session_state;
+
+ if (action_set == session_state->active_action_set) {
+ if (action_set->controller_pose_action) {
+ wm_xr_session_controller_data_clear(session_state);
+ action_set->controller_pose_action = NULL;
+ }
+ if (action_set->active_modal_action) {
+ action_set->active_modal_action = NULL;
+ }
+ session_state->active_action_set = NULL;
+ }
+
+ GHOST_XrDestroyActionSet(xr->runtime->context, action_set_name);
+}
+
+bool WM_xr_action_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ eXrActionType type,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const float *float_threshold,
+ wmOperatorType *ot,
+ IDProperty *op_properties,
+ eXrOpFlag op_flag)
+{
+ if (action_find(xr, action_set_name, action_name)) {
+ return false;
+ }
+
+ wmXrAction *action = action_create(action_name,
+ type,
+ count_subaction_paths,
+ subaction_paths,
+ float_threshold,
+ ot,
+ op_properties,
+ op_flag);
+
+ GHOST_XrActionInfo info = {
+ .name = action_name,
+ .count_subaction_paths = count_subaction_paths,
+ .subaction_paths = subaction_paths,
+ .states = action->states,
+ .customdata_free_fn = action_destroy,
+ .customdata = action,
+ };
+
+ switch (type) {
+ case XR_BOOLEAN_INPUT:
+ info.type = GHOST_kXrActionTypeBooleanInput;
+ break;
+ case XR_FLOAT_INPUT:
+ info.type = GHOST_kXrActionTypeFloatInput;
+ break;
+ case XR_VECTOR2F_INPUT:
+ info.type = GHOST_kXrActionTypeVector2fInput;
+ break;
+ case XR_POSE_INPUT:
+ info.type = GHOST_kXrActionTypePoseInput;
+ break;
+ case XR_VIBRATION_OUTPUT:
+ info.type = GHOST_kXrActionTypeVibrationOutput;
+ break;
+ }
+
+ if (!GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return;
+ }
+
+ if (action_set->controller_pose_action &&
+ STREQ(action_set->controller_pose_action->name, action_name)) {
+ if (action_set == xr->runtime->session_state.active_action_set) {
+ wm_xr_session_controller_data_clear(&xr->runtime->session_state);
+ }
+ action_set->controller_pose_action = NULL;
+ }
+ if (action_set->active_modal_action &&
+ STREQ(action_set->active_modal_action->name, action_name)) {
+ action_set->active_modal_action = NULL;
+ }
+
+ wmXrAction *action = action_find(xr, action_set_name, action_name);
+ if (!action) {
+ return;
+ }
+}
+
+bool WM_xr_action_space_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const wmXrPose *poses)
+{
+ GHOST_XrActionSpaceInfo info = {
+ .action_name = action_name,
+ .count_subaction_paths = count_subaction_paths,
+ .subaction_paths = subaction_paths,
+ };
+
+ GHOST_XrPose *ghost_poses = MEM_malloc_arrayN(
+ count_subaction_paths, sizeof(*ghost_poses), __func__);
+ for (unsigned int i = 0; i < count_subaction_paths; ++i) {
+ const wmXrPose *pose = &poses[i];
+ GHOST_XrPose *ghost_pose = &ghost_poses[i];
+ copy_v3_v3(ghost_pose->position, pose->position);
+ copy_qt_qt(ghost_pose->orientation_quat, pose->orientation_quat);
+ }
+ info.poses = ghost_poses;
+
+ bool ret = GHOST_XrCreateActionSpaces(xr->runtime->context, action_set_name, 1, &info) ? true :
+ false;
+ MEM_freeN(ghost_poses);
+ return ret;
+}
+
+void WM_xr_action_space_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths)
+{
+ GHOST_XrActionSpaceInfo info = {
+ .action_name = action_name,
+ .count_subaction_paths = count_subaction_paths,
+ .subaction_paths = subaction_paths,
+ };
+
+ GHOST_XrDestroyActionSpaces(xr->runtime->context, action_set_name, 1, &info);
+}
+
+bool WM_xr_action_binding_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths)
+{
+ GHOST_XrActionBindingInfo binding_info = {
+ .action_name = action_name,
+ .count_interaction_paths = count_interaction_paths,
+ .interaction_paths = interaction_paths,
+ };
+
+ GHOST_XrActionProfileInfo profile_info = {
+ .profile_path = profile_path,
+ .count_bindings = 1,
+ .bindings = &binding_info,
+ };
+
+ return GHOST_XrCreateActionBindings(xr->runtime->context, action_set_name, 1, &profile_info);
+}
+
+void WM_xr_action_binding_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths)
+{
+ GHOST_XrActionBindingInfo binding_info = {
+ .action_name = action_name,
+ .count_interaction_paths = count_interaction_paths,
+ .interaction_paths = interaction_paths,
+ };
+
+ GHOST_XrActionProfileInfo profile_info = {
+ .profile_path = profile_path,
+ .count_bindings = 1,
+ .bindings = &binding_info,
+ };
+
+ GHOST_XrDestroyActionBindings(xr->runtime->context, action_set_name, 1, &profile_info);
+}
+
+bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return false;
+ }
+
+ {
+ /* Unset active modal action (if any). */
+ wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set;
+ if (active_action_set) {
+ wmXrAction *active_modal_action = active_action_set->active_modal_action;
+ if (active_modal_action) {
+ if (active_modal_action->active_modal_path) {
+ active_modal_action->active_modal_path = NULL;
+ }
+ active_action_set->active_modal_action = NULL;
+ }
+ }
+ }
+
+ xr->runtime->session_state.active_action_set = action_set;
+
+ if (action_set->controller_pose_action) {
+ wm_xr_session_controller_data_populate(action_set->controller_pose_action, xr);
+ }
+
+ return true;
+}
+
+bool WM_xr_controller_pose_action_set(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return false;
+ }
+
+ wmXrAction *action = action_find(xr, action_set_name, action_name);
+ if (!action) {
+ return false;
+ }
+
+ action_set->controller_pose_action = action;
+
+ if (action_set == xr->runtime->session_state.active_action_set) {
+ wm_xr_session_controller_data_populate(action, xr);
+ }
+
+ return true;
+}
+
+bool WM_xr_action_state_get(const wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const char *subaction_path,
+ wmXrActionState *r_state)
+{
+ const wmXrAction *action = action_find((wmXrData *)xr, action_set_name, action_name);
+ if (!action) {
+ return false;
+ }
+
+ BLI_assert(action->type == (eXrActionType)r_state->type);
+
+ /* Find the action state corresponding to the subaction path. */
+ for (unsigned int i = 0; i < action->count_subaction_paths; ++i) {
+ if (STREQ(subaction_path, action->subaction_paths[i])) {
+ switch ((eXrActionType)r_state->type) {
+ case XR_BOOLEAN_INPUT:
+ r_state->state_boolean = ((bool *)action->states)[i];
+ break;
+ case XR_FLOAT_INPUT:
+ r_state->state_float = ((float *)action->states)[i];
+ break;
+ case XR_VECTOR2F_INPUT:
+ copy_v2_v2(r_state->state_vector2f, ((float(*)[2])action->states)[i]);
+ break;
+ case XR_POSE_INPUT: {
+ const GHOST_XrPose *pose = &((GHOST_XrPose *)action->states)[i];
+ copy_v3_v3(r_state->state_pose.position, pose->position);
+ copy_qt_qt(r_state->state_pose.orientation_quat, pose->orientation_quat);
+ break;
+ }
+ case XR_VIBRATION_OUTPUT:
+ BLI_assert_unreachable();
+ break;
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool WM_xr_haptic_action_apply(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const long long *duration,
+ const float *frequency,
+ const float *amplitude)
+{
+ return GHOST_XrApplyHapticAction(
+ xr->runtime->context, action_set_name, action_name, duration, frequency, amplitude) ?
+ true :
+ false;
+}
+
+void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name)
+{
+ GHOST_XrStopHapticAction(xr->runtime->context, action_set_name, action_name);
+}
+
+/** \} */ /* XR-Action API */
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c
index cc4a7e41e82..1f722855696 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c
@@ -45,6 +45,12 @@ void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4])
translate_m4(r_viewmat, -pose->position[0], -pose->position[1], -pose->position[2]);
}
+void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4])
+{
+ quat_to_mat4(r_mat, pose->orientation_quat);
+ copy_v3_v3(r_mat[3], pose->position);
+}
+
static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data,
const GHOST_XrDrawViewInfo *draw_view,
const XrSessionSettings *session_settings,
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h
index 25e3da3ffb4..9bf63be61dd 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h
+++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h
@@ -24,6 +24,21 @@
#include "wm_xr.h"
+struct wmXrActionSet;
+
+typedef struct wmXrControllerData {
+ /** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256).
+ This subaction path will later be combined with a component path, and that combined path should
+ also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path =
+ /input/trigger/value, interaction_path = /user/hand/left/input/trigger/value).
+ */
+ char subaction_path[64];
+ /** Last known controller pose (in world space) stored for queries. */
+ GHOST_XrPose pose;
+ /** The last known controller matrix, calculated from above's controller pose. */
+ float mat[4][4];
+} wmXrControllerData;
+
typedef struct wmXrSessionState {
bool is_started;
@@ -39,11 +54,23 @@ typedef struct wmXrSessionState {
Object *prev_base_pose_object;
/** Copy of XrSessionSettings.flag created on the last draw call, stored to detect changes. */
int prev_settings_flag;
+ /** Copy of wmXrDrawData.base_pose. */
+ GHOST_XrPose prev_base_pose;
+ /** Copy of GHOST_XrDrawViewInfo.local_pose. */
+ GHOST_XrPose prev_local_pose;
/** Copy of wmXrDrawData.eye_position_ofs. */
float prev_eye_position_ofs[3];
bool force_reset_to_base_pose;
bool is_view_data_set;
+
+ /** Last known controller data. */
+ wmXrControllerData controllers[2];
+
+ /** The currently active action set that will be updated on calls to
+ * wm_xr_session_actions_update(). If NULL, all action sets will be treated as active and
+ * updated. */
+ struct wmXrActionSet *active_action_set;
} wmXrSessionState;
typedef struct wmXrRuntimeData {
@@ -79,6 +106,40 @@ typedef struct wmXrDrawData {
float eye_position_ofs[3]; /* Local/view space. */
} wmXrDrawData;
+typedef struct wmXrAction {
+ char *name;
+ eXrActionType type;
+ unsigned int count_subaction_paths;
+ char **subaction_paths;
+ /** States for each subaction path. */
+ void *states;
+ /** Previous states, stored to determine XR events. */
+ void *states_prev;
+
+ /** Input threshold for float/vector2f actions. */
+ float float_threshold;
+
+ /** The currently active subaction path (if any) for modal actions. */
+ char **active_modal_path;
+
+ /** Operator to be called on XR events. */
+ struct wmOperatorType *ot;
+ IDProperty *op_properties;
+ eXrOpFlag op_flag;
+} wmXrAction;
+
+typedef struct wmXrActionSet {
+ char *name;
+
+ /** The XR pose action that determines the controller
+ * transforms. This is usually identified by the OpenXR path "/grip/pose" or "/aim/pose",
+ * although it could differ depending on the specification and hardware. */
+ wmXrAction *controller_pose_action;
+
+ /** The currently active modal action (if any). */
+ wmXrAction *active_modal_action;
+} wmXrActionSet;
+
wmXrRuntimeData *wm_xr_runtime_data_create(void);
void wm_xr_runtime_data_free(wmXrRuntimeData **runtime);
@@ -95,5 +156,12 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
void *wm_xr_session_gpu_binding_context_create(void);
void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle context);
+void wm_xr_session_actions_init(wmXrData *xr);
+void wm_xr_session_actions_update(wmXrData *xr);
+void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action,
+ wmXrData *xr);
+void wm_xr_session_controller_data_clear(wmXrSessionState *state);
+
void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]);
+void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]);
void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata);
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c
index b9ef40e3398..1ddbe228e05 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_session.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c
@@ -18,7 +18,9 @@
* \ingroup wm
*/
+#include "BKE_callbacks.h"
#include "BKE_context.h"
+#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_scene.h"
@@ -49,11 +51,24 @@ static CLG_LogRef LOG = {"wm.xr"};
/* -------------------------------------------------------------------- */
+static void wm_xr_session_create_cb(void)
+{
+ Main *bmain = G_MAIN;
+ wmWindowManager *wm = bmain->wm.first;
+ wmXrData *xr_data = &wm->xr;
+
+ /* Get action set data from Python. */
+ BKE_callback_exec_null(bmain, BKE_CB_EVT_XR_SESSION_START_PRE);
+
+ wm_xr_session_actions_init(xr_data);
+}
+
static void wm_xr_session_exit_cb(void *customdata)
{
wmXrData *xr_data = customdata;
xr_data->runtime->session_state.is_started = false;
+
if (xr_data->runtime->exit_fn) {
xr_data->runtime->exit_fn(xr_data);
}
@@ -65,6 +80,10 @@ static void wm_xr_session_exit_cb(void *customdata)
static void wm_xr_session_begin_info_create(wmXrData *xr_data,
GHOST_XrSessionBeginInfo *r_begin_info)
{
+ /* Callback for when the session is created. This is needed to create and bind OpenXR actions
+ * after the session is created but before it is started. */
+ r_begin_info->create_fn = wm_xr_session_create_cb;
+
/* WM-XR exit function, does some own stuff and calls callback passed to wm_xr_session_toggle(),
* to allow external code to execute its own session-exit logic. */
r_begin_info->exit_fn = wm_xr_session_exit_cb;
@@ -289,6 +308,7 @@ void wm_xr_session_draw_data_update(const wmXrSessionState *state,
/**
* Update information that is only stored for external state queries. E.g. for Python API to
* request the current (as in, last known) viewer pose.
+ * Controller data and action sets will be updated separately via wm_xr_session_actions_update().
*/
void wm_xr_session_state_update(const XrSessionSettings *settings,
const wmXrDrawData *draw_data,
@@ -322,6 +342,8 @@ void wm_xr_session_state_update(const XrSessionSettings *settings,
DEFAULT_SENSOR_WIDTH);
copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs);
+ memcpy(&state->prev_base_pose, &draw_data->base_pose, sizeof(state->prev_base_pose));
+ memcpy(&state->prev_local_pose, &draw_view->local_pose, sizeof(state->prev_local_pose));
state->prev_settings_flag = settings->flag;
state->prev_base_pose_type = settings->base_pose_type;
state->prev_base_pose_object = settings->base_pose_object;
@@ -373,6 +395,132 @@ bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr,
return true;
}
+bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_location[3])
+{
+ if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set ||
+ subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) {
+ zero_v3(r_location);
+ return false;
+ }
+
+ copy_v3_v3(r_location, xr->runtime->session_state.controllers[subaction_idx].pose.position);
+ return true;
+}
+
+bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_rotation[4])
+{
+ if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set ||
+ subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) {
+ unit_qt(r_rotation);
+ return false;
+ }
+
+ copy_v4_v4(r_rotation,
+ xr->runtime->session_state.controllers[subaction_idx].pose.orientation_quat);
+ return true;
+}
+
+/* -------------------------------------------------------------------- */
+/** \name XR-Session Actions
+ *
+ * XR action processing and event dispatching.
+ *
+ * \{ */
+
+void wm_xr_session_actions_init(wmXrData *xr)
+{
+ if (!xr->runtime) {
+ return;
+ }
+
+ GHOST_XrAttachActionSets(xr->runtime->context);
+}
+
+static void wm_xr_session_controller_mats_update(const XrSessionSettings *settings,
+ const wmXrAction *controller_pose_action,
+ wmXrSessionState *state)
+{
+ const unsigned int count = (unsigned int)min_ii(
+ (int)controller_pose_action->count_subaction_paths, (int)ARRAY_SIZE(state->controllers));
+
+ float view_ofs[3];
+ float base_inv[4][4];
+ float tmp[4][4];
+
+ zero_v3(view_ofs);
+ if ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) {
+ add_v3_v3(view_ofs, state->prev_local_pose.position);
+ }
+
+ wm_xr_pose_to_viewmat(&state->prev_base_pose, base_inv);
+ invert_m4(base_inv);
+
+ for (unsigned int i = 0; i < count; ++i) {
+ wmXrControllerData *controller = &state->controllers[i];
+
+ /* Calculate controller matrix in world space. */
+ wm_xr_controller_pose_to_mat(&((GHOST_XrPose *)controller_pose_action->states)[i], tmp);
+
+ /* Apply eye position and base pose offsets. */
+ sub_v3_v3(tmp[3], view_ofs);
+ mul_m4_m4m4(controller->mat, base_inv, tmp);
+
+ /* Save final pose. */
+ mat4_to_loc_quat(
+ controller->pose.position, controller->pose.orientation_quat, controller->mat);
+ }
+}
+
+void wm_xr_session_actions_update(wmXrData *xr)
+{
+ if (!xr->runtime) {
+ return;
+ }
+
+ GHOST_XrContextHandle xr_context = xr->runtime->context;
+ wmXrSessionState *state = &xr->runtime->session_state;
+ wmXrActionSet *active_action_set = state->active_action_set;
+
+ int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL);
+ if (!ret) {
+ return;
+ }
+
+ /* Only update controller mats for active action set. */
+ if (active_action_set) {
+ if (active_action_set->controller_pose_action) {
+ wm_xr_session_controller_mats_update(
+ &xr->session_settings, active_action_set->controller_pose_action, state);
+ }
+ }
+}
+
+void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, wmXrData *xr)
+{
+ wmXrSessionState *state = &xr->runtime->session_state;
+
+ const unsigned int count = (unsigned int)min_ii(
+ (int)ARRAY_SIZE(state->controllers), (int)controller_pose_action->count_subaction_paths);
+
+ for (unsigned int i = 0; i < count; ++i) {
+ wmXrControllerData *c = &state->controllers[i];
+ strcpy(c->subaction_path, controller_pose_action->subaction_paths[i]);
+ memset(&c->pose, 0, sizeof(c->pose));
+ zero_m4(c->mat);
+ }
+}
+
+void wm_xr_session_controller_data_clear(wmXrSessionState *state)
+{
+ memset(state->controllers, 0, sizeof(state->controllers));
+}
+
+/** \} */ /* XR-Session Actions */
+
/* -------------------------------------------------------------------- */
/** \name XR-Session Surface
*