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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/windowmanager')
-rw-r--r--source/blender/windowmanager/CMakeLists.txt1
-rw-r--r--source/blender/windowmanager/WM_api.h76
-rw-r--r--source/blender/windowmanager/WM_types.h19
-rw-r--r--source/blender/windowmanager/intern/wm_playanim.c447
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr.c5
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_actions.c480
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_draw.c6
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_intern.h68
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_session.c148
9 files changed, 1127 insertions, 123 deletions
diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt
index 0f26ec50816..183b22c9791 100644
--- a/source/blender/windowmanager/CMakeLists.txt
+++ b/source/blender/windowmanager/CMakeLists.txt
@@ -203,6 +203,7 @@ if(WITH_XR_OPENXR)
list(APPEND SRC
xr/intern/wm_xr.c
+ xr/intern/wm_xr_actions.c
xr/intern/wm_xr_draw.c
xr/intern/wm_xr_session.c
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 280ee75a50f..edd5b555e2f 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -71,6 +71,11 @@ struct wmTabletData;
struct wmNDOFMotionData;
#endif
+#ifdef WITH_XR_OPENXR
+struct wmXrActionState;
+struct wmXrPose;
+#endif
+
typedef struct wmGizmo wmGizmo;
typedef struct wmGizmoMap wmGizmoMap;
typedef struct wmGizmoMapType wmGizmoMapType;
@@ -929,7 +934,7 @@ void WM_generic_user_data_free(struct wmGenericUserData *wm_userdata);
bool WM_region_use_viewport(struct ScrArea *area, struct ARegion *region);
#ifdef WITH_XR_OPENXR
-/* wm_xr.c */
+/* wm_xr_session.c */
bool WM_xr_session_exists(const wmXrData *xr);
bool WM_xr_session_is_ready(const wmXrData *xr);
struct wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr);
@@ -939,7 +944,74 @@ bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_ro
bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr,
float r_viewmat[4][4],
float *r_focal_len);
-#endif
+bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_location[3]);
+bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_rotation[4]);
+
+/* wm_xr_actions.c */
+/* XR action functions to be called pre-XR session start.
+ * Note: The "destroy" functions can also be called post-session start. */
+bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name);
+void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name);
+bool WM_xr_action_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ eXrActionType type,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const float *float_threshold,
+ struct wmOperatorType *ot,
+ struct IDProperty *op_properties,
+ eXrOpFlag op_flag);
+void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name);
+bool WM_xr_action_space_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const struct wmXrPose *poses);
+void WM_xr_action_space_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths);
+bool WM_xr_action_binding_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths);
+void WM_xr_action_binding_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths);
+
+bool WM_xr_active_action_set_set(
+ wmXrData *xr, const char *action_set_name); /* If action_set_name is NULL, then
+ * all action sets will be treated as active. */
+bool WM_xr_controller_pose_action_set(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name);
+
+/* XR action functions to be called post-XR session start. */
+bool WM_xr_action_state_get(const wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const char *subaction_path,
+ struct wmXrActionState *r_state);
+bool WM_xr_haptic_action_apply(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const long long *duration,
+ const float *frequency,
+ const float *amplitude);
+void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name);
+#endif /* WITH_XR_OPENXR */
#ifdef __cplusplus
}
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index 884ae6952fd..eb17377d0dd 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -686,6 +686,25 @@ typedef struct wmNDOFMotionData {
} wmNDOFMotionData;
#endif /* WITH_INPUT_NDOF */
+#ifdef WITH_XR_OPENXR
+/* Similar to GHOST_XrPose. */
+typedef struct wmXrPose {
+ float position[3];
+ /* Blender convention (w, x, y, z) */
+ float orientation_quat[4];
+} wmXrPose;
+
+typedef struct wmXrActionState {
+ union {
+ bool state_boolean;
+ float state_float;
+ float state_vector2f[2];
+ wmXrPose state_pose;
+ };
+ int type; /* eXrActionType */
+} wmXrActionState;
+#endif
+
/** Timer flags. */
typedef enum {
/** Do not attempt to free customdata pointer even if non-NULL. */
diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c
index 85a05b88af7..5300649a0cd 100644
--- a/source/blender/windowmanager/intern/wm_playanim.c
+++ b/source/blender/windowmanager/intern/wm_playanim.c
@@ -47,9 +47,11 @@
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_path_util.h"
+#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
+#include "IMB_colormanagement.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@@ -95,64 +97,85 @@ static AUD_Device *audio_device = NULL;
struct PlayState;
static void playanim_window_zoom(struct PlayState *ps, const float zoom_offset);
+/**
+ * The current state of the player.
+ *
+ * \warning Don't store results of parsing command-line arguments
+ * in this struct if they need to persist across playing back different
+ * files as these will be cleared when playing other files (drag & drop).
+ */
typedef struct PlayState {
- /* window and viewport size */
+ /** Window and viewport size. */
int win_x, win_y;
- /* current zoom level */
+ /** Current zoom level. */
float zoom;
- /* playback state */
+ /** Playback direction (-1, 1). */
short direction;
+ /** Set the next frame to implement frame stepping (using shortcuts). */
short next_frame;
+ /** Playback once then wait. */
bool once;
- bool turbo;
+ /** Play forwards/backwards. */
bool pingpong;
+ /** Disable frame skipping. */
bool noskip;
+ /** Display current frame over the window. */
bool indicator;
+ /** Single-frame stepping has been enabled (frame loading and update pending). */
bool sstep;
+ /** Playback has stopped the image has been displayed. */
bool wait2;
+ /** Playback stopped state once stop/start variables have been handled. */
bool stopped;
+ /**
+ * When disabled the current animation will exit,
+ * after this either the application exits or a new animation window is opened.
+ *
+ * This is used so drag & drop can load new files which setup a newly created animation window.
+ */
bool go;
- /* waiting for images to load */
+ /** True when waiting for images to load. */
bool loading;
- /* x/y image flip */
+ /** X/Y image flip (set via key bindings). */
bool draw_flip[2];
+ /** The number of frames to step each update (default to 1, command line argument). */
int fstep;
- /* current picture */
+ /** Current frame (picture). */
struct PlayAnimPict *picture;
- /* set once at the start */
+ /** Image size in pixels, set once at the start. */
int ibufx, ibufy;
+ /** Mono-space font ID. */
int fontid;
- /* saves passing args */
- struct ImBuf *curframe_ibuf;
-
- /* restarts player for file drop */
+ /** Restarts player for file drop (drag & drop). */
char dropped_file[FILE_MAX];
+ /** Force update when scrubbing with the cursor. */
bool need_frame_update;
+ /** The current frame calculated by scrubbing the mouse cursor. */
int frame_cursor_x;
+
+ ColorManagedViewSettings view_settings;
+ ColorManagedDisplaySettings display_settings;
} PlayState;
/* for debugging */
#if 0
-void print_ps(PlayState *ps)
+static void print_ps(PlayState *ps)
{
printf("ps:\n");
printf(" direction=%d,\n", (int)ps->direction);
- printf(" next=%d,\n", ps->next);
printf(" once=%d,\n", ps->once);
- printf(" turbo=%d,\n", ps->turbo);
printf(" pingpong=%d,\n", ps->pingpong);
printf(" noskip=%d,\n", ps->noskip);
printf(" sstep=%d,\n", ps->sstep);
- printf(" pause=%d,\n", ps->pause);
printf(" wait2=%d,\n", ps->wait2);
printf(" stopped=%d,\n", ps->stopped);
printf(" go=%d,\n\n", ps->go);
@@ -269,8 +292,89 @@ static struct {
.pics_size_in_memory = 0,
.memory_limit = 0,
};
+
+static void frame_cache_add(PlayAnimPict *pic)
+{
+ pic->frame_cache_node = BLI_genericNodeN(pic);
+ BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node);
+ g_frame_cache.pics_len++;
+
+ if (g_frame_cache.memory_limit != 0) {
+ BLI_assert(pic->size_in_memory == 0);
+ pic->size_in_memory = IMB_get_size_in_memory(pic->ibuf);
+ g_frame_cache.pics_size_in_memory += pic->size_in_memory;
+ }
+}
+
+static void frame_cache_remove(PlayAnimPict *pic)
+{
+ LinkData *node = pic->frame_cache_node;
+ IMB_freeImBuf(pic->ibuf);
+ if (g_frame_cache.memory_limit != 0) {
+ BLI_assert(pic->size_in_memory != 0);
+ g_frame_cache.pics_size_in_memory -= pic->size_in_memory;
+ pic->size_in_memory = 0;
+ }
+ pic->ibuf = NULL;
+ pic->frame_cache_node = NULL;
+ BLI_freelinkN(&g_frame_cache.pics, node);
+ g_frame_cache.pics_len--;
+}
+
+/* Don't free the current frame by moving it to the head of the list. */
+static void frame_cache_touch(PlayAnimPict *pic)
+{
+ BLI_assert(pic->frame_cache_node->data == pic);
+ BLI_remlink(&g_frame_cache.pics, pic->frame_cache_node);
+ BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node);
+}
+
+static bool frame_cache_limit_exceeded(void)
+{
+ return g_frame_cache.memory_limit ?
+ (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) :
+ (g_frame_cache.pics_len > PLAY_FRAME_CACHE_MAX);
+}
+
+static void frame_cache_limit_apply(ImBuf *ibuf_keep)
+{
+ /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */
+ LinkData *node = g_frame_cache.pics.last;
+ while (node && frame_cache_limit_exceeded()) {
+ PlayAnimPict *pic = node->data;
+ BLI_assert(pic->frame_cache_node == node);
+
+ node = node->prev;
+ if (pic->ibuf && pic->ibuf != ibuf_keep) {
+ frame_cache_remove(pic);
+ }
+ }
+}
+
#endif /* USE_FRAME_CACHE_LIMIT */
+static ImBuf *ibuf_from_picture(PlayAnimPict *pic)
+{
+ ImBuf *ibuf = NULL;
+
+ if (pic->ibuf) {
+ ibuf = pic->ibuf;
+ }
+ else if (pic->anim) {
+ ibuf = IMB_anim_absolute(pic->anim, pic->frame, IMB_TC_NONE, IMB_PROXY_NONE);
+ }
+ else if (pic->mem) {
+ /* use correct colorspace here */
+ ibuf = IMB_ibImageFromMemory(pic->mem, pic->size, pic->IB_flags, NULL, pic->name);
+ }
+ else {
+ /* use correct colorspace here */
+ ibuf = IMB_loadiffname(pic->name, pic->IB_flags, NULL);
+ }
+
+ return ibuf;
+}
+
static PlayAnimPict *playanim_step(PlayAnimPict *playanim, int step)
{
if (step > 0) {
@@ -297,6 +401,151 @@ static int pupdate_time(void)
return (ptottime < 0);
}
+static void *ocio_transform_ibuf(PlayState *ps,
+ ImBuf *ibuf,
+ bool *r_glsl_used,
+ eGPUTextureFormat *r_format,
+ eGPUDataFormat *r_data,
+ void **r_buffer_cache_handle)
+{
+ void *display_buffer;
+ bool force_fallback = false;
+ *r_glsl_used = false;
+ force_fallback |= (ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL);
+ force_fallback |= (ibuf->dither != 0.0f);
+
+ /* Default */
+ *r_format = GPU_RGBA8;
+ *r_data = GPU_DATA_UBYTE;
+
+ /* Fallback to CPU based color space conversion. */
+ if (force_fallback) {
+ *r_glsl_used = false;
+ display_buffer = NULL;
+ }
+ else if (ibuf->rect_float) {
+ display_buffer = ibuf->rect_float;
+
+ *r_data = GPU_DATA_FLOAT;
+ if (ibuf->channels == 4) {
+ *r_format = GPU_RGBA16F;
+ }
+ else if (ibuf->channels == 3) {
+ /* Alpha is implicitly 1. */
+ *r_format = GPU_RGB16F;
+ }
+
+ if (ibuf->float_colorspace) {
+ *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings,
+ &ps->display_settings,
+ ibuf->float_colorspace,
+ ibuf->dither,
+ false,
+ false);
+ }
+ else {
+ *r_glsl_used = IMB_colormanagement_setup_glsl_draw(
+ &ps->view_settings, &ps->display_settings, ibuf->dither, false);
+ }
+ }
+ else if (ibuf->rect) {
+ display_buffer = ibuf->rect;
+ *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings,
+ &ps->display_settings,
+ ibuf->rect_colorspace,
+ ibuf->dither,
+ false,
+ false);
+ }
+ else {
+ display_buffer = NULL;
+ }
+
+ /* There is data to be displayed, but GLSL is not initialized
+ * properly, in this case we fallback to CPU-based display transform. */
+ if ((ibuf->rect || ibuf->rect_float) && !*r_glsl_used) {
+ display_buffer = IMB_display_buffer_acquire(
+ ibuf, &ps->view_settings, &ps->display_settings, r_buffer_cache_handle);
+ *r_format = GPU_RGBA8;
+ *r_data = GPU_DATA_UBYTE;
+ }
+
+ return display_buffer;
+}
+
+static void draw_display_buffer(PlayState *ps, ImBuf *ibuf)
+{
+ void *display_buffer;
+
+ /* Format needs to be created prior to any #immBindShader call.
+ * Do it here because OCIO binds its own shader. */
+ eGPUTextureFormat format;
+ eGPUDataFormat data;
+ bool glsl_used = false;
+ GPUVertFormat *imm_format = immVertexFormat();
+ uint pos = GPU_vertformat_attr_add(imm_format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+ uint texCoord = GPU_vertformat_attr_add(
+ imm_format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+
+ void *buffer_cache_handle = NULL;
+ display_buffer = ocio_transform_ibuf(ps, ibuf, &glsl_used, &format, &data, &buffer_cache_handle);
+
+ GPUTexture *texture = GPU_texture_create_2d("display_buf", ibuf->x, ibuf->y, 1, format, NULL);
+ GPU_texture_update(texture, data, display_buffer);
+ GPU_texture_filter_mode(texture, false);
+
+ GPU_texture_bind(texture, 0);
+
+ if (!glsl_used) {
+ immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR);
+ immUniformColor3f(1.0f, 1.0f, 1.0f);
+ immUniform1i("image", 0);
+ }
+
+ immBegin(GPU_PRIM_TRI_FAN, 4);
+
+ rctf preview;
+ rctf canvas;
+
+ BLI_rctf_init(&canvas, 0.0f, 1.0f, 0.0f, 1.0f);
+ BLI_rctf_init(&preview, 0.0f, 1.0f, 0.0f, 1.0f);
+
+ if (ps->draw_flip[0]) {
+ SWAP(float, canvas.xmin, canvas.xmax);
+ }
+ if (ps->draw_flip[1]) {
+ SWAP(float, canvas.ymin, canvas.ymax);
+ }
+
+ immAttr2f(texCoord, canvas.xmin, canvas.ymin);
+ immVertex2f(pos, preview.xmin, preview.ymin);
+
+ immAttr2f(texCoord, canvas.xmin, canvas.ymax);
+ immVertex2f(pos, preview.xmin, preview.ymax);
+
+ immAttr2f(texCoord, canvas.xmax, canvas.ymax);
+ immVertex2f(pos, preview.xmax, preview.ymax);
+
+ immAttr2f(texCoord, canvas.xmax, canvas.ymin);
+ immVertex2f(pos, preview.xmax, preview.ymin);
+
+ immEnd();
+
+ GPU_texture_unbind(texture);
+ GPU_texture_free(texture);
+
+ if (!glsl_used) {
+ immUnbindProgram();
+ }
+ else {
+ IMB_colormanagement_finish_glsl_draw();
+ }
+
+ if (buffer_cache_handle) {
+ IMB_display_buffer_release(buffer_cache_handle);
+ }
+}
+
static void playanim_toscreen(
PlayState *ps, PlayAnimPict *picture, struct ImBuf *ibuf, int fontid, int fstep)
{
@@ -304,13 +553,6 @@ static void playanim_toscreen(
printf("%s: no ibuf for picture '%s'\n", __func__, picture ? picture->name : "<NIL>");
return;
}
- if (ibuf->rect == NULL && ibuf->rect_float) {
- IMB_rect_from_float(ibuf);
- imb_freerectfloatImBuf(ibuf);
- }
- if (ibuf->rect == NULL) {
- return;
- }
GHOST_ActivateWindowDrawingContext(g_WS.ghost_window);
@@ -340,19 +582,7 @@ static void playanim_toscreen(
8);
}
- IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR);
-
- immDrawPixelsTex(&state,
- offs_x + (ps->draw_flip[0] ? span_x : 0.0f),
- offs_y + (ps->draw_flip[1] ? span_y : 0.0f),
- ibuf->x,
- ibuf->y,
- GPU_RGBA8,
- false,
- ibuf->rect,
- ((ps->draw_flip[0] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_x),
- ((ps->draw_flip[1] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_y),
- NULL);
+ draw_display_buffer(ps, ibuf);
GPU_blend(GPU_BLEND_NONE);
@@ -432,6 +662,14 @@ static void build_pict_list_ex(
}
}
else {
+ /* Load images into cache until the cache is full,
+ * this resolves choppiness for images that are slow to load, see: T81751. */
+#ifdef USE_FRAME_CACHE_LIMIT
+ bool fill_cache = true;
+#else
+ bool fill_cache = false;
+#endif
+
int count = 0;
int fp_framenr;
@@ -518,22 +756,33 @@ static void build_pict_list_ex(
pupdate_time();
- if (ptottime > 1.0) {
+ const bool display_imbuf = ptottime > 1.0;
+
+ if (display_imbuf || fill_cache) {
/* OCIO_TODO: support different input color space */
- struct ImBuf *ibuf;
- if (picture->mem) {
- ibuf = IMB_ibImageFromMemory(
- picture->mem, picture->size, picture->IB_flags, NULL, picture->name);
- }
- else {
- ibuf = IMB_loadiffname(picture->name, picture->IB_flags, NULL);
- }
+ ImBuf *ibuf = ibuf_from_picture(picture);
+
if (ibuf) {
- playanim_toscreen(ps, picture, ibuf, fontid, fstep);
- IMB_freeImBuf(ibuf);
+ if (display_imbuf) {
+ playanim_toscreen(ps, picture, ibuf, fontid, fstep);
+ }
+#ifdef USE_FRAME_CACHE_LIMIT
+ if (fill_cache) {
+ picture->ibuf = ibuf;
+ frame_cache_add(picture);
+ fill_cache = !frame_cache_limit_exceeded();
+ }
+ else
+#endif
+ {
+ IMB_freeImBuf(ibuf);
+ }
+ }
+
+ if (display_imbuf) {
+ pupdate_time();
+ ptottime = 0.0;
}
- pupdate_time();
- ptottime = 0.0;
}
/* create a new filepath each time */
@@ -641,16 +890,14 @@ static void change_frame(PlayState *ps)
static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void)
{
PlayState *ps = (PlayState *)ps_void;
- GHOST_TEventType type = GHOST_GetEventType(evt);
- int val;
+ const GHOST_TEventType type = GHOST_GetEventType(evt);
+ /* Convert ghost event into value keyboard or mouse. */
+ const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown);
// print_ps(ps);
playanim_event_qual_update();
- /* convert ghost event into value keyboard or mouse */
- val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown);
-
/* first check if we're busy loading files */
if (ps->loading) {
switch (type) {
@@ -674,8 +921,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void)
return 1;
}
- if (ps->wait2 && ps->stopped) {
- ps->stopped = false;
+ if (ps->wait2 && ps->stopped == false) {
+ ps->stopped = true;
}
if (ps->wait2) {
@@ -834,9 +1081,9 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void)
case GHOST_kKeyNumpadSlash:
if (val) {
if (g_WS.qual & WS_QUAL_SHIFT) {
- if (ps->curframe_ibuf) {
+ if (ps->picture && ps->picture->ibuf) {
printf(" Name: %s | Speed: %.2f frames/s\n",
- ps->curframe_ibuf->name,
+ ps->picture->ibuf->name,
ps->fstep / swaptime);
}
}
@@ -1073,7 +1320,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void)
playanim_gl_matrix();
ptottime = 0.0;
- playanim_toscreen(ps, ps->picture, ps->curframe_ibuf, ps->fontid, ps->fstep);
+ playanim_toscreen(
+ ps, ps->picture, ps->picture ? ps->picture->ibuf : NULL, ps->fontid, ps->fstep);
break;
}
@@ -1150,7 +1398,9 @@ static void playanim_window_zoom(PlayState *ps, const float zoom_offset)
GHOST_SetClientSize(g_WS.ghost_window, sizex, sizey);
}
-/* return path for restart */
+/**
+ * \return The a path used to restart the animation player or NULL to exit.
+ */
static char *wm_main_playanim_intern(int argc, const char **argv)
{
struct ImBuf *ibuf = NULL;
@@ -1169,7 +1419,6 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
ps.direction = true;
ps.next_frame = 1;
ps.once = false;
- ps.turbo = false;
ps.pingpong = false;
ps.noskip = false;
ps.sstep = false;
@@ -1187,6 +1436,11 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
ps.fontid = -1;
+ STRNCPY(ps.display_settings.display_device,
+ IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE));
+ IMB_colormanagement_init_default_view_settings(&ps.view_settings, &ps.display_settings);
+
+ /* Skip the first argument which is assumed to be '-a' (used to launch this player). */
while (argc > 1) {
if (argv[1][0] == '-') {
switch (argv[1][1]) {
@@ -1423,21 +1677,8 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
IMB_freeImBuf(ibuf);
}
#endif
- if (ps.picture->ibuf) {
- ibuf = ps.picture->ibuf;
- }
- else if (ps.picture->anim) {
- ibuf = IMB_anim_absolute(ps.picture->anim, ps.picture->frame, IMB_TC_NONE, IMB_PROXY_NONE);
- }
- else if (ps.picture->mem) {
- /* use correct colorspace here */
- ibuf = IMB_ibImageFromMemory(
- ps.picture->mem, ps.picture->size, ps.picture->IB_flags, NULL, ps.picture->name);
- }
- else {
- /* use correct colorspace here */
- ibuf = IMB_loadiffname(ps.picture->name, ps.picture->IB_flags, NULL);
- }
+
+ ibuf = ibuf_from_picture(ps.picture);
if (ibuf) {
#ifdef USE_IMB_CACHE
@@ -1446,50 +1687,12 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
#ifdef USE_FRAME_CACHE_LIMIT
if (ps.picture->frame_cache_node == NULL) {
- ps.picture->frame_cache_node = BLI_genericNodeN(ps.picture);
- BLI_addhead(&g_frame_cache.pics, ps.picture->frame_cache_node);
- g_frame_cache.pics_len++;
-
- if (g_frame_cache.memory_limit != 0) {
- BLI_assert(ps.picture->size_in_memory == 0);
- ps.picture->size_in_memory = IMB_get_size_in_memory(ps.picture->ibuf);
- g_frame_cache.pics_size_in_memory += ps.picture->size_in_memory;
- }
+ frame_cache_add(ps.picture);
}
else {
- /* Don't free the current frame by moving it to the head of the list. */
- BLI_assert(ps.picture->frame_cache_node->data == ps.picture);
- BLI_remlink(&g_frame_cache.pics, ps.picture->frame_cache_node);
- BLI_addhead(&g_frame_cache.pics, ps.picture->frame_cache_node);
- }
-
- /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */
- LinkData *node = g_frame_cache.pics.last;
- while (node && (g_frame_cache.memory_limit ?
- (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) :
- (g_frame_cache.pics_len > PLAY_FRAME_CACHE_MAX))) {
- PlayAnimPict *pic = node->data;
- BLI_assert(pic->frame_cache_node == node);
-
- if (pic->ibuf && pic->ibuf != ibuf) {
- LinkData *node_tmp;
- IMB_freeImBuf(pic->ibuf);
- if (g_frame_cache.memory_limit != 0) {
- BLI_assert(pic->size_in_memory != 0);
- g_frame_cache.pics_size_in_memory -= pic->size_in_memory;
- pic->size_in_memory = 0;
- }
- pic->ibuf = NULL;
- pic->frame_cache_node = NULL;
- node_tmp = node->prev;
- BLI_freelinkN(&g_frame_cache.pics, node);
- g_frame_cache.pics_len--;
- node = node_tmp;
- }
- else {
- node = node->prev;
- }
+ frame_cache_touch(ps.picture);
}
+ frame_cache_limit_apply(ibuf);
#endif /* USE_FRAME_CACHE_LIMIT */
@@ -1538,14 +1741,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
ps.wait2 = ps.sstep;
- if (ps.wait2 == false && ps.stopped == false) {
- ps.stopped = true;
+ if (ps.wait2 == false && ps.stopped) {
+ ps.stopped = false;
}
pupdate_time();
if (ps.picture && ps.next_frame) {
- /* always at least set one step */
+ /* Advance to the next frame, always at least set one step.
+ * Implement frame-skipping when enabled and playback is not fast enough. */
while (ps.picture) {
ps.picture = playanim_step(ps.picture, ps.next_frame);
@@ -1558,7 +1762,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
}
}
- if (ps.wait2 || ptottime < swaptime || ps.turbo || ps.noskip) {
+ if (ps.wait2 || ptottime < swaptime || ps.noskip) {
break;
}
ptottime -= swaptime;
@@ -1602,6 +1806,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv)
#ifdef USE_FRAME_CACHE_LIMIT
BLI_freelistN(&g_frame_cache.pics);
g_frame_cache.pics_len = 0;
+ g_frame_cache.pics_size_in_memory = 0;
#endif
#ifdef WITH_AUDASPACE
diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c
index 439d611b085..2a67c2bee9f 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr.c
@@ -128,6 +128,11 @@ bool wm_xr_events_handle(wmWindowManager *wm)
if (wm->xr.runtime && wm->xr.runtime->context) {
GHOST_XrEventsHandle(wm->xr.runtime->context);
+ /* Process OpenXR action events. */
+ if (WM_xr_session_is_ready(&wm->xr)) {
+ wm_xr_session_actions_update(&wm->xr);
+ }
+
/* wm_window_process_events() uses the return value to determine if it can put the main thread
* to sleep for some milliseconds. We never want that to happen while the VR session runs on
* the main thread. So always return true. */
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actions.c b/source/blender/windowmanager/xr/intern/wm_xr_actions.c
new file mode 100644
index 00000000000..51ed3dcfd3c
--- /dev/null
+++ b/source/blender/windowmanager/xr/intern/wm_xr_actions.c
@@ -0,0 +1,480 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup wm
+ *
+ * \name Window-Manager XR Actions
+ *
+ * Uses the Ghost-XR API to manage OpenXR actions.
+ * All functions are designed to be usable by RNA / the Python API.
+ */
+
+#include "BLI_math.h"
+
+#include "GHOST_C-api.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "wm_xr_intern.h"
+
+/* -------------------------------------------------------------------- */
+/** \name XR-Action API
+ *
+ * API functions for managing OpenXR actions.
+ *
+ * \{ */
+
+static wmXrActionSet *action_set_create(const char *action_set_name)
+{
+ wmXrActionSet *action_set = MEM_callocN(sizeof(*action_set), __func__);
+ action_set->name = MEM_mallocN(strlen(action_set_name) + 1, "XrActionSet_Name");
+ strcpy(action_set->name, action_set_name);
+
+ return action_set;
+}
+
+static void action_set_destroy(void *val)
+{
+ wmXrActionSet *action_set = val;
+
+ MEM_SAFE_FREE(action_set->name);
+
+ MEM_freeN(action_set);
+}
+
+static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name)
+{
+ return GHOST_XrGetActionSetCustomdata(xr->runtime->context, action_set_name);
+}
+
+static wmXrAction *action_create(const char *action_name,
+ eXrActionType type,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const float *float_threshold,
+ wmOperatorType *ot,
+ IDProperty *op_properties,
+ eXrOpFlag op_flag)
+{
+ wmXrAction *action = MEM_callocN(sizeof(*action), __func__);
+ action->name = MEM_mallocN(strlen(action_name) + 1, "XrAction_Name");
+ strcpy(action->name, action_name);
+ action->type = type;
+
+ const unsigned int count = count_subaction_paths;
+ action->count_subaction_paths = count;
+
+ action->subaction_paths = MEM_mallocN(sizeof(*action->subaction_paths) * count,
+ "XrAction_SubactionPaths");
+ for (unsigned int i = 0; i < count; ++i) {
+ action->subaction_paths[i] = MEM_mallocN(strlen(subaction_paths[i]) + 1,
+ "XrAction_SubactionPath");
+ strcpy(action->subaction_paths[i], subaction_paths[i]);
+ }
+
+ size_t size;
+ switch (type) {
+ case XR_BOOLEAN_INPUT:
+ size = sizeof(bool);
+ break;
+ case XR_FLOAT_INPUT:
+ size = sizeof(float);
+ break;
+ case XR_VECTOR2F_INPUT:
+ size = sizeof(float) * 2;
+ break;
+ case XR_POSE_INPUT:
+ size = sizeof(GHOST_XrPose);
+ break;
+ case XR_VIBRATION_OUTPUT:
+ return action;
+ }
+ action->states = MEM_calloc_arrayN(count, size, "XrAction_States");
+ action->states_prev = MEM_calloc_arrayN(count, size, "XrAction_StatesPrev");
+
+ if (float_threshold) {
+ BLI_assert(type == XR_FLOAT_INPUT || type == XR_VECTOR2F_INPUT);
+ action->float_threshold = *float_threshold;
+ CLAMP(action->float_threshold, 0.0f, 1.0f);
+ }
+
+ action->ot = ot;
+ action->op_properties = op_properties;
+ action->op_flag = op_flag;
+
+ return action;
+}
+
+static void action_destroy(void *val)
+{
+ wmXrAction *action = val;
+
+ MEM_SAFE_FREE(action->name);
+
+ const unsigned int count = action->count_subaction_paths;
+ char **subaction_paths = action->subaction_paths;
+ if (subaction_paths) {
+ for (unsigned int i = 0; i < count; ++i) {
+ MEM_SAFE_FREE(subaction_paths[i]);
+ }
+ MEM_freeN(subaction_paths);
+ }
+
+ MEM_SAFE_FREE(action->states);
+ MEM_SAFE_FREE(action->states_prev);
+
+ MEM_freeN(action);
+}
+
+static wmXrAction *action_find(wmXrData *xr, const char *action_set_name, const char *action_name)
+{
+ return GHOST_XrGetActionCustomdata(xr->runtime->context, action_set_name, action_name);
+}
+
+bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name)
+{
+ if (action_set_find(xr, action_set_name)) {
+ return false;
+ }
+
+ wmXrActionSet *action_set = action_set_create(action_set_name);
+
+ GHOST_XrActionSetInfo info = {
+ .name = action_set_name,
+ .customdata_free_fn = action_set_destroy,
+ .customdata = action_set,
+ };
+
+ if (!GHOST_XrCreateActionSet(xr->runtime->context, &info)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return;
+ }
+
+ wmXrSessionState *session_state = &xr->runtime->session_state;
+
+ if (action_set == session_state->active_action_set) {
+ if (action_set->controller_pose_action) {
+ wm_xr_session_controller_data_clear(session_state);
+ action_set->controller_pose_action = NULL;
+ }
+ if (action_set->active_modal_action) {
+ action_set->active_modal_action = NULL;
+ }
+ session_state->active_action_set = NULL;
+ }
+
+ GHOST_XrDestroyActionSet(xr->runtime->context, action_set_name);
+}
+
+bool WM_xr_action_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ eXrActionType type,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const float *float_threshold,
+ wmOperatorType *ot,
+ IDProperty *op_properties,
+ eXrOpFlag op_flag)
+{
+ if (action_find(xr, action_set_name, action_name)) {
+ return false;
+ }
+
+ wmXrAction *action = action_create(action_name,
+ type,
+ count_subaction_paths,
+ subaction_paths,
+ float_threshold,
+ ot,
+ op_properties,
+ op_flag);
+
+ GHOST_XrActionInfo info = {
+ .name = action_name,
+ .count_subaction_paths = count_subaction_paths,
+ .subaction_paths = subaction_paths,
+ .states = action->states,
+ .customdata_free_fn = action_destroy,
+ .customdata = action,
+ };
+
+ switch (type) {
+ case XR_BOOLEAN_INPUT:
+ info.type = GHOST_kXrActionTypeBooleanInput;
+ break;
+ case XR_FLOAT_INPUT:
+ info.type = GHOST_kXrActionTypeFloatInput;
+ break;
+ case XR_VECTOR2F_INPUT:
+ info.type = GHOST_kXrActionTypeVector2fInput;
+ break;
+ case XR_POSE_INPUT:
+ info.type = GHOST_kXrActionTypePoseInput;
+ break;
+ case XR_VIBRATION_OUTPUT:
+ info.type = GHOST_kXrActionTypeVibrationOutput;
+ break;
+ }
+
+ if (!GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return;
+ }
+
+ if (action_set->controller_pose_action &&
+ STREQ(action_set->controller_pose_action->name, action_name)) {
+ if (action_set == xr->runtime->session_state.active_action_set) {
+ wm_xr_session_controller_data_clear(&xr->runtime->session_state);
+ }
+ action_set->controller_pose_action = NULL;
+ }
+ if (action_set->active_modal_action &&
+ STREQ(action_set->active_modal_action->name, action_name)) {
+ action_set->active_modal_action = NULL;
+ }
+
+ wmXrAction *action = action_find(xr, action_set_name, action_name);
+ if (!action) {
+ return;
+ }
+}
+
+bool WM_xr_action_space_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths,
+ const wmXrPose *poses)
+{
+ GHOST_XrActionSpaceInfo info = {
+ .action_name = action_name,
+ .count_subaction_paths = count_subaction_paths,
+ .subaction_paths = subaction_paths,
+ };
+
+ GHOST_XrPose *ghost_poses = MEM_malloc_arrayN(
+ count_subaction_paths, sizeof(*ghost_poses), __func__);
+ for (unsigned int i = 0; i < count_subaction_paths; ++i) {
+ const wmXrPose *pose = &poses[i];
+ GHOST_XrPose *ghost_pose = &ghost_poses[i];
+ copy_v3_v3(ghost_pose->position, pose->position);
+ copy_qt_qt(ghost_pose->orientation_quat, pose->orientation_quat);
+ }
+ info.poses = ghost_poses;
+
+ bool ret = GHOST_XrCreateActionSpaces(xr->runtime->context, action_set_name, 1, &info) ? true :
+ false;
+ MEM_freeN(ghost_poses);
+ return ret;
+}
+
+void WM_xr_action_space_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ unsigned int count_subaction_paths,
+ const char **subaction_paths)
+{
+ GHOST_XrActionSpaceInfo info = {
+ .action_name = action_name,
+ .count_subaction_paths = count_subaction_paths,
+ .subaction_paths = subaction_paths,
+ };
+
+ GHOST_XrDestroyActionSpaces(xr->runtime->context, action_set_name, 1, &info);
+}
+
+bool WM_xr_action_binding_create(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths)
+{
+ GHOST_XrActionBindingInfo binding_info = {
+ .action_name = action_name,
+ .count_interaction_paths = count_interaction_paths,
+ .interaction_paths = interaction_paths,
+ };
+
+ GHOST_XrActionProfileInfo profile_info = {
+ .profile_path = profile_path,
+ .count_bindings = 1,
+ .bindings = &binding_info,
+ };
+
+ return GHOST_XrCreateActionBindings(xr->runtime->context, action_set_name, 1, &profile_info);
+}
+
+void WM_xr_action_binding_destroy(wmXrData *xr,
+ const char *action_set_name,
+ const char *profile_path,
+ const char *action_name,
+ unsigned int count_interaction_paths,
+ const char **interaction_paths)
+{
+ GHOST_XrActionBindingInfo binding_info = {
+ .action_name = action_name,
+ .count_interaction_paths = count_interaction_paths,
+ .interaction_paths = interaction_paths,
+ };
+
+ GHOST_XrActionProfileInfo profile_info = {
+ .profile_path = profile_path,
+ .count_bindings = 1,
+ .bindings = &binding_info,
+ };
+
+ GHOST_XrDestroyActionBindings(xr->runtime->context, action_set_name, 1, &profile_info);
+}
+
+bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return false;
+ }
+
+ {
+ /* Unset active modal action (if any). */
+ wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set;
+ if (active_action_set) {
+ wmXrAction *active_modal_action = active_action_set->active_modal_action;
+ if (active_modal_action) {
+ if (active_modal_action->active_modal_path) {
+ active_modal_action->active_modal_path = NULL;
+ }
+ active_action_set->active_modal_action = NULL;
+ }
+ }
+ }
+
+ xr->runtime->session_state.active_action_set = action_set;
+
+ if (action_set->controller_pose_action) {
+ wm_xr_session_controller_data_populate(action_set->controller_pose_action, xr);
+ }
+
+ return true;
+}
+
+bool WM_xr_controller_pose_action_set(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name)
+{
+ wmXrActionSet *action_set = action_set_find(xr, action_set_name);
+ if (!action_set) {
+ return false;
+ }
+
+ wmXrAction *action = action_find(xr, action_set_name, action_name);
+ if (!action) {
+ return false;
+ }
+
+ action_set->controller_pose_action = action;
+
+ if (action_set == xr->runtime->session_state.active_action_set) {
+ wm_xr_session_controller_data_populate(action, xr);
+ }
+
+ return true;
+}
+
+bool WM_xr_action_state_get(const wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const char *subaction_path,
+ wmXrActionState *r_state)
+{
+ const wmXrAction *action = action_find((wmXrData *)xr, action_set_name, action_name);
+ if (!action) {
+ return false;
+ }
+
+ BLI_assert(action->type == (eXrActionType)r_state->type);
+
+ /* Find the action state corresponding to the subaction path. */
+ for (unsigned int i = 0; i < action->count_subaction_paths; ++i) {
+ if (STREQ(subaction_path, action->subaction_paths[i])) {
+ switch ((eXrActionType)r_state->type) {
+ case XR_BOOLEAN_INPUT:
+ r_state->state_boolean = ((bool *)action->states)[i];
+ break;
+ case XR_FLOAT_INPUT:
+ r_state->state_float = ((float *)action->states)[i];
+ break;
+ case XR_VECTOR2F_INPUT:
+ copy_v2_v2(r_state->state_vector2f, ((float(*)[2])action->states)[i]);
+ break;
+ case XR_POSE_INPUT: {
+ const GHOST_XrPose *pose = &((GHOST_XrPose *)action->states)[i];
+ copy_v3_v3(r_state->state_pose.position, pose->position);
+ copy_qt_qt(r_state->state_pose.orientation_quat, pose->orientation_quat);
+ break;
+ }
+ case XR_VIBRATION_OUTPUT:
+ BLI_assert_unreachable();
+ break;
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool WM_xr_haptic_action_apply(wmXrData *xr,
+ const char *action_set_name,
+ const char *action_name,
+ const long long *duration,
+ const float *frequency,
+ const float *amplitude)
+{
+ return GHOST_XrApplyHapticAction(
+ xr->runtime->context, action_set_name, action_name, duration, frequency, amplitude) ?
+ true :
+ false;
+}
+
+void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name)
+{
+ GHOST_XrStopHapticAction(xr->runtime->context, action_set_name, action_name);
+}
+
+/** \} */ /* XR-Action API */
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c
index cc4a7e41e82..1f722855696 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c
@@ -45,6 +45,12 @@ void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4])
translate_m4(r_viewmat, -pose->position[0], -pose->position[1], -pose->position[2]);
}
+void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4])
+{
+ quat_to_mat4(r_mat, pose->orientation_quat);
+ copy_v3_v3(r_mat[3], pose->position);
+}
+
static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data,
const GHOST_XrDrawViewInfo *draw_view,
const XrSessionSettings *session_settings,
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h
index 25e3da3ffb4..9bf63be61dd 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h
+++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h
@@ -24,6 +24,21 @@
#include "wm_xr.h"
+struct wmXrActionSet;
+
+typedef struct wmXrControllerData {
+ /** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256).
+ This subaction path will later be combined with a component path, and that combined path should
+ also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path =
+ /input/trigger/value, interaction_path = /user/hand/left/input/trigger/value).
+ */
+ char subaction_path[64];
+ /** Last known controller pose (in world space) stored for queries. */
+ GHOST_XrPose pose;
+ /** The last known controller matrix, calculated from above's controller pose. */
+ float mat[4][4];
+} wmXrControllerData;
+
typedef struct wmXrSessionState {
bool is_started;
@@ -39,11 +54,23 @@ typedef struct wmXrSessionState {
Object *prev_base_pose_object;
/** Copy of XrSessionSettings.flag created on the last draw call, stored to detect changes. */
int prev_settings_flag;
+ /** Copy of wmXrDrawData.base_pose. */
+ GHOST_XrPose prev_base_pose;
+ /** Copy of GHOST_XrDrawViewInfo.local_pose. */
+ GHOST_XrPose prev_local_pose;
/** Copy of wmXrDrawData.eye_position_ofs. */
float prev_eye_position_ofs[3];
bool force_reset_to_base_pose;
bool is_view_data_set;
+
+ /** Last known controller data. */
+ wmXrControllerData controllers[2];
+
+ /** The currently active action set that will be updated on calls to
+ * wm_xr_session_actions_update(). If NULL, all action sets will be treated as active and
+ * updated. */
+ struct wmXrActionSet *active_action_set;
} wmXrSessionState;
typedef struct wmXrRuntimeData {
@@ -79,6 +106,40 @@ typedef struct wmXrDrawData {
float eye_position_ofs[3]; /* Local/view space. */
} wmXrDrawData;
+typedef struct wmXrAction {
+ char *name;
+ eXrActionType type;
+ unsigned int count_subaction_paths;
+ char **subaction_paths;
+ /** States for each subaction path. */
+ void *states;
+ /** Previous states, stored to determine XR events. */
+ void *states_prev;
+
+ /** Input threshold for float/vector2f actions. */
+ float float_threshold;
+
+ /** The currently active subaction path (if any) for modal actions. */
+ char **active_modal_path;
+
+ /** Operator to be called on XR events. */
+ struct wmOperatorType *ot;
+ IDProperty *op_properties;
+ eXrOpFlag op_flag;
+} wmXrAction;
+
+typedef struct wmXrActionSet {
+ char *name;
+
+ /** The XR pose action that determines the controller
+ * transforms. This is usually identified by the OpenXR path "/grip/pose" or "/aim/pose",
+ * although it could differ depending on the specification and hardware. */
+ wmXrAction *controller_pose_action;
+
+ /** The currently active modal action (if any). */
+ wmXrAction *active_modal_action;
+} wmXrActionSet;
+
wmXrRuntimeData *wm_xr_runtime_data_create(void);
void wm_xr_runtime_data_free(wmXrRuntimeData **runtime);
@@ -95,5 +156,12 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
void *wm_xr_session_gpu_binding_context_create(void);
void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle context);
+void wm_xr_session_actions_init(wmXrData *xr);
+void wm_xr_session_actions_update(wmXrData *xr);
+void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action,
+ wmXrData *xr);
+void wm_xr_session_controller_data_clear(wmXrSessionState *state);
+
void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]);
+void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]);
void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata);
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c
index b9ef40e3398..1ddbe228e05 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_session.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c
@@ -18,7 +18,9 @@
* \ingroup wm
*/
+#include "BKE_callbacks.h"
#include "BKE_context.h"
+#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_scene.h"
@@ -49,11 +51,24 @@ static CLG_LogRef LOG = {"wm.xr"};
/* -------------------------------------------------------------------- */
+static void wm_xr_session_create_cb(void)
+{
+ Main *bmain = G_MAIN;
+ wmWindowManager *wm = bmain->wm.first;
+ wmXrData *xr_data = &wm->xr;
+
+ /* Get action set data from Python. */
+ BKE_callback_exec_null(bmain, BKE_CB_EVT_XR_SESSION_START_PRE);
+
+ wm_xr_session_actions_init(xr_data);
+}
+
static void wm_xr_session_exit_cb(void *customdata)
{
wmXrData *xr_data = customdata;
xr_data->runtime->session_state.is_started = false;
+
if (xr_data->runtime->exit_fn) {
xr_data->runtime->exit_fn(xr_data);
}
@@ -65,6 +80,10 @@ static void wm_xr_session_exit_cb(void *customdata)
static void wm_xr_session_begin_info_create(wmXrData *xr_data,
GHOST_XrSessionBeginInfo *r_begin_info)
{
+ /* Callback for when the session is created. This is needed to create and bind OpenXR actions
+ * after the session is created but before it is started. */
+ r_begin_info->create_fn = wm_xr_session_create_cb;
+
/* WM-XR exit function, does some own stuff and calls callback passed to wm_xr_session_toggle(),
* to allow external code to execute its own session-exit logic. */
r_begin_info->exit_fn = wm_xr_session_exit_cb;
@@ -289,6 +308,7 @@ void wm_xr_session_draw_data_update(const wmXrSessionState *state,
/**
* Update information that is only stored for external state queries. E.g. for Python API to
* request the current (as in, last known) viewer pose.
+ * Controller data and action sets will be updated separately via wm_xr_session_actions_update().
*/
void wm_xr_session_state_update(const XrSessionSettings *settings,
const wmXrDrawData *draw_data,
@@ -322,6 +342,8 @@ void wm_xr_session_state_update(const XrSessionSettings *settings,
DEFAULT_SENSOR_WIDTH);
copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs);
+ memcpy(&state->prev_base_pose, &draw_data->base_pose, sizeof(state->prev_base_pose));
+ memcpy(&state->prev_local_pose, &draw_view->local_pose, sizeof(state->prev_local_pose));
state->prev_settings_flag = settings->flag;
state->prev_base_pose_type = settings->base_pose_type;
state->prev_base_pose_object = settings->base_pose_object;
@@ -373,6 +395,132 @@ bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr,
return true;
}
+bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_location[3])
+{
+ if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set ||
+ subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) {
+ zero_v3(r_location);
+ return false;
+ }
+
+ copy_v3_v3(r_location, xr->runtime->session_state.controllers[subaction_idx].pose.position);
+ return true;
+}
+
+bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr,
+ unsigned int subaction_idx,
+ float r_rotation[4])
+{
+ if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set ||
+ subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) {
+ unit_qt(r_rotation);
+ return false;
+ }
+
+ copy_v4_v4(r_rotation,
+ xr->runtime->session_state.controllers[subaction_idx].pose.orientation_quat);
+ return true;
+}
+
+/* -------------------------------------------------------------------- */
+/** \name XR-Session Actions
+ *
+ * XR action processing and event dispatching.
+ *
+ * \{ */
+
+void wm_xr_session_actions_init(wmXrData *xr)
+{
+ if (!xr->runtime) {
+ return;
+ }
+
+ GHOST_XrAttachActionSets(xr->runtime->context);
+}
+
+static void wm_xr_session_controller_mats_update(const XrSessionSettings *settings,
+ const wmXrAction *controller_pose_action,
+ wmXrSessionState *state)
+{
+ const unsigned int count = (unsigned int)min_ii(
+ (int)controller_pose_action->count_subaction_paths, (int)ARRAY_SIZE(state->controllers));
+
+ float view_ofs[3];
+ float base_inv[4][4];
+ float tmp[4][4];
+
+ zero_v3(view_ofs);
+ if ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) {
+ add_v3_v3(view_ofs, state->prev_local_pose.position);
+ }
+
+ wm_xr_pose_to_viewmat(&state->prev_base_pose, base_inv);
+ invert_m4(base_inv);
+
+ for (unsigned int i = 0; i < count; ++i) {
+ wmXrControllerData *controller = &state->controllers[i];
+
+ /* Calculate controller matrix in world space. */
+ wm_xr_controller_pose_to_mat(&((GHOST_XrPose *)controller_pose_action->states)[i], tmp);
+
+ /* Apply eye position and base pose offsets. */
+ sub_v3_v3(tmp[3], view_ofs);
+ mul_m4_m4m4(controller->mat, base_inv, tmp);
+
+ /* Save final pose. */
+ mat4_to_loc_quat(
+ controller->pose.position, controller->pose.orientation_quat, controller->mat);
+ }
+}
+
+void wm_xr_session_actions_update(wmXrData *xr)
+{
+ if (!xr->runtime) {
+ return;
+ }
+
+ GHOST_XrContextHandle xr_context = xr->runtime->context;
+ wmXrSessionState *state = &xr->runtime->session_state;
+ wmXrActionSet *active_action_set = state->active_action_set;
+
+ int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL);
+ if (!ret) {
+ return;
+ }
+
+ /* Only update controller mats for active action set. */
+ if (active_action_set) {
+ if (active_action_set->controller_pose_action) {
+ wm_xr_session_controller_mats_update(
+ &xr->session_settings, active_action_set->controller_pose_action, state);
+ }
+ }
+}
+
+void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, wmXrData *xr)
+{
+ wmXrSessionState *state = &xr->runtime->session_state;
+
+ const unsigned int count = (unsigned int)min_ii(
+ (int)ARRAY_SIZE(state->controllers), (int)controller_pose_action->count_subaction_paths);
+
+ for (unsigned int i = 0; i < count; ++i) {
+ wmXrControllerData *c = &state->controllers[i];
+ strcpy(c->subaction_path, controller_pose_action->subaction_paths[i]);
+ memset(&c->pose, 0, sizeof(c->pose));
+ zero_m4(c->mat);
+ }
+}
+
+void wm_xr_session_controller_data_clear(wmXrSessionState *state)
+{
+ memset(state->controllers, 0, sizeof(state->controllers));
+}
+
+/** \} */ /* XR-Session Actions */
+
/* -------------------------------------------------------------------- */
/** \name XR-Session Surface
*