Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* All XR functionality is accessed through a #GHOST_XrContext handle.
* The lifetime of this context also determines the lifetime of the OpenXR instance, which is the
* representation of the OpenXR runtime connection within the application.
*/
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_idprop.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "DNA_scene_types.h"
#include "DNA_windowmanager_types.h"
#include "ED_screen.hh"
#include "GHOST_IXrContext.hh"
#include "GHOST_Types.hh"
#include "GHOST_Xr-api.hh"
#include "GPU_context.hh"
#include "MEM_guardedalloc.h"
#include "WM_api.hh"
#include "wm_xr_intern.hh"
namespace blender {
struct wmXrErrorHandlerData {
wmWindowManager *wm;
};
/* -------------------------------------------------------------------- */
static void wm_xr_error_handler(const GHOST_XrError *error)
{
wmXrErrorHandlerData *handler_data = static_cast<wmXrErrorHandlerData *>(error->customdata);
wmWindowManager *wm = handler_data->wm;
wmWindow *xr_root_win = wm->xr.runtime ? CTX_wm_window(wm->xr.runtime->b_context) : nullptr;
BKE_reports_clear(&wm->runtime->reports);
WM_global_report(RPT_ERROR, error->user_message);
/* Internally rely on the first WM window as a fallback when `xr_root_win` is nullptr. */
WM_report_banner_show(wm, xr_root_win);
if (wm->xr.runtime) {
/* Just play safe and destroy the entire runtime data, including context. */
wm_xr_runtime_data_free(&wm->xr.runtime);
}
}
bool wm_xr_init(bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
if (wm->xr.runtime && wm->xr.runtime->ghost_context) {
return true;
}
static wmXrErrorHandlerData error_customdata;
/* Set up error handling. */
error_customdata.wm = wm;
GHOST_XrErrorHandler(wm_xr_error_handler, &error_customdata);
{
Vector<GHOST_TXrGraphicsBinding> gpu_bindings_candidates;
switch (GPU_backend_get_type()) {
#ifdef WITH_OPENGL_BACKEND
case GPU_BACKEND_OPENGL:
gpu_bindings_candidates.append(GHOST_kXrGraphicsOpenGL);
# ifdef WIN32
gpu_bindings_candidates.append(GHOST_kXrGraphicsOpenGLD3D11);
# endif
break;
#endif
#ifdef WITH_VULKAN_BACKEND
case GPU_BACKEND_VULKAN:
gpu_bindings_candidates.append(GHOST_kXrGraphicsVulkan);
# ifdef WIN32
gpu_bindings_candidates.append(GHOST_kXrGraphicsVulkanD3D11);
# endif
break;
#endif
#ifdef WITH_METAL_BACKEND
case GPU_BACKEND_METAL:
gpu_bindings_candidates.append(GHOST_kXrGraphicsMetal);
break;
#endif
default:
break;
}
GHOST_XrContextCreateInfo create_info{
/*gpu_binding_candidates*/ gpu_bindings_candidates.data(),
/*gpu_binding_candidates_count*/ uint32_t(gpu_bindings_candidates.size()),
};
if (G.debug & G_DEBUG_XR) {
create_info.context_flag |= GHOST_kXrContextDebug;
}
if (G.debug & G_DEBUG_XR_TIME) {
create_info.context_flag |= GHOST_kXrContextDebugTime;
}
#ifdef WIN32
if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_WIN, GPU_DRIVER_ANY)) {
create_info.context_flag |= GHOST_kXrContextGpuNVIDIA;
}
#endif
GHOST_IXrContext *ghost_context;
if (!(ghost_context = GHOST_XrContextCreate(&create_info))) {
return false;
}
/* Set up context callbacks. */
GHOST_XrGraphicsContextBindFuncs(ghost_context,
wm_xr_session_gpu_binding_context_create,
wm_xr_session_gpu_binding_context_destroy);
GHOST_XrDrawViewFunc(ghost_context, wm_xr_draw_view);
GHOST_XrPassthroughEnabledFunc(ghost_context, wm_xr_passthrough_enabled);
GHOST_XrDisablePassthroughFunc(ghost_context, wm_xr_disable_passthrough);
if (!wm->xr.runtime) {
wm->xr.runtime = wm_xr_runtime_data_create();
wm->xr.runtime->ghost_context = ghost_context;
/* Create a minimal XR-specific context. */
wm->xr.runtime->b_context = CTX_create();
/* Base Main and WM pointers. */
CTX_wm_manager_set(wm->xr.runtime->b_context, CTX_wm_manager(C));
CTX_data_main_set(wm->xr.runtime->b_context, CTX_data_main(C));
/* Create the XR offscreen area (independent of any bScreen). */
wm->xr.runtime->offscreen_area = ED_area_offscreen_create(CTX_wm_window(C), SPACE_VIEW3D);
WM_xr_session_context_ensure(&wm->xr, wm);
}
}
BLI_assert(wm->xr.runtime && wm->xr.runtime->ghost_context && wm->xr.runtime->b_context);
return true;
}
void wm_xr_exit(wmWindowManager *wm)
{
if (wm->xr.runtime != nullptr) {
wm_xr_runtime_data_free(&wm->xr.runtime);
}
/* See #wm_xr_data_free for logic that frees window-manager XR data
* that may exist even when built without XR. */
}
bool wm_xr_events_handle(wmWindowManager *wm)
{
if (wm->xr.runtime && wm->xr.runtime->ghost_context) {
GHOST_XrEventsHandle(wm->xr.runtime->ghost_context);
/* Process OpenXR action events. */
if (WM_xr_session_is_ready(&wm->xr)) {
wm_xr_session_actions_update(wm);
}
/* #wm_window_events_process() 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. */
return true;
}
return false;
}
/* -------------------------------------------------------------------- */
/** \name XR Runtime Data
* \{ */
wmXrRuntimeData *wm_xr_runtime_data_create()
{
wmXrRuntimeData *runtime = MEM_new_zeroed<wmXrRuntimeData>(__func__);
return runtime;
}
void wm_xr_runtime_data_free(wmXrRuntimeData **runtime)
{
/* This function may be called recursively via the #GHOST_XrContextDestroy session exit callback.
* Guard against double-free by nulling pointers after freeing. */
/* Destroy context if still alive. */
if ((*runtime)->ghost_context != nullptr) {
GHOST_IXrContext *ghost_context = (*runtime)->ghost_context;
/* Set to nullptr before calling XrContextDestroy to prevent recursive calls. */
(*runtime)->ghost_context = nullptr;
GHOST_XrContextDestroy(ghost_context);
}
/* Free remaining runtime data. */
if (*runtime != nullptr) {
ScrArea *xr_offscreen_area = (*runtime)->offscreen_area;
BLI_assert(xr_offscreen_area);
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
wmWindow *xr_win = wm_xr_session_root_window_or_fallback_get(wm, (*runtime));
WM_event_remove_handlers_by_area(&xr_win->runtime->handlers, xr_offscreen_area);
ED_area_offscreen_free(wm, xr_win, xr_offscreen_area);
CTX_free((*runtime)->b_context);
wm_xr_session_data_free(&(*runtime)->session_state);
WM_xr_actionmaps_clear(*runtime);
MEM_SAFE_DELETE(*runtime);
*runtime = nullptr;
}
}
/** \} */ /* XR Runtime Data. */
} // namespace blender

View File

