XR Controller Support Step 1: Internal Abstractions for OpenXR Actions

Adds internal API for creating and managing OpenXR actions at the
GHOST and WM layers. Does not bring about any changes for users since
XR action functionality is not yet exposed in the Python API (will be
added in a subsequent patch).

OpenXR actions are a means to communicate with XR input devices and
can be used to retrieve button/pose states or apply haptic feedback.
Actions are bound to device inputs via a semantic path binding
(https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-interaction-profiles),
which serves as an XR version of keymaps.

Main features:

- Abstraction of OpenXR action management functions to GHOST-XR,
  WM-XR APIs.
- New "xr_session_start_pre" callback for creating actions at
  appropriate point in the XR session.
- Creation of name-identifiable action sets/actions.
- Binding of actions to controller inputs.
- Acquisition of controller button states.
- Acquisition of controller poses.
- Application of controller haptic feedback.
- Carefully designed error handling and useful error reporting
  (e.g. action set/action name included in error message).

Reviewed By: Julian Eisel

Differential Revision: http://developer.blender.org/D10942
This commit is contained in:
Peter Kim 2021-05-16 03:33:10 +09:00
parent 3e6609a0dc
commit cb12fb78ca
25 changed files with 2223 additions and 24 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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,

View File

@ -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 */

View File

@ -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 */

View File

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

View File

@ -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);
}
}
/** \} */

View File

@ -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;
};
/* -------------------------------------------------------------------- */

View File

@ -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)
{

View File

@ -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;

View File

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

View File

@ -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 */

View File

@ -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. */

View File

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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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. */

View File

@ -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. */

View File

@ -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 */

View File

@ -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,

View File

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

View File

@ -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
*