Addresses the remaining portions of T77137 (Python API for Controller Interaction), which was partially completed by D10942. Adds an XR "action maps" system for loading XR action data from a Python script. Action maps are accessible via the Python API, and are used to pass default actions to the VR session during the xr_session_start_pre() callback. Since action maps are stored only as runtime data, they will be cleaned up with the rest of the VR runtime data on file read or exit. Reviewed By: Julian Eisel, Hans Goudey Differential Revision: https://developer.blender.org/D10943
842 lines
28 KiB
C++
842 lines
28 KiB
C++
/*
|
|
* 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 <algorithm>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <list>
|
|
#include <sstream>
|
|
|
|
#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"
|
|
#include "GHOST_Xr_intern.h"
|
|
|
|
#include "GHOST_XrSession.h"
|
|
|
|
struct OpenXRSessionData {
|
|
XrSystemId system_id = XR_NULL_SYSTEM_ID;
|
|
XrSession session = XR_NULL_HANDLE;
|
|
XrSessionState session_state = XR_SESSION_STATE_UNKNOWN;
|
|
|
|
/* Only stereo rendering supported now. */
|
|
const XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
|
XrSpace reference_space;
|
|
XrSpace view_space;
|
|
std::vector<XrView> views;
|
|
std::vector<GHOST_XrSwapchain> swapchains;
|
|
|
|
std::map<std::string, GHOST_XrActionSet> action_sets;
|
|
};
|
|
|
|
struct GHOST_XrDrawInfo {
|
|
XrFrameState frame_state;
|
|
|
|
/** Time at frame start to benchmark frame render durations. */
|
|
std::chrono::high_resolution_clock::time_point frame_begin_time;
|
|
/* Time previous frames took for rendering (in ms). */
|
|
std::list<double> last_frame_times;
|
|
};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Create, Initialize and Destruct
|
|
* \{ */
|
|
|
|
GHOST_XrSession::GHOST_XrSession(GHOST_XrContext &xr_context)
|
|
: m_context(&xr_context), m_oxr(std::make_unique<OpenXRSessionData>())
|
|
{
|
|
}
|
|
|
|
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));
|
|
}
|
|
if (m_oxr->view_space != XR_NULL_HANDLE) {
|
|
CHECK_XR_ASSERT(xrDestroySpace(m_oxr->view_space));
|
|
}
|
|
if (m_oxr->session != XR_NULL_HANDLE) {
|
|
CHECK_XR_ASSERT(xrDestroySession(m_oxr->session));
|
|
}
|
|
|
|
m_oxr->session = XR_NULL_HANDLE;
|
|
m_oxr->session_state = XR_SESSION_STATE_UNKNOWN;
|
|
|
|
m_context->getCustomFuncs().session_exit_fn(m_context->getCustomFuncs().session_exit_customdata);
|
|
}
|
|
|
|
/**
|
|
* A system in OpenXR the combination of some sort of HMD plus controllers and whatever other
|
|
* devices are managed through OpenXR. So this attempts to init the HMD and the other devices.
|
|
*/
|
|
void GHOST_XrSession::initSystem()
|
|
{
|
|
assert(m_context->getInstance() != XR_NULL_HANDLE);
|
|
assert(m_oxr->system_id == XR_NULL_SYSTEM_ID);
|
|
|
|
XrSystemGetInfo system_info = {};
|
|
system_info.type = XR_TYPE_SYSTEM_GET_INFO;
|
|
system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
|
|
|
CHECK_XR(xrGetSystem(m_context->getInstance(), &system_info, &m_oxr->system_id),
|
|
"Failed to get device information. Is a device plugged in?");
|
|
}
|
|
|
|
/** \} */ /* Create, Initialize and Destruct */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name State Management
|
|
* \{ */
|
|
|
|
static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose)
|
|
{
|
|
XrReferenceSpaceCreateInfo create_info = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
|
|
create_info.poseInReferenceSpace.orientation.w = 1.0f;
|
|
|
|
create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
|
|
#if 0
|
|
/* TODO
|
|
*
|
|
* Proper reference space set up is not supported yet. We simply hand OpenXR
|
|
* the global space as reference space and apply its pose onto the active
|
|
* camera matrix to get a basic viewing experience going. If there's no active
|
|
* camera with stick to the world origin.
|
|
*
|
|
* Once we have proper reference space set up (i.e. a way to define origin, up-
|
|
* direction and an initial view rotation perpendicular to the up-direction),
|
|
* we can hand OpenXR a proper reference pose/space.
|
|
*/
|
|
create_info.poseInReferenceSpace.position.x = base_pose->position[0];
|
|
create_info.poseInReferenceSpace.position.y = base_pose->position[1];
|
|
create_info.poseInReferenceSpace.position.z = base_pose->position[2];
|
|
create_info.poseInReferenceSpace.orientation.x = base_pose->orientation_quat[1];
|
|
create_info.poseInReferenceSpace.orientation.y = base_pose->orientation_quat[2];
|
|
create_info.poseInReferenceSpace.orientation.z = base_pose->orientation_quat[3];
|
|
create_info.poseInReferenceSpace.orientation.w = base_pose->orientation_quat[0];
|
|
#else
|
|
(void)base_pose;
|
|
#endif
|
|
|
|
XrResult result = xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space);
|
|
|
|
if (XR_FAILED(result)) {
|
|
/* One of the rare cases where we don't want to immediately throw an exception on failure,
|
|
* since run-times are not required to support the stage reference space. Although we need the
|
|
* stage reference space for absolute tracking, if the runtime doesn't support it then just
|
|
* fallback to the local space. */
|
|
if (result == XR_ERROR_REFERENCE_SPACE_UNSUPPORTED) {
|
|
printf(
|
|
"Warning: XR runtime does not support stage reference space, disabling absolute "
|
|
"tracking.\n");
|
|
|
|
create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
|
CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space),
|
|
"Failed to create local reference space.");
|
|
}
|
|
else {
|
|
throw GHOST_XrException("Failed to create stage reference space.", result);
|
|
}
|
|
}
|
|
else {
|
|
/* Check if tracking bounds are valid. Tracking bounds may be invalid if the user did not
|
|
* define a tracking space via the XR runtime. */
|
|
XrExtent2Df extents;
|
|
CHECK_XR(xrGetReferenceSpaceBoundsRect(oxr.session, XR_REFERENCE_SPACE_TYPE_STAGE, &extents),
|
|
"Failed to get stage reference space bounds.");
|
|
if (extents.width == 0.0f || extents.height == 0.0f) {
|
|
printf(
|
|
"Warning: Invalid stage reference space bounds, disabling absolute tracking. To enable "
|
|
"absolute tracking, please define a tracking space via the XR runtime.\n");
|
|
|
|
/* Fallback to local space. */
|
|
if (oxr.reference_space != XR_NULL_HANDLE) {
|
|
CHECK_XR(xrDestroySpace(oxr.reference_space), "Failed to destroy stage reference space.");
|
|
}
|
|
|
|
create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
|
CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space),
|
|
"Failed to create local reference space.");
|
|
}
|
|
}
|
|
|
|
create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
|
|
CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.view_space),
|
|
"Failed to create view reference space.");
|
|
}
|
|
|
|
void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
|
|
{
|
|
assert(m_context->getInstance() != XR_NULL_HANDLE);
|
|
assert(m_oxr->session == XR_NULL_HANDLE);
|
|
if (m_context->getCustomFuncs().gpu_ctx_bind_fn == nullptr) {
|
|
throw GHOST_XrException(
|
|
"Invalid API usage: No way to bind graphics context to the XR session. Call "
|
|
"GHOST_XrGraphicsContextBindFuncs() with valid parameters before starting the "
|
|
"session (through GHOST_XrSessionStart()).");
|
|
}
|
|
|
|
initSystem();
|
|
|
|
bindGraphicsContext();
|
|
if (m_gpu_ctx == nullptr) {
|
|
throw GHOST_XrException(
|
|
"Invalid API usage: No graphics context returned through the callback set with "
|
|
"GHOST_XrGraphicsContextBindFuncs(). This is required for session starting (through "
|
|
"GHOST_XrSessionStart()).");
|
|
}
|
|
|
|
std::string requirement_str;
|
|
m_gpu_binding = GHOST_XrGraphicsBindingCreateFromType(m_context->getGraphicsBindingType(),
|
|
*m_gpu_ctx);
|
|
if (!m_gpu_binding->checkVersionRequirements(
|
|
*m_gpu_ctx, m_context->getInstance(), m_oxr->system_id, &requirement_str)) {
|
|
std::ostringstream strstream;
|
|
strstream << "Available graphics context version does not meet the following requirements: "
|
|
<< requirement_str;
|
|
throw GHOST_XrException(strstream.str().data());
|
|
}
|
|
m_gpu_binding->initFromGhostContext(*m_gpu_ctx);
|
|
|
|
XrSessionCreateInfo create_info = {};
|
|
create_info.type = XR_TYPE_SESSION_CREATE_INFO;
|
|
create_info.systemId = m_oxr->system_id;
|
|
create_info.next = &m_gpu_binding->oxr_binding;
|
|
|
|
CHECK_XR(xrCreateSession(m_context->getInstance(), &create_info, &m_oxr->session),
|
|
"Failed to create VR session. The OpenXR runtime may have additional requirements for "
|
|
"the graphics driver that are not met. Other causes are possible too however.\nTip: "
|
|
"The --debug-xr command line option for Blender might allow the runtime to output "
|
|
"detailed error information to the command line.");
|
|
|
|
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()
|
|
{
|
|
xrRequestExitSession(m_oxr->session);
|
|
}
|
|
|
|
void GHOST_XrSession::beginSession()
|
|
{
|
|
XrSessionBeginInfo begin_info = {XR_TYPE_SESSION_BEGIN_INFO};
|
|
begin_info.primaryViewConfigurationType = m_oxr->view_type;
|
|
CHECK_XR(xrBeginSession(m_oxr->session, &begin_info), "Failed to cleanly begin the VR session.");
|
|
}
|
|
|
|
void GHOST_XrSession::endSession()
|
|
{
|
|
assert(m_oxr->session != XR_NULL_HANDLE);
|
|
CHECK_XR(xrEndSession(m_oxr->session), "Failed to cleanly end the VR session.");
|
|
}
|
|
|
|
GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent(
|
|
const XrEventDataSessionStateChanged &lifecycle)
|
|
{
|
|
m_oxr->session_state = lifecycle.state;
|
|
|
|
/* Runtime may send events for apparently destroyed session. Our handle should be NULL then. */
|
|
assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session);
|
|
|
|
switch (lifecycle.state) {
|
|
case XR_SESSION_STATE_READY:
|
|
beginSession();
|
|
break;
|
|
case XR_SESSION_STATE_STOPPING:
|
|
endSession();
|
|
break;
|
|
case XR_SESSION_STATE_EXITING:
|
|
case XR_SESSION_STATE_LOSS_PENDING:
|
|
return SESSION_DESTROY;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return SESSION_KEEP_ALIVE;
|
|
}
|
|
/** \} */ /* State Management */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Drawing
|
|
* \{ */
|
|
|
|
void GHOST_XrSession::prepareDrawing()
|
|
{
|
|
std::vector<XrViewConfigurationView> view_configs;
|
|
uint32_t view_count;
|
|
|
|
CHECK_XR(
|
|
xrEnumerateViewConfigurationViews(
|
|
m_context->getInstance(), m_oxr->system_id, m_oxr->view_type, 0, &view_count, nullptr),
|
|
"Failed to get count of view configurations.");
|
|
view_configs.resize(view_count, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
|
|
CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(),
|
|
m_oxr->system_id,
|
|
m_oxr->view_type,
|
|
view_configs.size(),
|
|
&view_count,
|
|
view_configs.data()),
|
|
"Failed to get count of view configurations.");
|
|
|
|
for (const XrViewConfigurationView &view_config : view_configs) {
|
|
m_oxr->swapchains.emplace_back(*m_gpu_binding, m_oxr->session, view_config);
|
|
}
|
|
|
|
m_oxr->views.resize(view_count, {XR_TYPE_VIEW});
|
|
|
|
m_draw_info = std::make_unique<GHOST_XrDrawInfo>();
|
|
}
|
|
|
|
void GHOST_XrSession::beginFrameDrawing()
|
|
{
|
|
XrFrameWaitInfo wait_info = {XR_TYPE_FRAME_WAIT_INFO};
|
|
XrFrameBeginInfo begin_info = {XR_TYPE_FRAME_BEGIN_INFO};
|
|
XrFrameState frame_state = {XR_TYPE_FRAME_STATE};
|
|
|
|
/* TODO Blocking call. Drawing should run on a separate thread to avoid interferences. */
|
|
CHECK_XR(xrWaitFrame(m_oxr->session, &wait_info, &frame_state),
|
|
"Failed to synchronize frame rates between Blender and the device.");
|
|
|
|
CHECK_XR(xrBeginFrame(m_oxr->session, &begin_info),
|
|
"Failed to submit frame rendering start state.");
|
|
|
|
m_draw_info->frame_state = frame_state;
|
|
|
|
if (m_context->isDebugTimeMode()) {
|
|
m_draw_info->frame_begin_time = std::chrono::high_resolution_clock::now();
|
|
}
|
|
}
|
|
|
|
static void print_debug_timings(GHOST_XrDrawInfo &draw_info)
|
|
{
|
|
/** Render time of last 8 frames (in ms) to calculate an average. */
|
|
std::chrono::duration<double, std::milli> duration = std::chrono::high_resolution_clock::now() -
|
|
draw_info.frame_begin_time;
|
|
const double duration_ms = duration.count();
|
|
const int avg_frame_count = 8;
|
|
double avg_ms_tot = 0.0;
|
|
|
|
if (draw_info.last_frame_times.size() >= avg_frame_count) {
|
|
draw_info.last_frame_times.pop_front();
|
|
assert(draw_info.last_frame_times.size() == avg_frame_count - 1);
|
|
}
|
|
draw_info.last_frame_times.push_back(duration_ms);
|
|
for (double ms_iter : draw_info.last_frame_times) {
|
|
avg_ms_tot += ms_iter;
|
|
}
|
|
|
|
printf("VR frame render time: %.0fms - %.2f FPS (%.2f FPS 8 frames average)\n",
|
|
duration_ms,
|
|
1000.0 / duration_ms,
|
|
1000.0 / (avg_ms_tot / draw_info.last_frame_times.size()));
|
|
}
|
|
|
|
void GHOST_XrSession::endFrameDrawing(std::vector<XrCompositionLayerBaseHeader *> &layers)
|
|
{
|
|
XrFrameEndInfo end_info = {XR_TYPE_FRAME_END_INFO};
|
|
|
|
end_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
|
|
end_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
|
end_info.layerCount = layers.size();
|
|
end_info.layers = layers.data();
|
|
|
|
CHECK_XR(xrEndFrame(m_oxr->session, &end_info), "Failed to submit rendered frame.");
|
|
|
|
if (m_context->isDebugTimeMode()) {
|
|
print_debug_timings(*m_draw_info);
|
|
}
|
|
}
|
|
|
|
void GHOST_XrSession::draw(void *draw_customdata)
|
|
{
|
|
std::vector<XrCompositionLayerProjectionView>
|
|
projection_layer_views; /* Keep alive until #xrEndFrame() call! */
|
|
XrCompositionLayerProjection proj_layer;
|
|
std::vector<XrCompositionLayerBaseHeader *> layers;
|
|
|
|
beginFrameDrawing();
|
|
|
|
if (m_draw_info->frame_state.shouldRender) {
|
|
proj_layer = drawLayer(projection_layer_views, draw_customdata);
|
|
layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader *>(&proj_layer));
|
|
}
|
|
|
|
endFrameDrawing(layers);
|
|
}
|
|
|
|
static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info)
|
|
{
|
|
/* Set and convert to Blender coordinate space. */
|
|
copy_openxr_pose_to_ghost_pose(view.pose, r_info.eye_pose);
|
|
|
|
r_info.fov.angle_left = view.fov.angleLeft;
|
|
r_info.fov.angle_right = view.fov.angleRight;
|
|
r_info.fov.angle_up = view.fov.angleUp;
|
|
r_info.fov.angle_down = view.fov.angleDown;
|
|
}
|
|
|
|
void GHOST_XrSession::drawView(GHOST_XrSwapchain &swapchain,
|
|
XrCompositionLayerProjectionView &r_proj_layer_view,
|
|
XrSpaceLocation &view_location,
|
|
XrView &view,
|
|
uint32_t view_idx,
|
|
void *draw_customdata)
|
|
{
|
|
XrSwapchainImageBaseHeader *swapchain_image = swapchain.acquireDrawableSwapchainImage();
|
|
GHOST_XrDrawViewInfo draw_view_info = {};
|
|
|
|
r_proj_layer_view.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
|
|
r_proj_layer_view.pose = view.pose;
|
|
r_proj_layer_view.fov = view.fov;
|
|
swapchain.updateCompositionLayerProjectViewSubImage(r_proj_layer_view.subImage);
|
|
|
|
assert(view_idx < 256);
|
|
draw_view_info.view_idx = (char)view_idx;
|
|
draw_view_info.expects_srgb_buffer = swapchain.isBufferSRGB();
|
|
draw_view_info.ofsx = r_proj_layer_view.subImage.imageRect.offset.x;
|
|
draw_view_info.ofsy = r_proj_layer_view.subImage.imageRect.offset.y;
|
|
draw_view_info.width = r_proj_layer_view.subImage.imageRect.extent.width;
|
|
draw_view_info.height = r_proj_layer_view.subImage.imageRect.extent.height;
|
|
copy_openxr_pose_to_ghost_pose(view_location.pose, draw_view_info.local_pose);
|
|
ghost_xr_draw_view_info_from_view(view, draw_view_info);
|
|
|
|
/* Draw! */
|
|
m_context->getCustomFuncs().draw_view_fn(&draw_view_info, draw_customdata);
|
|
m_gpu_binding->submitToSwapchainImage(*swapchain_image, draw_view_info);
|
|
|
|
swapchain.releaseImage();
|
|
}
|
|
|
|
XrCompositionLayerProjection GHOST_XrSession::drawLayer(
|
|
std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata)
|
|
{
|
|
XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO};
|
|
XrViewState view_state = {XR_TYPE_VIEW_STATE};
|
|
XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
|
|
XrSpaceLocation view_location{XR_TYPE_SPACE_LOCATION};
|
|
uint32_t view_count;
|
|
|
|
viewloc_info.viewConfigurationType = m_oxr->view_type;
|
|
viewloc_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
|
|
viewloc_info.space = m_oxr->reference_space;
|
|
|
|
CHECK_XR(xrLocateViews(m_oxr->session,
|
|
&viewloc_info,
|
|
&view_state,
|
|
m_oxr->views.size(),
|
|
&view_count,
|
|
m_oxr->views.data()),
|
|
"Failed to query frame view and projection state.");
|
|
assert(m_oxr->swapchains.size() == view_count);
|
|
|
|
CHECK_XR(
|
|
xrLocateSpace(
|
|
m_oxr->view_space, m_oxr->reference_space, viewloc_info.displayTime, &view_location),
|
|
"Failed to query frame view space");
|
|
|
|
r_proj_layer_views.resize(view_count);
|
|
|
|
for (uint32_t view_idx = 0; view_idx < view_count; view_idx++) {
|
|
drawView(m_oxr->swapchains[view_idx],
|
|
r_proj_layer_views[view_idx],
|
|
view_location,
|
|
m_oxr->views[view_idx],
|
|
view_idx,
|
|
draw_customdata);
|
|
}
|
|
|
|
layer.space = m_oxr->reference_space;
|
|
layer.viewCount = r_proj_layer_views.size();
|
|
layer.views = r_proj_layer_views.data();
|
|
|
|
return layer;
|
|
}
|
|
|
|
bool GHOST_XrSession::needsUpsideDownDrawing() const
|
|
{
|
|
return m_gpu_binding && m_gpu_binding->needsUpsideDownDrawing(*m_gpu_ctx);
|
|
}
|
|
|
|
/** \} */ /* Drawing */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name State Queries
|
|
* \{ */
|
|
|
|
bool GHOST_XrSession::isRunning() const
|
|
{
|
|
if (m_oxr->session == XR_NULL_HANDLE) {
|
|
return false;
|
|
}
|
|
switch (m_oxr->session_state) {
|
|
case XR_SESSION_STATE_READY:
|
|
case XR_SESSION_STATE_SYNCHRONIZED:
|
|
case XR_SESSION_STATE_VISIBLE:
|
|
case XR_SESSION_STATE_FOCUSED:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** \} */ /* State Queries */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Graphics Context Injection
|
|
*
|
|
* Sessions need access to Ghost graphics context information. Additionally, this API allows
|
|
* creating contexts on the fly (created on start, destructed on end). For this, callbacks to bind
|
|
* (potentially create) and unbind (potentially destruct) a Ghost graphics context have to be set,
|
|
* which will be called on session start and end respectively.
|
|
*
|
|
* \{ */
|
|
|
|
void GHOST_XrSession::bindGraphicsContext()
|
|
{
|
|
const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
|
|
assert(custom_funcs.gpu_ctx_bind_fn);
|
|
m_gpu_ctx = static_cast<GHOST_Context *>(custom_funcs.gpu_ctx_bind_fn());
|
|
}
|
|
|
|
void GHOST_XrSession::unbindGraphicsContext()
|
|
{
|
|
const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
|
|
if (custom_funcs.gpu_ctx_unbind_fn) {
|
|
custom_funcs.gpu_ctx_unbind_fn((GHOST_ContextHandle)m_gpu_ctx);
|
|
}
|
|
m_gpu_ctx = nullptr;
|
|
}
|
|
|
|
/** \} */ /* 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::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();
|
|
XrSession session = m_oxr->session;
|
|
|
|
for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) {
|
|
const GHOST_XrActionProfileInfo &info = infos[profile_idx];
|
|
|
|
GHOST_XrAction *action = action_set->findAction(info.action_name);
|
|
if (action == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
action->createBinding(instance, session, info);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GHOST_XrSession::destroyActionBindings(const char *action_set_name,
|
|
uint32_t count,
|
|
const char *const *action_names,
|
|
const char *const *profile_paths)
|
|
{
|
|
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) {
|
|
GHOST_XrAction *action = action_set->findAction(action_names[i]);
|
|
if (action == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
action->destroyBinding(profile_paths[i]);
|
|
}
|
|
}
|
|
|
|
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 char **subaction_path,
|
|
const int64_t &duration,
|
|
const float &frequency,
|
|
const float &litude)
|
|
{
|
|
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
|
if (action_set == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
GHOST_XrAction *action = action_set->findAction(action_name);
|
|
if (action == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
action->applyHapticFeedback(
|
|
m_oxr->session, action_name, subaction_path, duration, frequency, amplitude);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GHOST_XrSession::stopHapticAction(const char *action_set_name,
|
|
const char *action_name,
|
|
const char **subaction_path)
|
|
{
|
|
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, subaction_path);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
uint32_t GHOST_XrSession::getActionCount(const char *action_set_name)
|
|
{
|
|
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
|
if (action_set == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
return action_set->getActionCount();
|
|
}
|
|
|
|
void GHOST_XrSession::getActionCustomdataArray(const char *action_set_name,
|
|
void **r_customdata_array)
|
|
{
|
|
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
|
if (action_set == nullptr) {
|
|
return;
|
|
}
|
|
|
|
action_set->getActionCustomdataArray(r_customdata_array);
|
|
}
|
|
|
|
/** \} */ /* Actions */
|