@@ -0,0 +1,544 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \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_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "GHOST_Xr-api.hh"
#include "MEM_guardedalloc.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm_xr_intern.hh"
#include <cstring>
namespace blender {
/* -------------------------------------------------------------------- */
/** \name XR-Action API
*
* API functions for managing OpenXR actions.
*
* \{ */
static wmXrActionSet *action_set_create(const char *action_set_name)
{
wmXrActionSet *action_set = MEM_new_zeroed<wmXrActionSet>(__func__);
action_set->name = BLI_strdup(action_set_name);
return action_set;
}
static void action_set_destroy(void *val)
{
wmXrActionSet *action_set = static_cast<wmXrActionSet *>(val);
MEM_SAFE_DELETE(action_set->name);
action_set->active_modal_actions.free_no_destruct();
action_set->active_haptic_actions.free_no_destruct();
MEM_delete(action_set);
}
static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name)
{
return static_cast<wmXrActionSet *>(
GHOST_XrGetActionSetCustomdata(xr->runtime->ghost_context, action_set_name));
}
static wmXrAction *action_create(const char *action_name,
eXrActionType type,
const ListBaseT<XrUserPath> *user_paths,
wmOperatorType *ot,
IDProperty *op_properties,
const char *haptic_name,
const int64_t *haptic_duration,
const float *haptic_frequency,
const float *haptic_amplitude,
eXrOpFlag op_flag,
eXrActionFlag action_flag,
eXrHapticFlag haptic_flag)
{
wmXrAction *action = MEM_new_zeroed<wmXrAction>(__func__);
action->name = BLI_strdup(action_name);
action->type = type;
const uint count = uint(user_paths->count());
action->count_subaction_paths = count;
action->subaction_paths = MEM_new_array_uninitialized<char *>(count, "XrAction_SubactionPaths");
for (auto [subaction_idx, user_path] : user_paths->enumerate()) {
action->subaction_paths[subaction_idx] = BLI_strdup(user_path.path);
}
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_new_array_zeroed(count, size, "XrAction_States");
action->states_prev = MEM_new_array_zeroed(count, size, "XrAction_StatesPrev");
const bool is_float_action = ELEM(type, XR_FLOAT_INPUT, XR_VECTOR2F_INPUT);
const bool is_button_action = (is_float_action || type == XR_BOOLEAN_INPUT);
if (is_float_action) {
action->float_thresholds = MEM_new_array_zeroed<float>(count, "XrAction_FloatThresholds");
}
if (is_button_action) {
action->axis_flags = MEM_new_array_zeroed<eXrAxisFlag>(count, "XrAction_AxisFlags");
}
action->ot = ot;
action->op_properties = op_properties;
if (haptic_name) {
BLI_assert(is_button_action);
action->haptic_name = BLI_strdup(haptic_name);
action->haptic_duration = *haptic_duration;
action->haptic_frequency = *haptic_frequency;
action->haptic_amplitude = *haptic_amplitude;
}
action->op_flag = op_flag;
action->action_flag = action_flag;
action->haptic_flag = haptic_flag;
return action;
}
static void action_destroy(void *val)
{
wmXrAction *action = static_cast<wmXrAction *>(val);
MEM_SAFE_DELETE(action->name);
char **subaction_paths = action->subaction_paths;
if (subaction_paths) {
for (uint i = 0; i < action->count_subaction_paths; ++i) {
MEM_SAFE_DELETE(subaction_paths[i]);
}
MEM_delete(subaction_paths);
}
MEM_SAFE_DELETE_VOID(action->states);
MEM_SAFE_DELETE_VOID(action->states_prev);
MEM_SAFE_DELETE(action->float_thresholds);
MEM_SAFE_DELETE(action->axis_flags);
MEM_SAFE_DELETE(action->haptic_name);
MEM_delete(action);
}
static wmXrAction *action_find(wmXrData *xr, const char *action_set_name, const char *action_name)
{
return static_cast<wmXrAction *>(
GHOST_XrGetActionCustomdata(xr->runtime->ghost_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{};
info.name = action_set_name;
info.customdata_free_fn = action_set_destroy;
info.customdata = action_set;
if (!GHOST_XrCreateActionSet(xr->runtime->ghost_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_grip_action || action_set->controller_aim_action) {
wm_xr_session_controller_data_clear(session_state);
action_set->controller_grip_action = action_set->controller_aim_action = nullptr;
}
action_set->active_modal_actions.free_no_destruct();
action_set->active_haptic_actions.free_no_destruct();
session_state->active_action_set = nullptr;
}
GHOST_XrDestroyActionSet(xr->runtime->ghost_context, action_set_name);
}
bool WM_xr_action_create(wmXrData *xr,
const char *action_set_name,
const char *action_name,
eXrActionType type,
const ListBaseT<XrUserPath> *user_paths,
wmOperatorType *ot,
IDProperty *op_properties,
const char *haptic_name,
const int64_t *haptic_duration,
const float *haptic_frequency,
const float *haptic_amplitude,
eXrOpFlag op_flag,
eXrActionFlag action_flag,
eXrHapticFlag haptic_flag)
{
if (action_find(xr, action_set_name, action_name)) {
return false;
}
wmXrAction *action = action_create(action_name,
type,
user_paths,
ot,
op_properties,
haptic_name,
haptic_duration,
haptic_frequency,
haptic_amplitude,
op_flag,
action_flag,
haptic_flag);
const uint count = uint(user_paths->count());
char **subaction_paths = MEM_new_array_zeroed<char *>(count, "XrAction_SubactionPathPointers");
for (auto [subaction_idx, user_path] : user_paths->enumerate()) {
subaction_paths[subaction_idx] = (char *)user_path.path;
}
GHOST_XrActionInfo info{};
info.name = action_name;
info.count_subaction_paths = count;
info.subaction_paths = const_cast<const char **>(subaction_paths);
info.states = action->states;
info.float_thresholds = action->float_thresholds;
info.axis_flags = reinterpret_cast<int16_t *>(action->axis_flags);
info.customdata_free_fn = action_destroy;
info.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;
}
const bool success = GHOST_XrCreateActions(
xr->runtime->ghost_context, action_set_name, 1, &info);
MEM_delete(subaction_paths);
return success;
}
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;
}
wmXrAction *action = action_find(xr, action_set_name, action_name);
if (!action) {
return;
}
if ((action_set->controller_grip_action &&
STREQ(action_set->controller_grip_action->name, action_name)) ||
(action_set->controller_aim_action &&
STREQ(action_set->controller_aim_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_grip_action = action_set->controller_aim_action = nullptr;
}
for (LinkData &ld : action_set->active_modal_actions) {
wmXrAction *active_modal_action = static_cast<wmXrAction *>(ld.data);
if (STREQ(active_modal_action->name, action_name)) {
BLI_freelinkN(&action_set->active_modal_actions, &ld);
break;
}
}
for (wmXrHapticAction &ha : action_set->active_haptic_actions.items_mutable()) {
if (STREQ(ha.action->name, action_name)) {
BLI_freelinkN(&action_set->active_haptic_actions, &ha);
}
}
GHOST_XrDestroyActions(xr->runtime->ghost_context, action_set_name, 1, &action_name);
}
bool WM_xr_action_binding_create(wmXrData *xr,
const char *action_set_name,
const char *action_name,
const char *profile_path,
const ListBaseT<XrUserPath> *user_paths,
const ListBaseT<XrComponentPath> *component_paths,
const float *float_thresholds,
const eXrAxisFlag *axis_flags,
const wmXrPose *poses)
{
const uint count = uint(user_paths->count());
BLI_assert(count == uint(component_paths->count()));
GHOST_XrActionBindingInfo *binding_infos = MEM_new_array_zeroed<GHOST_XrActionBindingInfo>(
count, "XrActionBinding_Infos");
char **subaction_paths = MEM_new_array_zeroed<char *>(count,
"XrActionBinding_SubactionPathPointers");
for (uint i = 0; i < count; ++i) {
GHOST_XrActionBindingInfo *binding_info = &binding_infos[i];
const XrUserPath *user_path = static_cast<const XrUserPath *>(BLI_findlink(user_paths, i));
const XrComponentPath *component_path = static_cast<const XrComponentPath *>(
BLI_findlink(component_paths, i));
subaction_paths[i] = const_cast<char *>(user_path->path);
binding_info->component_path = component_path->path;
if (float_thresholds) {
binding_info->float_threshold = float_thresholds[i];
}
if (axis_flags) {
binding_info->axis_flag = axis_flags[i];
}
if (poses) {
copy_v3_v3(binding_info->pose.position, poses[i].position);
copy_qt_qt(binding_info->pose.orientation_quat, poses[i].orientation_quat);
}
}
GHOST_XrActionProfileInfo profile_info{};
profile_info.action_name = action_name;
profile_info.profile_path = profile_path;
profile_info.count_subaction_paths = count;
profile_info.subaction_paths = const_cast<const char **>(subaction_paths);
profile_info.bindings = binding_infos;
const bool success = GHOST_XrCreateActionBindings(
xr->runtime->ghost_context, action_set_name, 1, &profile_info);
MEM_delete(subaction_paths);
MEM_delete(binding_infos);
return success;
}
void WM_xr_action_binding_destroy(wmXrData *xr,
const char *action_set_name,
const char *action_name,
const char *profile_path)
{
GHOST_XrDestroyActionBindings(
xr->runtime->ghost_context, action_set_name, 1, &action_name, &profile_path);
}
bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name, bool delayed)
{
wmXrActionSet *action_set = action_set_find(xr, action_set_name);
if (!action_set) {
return false;
}
if (delayed) {
/* Save name to activate action set later, before next actions sync
* (see #wm_xr_session_actions_update()). */
STRNCPY(xr->runtime->session_state.active_action_set_next, action_set_name);
return true;
}
{
/* Clear any active modal/haptic actions. */
wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set;
if (active_action_set) {
active_action_set->active_modal_actions.free_no_destruct();
active_action_set->active_haptic_actions.free_no_destruct();
}
}
xr->runtime->session_state.active_action_set = action_set;
if (action_set->controller_grip_action && action_set->controller_aim_action) {
wm_xr_session_controller_data_populate(
action_set->controller_grip_action, action_set->controller_aim_action, xr);
}
else {
wm_xr_session_controller_data_clear(&xr->runtime->session_state);
}
return true;
}
bool WM_xr_controller_pose_actions_set(wmXrData *xr,
const char *action_set_name,
const char *grip_action_name,
const char *aim_action_name)
{
wmXrActionSet *action_set = action_set_find(xr, action_set_name);
if (!action_set) {
return false;
}
wmXrAction *grip_action = action_find(xr, action_set_name, grip_action_name);
if (!grip_action) {
return false;
}
wmXrAction *aim_action = action_find(xr, action_set_name, aim_action_name);
if (!aim_action) {
return false;
}
/* Ensure consistent subaction paths. */
const uint count = grip_action->count_subaction_paths;
if (count != aim_action->count_subaction_paths) {
return false;
}
for (uint i = 0; i < count; ++i) {
if (!STREQ(grip_action->subaction_paths[i], aim_action->subaction_paths[i])) {
return false;
}
}
action_set->controller_grip_action = grip_action;
action_set->controller_aim_action = aim_action;
if (action_set == xr->runtime->session_state.active_action_set) {
wm_xr_session_controller_data_populate(grip_action, aim_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(const_cast<wmXrData *>(xr), action_set_name, action_name);
if (!action) {
return false;
}
r_state->type = int(action->type);
/* Find the action state corresponding to the subaction path. */
for (uint i = 0; i < action->count_subaction_paths; ++i) {
if (STREQ(subaction_path, action->subaction_paths[i])) {
switch (action->type) {
case XR_BOOLEAN_INPUT:
r_state->state_boolean = (static_cast<bool *>(action->states))[i];
break;
case XR_FLOAT_INPUT:
r_state->state_float = (static_cast<float *>(action->states))[i];
break;
case XR_VECTOR2F_INPUT:
copy_v2_v2(r_state->state_vector2f, (static_cast<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 char *subaction_path,
const int64_t *duration,
const float *frequency,
const float *amplitude)
{
return GHOST_XrApplyHapticAction(xr->runtime->ghost_context,
action_set_name,
action_name,
subaction_path,
duration,
frequency,
amplitude) ?
true :
false;
}
void WM_xr_haptic_action_stop(wmXrData *xr,
const char *action_set_name,
const char *action_name,
const char *subaction_path)
{
GHOST_XrStopHapticAction(
xr->runtime->ghost_context, action_set_name, action_name, subaction_path);
}
/** \} */ /* XR-Action API. */
} // namespace blender

View File

@@ -0,0 +1,544 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* \name Window-Manager XR Action Maps
*
* XR actionmap API, similar to WM keymap API.
*/
#include <cmath>
#include <cstring>
#include "BKE_idprop.hh"
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "MEM_guardedalloc.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm_xr_intern.hh"
namespace blender {
#define WM_XR_ACTIONMAP_STR_DEFAULT "actionmap"
#define WM_XR_ACTIONMAP_ITEM_STR_DEFAULT "action"
#define WM_XR_ACTIONMAP_BINDING_STR_DEFAULT "binding"
/* -------------------------------------------------------------------- */
/** \name Action Map Binding
*
* Binding in an XR action map item, that maps an action to an XR input.
* \{ */
XrActionMapBinding *WM_xr_actionmap_binding_new(XrActionMapItem *ami,
const char *name,
bool replace_existing)
{
XrActionMapBinding *amb_prev = WM_xr_actionmap_binding_find(ami, name);
if (amb_prev && replace_existing) {
return amb_prev;
}
XrActionMapBinding *amb = MEM_new<XrActionMapBinding>(__func__);
STRNCPY_UTF8(amb->name, name);
if (amb_prev) {
WM_xr_actionmap_binding_ensure_unique(ami, amb);
}
BLI_addtail(&ami->bindings, amb);
/* Set non-zero threshold by default. */
amb->float_threshold = 0.3f;
return amb;
}
static XrActionMapBinding *wm_xr_actionmap_binding_find_except(XrActionMapItem *ami,
const char *name,
XrActionMapBinding *ambexcept)
{
for (XrActionMapBinding &amb : ami->bindings) {
if (STREQLEN(name, amb.name, MAX_NAME) && (&amb != ambexcept)) {
return &amb;
}
}
return nullptr;
}
void WM_xr_actionmap_binding_ensure_unique(XrActionMapItem *ami, XrActionMapBinding *amb)
{
char name[MAX_NAME];
char *suffix;
size_t baselen;
size_t idx = 0;
baselen = STRNCPY_UTF8_RLEN(name, amb->name);
suffix = &name[baselen];
while (wm_xr_actionmap_binding_find_except(ami, name, amb)) {
if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) {
/* Use default base name. */
baselen = STRNCPY_UTF8_RLEN(name, WM_XR_ACTIONMAP_BINDING_STR_DEFAULT);
suffix = &name[baselen];
idx = 0;
}
else {
BLI_snprintf_utf8(suffix, MAX_NAME, "%zu", idx);
}
}
STRNCPY_UTF8(amb->name, name);
}
static XrActionMapBinding *wm_xr_actionmap_binding_copy(XrActionMapBinding *amb_src)
{
XrActionMapBinding *amb_dst = MEM_dupalloc(amb_src);
amb_dst->prev = amb_dst->next = nullptr;
amb_dst->component_paths.clear_no_delete();
for (XrComponentPath &path : amb_src->component_paths) {
XrComponentPath *path_new = MEM_dupalloc(&path);
BLI_addtail(&amb_dst->component_paths, path_new);
}
return amb_dst;
}
XrActionMapBinding *WM_xr_actionmap_binding_add_copy(XrActionMapItem *ami,
XrActionMapBinding *amb_src)
{
XrActionMapBinding *amb_dst = wm_xr_actionmap_binding_copy(amb_src);
WM_xr_actionmap_binding_ensure_unique(ami, amb_dst);
BLI_addtail(&ami->bindings, amb_dst);
return amb_dst;
}
static void wm_xr_actionmap_binding_clear(XrActionMapBinding *amb)
{
amb->component_paths.free_no_destruct();
}
bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *amb)
{
int idx = BLI_findindex(&ami->bindings, amb);
if (idx != -1) {
wm_xr_actionmap_binding_clear(amb);
BLI_freelinkN(&ami->bindings, amb);
if (idx <= ami->selbinding) {
if (--ami->selbinding < 0) {
ami->selbinding = 0;
}
}
return true;
}
return false;
}
XrActionMapBinding *WM_xr_actionmap_binding_find(XrActionMapItem *ami, const char *name)
{
for (XrActionMapBinding &amb : ami->bindings) {
if (STREQLEN(name, amb.name, MAX_NAME)) {
return &amb;
}
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Action Map Item
*
* Item in an XR action map, that maps an XR event to an operator, pose, or haptic output.
* \{ */
static void wm_xr_actionmap_item_properties_set(XrActionMapItem *ami)
{
WM_operator_properties_alloc(&(ami->op_properties_ptr), &(ami->op_properties), ami->op);
WM_operator_properties_sanitize(ami->op_properties_ptr, true);
}
static void wm_xr_actionmap_item_properties_free(XrActionMapItem *ami)
{
if (ami->op_properties_ptr) {
WM_operator_properties_free(ami->op_properties_ptr);
MEM_delete(ami->op_properties_ptr);
ami->op_properties_ptr = nullptr;
ami->op_properties = nullptr;
}
else {
BLI_assert(ami->op_properties == nullptr);
}
}
static void wm_xr_actionmap_item_clear(XrActionMapItem *ami)
{
for (XrActionMapBinding &amb : ami->bindings) {
wm_xr_actionmap_binding_clear(&amb);
}
ami->bindings.free_no_destruct();
ami->selbinding = 0;
wm_xr_actionmap_item_properties_free(ami);
ami->user_paths.free_no_destruct();
}
void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami)
{
switch (ami->type) {
case XR_BOOLEAN_INPUT:
case XR_FLOAT_INPUT:
case XR_VECTOR2F_INPUT:
break;
case XR_POSE_INPUT:
case XR_VIBRATION_OUTPUT:
wm_xr_actionmap_item_properties_free(ami);
memset(ami->op, 0, sizeof(ami->op));
return;
}
if (ami->op[0] == 0) {
wm_xr_actionmap_item_properties_free(ami);
return;
}
if (ami->op_properties_ptr == nullptr) {
wm_xr_actionmap_item_properties_set(ami);
}
else {
wmOperatorType *ot = WM_operatortype_find(ami->op, false);
if (ot) {
if (ot->srna != ami->op_properties_ptr->type) {
/* Matches wm_xr_actionmap_item_properties_set() but doesn't alloc new ptr. */
*ami->op_properties_ptr = WM_operator_properties_create_ptr(ot);
if (ami->op_properties) {
ami->op_properties_ptr->data = ami->op_properties;
}
WM_operator_properties_sanitize(ami->op_properties_ptr, true);
}
}
else {
wm_xr_actionmap_item_properties_free(ami);
}
}
}
XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap,
const char *name,
bool replace_existing)
{
XrActionMapItem *ami_prev = WM_xr_actionmap_item_find(actionmap, name);
if (ami_prev && replace_existing) {
wm_xr_actionmap_item_properties_free(ami_prev);
return ami_prev;
}
XrActionMapItem *ami = MEM_new<XrActionMapItem>(__func__);
STRNCPY_UTF8(ami->name, name);
if (ami_prev) {
WM_xr_actionmap_item_ensure_unique(actionmap, ami);
}
BLI_addtail(&actionmap->items, ami);
/* Set type to float (button) input by default. */
ami->type = XR_FLOAT_INPUT;
return ami;
}
static XrActionMapItem *wm_xr_actionmap_item_find_except(XrActionMap *actionmap,
const char *name,
const XrActionMapItem *amiexcept)
{
for (XrActionMapItem &ami : actionmap->items) {
if (STREQLEN(name, ami.name, MAX_NAME) && (&ami != amiexcept)) {
return &ami;
}
}
return nullptr;
}
void WM_xr_actionmap_item_ensure_unique(XrActionMap *actionmap, XrActionMapItem *ami)
{
char name[MAX_NAME];
char *suffix;
size_t baselen;
size_t idx = 0;
baselen = STRNCPY_UTF8_RLEN(name, ami->name);
suffix = &name[baselen];
while (wm_xr_actionmap_item_find_except(actionmap, name, ami)) {
if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) {
/* Use default base name. */
baselen = STRNCPY_UTF8_RLEN(name, WM_XR_ACTIONMAP_ITEM_STR_DEFAULT);
suffix = &name[baselen];
idx = 0;
}
else {
BLI_snprintf_utf8(suffix, MAX_NAME, "%zu", idx);
}
}
STRNCPY_UTF8(ami->name, name);
}
static XrActionMapItem *wm_xr_actionmap_item_copy(XrActionMapItem *ami_src)
{
XrActionMapItem *ami_dst = MEM_dupalloc(ami_src);
ami_dst->prev = ami_dst->next = nullptr;
ami_dst->bindings.clear_no_delete();
for (XrActionMapBinding &amb : ami_src->bindings) {
XrActionMapBinding *amb_new = wm_xr_actionmap_binding_copy(&amb);
BLI_addtail(&ami_dst->bindings, amb_new);
}
if (ami_dst->op_properties) {
ami_dst->op_properties_ptr = MEM_new<PointerRNA>("wmOpItemPtr");
*ami_dst->op_properties_ptr = WM_operator_properties_create(ami_dst->op);
ami_dst->op_properties = IDP_CopyProperty(ami_src->op_properties);
ami_dst->op_properties_ptr->data = ami_dst->op_properties;
}
else {
ami_dst->op_properties = nullptr;
ami_dst->op_properties_ptr = nullptr;
}
ami_dst->user_paths.clear_no_delete();
for (XrUserPath &path : ami_src->user_paths) {
XrUserPath *path_new = MEM_dupalloc(&path);
BLI_addtail(&ami_dst->user_paths, path_new);
}
return ami_dst;
}
XrActionMapItem *WM_xr_actionmap_item_add_copy(XrActionMap *actionmap, XrActionMapItem *ami_src)
{
XrActionMapItem *ami_dst = wm_xr_actionmap_item_copy(ami_src);
WM_xr_actionmap_item_ensure_unique(actionmap, ami_dst);
BLI_addtail(&actionmap->items, ami_dst);
return ami_dst;
}
bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami)
{
int idx = BLI_findindex(&actionmap->items, ami);
if (idx != -1) {
wm_xr_actionmap_item_clear(ami);
BLI_freelinkN(&actionmap->items, ami);
if (idx <= actionmap->selitem) {
if (--actionmap->selitem < 0) {
actionmap->selitem = 0;
}
}
return true;
}
return false;
}
XrActionMapItem *WM_xr_actionmap_item_find(XrActionMap *actionmap, const char *name)
{
for (XrActionMapItem &ami : actionmap->items) {
if (STREQLEN(name, ami.name, MAX_NAME)) {
return &ami;
}
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Action Map
*
* List of XR action map items.
* \{ */
XrActionMap *WM_xr_actionmap_new(wmXrRuntimeData *runtime, const char *name, bool replace_existing)
{
XrActionMap *am_prev = WM_xr_actionmap_find(runtime, name);
if (am_prev && replace_existing) {
WM_xr_actionmap_clear(am_prev);
return am_prev;
}
XrActionMap *am = MEM_new<XrActionMap>(__func__);
STRNCPY_UTF8(am->name, name);
if (am_prev) {
WM_xr_actionmap_ensure_unique(runtime, am);
}
BLI_addtail(&runtime->actionmaps, am);
return am;
}
static XrActionMap *wm_xr_actionmap_find_except(wmXrRuntimeData *runtime,
const char *name,
const XrActionMap *am_except)
{
for (XrActionMap &am : runtime->actionmaps) {
if (STREQLEN(name, am.name, MAX_NAME) && (&am != am_except)) {
return &am;
}
}
return nullptr;
}
void WM_xr_actionmap_ensure_unique(wmXrRuntimeData *runtime, XrActionMap *actionmap)
{
char name[MAX_NAME];
char *suffix;
size_t baselen;
size_t idx = 0;
baselen = STRNCPY_UTF8_RLEN(name, actionmap->name);
suffix = &name[baselen];
while (wm_xr_actionmap_find_except(runtime, name, actionmap)) {
if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) {
/* Use default base name. */
baselen = STRNCPY_UTF8_RLEN(name, WM_XR_ACTIONMAP_STR_DEFAULT);
suffix = &name[baselen];
idx = 0;
}
else {
BLI_snprintf_utf8(suffix, MAX_NAME, "%zu", idx);
}
}
STRNCPY_UTF8(actionmap->name, name);
}
static XrActionMap *wm_xr_actionmap_copy(XrActionMap *am_src)
{
XrActionMap *am_dst = MEM_dupalloc(am_src);
am_dst->prev = am_dst->next = nullptr;
am_dst->items.clear_no_delete();
for (XrActionMapItem &ami : am_src->items) {
XrActionMapItem *ami_new = wm_xr_actionmap_item_copy(&ami);
BLI_addtail(&am_dst->items, ami_new);
}
return am_dst;
}
XrActionMap *WM_xr_actionmap_add_copy(wmXrRuntimeData *runtime, XrActionMap *am_src)
{
XrActionMap *am_dst = wm_xr_actionmap_copy(am_src);
WM_xr_actionmap_ensure_unique(runtime, am_dst);
BLI_addtail(&runtime->actionmaps, am_dst);
return am_dst;
}
bool WM_xr_actionmap_remove(wmXrRuntimeData *runtime, XrActionMap *actionmap)
{
int idx = BLI_findindex(&runtime->actionmaps, actionmap);
if (idx != -1) {
WM_xr_actionmap_clear(actionmap);
BLI_freelinkN(&runtime->actionmaps, actionmap);
if (idx <= runtime->actactionmap) {
if (--runtime->actactionmap < 0) {
runtime->actactionmap = 0;
}
}
if (idx <= runtime->selactionmap) {
if (--runtime->selactionmap < 0) {
runtime->selactionmap = 0;
}
}
return true;
}
return false;
}
XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name)
{
for (XrActionMap &am : runtime->actionmaps) {
if (STREQLEN(name, am.name, MAX_NAME)) {
return &am;
}
}
return nullptr;
}
void WM_xr_actionmap_clear(XrActionMap *actionmap)
{
for (XrActionMapItem &ami : actionmap->items) {
wm_xr_actionmap_item_clear(&ami);
}
actionmap->items.free_no_destruct();
actionmap->selitem = 0;
}
void WM_xr_actionmaps_clear(wmXrRuntimeData *runtime)
{
for (XrActionMap &am : runtime->actionmaps) {
WM_xr_actionmap_clear(&am);
}
runtime->actionmaps.free_no_destruct();
runtime->actactionmap = runtime->selactionmap = 0;
}
ListBaseT<XrActionMap> *WM_xr_actionmaps_get(wmXrRuntimeData *runtime)
{
return &runtime->actionmaps;
}
short WM_xr_actionmap_active_index_get(const wmXrRuntimeData *runtime)
{
return runtime->actactionmap;
}
void WM_xr_actionmap_active_index_set(wmXrRuntimeData *runtime, short idx)
{
runtime->actactionmap = idx;
}
short WM_xr_actionmap_selected_index_get(const wmXrRuntimeData *runtime)
{
return runtime->selactionmap;
}
void WM_xr_actionmap_selected_index_set(wmXrRuntimeData *runtime, short idx)
{
runtime->selactionmap = idx;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,453 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* \name Window-Manager XR Drawing
*
* Implements Blender specific drawing functionality for use with the Ghost-XR API.
*/
#include <cstring>
#include "DNA_userdef_types.h"
#include "BLI_listbase.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BKE_context.hh"
#include "BKE_scene.hh"
#include "ED_view3d_offscreen.hh"
#include "GHOST_Xr-api.hh"
#include "GPU_batch_presets.hh"
#include "GPU_immediate.hh"
#include "GPU_matrix.hh"
#include "GPU_state.hh"
#include "GPU_viewport.hh"
#include "UI_resources.hh"
#include "WM_api.hh"
#include "wm_xr_intern.hh"
namespace blender {
void wm_xr_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);
}
void wm_xr_pose_scale_to_mat(const GHOST_XrPose *pose, float scale, float r_mat[4][4])
{
wm_xr_pose_to_mat(pose, r_mat);
BLI_assert(scale > 0.0f);
mul_v3_fl(r_mat[0], scale);
mul_v3_fl(r_mat[1], scale);
mul_v3_fl(r_mat[2], scale);
}
void wm_xr_pose_to_imat(const GHOST_XrPose *pose, float r_imat[4][4])
{
float iquat[4];
invert_qt_qt_normalized(iquat, pose->orientation_quat);
quat_to_mat4(r_imat, iquat);
translate_m4(r_imat, -pose->position[0], -pose->position[1], -pose->position[2]);
}
void wm_xr_pose_scale_to_imat(const GHOST_XrPose *pose, float scale, float r_imat[4][4])
{
float iquat[4];
invert_qt_qt_normalized(iquat, pose->orientation_quat);
quat_to_mat4(r_imat, iquat);
BLI_assert(scale > 0.0f);
scale = 1.0f / scale;
mul_v3_fl(r_imat[0], scale);
mul_v3_fl(r_imat[1], scale);
mul_v3_fl(r_imat[2], scale);
translate_m4(r_imat, -pose->position[0], -pose->position[1], -pose->position[2]);
}
static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data,
const GHOST_XrDrawViewInfo *draw_view,
const XrSessionSettings *session_settings,
const wmXrSessionState *session_state,
float r_viewmat[4][4],
float r_projmat[4][4])
{
GHOST_XrPose eye_pose;
float eye_inv[4][4], base_inv[4][4], nav_inv[4][4], m[4][4];
/* Calculate inverse eye matrix. */
copy_qt_qt(eye_pose.orientation_quat, draw_view->eye_pose.orientation_quat);
copy_v3_v3(eye_pose.position, draw_view->eye_pose.position);
if ((session_settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) {
sub_v3_v3(eye_pose.position, draw_view->local_pose.position);
}
if ((session_settings->flag & XR_SESSION_USE_ABSOLUTE_TRACKING) == 0) {
sub_v3_v3(eye_pose.position, draw_data->eye_position_ofs);
}
wm_xr_pose_to_imat(&eye_pose, eye_inv);
/* Apply base pose and navigation. */
wm_xr_pose_scale_to_imat(&draw_data->base_pose, draw_data->base_scale, base_inv);
wm_xr_pose_scale_to_imat(&session_state->nav_pose_last_actions_sync,
session_state->viewer_scale_last_actions_sync,
nav_inv);
mul_m4_m4m4(m, eye_inv, base_inv);
mul_m4_m4m4(r_viewmat, m, nav_inv);
perspective_m4_fov(r_projmat,
draw_view->fov.angle_left,
draw_view->fov.angle_right,
draw_view->fov.angle_up,
draw_view->fov.angle_down,
session_settings->clip_start,
session_settings->clip_end);
}
static void wm_xr_draw_viewport_buffers_to_active_framebuffer(
const wmXrRuntimeData *runtime_data,
const wmXrSurfaceData *surface_data,
const GHOST_XrDrawViewInfo *draw_view)
{
const wmXrViewportPair *vp = static_cast<const wmXrViewportPair *>(
BLI_findlink(&surface_data->viewports, draw_view->view_idx));
BLI_assert(vp && vp->viewport);
const bool is_upside_down = GHOST_XrSessionNeedsUpsideDownDrawing(runtime_data->ghost_context);
rcti rect{};
rect.xmin = 0;
rect.ymin = 0;
rect.xmax = draw_view->width - 1;
rect.ymax = draw_view->height - 1;
wmViewport(&rect);
/* For upside down contexts, draw with inverted y-values. */
if (is_upside_down) {
std::swap(rect.ymin, rect.ymax);
}
GPU_viewport_draw_to_screen_ex(vp->viewport, 0, &rect, draw_view->expects_srgb_buffer, true);
}
void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata)
{
wmXrDrawData *draw_data = static_cast<wmXrDrawData *>(customdata);
wmXrData *xr_data = draw_data->xr_data;
wmXrSurfaceData *surface_data = draw_data->surface_data;
wmXrSessionState *session_state = &xr_data->runtime->session_state;
XrSessionSettings *settings = &xr_data->session_settings;
const int display_flags = V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS | settings->draw_flags;
float viewmat[4][4], winmat[4][4];
BLI_assert(WM_xr_session_is_ready(xr_data));
wm_xr_session_draw_data_update(session_state, settings, draw_view, draw_data);
wm_xr_draw_matrices_create(draw_data, draw_view, settings, session_state, viewmat, winmat);
wm_xr_session_state_update(settings, draw_data, draw_view, session_state);
if (!wm_xr_session_surface_offscreen_ensure(surface_data, draw_view)) {
return;
}
const wmXrViewportPair *vp = static_cast<const wmXrViewportPair *>(
BLI_findlink(&surface_data->viewports, draw_view->view_idx));
BLI_assert(vp && vp->offscreen && vp->viewport);
/* In case a framebuffer is still bound from drawing the last eye. */
GPU_framebuffer_restore();
/* Some systems have drawing glitches without this. */
GPU_clear_depth(1.0f);
/* XR context is ensured before each draw in #wm_xr_session_surface_draw. */
bContext *xr_context = WM_xr_session_context_get(xr_data);
Scene *scene = CTX_data_scene(xr_context);
/* The XR context depsgraph is separately evaluated outside of drawing within the XR surface
* #do_depsgraph callback. Thus, obtain the depsgraph directly without evaluating it. */
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(xr_context);
if (draw_view->view_idx == 0) {
/* Only render location scouting viewfinder on first eye draw. */
wm_xr_viewfinder_render_view(xr_data);
}
/* Draws the view into the surface_data->viewport's frame-buffers. */
ED_view3d_draw_offscreen_simple(depsgraph,
scene,
&settings->shading,
xr_context,
eDrawType(settings->shading.type),
settings->object_type_exclude_viewport,
settings->object_type_exclude_select,
draw_view->width,
draw_view->height,
display_flags,
viewmat,
winmat,
settings->clip_start,
settings->clip_end,
session_state->vignette_aperture,
true,
false,
true,
nullptr,
false,
nullptr,
vp->offscreen,
vp->viewport);
/* The draw-manager uses both GPUOffscreen and GPUViewport to manage frame and texture buffers. A
* call to GPU_viewport_draw_to_screen() is still needed to get the final result from the
* viewport buffers composited together and potentially color managed for display on screen.
* It needs a bound frame-buffer to draw into, for which we simply reuse the GPUOffscreen one.
*
* In a next step, Ghost-XR will use the currently bound frame-buffer to retrieve the image
* to be submitted to the OpenXR swap-chain. So do not un-bind the off-screen yet! */
GPU_offscreen_bind(vp->offscreen, false);
wm_xr_draw_viewport_buffers_to_active_framebuffer(xr_data->runtime, surface_data, draw_view);
}
bool wm_xr_passthrough_enabled(void *customdata)
{
wmXrDrawData *draw_data = static_cast<wmXrDrawData *>(customdata);
wmXrData *xr_data = draw_data->xr_data;
XrSessionSettings *settings = &xr_data->session_settings;
return (settings->draw_flags & V3D_OFSDRAW_XR_SHOW_PASSTHROUGH) != 0;
}
void wm_xr_disable_passthrough(void *customdata)
{
wmXrDrawData *draw_data = static_cast<wmXrDrawData *>(customdata);
wmXrData *xr_data = draw_data->xr_data;
XrSessionSettings *settings = &xr_data->session_settings;
settings->draw_flags &= ~V3D_OFSDRAW_XR_SHOW_PASSTHROUGH;
WM_global_report(RPT_INFO, "Passthrough not available");
}
static gpu::Batch *wm_xr_controller_model_batch_create(GHOST_IXrContext *xr_context,
const char *subaction_path)
{
GHOST_XrControllerModelData model_data;
if (!GHOST_XrGetControllerModelData(xr_context, subaction_path, &model_data) ||
model_data.count_vertices < 1)
{
return nullptr;
}
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SFLOAT_32_32_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, model_data.count_vertices);
vbo->data<GHOST_XrControllerModelVertex>().copy_from(
{model_data.vertices, model_data.count_vertices});
gpu::IndexBuf *ibo = nullptr;
if (model_data.count_indices > 0 && ((model_data.count_indices % 3) == 0)) {
GPUIndexBufBuilder ibo_builder;
const uint prim_len = model_data.count_indices / 3;
GPU_indexbuf_init(&ibo_builder, GPU_PRIM_TRIS, prim_len, model_data.count_vertices);
for (uint i = 0; i < prim_len; ++i) {
const uint32_t *idx = &model_data.indices[i * 3];
GPU_indexbuf_add_tri_verts(&ibo_builder, idx[0], idx[1], idx[2]);
}
ibo = GPU_indexbuf_build(&ibo_builder);
}
return GPU_batch_create_ex(GPU_PRIM_TRIS, vbo, ibo, GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
}
static void wm_xr_controller_model_draw(const XrSessionSettings *settings,
GHOST_IXrContext *xr_context,
wmXrSessionState *state)
{
GHOST_XrControllerModelData model_data;
float color[4];
switch (settings->controller_draw_style) {
case XR_CONTROLLER_DRAW_DARK:
case XR_CONTROLLER_DRAW_DARK_RAY:
color[0] = color[1] = color[2] = 0.0f;
color[3] = 0.4f;
break;
case XR_CONTROLLER_DRAW_LIGHT:
case XR_CONTROLLER_DRAW_LIGHT_RAY:
color[0] = 0.422f;
color[1] = 0.438f;
color[2] = 0.446f;
color[3] = 0.4f;
break;
}
GPU_depth_test(GPU_DEPTH_NONE);
GPU_blend(GPU_BLEND_ALPHA);
for (wmXrController &controller : state->controllers) {
if (!controller.grip_active) {
continue;
}
gpu::Batch *model = controller.model;
if (!model) {
model = controller.model = wm_xr_controller_model_batch_create(xr_context,
controller.subaction_path);
}
if (model &&
GHOST_XrGetControllerModelData(xr_context, controller.subaction_path, &model_data) &&
model_data.count_components > 0)
{
GPU_batch_program_set_builtin(model, GPU_SHADER_3D_UNIFORM_COLOR);
GPU_batch_uniform_4fv(model, "color", color);
GPU_matrix_push();
GPU_matrix_mul(controller.grip_mat);
for (uint component_idx = 0; component_idx < model_data.count_components; ++component_idx) {
const GHOST_XrControllerModelComponent *component = &model_data.components[component_idx];
GPU_matrix_push();
GPU_matrix_mul(component->transform);
GPU_batch_draw_range(model,
model->elem ? component->index_offset : component->vertex_offset,
model->elem ? component->index_count : component->vertex_count);
GPU_matrix_pop();
}
GPU_matrix_pop();
}
else {
/* Fallback. */
const float scale = 0.05f;
gpu::Batch *sphere = GPU_batch_preset_sphere(2);
GPU_batch_program_set_builtin(sphere, GPU_SHADER_3D_UNIFORM_COLOR);
GPU_batch_uniform_4fv(sphere, "color", color);
GPU_matrix_push();
GPU_matrix_mul(controller.grip_mat);
GPU_matrix_scale_1f(scale);
GPU_batch_draw(sphere);
GPU_matrix_pop();
}
}
}
static void wm_xr_controller_aim_draw(const XrSessionSettings *settings, wmXrSessionState *state)
{
const bool draw_ray = ELEM(
settings->controller_draw_style, XR_CONTROLLER_DRAW_DARK_RAY, XR_CONTROLLER_DRAW_LIGHT_RAY);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
uint col = GPU_vertformat_attr_add(format, "color", gpu::VertAttrType::SFLOAT_32_32_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_FLAT_COLOR);
float viewport[4];
GPU_viewport_size_get_f(viewport);
immUniform2fv("viewportSize", &viewport[2]);
immUniform1f("lineWidth", 3.0f * U.pixelsize);
if (draw_ray) {
const float color[4] = {0.33f, 0.33f, 1.0f, 0.5f};
const float scale = settings->clip_end;
float ray[3];
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
GPU_blend(GPU_BLEND_ALPHA);
for (wmXrController &controller : state->controllers) {
if (!controller.grip_active) {
continue;
}
immBegin(GPU_PRIM_LINES, 2);
const float (*mat)[4] = controller.aim_mat;
madd_v3_v3v3fl(ray, mat[3], mat[2], -scale);
immAttrSkip(col);
immVertex3fv(pos, mat[3]);
immAttr4fv(col, color);
immVertex3fv(pos, ray);
immEnd();
}
}
else {
const float r[4] = {255 / 255.0f, 51 / 255.0f, 82 / 255.0f, 255 / 255.0f};
const float g[4] = {139 / 255.0f, 220 / 255.0f, 0 / 255.0f, 255 / 255.0f};
const float b[4] = {40 / 255.0f, 144 / 255.0f, 255 / 255.0f, 255 / 255.0f};
const float scale = 0.01f;
float x_axis[3], y_axis[3], z_axis[3];
GPU_depth_test(GPU_DEPTH_NONE);
GPU_blend(GPU_BLEND_NONE);
for (wmXrController &controller : state->controllers) {
if (!controller.grip_active) {
continue;
}
immBegin(GPU_PRIM_LINES, 6);
const float (*mat)[4] = controller.aim_mat;
madd_v3_v3v3fl(x_axis, mat[3], mat[0], scale);
madd_v3_v3v3fl(y_axis, mat[3], mat[1], scale);
madd_v3_v3v3fl(z_axis, mat[3], mat[2], scale);
immAttrSkip(col);
immVertex3fv(pos, mat[3]);
immAttr4fv(col, r);
immVertex3fv(pos, x_axis);
immAttrSkip(col);
immVertex3fv(pos, mat[3]);
immAttr4fv(col, g);
immVertex3fv(pos, y_axis);
immAttrSkip(col);
immVertex3fv(pos, mat[3]);
immAttr4fv(col, b);
immVertex3fv(pos, z_axis);
immEnd();
}
}
immUnbindProgram();
}
void wm_xr_draw_controllers(const bContext *C, ARegion * /*region*/, void *customdata)
{
wmXrData *xr = static_cast<wmXrData *>(customdata);
const XrSessionSettings *settings = &xr->session_settings;
GHOST_IXrContext *xr_context = xr->runtime->ghost_context;
wmXrSessionState *state = &xr->runtime->session_state;
wm_xr_controller_model_draw(settings, xr_context, state);
wm_xr_controller_aim_draw(settings, state);
wm_xr_viewfinder_draw(C, settings, state);
}
} // namespace blender

View File

@@ -0,0 +1,334 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#pragma once
#include "CLG_log.h"
#include "GHOST_IContext.hh"
#include "GHOST_IXrContext.hh"
#include "GHOST_Types.hh"
#include "DNA_listBase.h"
#include "DNA_xr_types.h"
#include "wm_xr.hh"
namespace blender {
struct bContext;
struct ARegion;
struct Camera;
struct GPUOffScreen;
struct Object;
struct wmWindow;
struct wmWindowManager;
struct wmXrActionSet;
struct wmXrController;
struct wmXrData;
namespace gpu {
class Texture;
}
struct wmXrViewfinderState {
/* Internal Runtime values. */
float capture_position[3];
float capture_orientation_quat[4];
GPUOffScreen *offscreen;
GPUViewport *viewport;
gpu::Texture *backside_logo_texture;
Camera *render_cam_data_id;
double smoothing_delta_t;
/* Runtime values set by RNA methods called from Python. */
double last_flash_trigger_time;
double last_focus_hit_time;
bool last_focus_hit_success;
/* Capture settings. */
bool capture_dof_enabled;
float capture_lens_focal;
float capture_dof_distance;
float capture_dof_fstop;
/* Playback settings. */
bool playback_show_active_capture_in_space_enabled;
/** Active modes, concept differs from the rest of the Blender UI. */
eXrViewfinderMode active_mode;
eXrViewfinderLiveAction active_action_live;
eXrViewfinderPlaybackAction active_action_playback;
eXrViewfinderConfirmAction active_action_confirm;
/* Constants. */
/* Using a Viewfinder view resolution that isn't too high helps with performances, and
* also increases the displayed overlay line width. */
static constexpr int view_resolution = 800;
};
struct wmXrSessionState {
bool is_started;
/** Last known viewer pose (centroid of eyes, in world space) stored for queries. */
GHOST_XrPose viewer_pose;
/** The last known view matrix, calculated from the above viewer pose. */
float viewer_viewmat[4][4];
/** The last known viewer matrix, without navigation applied. */
float viewer_mat_base[4][4];
float focal_len;
wmXrViewfinderState viewfinder;
/** Copy of XrSessionSettings.base_pose_ data to detect changes that need
* resetting to base pose. */
char prev_base_pose_type; /* #eXRSessionBasePoseType. */
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 XrSessionSettings.view_scale, stored to detect changes. */
float prev_view_scale_setting;
/** Copy of wmXrDrawData.base_pose. */
GHOST_XrPose prev_base_pose;
/** Copy of wmXrDrawData.base_scale. */
float prev_base_scale;
/** 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;
bool swap_hands;
/** Current navigation transforms. */
GHOST_XrPose nav_pose;
float nav_scale;
float viewer_scale;
/** Navigation transforms and viewer scale from the last action sync, used to calculate the
* viewer/controller poses. */
GHOST_XrPose nav_pose_last_actions_sync;
float viewer_scale_last_actions_sync;
bool is_navigation_dirty;
/** Last known controller data. */
ListBaseT<wmXrController> controllers;
/** 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;
/* Name of the action set (if any) to activate before the next actions sync. */
char active_action_set_next[64]; /* #MAX_NAME. */
/** The current view vignette aperture, appears on movement. */
float vignette_aperture;
/** Timestamp of last vignette update, used for delta time calculation. */
double vignette_last_update_time;
};
struct wmXrRuntimeData {
/* GHOST XR context. */
GHOST_IXrContext *ghost_context;
/* XR-specific Blender context. */
bContext *b_context;
/* Owning pointer to the XR offscreen area. Must be freed on XR session exit. */
ScrArea *offscreen_area;
/** Although this struct is internal, RNA gets a handle to this for state information queries. */
wmXrSessionState session_state;
wmXrSessionExitFn exit_fn;
ListBaseT<XrActionMap> actionmaps;
short actactionmap;
short selactionmap;
};
struct wmXrViewportPair {
struct wmXrViewportPair *next, *prev;
struct GPUOffScreen *offscreen;
struct GPUViewport *viewport;
};
struct wmXrSurfaceData {
/** Off-screen buffers/viewports for each view. */
ListBaseT<wmXrViewportPair> viewports;
/** Dummy region type for controller draw callback. */
struct ARegionType *controller_art;
/** Controller draw callback handle. */
void *controller_draw_handle;
};
struct wmXrDrawData {
wmXrData *xr_data;
wmXrSurfaceData *surface_data;
/** The pose (location + rotation) to which eye deltas will be applied to when drawing (world
* space). With positional tracking enabled, it should be the same as the base pose, when
* disabled it also contains a location delta from the moment the option was toggled. */
GHOST_XrPose base_pose;
/** Base scale (uniform, world space). */
float base_scale;
/** Offset to _subtract_ from the OpenXR eye and viewer pose to get the wanted effective pose
* (e.g. a pose exactly at the landmark position). */
float eye_position_ofs[3]; /* Local/view space. */
};
struct wmXrController {
struct wmXrController *next, *prev;
/** OpenXR user path identifier. */
char subaction_path[64]; /* #XR_MAX_USER_PATH_LENGTH. */
/** Pose (in world space) that represents the user's hand when holding the controller. */
bool grip_active;
GHOST_XrPose grip_pose;
float grip_mat[4][4];
float grip_mat_base[4][4];
/** Pose (in world space) that represents the controller's aiming source. */
bool aim_active;
GHOST_XrPose aim_pose;
float aim_mat[4][4];
float aim_mat_base[4][4];
/** Controller model. */
gpu::Batch *model;
};
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 thresholds/regions for each subaction path. */
float *float_thresholds;
eXrAxisFlag *axis_flags;
/** The currently active subaction path (if any) for modal actions. */
const char *active_modal_path;
/** Operator to be called on XR events. */
struct wmOperatorType *ot;
IDProperty *op_properties;
/** Haptics. */
char *haptic_name;
int64_t haptic_duration;
float haptic_frequency;
float haptic_amplitude;
/** Flags. */
eXrOpFlag op_flag;
eXrActionFlag action_flag;
eXrHapticFlag haptic_flag;
};
struct wmXrHapticAction {
struct wmXrHapticAction *next, *prev;
wmXrAction *action;
const char *subaction_path;
int64_t time_start;
};
struct wmXrActionSet {
char *name;
/** XR pose actions that determine the controller grip/aim transforms. */
wmXrAction *controller_grip_action;
wmXrAction *controller_aim_action;
/** Currently active modal actions. */
ListBaseT<LinkData> active_modal_actions;
/** Currently active haptic actions. */
ListBaseT<wmXrHapticAction> active_haptic_actions;
};
/* `wm_xr.cc` */
wmXrRuntimeData *wm_xr_runtime_data_create();
void wm_xr_runtime_data_free(wmXrRuntimeData **runtime);
/* `wm_xr_session.cc` */
void wm_xr_session_data_free(wmXrSessionState *state);
wmWindow *wm_xr_session_root_window_or_fallback_get(const wmWindowManager *wm,
const wmXrRuntimeData *runtime_data);
void wm_xr_session_draw_data_update(wmXrSessionState *state,
const XrSessionSettings *settings,
const GHOST_XrDrawViewInfo *draw_view,
wmXrDrawData *draw_data);
/**
* 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,
const GHOST_XrDrawViewInfo *draw_view,
wmXrSessionState *state);
bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
const GHOST_XrDrawViewInfo *draw_view);
GHOST_IContext *wm_xr_session_gpu_binding_context_create();
void wm_xr_session_gpu_binding_context_destroy(GHOST_IContext *context);
void wm_xr_session_actions_init(wmXrData *xr);
void wm_xr_session_actions_update(wmWindowManager *wm);
void wm_xr_session_controller_data_populate(const wmXrAction *grip_action,
const wmXrAction *aim_action,
wmXrData *xr);
void wm_xr_session_controller_data_clear(wmXrSessionState *state);
/* `wm_xr_draw.cc` */
void wm_xr_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]);
void wm_xr_pose_scale_to_mat(const GHOST_XrPose *pose, float scale, float r_mat[4][4]);
void wm_xr_pose_to_imat(const GHOST_XrPose *pose, float r_imat[4][4]);
void wm_xr_pose_scale_to_imat(const GHOST_XrPose *pose, float scale, float r_imat[4][4]);
/**
* \brief Draw a viewport for a single eye.
*
* This is the main viewport drawing function for VR sessions. It's assigned to Ghost-XR as a
* callback (see GHOST_XrDrawViewFunc()) and executed for each view (read: eye).
*/
void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata);
void wm_xr_draw_controllers(const bContext *C, ARegion *region, void *customdata);
/**
* \brief Check if XR passthrough is enabled.
*
* Needed to add or not the passthrough composition layer.
* It's assigned to Ghost-XR as a callback (see GHOST_XrPassthroughEnabledFunc()).
*/
bool wm_xr_passthrough_enabled(void *customdata);
/**
* \brief Disable XR passthrough if not supported.
*
* In case passthrough is not supported by the XR runtime, force un-check the toggle in the GUI.
* It's assigned to Ghost-XR as a callback (see GHOST_XrDisablePassthroughFunc()).
*/
void wm_xr_disable_passthrough(void *customdata);
/* `wm_xr_location_scouting.cc` */
bool wm_xr_viewfinder_operator_event_match_hand(bContext *C, const wmEvent *event);
void wm_xr_viewfinder_render_view(wmXrData *xr_data);
void wm_xr_viewfinder_draw(const bContext *C,
const XrSessionSettings *settings,
wmXrSessionState *state);
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#pragma once
namespace blender {
struct wmWindow;
struct wmWindowManager;
struct wmXrData;
using wmXrSessionExitFn = void (*)(const wmXrData *xr_data);
/* `wm_xr.cc` */
bool wm_xr_init(bContext *C);
void wm_xr_exit(wmWindowManager *wm);
void wm_xr_session_toggle(wmWindowManager *wm, wmXrSessionExitFn session_exit_fn);
bool wm_xr_events_handle(wmWindowManager *wm);
/* `wm_xr_operators.cc` */
void wm_xr_operatortypes_register();
/* `wm_xr_location_scouting.cc` */
/* NOTE: Keep in sync with the Python VR Scene Inspection add-on VRCapture class.
* See comment in #wm_xr_location_scouting_get_active_capture. */
struct XrLocationScoutingCapture {
float3 position;
float4 orientation_quat;
float lens_focal;
bool dof_enabled;
float dof_distance;
float dof_fstop;
};
bool wm_xr_location_scouting_is_captures_empty(Scene *scene);
std::optional<XrLocationScoutingCapture> wm_xr_location_scouting_get_active_capture(Scene *scene);
} // namespace blender